diff --git a/memcached/index.d.ts b/memcached/index.d.ts new file mode 100644 index 0000000000..23d892309b --- /dev/null +++ b/memcached/index.d.ts @@ -0,0 +1,322 @@ +// Type definitions for memcached 2.2 +// Project: https://github.com/3rd-Eden/memcached +// Definitions by: KentarouTakeda +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +/// + +import events = require("events"); +export = Memcached; + +declare class Memcached extends events.EventEmitter { + + static config: Memcached.options; + + /** + * Connect to a single server. + * @param location Server location e.g. "127.0.0.1:11211" + * @param options options + */ + constructor(location: string, options?: Memcached.options); + + /** + * Connect to a cluster of Memcached servers. + * @param location Server locations e.g. ["127.0.0.1:11211","127.0.0.1:11212"] + * @param options options + */ + constructor(location: string[], options?: Memcached.options); + + /** + * Connect to servers with weight. + * @param location Server locations e.g. {"127.0.0.1:11211": 1,"127.0.0.1:11212": 2} + * @param options options + */ + constructor(location: {[server: string]: number}, options ?: Memcached.options); + + /** + * Touches the given key. + * @param key The key + * @param lifetime After how long should the key expire measured in seconds + * @param cb + */ + touch(key: string, lifetime: number, cb: (this: Memcached.CommandData, err: any) => void): void; + + /** + * Get the value for the given key. + * @param key The key + * @param cb + */ + get(key: string, cb: (this: Memcached.CommandData, err: any, data: any) => void): void; + + /** + * Get the value and the CAS id. + * @param key The key + * @param cb + */ + gets(key: string, cb: (this: Memcached.CommandData, err: any, data: {[key: string]: any, cas: string}) => void): void; + + /** + * Retrieves a bunch of values from multiple keys. + * @param keys all the keys that needs to be fetched + * @param cb + */ + getMulti(keys: string[], cb: (this: undefined, err: any, data: {[key: string]: any}) => void): void; + + /** + * Stores a new value in Memcached. + * + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param lifetime + * @param cb + */ + set(key: string, value: any, lifetime: number, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Replaces the value in memcached. + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param lifetime + * @param cb + */ + replace(key: string, value: any, lifetime: number, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Add the value, only if it's not in memcached already. + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param lifetime + * @param cb + */ + add(key: string, value: any, lifetime: number, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Add the value, only if it matches the given CAS value. + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param cas + * @param lifetime + * @param cb + */ + cas(key: string, value: any, cas: string, lifetime: number, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Add the given value string to the value of an existing item. + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param cb + */ + append(key: string, value: any, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Add the given value string to the value of an existing item. + * @param key The key + * @param value Either a buffer, JSON, number or string that you want to store. + * @param cb + */ + prepend(key: string, value: any, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Increment a given key. + * @param key The key + * @param amount The increment + * @param cb + */ + incr(key: string, amount: number, cb: (this: Memcached.CommandData, err: any, result: boolean|number) => void): void; + + /** + * Decrement a given key. + * @param key The key + * @param amount The decrement + * @param cb + */ + decr(key: string, amount: number, cb: (this: Memcached.CommandData, err: any, result: boolean|number) => void): void; + + /** + * Remove the key from memcached. + * @param key The key + * @param cb + */ + del(key: string, cb: (this: Memcached.CommandData, err: any, result: boolean) => void): void; + + /** + * Retrieves the version number of your server. + * @param cb + */ + version(cb: (err: any, version: Memcached.VersionData[]) => void): void; + + /** + * Retrieves your stats settings. + * @param cb + */ + settings(cb: (err: any, settings: Memcached.StatusData[]) => void): void; + + /** + * Retrieves stats from your memcached server. + * @param cb + */ + stats(cb: (err: any, stats: Memcached.StatusData[]) => void): void; + + /** + * Retrieves stats slabs information. + * @param cb + */ + slabs(cb: (err: any, stats: Memcached.StatusData[]) => void): void; + + /** + * Retrieves stats items information. + * @param cb + */ + items(cb: (err: any, stats: Memcached.StatusData[]) => void): void; + + /** + * Inspect cache, see examples for a detailed explanation. + * @param server + * @param slabid + * @param number + * @param cb + */ + cachedump(server: string, slabid: number, number: number, cb: (err: any, cachedump: Memcached.CacheDumpData|Memcached.CacheDumpData[]) => void): void; + + /** + * Flushes the memcached server. + * @param cb + */ + flush(cb: (this: undefined, err: any, results: boolean[]) => void): void; + + /** + * a issue occurred on one a server, we are going to attempt a retry next. + */ + on(event: "issue", cb: (err: Memcached.IssueData) => void): this; + + /** + * a server has been marked as failure or dead. + */ + on(event: "failure", cb: (err: Memcached.IssueData) => void): this; + + /** + * we are going to attempt to reconnect the to the failed server. + */ + on(event: "reconnecting", cb: (err: Memcached.IssueData) => void): this; + + /** + * successfully reconnected to the memcached server. + */ + on(event: "reconnect", cb: (err: Memcached.IssueData) => void): this; + + /** + * removing the server from our consistent hashing. + */ + on( event: "remove", cb: (err: Memcached.IssueData) => void): this; + + /** + * Closes all active memcached connections. + */ + end(): void; +} + +declare namespace Memcached { + export interface IssueData { + server: string; + tokens: [string, string]; + messages: string[]; + failures ?: number; + totalFailures ?: number; + totalReconnectsAttempted ?: number; + totalReconnectsSuccess ?: number; + totalReconnectsFailed ?: number; + totalDownTime ?: number; + } + + export interface CommandData { + start: number; + execution: number; + callback: (...args : any[]) => any; + type: string; + command: string; + validate: Array<[string, (...args : any[]) => any]>; + cas ?: string; + redundancyEnabled ?: boolean; + key ?: string; + value ?: any; + lifetime ?: number; + } + + export interface StatusData { + server ?: string; + [key: string]: string|boolean|number|undefined; + } + + export interface VersionData extends StatusData { + version: string; + major: string; + minor: string; + bugfix: string; + } + + export interface CacheDumpData { + key: string; + b: number; + s: number; + } + + export interface options { + /** + * 250, the maximum key size allowed. + */ + maxKeySize ?: number; + /** + * 2592000, the maximum expiration time of keys (in seconds). + */ + maxExpiration ?: number; + /** + * 1048576, the maximum size of a value. + */ + maxValue ?: number; + /** + * 10, the maximum size of the connection pool. + */ + poolSize ?: number; + /** + * md5, the hashing algorithm used to generate the hashRing values. + */ + algorithm ?: string; + /** + * 18000000, the time between reconnection attempts (in milliseconds). + */ + reconnect ?: number; + /** + * 5000, the time after which Memcached sends a connection timeout (in milliseconds). + */ + timeout ?: number; + /** + * 5, the number of socket allocation retries per request. + */ + retries ?: number; + /** + * 5, the number of failed-attempts to a server before it is regarded as 'dead'. + */ + failures ?: number; + /** + * 30000, the time between a server failure and an attempt to set it up back in service. + */ + retry ?: number; + /** + * false, if true, authorizes the automatic removal of dead servers from the pool. + */ + remove ?: boolean; + /** + * undefined, an array of server_locations to replace servers that fail and that are removed from the consistent hashing scheme. + */ + failOverServers ?: string|string[]; + /** + * true, whether to use md5 as hashing scheme when keys exceed maxKeySize. + */ + keyCompression ?: boolean; + /** + * 5000, the idle timeout for the connections. + */ + idle ?: number; + } +} diff --git a/memcached/memcached-tests.ts b/memcached/memcached-tests.ts new file mode 100644 index 0000000000..7671bebcb6 --- /dev/null +++ b/memcached/memcached-tests.ts @@ -0,0 +1,429 @@ +import Memcached = require('memcached'); +import assert = require('assert'); + +namespace main { + function isString(expected?: string): void { + if (expected !== undefined && typeof expected !== 'string') { + throw new Error(); + } + }; + function isArray(expected?: any[]): void { + if (expected !== undefined && !Array.isArray(expected)) { + throw new Error(); + } + }; + function isNumber(expected?: number): void { + if (expected !== undefined && typeof expected !== 'number') { + throw new Error(); + } + }; + function isBoolean(expected?: boolean): void { + if (expected !== undefined && typeof expected !== 'boolean') { + throw new Error(); + } + }; + function isFunction(expected?: (...args : any[]) => void): void { + if (expected !== undefined && typeof expected !== 'function') { + throw new Error(); + } + }; + function isVoid(expected?: undefined): void { + if (expected !== undefined) { + throw new Error(); + } + }; + + namespace test_global_options { + Memcached.config.maxKeySize = 250; + Memcached.config.maxExpiration = 2592000; + Memcached.config.maxValue = 1048576; + Memcached.config.poolSize = 10; + Memcached.config.algorithm = 'md5'; + Memcached.config.reconnect = 1000; + Memcached.config.timeout = 100; + Memcached.config.retries = 1; + Memcached.config.failures = 5; + Memcached.config.retry = 100; + Memcached.config.remove = false; + Memcached.config.failOverServers = undefined; + Memcached.config.keyCompression = true; + Memcached.config.idle = 5000; + } + + namespace test_constructor_with_string { + const location = '127.0.0.1:11211'; + + const memcached = new Memcached(location); + memcached.end(); + } + + namespace test_constructor_with_array { + const location = [ + '127.0.0.1:11211', + '127.0.0.1:11212', + '127.0.0.1:11213' + ]; + + const memcached = new Memcached(location); + memcached.end(); + } + + namespace test_constructor_with_object { + const location = { + '127.0.0.1:11211': 1, + '127.0.0.1:11212': 2, + '127.0.0.1:11213': 3 + }; + + const memcached = new Memcached(location); + memcached.end(); + } + + namespace test_constructor_with_options { + const location = '127.0.0.1:11211'; + const options: Memcached.options = { + maxKeySize: 250, + maxExpiration: 2592000, + maxValue: 1048576, + poolSize: 10, + algorithm: 'md5', + reconnect: 18000000, + timeout: 5000, + retries: 5, + failures: 5, + retry: 30000, + remove: false, + failOverServers: undefined, + keyCompression: true, + idle: 5000, + }; + + const memcached = new Memcached(location, options); + memcached.end(); + } + + const memcached = new Memcached('127.0.0.1:11211'); + + namespace test_events { + function isIssue(expected: Memcached.IssueData) { + isString(expected.server); + isArray(expected.tokens); + isArray(expected.messages); + isNumber(expected.failures); + isNumber(expected.totalFailures); + isNumber(expected.totalReconnectsAttempted); + isNumber(expected.totalReconnectsSuccess); + isNumber(expected.totalReconnectsFailed); + isNumber(expected.totalDownTime); + } + memcached.on('issue', err => isIssue(err)); + memcached.on('failure', err => isIssue(err)); + memcached.on('reconnecting', err => isIssue(err)); + memcached.on('reconnect', err => isIssue(err)); + memcached.on('remove', err => isIssue(err)); + } + + function isCommandData(expected: Memcached.CommandData) { + isNumber(expected.start); + isNumber(expected.execution); + isFunction(expected.callback); + isString(expected.type); + isString(expected.command); + isArray(expected.validate); + isBoolean(expected.redundancyEnabled); + isString(expected.key); + isNumber(expected.lifetime); + isString(expected.cas); + } + namespace test_set { + memcached.set('key', 'value', 3600, function(err) { + isCommandData(this); + }); + } + namespace test_touch { + memcached.touch('key', 3600, function(err) { + isCommandData(this); + }); + } + namespace test_get { + memcached.get('key', function(err, data) { + isCommandData(this); + }); + } + namespace test_getMulti { + memcached.getMulti(['foo', 'bar'], function(err, data) { + isVoid(this); + assert(typeof data, 'object'); + }); + } + namespace test_cas { + const promises = []; + const key = 'caskey'; + const value = 'casvalue'; + + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, function() { + isCommandData(this); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.gets(key, function(err, data) { + isCommandData(this); + memcached.cas(key, value, data.cas, 0, function(err, result) { + isCommandData(this); + assert(result); + resolve(); + }); + }); + })) + .then(() => new Promise(resolve => { + memcached.cas(key, value, '99', 0, function(err, result) { + isCommandData(this); + assert(!result); + resolve(); + }); + })) + ; + + } + namespace test_replace { + const promises = []; + const key = 'replacekey'; + const value = 'replacevalue'; + + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, () => { + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.replace(key, value, 0, function(err, result) { + assert(result); + isCommandData(this); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.replace('noexistss', value, 0, function(err, result) { + assert(!result); + isCommandData(this); + resolve(); + }); + })) + ; + } + namespace test_add { + const promises = []; + const key = 'addkey'; + const value = 'addvalue'; + + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.add(key, value, 0, function(err, result) { + assert(result); + isCommandData(this); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.add(key, value, 0, function(err, result) { + assert(!result); + isCommandData(this); + resolve(); + }); + })) + ; + } + namespace test_append { + const promises = []; + const key = 'appendkey'; + const value = 'appendvalue'; + + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, (err, result) => { + isBoolean(result); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.append(key, value, (err, result) => { + isBoolean(result); + resolve(); + }); + })) + ; + } + namespace test_prepend { + const promises = []; + const key = 'prependkey'; + const value = 'prependvalue'; + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, (err, result) => { + isBoolean(result); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.prepend(key, value, function(err, result) { + isBoolean(result); + isCommandData(this); + resolve(); + }); + })) + ; + } + namespace test_incr { + const promises = []; + const key = 'incrkey'; + const value = 2; + + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, (err, result) => { + isBoolean(result); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.incr(key, value, function(err, result) { + assert.deepStrictEqual(result, 4); + isCommandData(this); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.incr('noexists', value, function(err, result) { + assert.deepStrictEqual(result, false); + isCommandData(this); + resolve(); + }); + })) + ; + } + namespace test_decr { + const promises = []; + const key = 'decrkey'; + const value = 2; + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, function(err, result) { + isCommandData(this); + isBoolean(result); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.decr(key, value, function(err, result) { + assert.deepStrictEqual(result, 0); + isCommandData(this); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.decr('noexists', value, function(err, result) { + assert.deepStrictEqual(result, false); + isCommandData(this); + resolve(); + }); + })) + ; + } + namespace test_decr { + const promises = []; + const key = 'delkey'; + const value = 'delvalue'; + Promise.resolve() + .then(() => new Promise(resolve => { + memcached.set(key, value, 0, (err, result) => { + isBoolean(result); + resolve(); + }); + })) + .then(() => new Promise(resolve => { + memcached.del(key, function(err, result) { + isBoolean(result); + isCommandData(this); + resolve(); + }); + })) + ; + } + + namespace test_version { + memcached.version((err, data) => { + for (const version of data) { + isString(version.server); + isString(version.version); + isString(version.major); + isString(version.minor); + isString(version.bugfix); + } + }); + } + namespace test_stats { + memcached.stats((err : any, data: Memcached.StatusData[]) => { + isArray(data); + for (const stat of data) { + isString(stat.server); + } + }); + }; + namespace test_settings { + memcached.settings((err : any, data: Memcached.StatusData[]) => { + isArray(data); + for (const setting of data) { + isString(setting.server); + } + }); + }; + namespace test_slabs { + memcached.slabs((err : any, data: Memcached.StatusData[]) => { + isArray(data); + for (const setting of data) { + isString(setting.server); + } + }); + }; + let promise: Promise; + namespace test_items { + promise = new Promise(resolve => { + memcached.items((err, data) => { + isArray(data); + for (const setting of data) { + isString(setting.server); + } + resolve(data); + }); + }); + }; + namespace test_cachedump { + promise.then(data => { + for (const node of data) { + const server = node.server; + delete node.server; + Object.keys(node).forEach(slabid => { + memcached.cachedump(server, Number(slabid), node[slabid].number, (err, data) => { + if (!Array.isArray(data)) data = [data]; + data.forEach(cache => { + isNumber(cache.b); + isNumber(cache.s); + isString(cache.key); + }); + }); + }); + } + }); + }; + + namespace test_flush { + memcached.flush(function(err, results) { + isVoid(this); + isArray(results); + }); + }; +} diff --git a/memcached/tsconfig.json b/memcached/tsconfig.json new file mode 100644 index 0000000000..263ad39c54 --- /dev/null +++ b/memcached/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "noImplicitReturns": true, + "noImplicitAny": true, + "noImplicitThis": true, + "lib": ["es6"], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "module": "commonjs", + "target": "es5", + "sourceMap": false, + "baseUrl": "../", + "typeRoots": ["../"] + }, + "files": [ + "index.d.ts", + "memcached-tests.ts" + ] +} diff --git a/memcached/tslint.json b/memcached/tslint.json new file mode 100644 index 0000000000..68d10d72a6 --- /dev/null +++ b/memcached/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../tslint.json", + "rules": { + "unified-signatures": false + } +}