memx 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.js +436 -94
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +421 -76
- package/dist/index.mjs.map +2 -2
- package/index.d.ts +147 -68
- package/package.json +7 -7
- package/src/buffers.ts +9 -0
- package/src/client.ts +152 -78
- package/src/cluster.ts +24 -12
- package/src/connection.ts +1 -1
- package/src/encode.ts +1 -1
- package/src/fake.ts +222 -0
- package/src/index.ts +4 -0
- package/src/internals.ts +5 -0
- package/src/server.ts +28 -17
- package/src/types.ts +15 -9
- package/src/utils.ts +161 -0
package/src/client.ts
CHANGED
|
@@ -4,9 +4,32 @@ import { isTypedArray } from 'util/types'
|
|
|
4
4
|
import { ClusterAdapter, ClusterOptions } from './cluster'
|
|
5
5
|
import { EMPTY_BUFFER, FLAGS } from './constants'
|
|
6
6
|
import { typedArrayFlags } from './internals'
|
|
7
|
-
import { Adapter, Counter, Stats } from './types'
|
|
7
|
+
import { Adapter, AdapterResult, Counter, Stats } from './types'
|
|
8
|
+
|
|
9
|
+
/** JSON replacere function serializing `bigint`, {@link Date}, {@link Set} and {@link Map}. */
|
|
10
|
+
function replacer(this: any, key: string, value: any): any {
|
|
11
|
+
if (typeof this[key] === 'bigint') return [ '\0__$BIGINT$__\0', this[key].toString() ]
|
|
12
|
+
if (this[key] instanceof Date) return [ '\0__$DATE$__\0', this[key].toISOString() ]
|
|
13
|
+
if (this[key] instanceof Set) return [ '\0__$SET$__\0', ...value ]
|
|
14
|
+
if (this[key] instanceof Map) return [ '\0__$MAP$__\0', ...value.entries() ]
|
|
15
|
+
return value
|
|
16
|
+
}
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
/** JSON reviver function deserializing `bigint`, {@link Date}, {@link Set} and {@link Map}. */
|
|
19
|
+
function reviver(this: any, key: string, value: any): any {
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
switch (value[0]) {
|
|
22
|
+
case '\0__$BIGINT$__\0': return BigInt(value[1])
|
|
23
|
+
case '\0__$DATE$__\0': return new Date(value[1])
|
|
24
|
+
case '\0__$SET$__\0': return new Set(value.slice(1))
|
|
25
|
+
case '\0__$MAP$__\0': return new Map(value.slice(1))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Convert a {@link Serializable} or {@link Appendable} value into a {@link Buffer}. */
|
|
32
|
+
function toBuffer<T>(value: Serializable | Appendable, options: T): [ Buffer, T & { flags: number } ] {
|
|
10
33
|
if (Buffer.isBuffer(value)) return [ value, { ...options, flags: FLAGS.BUFFER } ]
|
|
11
34
|
|
|
12
35
|
switch (typeof value) {
|
|
@@ -35,32 +58,104 @@ function toBuffer<T>(value: any, options: T): [ Buffer, T & { flags: number } ]
|
|
|
35
58
|
if (value === null) return [ EMPTY_BUFFER, { ...options, flags: FLAGS.NULL } ]
|
|
36
59
|
|
|
37
60
|
// any other "object" gets serialized as JSON
|
|
38
|
-
|
|
61
|
+
const json = JSON.stringify(value, replacer)
|
|
62
|
+
return [ Buffer.from(json, 'utf-8'), { ...options, flags: FLAGS.JSON } ]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Node's own types doesn't provide this... Make our own */
|
|
66
|
+
type TypedArrayConstructor<T extends NodeJS.TypedArray> = {
|
|
67
|
+
new (buffer: ArrayBuffer, offset?: number, length?: number): T
|
|
68
|
+
BYTES_PER_ELEMENT: number
|
|
39
69
|
}
|
|
40
70
|
|
|
71
|
+
function fromBuffer<T extends Serializable>(result: AdapterResult): ClientResult<T> {
|
|
72
|
+
try {
|
|
73
|
+
const { flags, value, cas } = result
|
|
74
|
+
switch (flags) {
|
|
75
|
+
case FLAGS.BIGINT:
|
|
76
|
+
return { value: BigInt(value.toString('utf-8')) as T, cas }
|
|
77
|
+
case FLAGS.BOOLEAN:
|
|
78
|
+
return { value: !!value[0] as T, cas }
|
|
79
|
+
case FLAGS.NUMBER:
|
|
80
|
+
return { value: Number(value.toString('utf-8' )) as T, cas }
|
|
81
|
+
case FLAGS.STRING:
|
|
82
|
+
return { value: value.toString('utf-8' ) as T, cas }
|
|
83
|
+
|
|
84
|
+
case FLAGS.NULL:
|
|
85
|
+
return { value: null as T, cas }
|
|
86
|
+
case FLAGS.JSON:
|
|
87
|
+
return { value: JSON.parse(value.toString('utf-8' ), reviver) as T, cas }
|
|
88
|
+
|
|
89
|
+
case FLAGS.UINT8ARRAY:
|
|
90
|
+
return { value: makeTypedArray(Uint8Array, value) as T, cas }
|
|
91
|
+
case FLAGS.UINT8CLAMPEDARRAY:
|
|
92
|
+
return { value: makeTypedArray(Uint8ClampedArray, value) as T, cas }
|
|
93
|
+
case FLAGS.UINT16ARRAY:
|
|
94
|
+
return { value: makeTypedArray(Uint16Array, value) as T, cas }
|
|
95
|
+
case FLAGS.UINT32ARRAY:
|
|
96
|
+
return { value: makeTypedArray(Uint32Array, value) as T, cas }
|
|
97
|
+
case FLAGS.INT8ARRAY:
|
|
98
|
+
return { value: makeTypedArray(Int8Array, value) as T, cas }
|
|
99
|
+
case FLAGS.INT16ARRAY:
|
|
100
|
+
return { value: makeTypedArray(Int16Array, value) as T, cas }
|
|
101
|
+
case FLAGS.INT32ARRAY:
|
|
102
|
+
return { value: makeTypedArray(Int32Array, value) as T, cas }
|
|
103
|
+
case FLAGS.BIGUINT64ARRAY:
|
|
104
|
+
return { value: makeTypedArray(BigUint64Array, value) as T, cas }
|
|
105
|
+
case FLAGS.BIGINT64ARRAY:
|
|
106
|
+
return { value: makeTypedArray(BigInt64Array, value) as T, cas }
|
|
107
|
+
case FLAGS.FLOAT32ARRAY:
|
|
108
|
+
return { value: makeTypedArray(Float32Array, value) as T, cas }
|
|
109
|
+
case FLAGS.FLOAT64ARRAY:
|
|
110
|
+
return { value: makeTypedArray(Float64Array, value) as T, cas }
|
|
111
|
+
|
|
112
|
+
case FLAGS.BUFFER:
|
|
113
|
+
default:
|
|
114
|
+
return { value: Buffer.from(value) as T, cas }
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
result.recycle()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Create a {@link NodeJS.TypedArray} copying the contents of its source {@link Buffer} */
|
|
41
122
|
function makeTypedArray<T extends NodeJS.TypedArray>(
|
|
42
|
-
constructor:
|
|
123
|
+
constructor: TypedArrayConstructor<T>,
|
|
43
124
|
source: Buffer,
|
|
44
|
-
bytesPerValue: number,
|
|
45
125
|
): T {
|
|
46
126
|
const clone = Buffer.from(source)
|
|
47
127
|
const { buffer, byteOffset, byteLength } = clone
|
|
48
|
-
return new constructor(buffer, byteOffset, byteLength /
|
|
128
|
+
return new constructor(buffer, byteOffset, byteLength / constructor.BYTES_PER_ELEMENT)
|
|
49
129
|
}
|
|
50
130
|
|
|
131
|
+
/* ========================================================================== */
|
|
132
|
+
|
|
133
|
+
/** Types that can be serialized by our {@link Client}. */
|
|
51
134
|
export type Serializable = bigint | string | number | boolean | null | object
|
|
135
|
+
|
|
136
|
+
/** Types that can be appended/prepended by our {@link Client}. */
|
|
52
137
|
export type Appendable = string | NodeJS.TypedArray
|
|
53
138
|
|
|
139
|
+
/** The `ClientResult` interface associate a value with its _CAS_. */
|
|
54
140
|
export interface ClientResult<T extends Serializable> {
|
|
141
|
+
/** The value returned by the {@link Client} */
|
|
55
142
|
value: T
|
|
143
|
+
/** The _CAS_ of the value being returned */
|
|
56
144
|
cas: bigint
|
|
57
145
|
}
|
|
58
146
|
|
|
147
|
+
/**
|
|
148
|
+
* A `Client` represents a high-level client for a _Memcached_ server.
|
|
149
|
+
*/
|
|
59
150
|
export class Client {
|
|
60
151
|
#adapter!: Adapter
|
|
152
|
+
#prefix: string
|
|
61
153
|
|
|
154
|
+
/** Construct a new {@link Client} from environment variables */
|
|
62
155
|
constructor()
|
|
156
|
+
/** Construct a new {@link Client} wrapping an existing `Adapter` */
|
|
63
157
|
constructor(adapter: Adapter)
|
|
158
|
+
/** Construct a new {@link Client} given the specified {@link ClusterOptions} */
|
|
64
159
|
constructor(options: ClusterOptions)
|
|
65
160
|
|
|
66
161
|
constructor(adapterOrOptions?: Adapter | ClusterOptions) {
|
|
@@ -72,76 +167,59 @@ export class Client {
|
|
|
72
167
|
this.#adapter = new ClusterAdapter(adapterOrOptions)
|
|
73
168
|
}
|
|
74
169
|
|
|
170
|
+
this.#prefix = ''
|
|
171
|
+
|
|
75
172
|
assert(this.#adapter, 'Invalid client constructor arguments')
|
|
76
173
|
}
|
|
77
174
|
|
|
175
|
+
/** Return the {@link Adapter} backing this {@link Client} instance */
|
|
78
176
|
get adapter(): Adapter {
|
|
79
177
|
return this.#adapter
|
|
80
178
|
}
|
|
81
179
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
case FLAGS.INT16ARRAY:
|
|
114
|
-
return { value: makeTypedArray(Int16Array, value, 2) as T, cas }
|
|
115
|
-
case FLAGS.INT32ARRAY:
|
|
116
|
-
return { value: makeTypedArray(Int32Array, value, 4) as T, cas }
|
|
117
|
-
case FLAGS.BIGUINT64ARRAY:
|
|
118
|
-
return { value: makeTypedArray(BigUint64Array, value, 8) as T, cas }
|
|
119
|
-
case FLAGS.BIGINT64ARRAY:
|
|
120
|
-
return { value: makeTypedArray(BigInt64Array, value, 8) as T, cas }
|
|
121
|
-
case FLAGS.FLOAT32ARRAY:
|
|
122
|
-
return { value: makeTypedArray(Float32Array, value, 4) as T, cas }
|
|
123
|
-
case FLAGS.FLOAT64ARRAY:
|
|
124
|
-
return { value: makeTypedArray(Float64Array, value, 8) as T, cas }
|
|
125
|
-
|
|
126
|
-
case FLAGS.BUFFER:
|
|
127
|
-
default:
|
|
128
|
-
return { value: Buffer.from(value) as T, cas }
|
|
129
|
-
}
|
|
130
|
-
} finally {
|
|
131
|
-
result.recycle()
|
|
132
|
-
}
|
|
180
|
+
/** Return the prefix prepended to all keys managed by this {@link Client} */
|
|
181
|
+
get prefix(): string {
|
|
182
|
+
return this.#prefix
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Return a new {@link Client} prefixing keys with the specified `string` */
|
|
186
|
+
withPrefix(prefix: string): Client {
|
|
187
|
+
assert(prefix, 'Invalid prefix')
|
|
188
|
+
const client = new Client(this.#adapter)
|
|
189
|
+
client.#prefix = prefix
|
|
190
|
+
return client
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async get<T extends Serializable>(key: string): Promise<T | undefined> {
|
|
194
|
+
const result = await this.#adapter.get(this.#prefix + key)
|
|
195
|
+
return result && fromBuffer<T>(result).value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async gat<T extends Serializable>(key: string, ttl: number): Promise<T | undefined> {
|
|
199
|
+
const result = await this.#adapter.gat(this.#prefix + key, ttl)
|
|
200
|
+
return result && fromBuffer<T>(result).value
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async getc<T extends Serializable>(key: string): Promise<ClientResult<T> | undefined> {
|
|
204
|
+
const result = await this.#adapter.get(this.#prefix + key)
|
|
205
|
+
return result && fromBuffer(result)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async gatc<T extends Serializable>(key: string, ttl: number): Promise<ClientResult<T> | undefined> {
|
|
209
|
+
const result = await this.#adapter.gat(this.#prefix + key, ttl)
|
|
210
|
+
return result && fromBuffer(result)
|
|
133
211
|
}
|
|
134
212
|
|
|
135
|
-
async set(key: string, value: Serializable, options?: { cas?: bigint, ttl?: number }): Promise<bigint |
|
|
136
|
-
return this.#adapter.set(key, ...toBuffer(value, options))
|
|
213
|
+
async set(key: string, value: Serializable, options?: { cas?: bigint, ttl?: number }): Promise<bigint | undefined> {
|
|
214
|
+
return this.#adapter.set(this.#prefix + key, ...toBuffer(value, options))
|
|
137
215
|
}
|
|
138
216
|
|
|
139
|
-
async add(key: string, value: Serializable, options?: {
|
|
140
|
-
return this.#adapter.add(key, ...toBuffer(value, options))
|
|
217
|
+
async add(key: string, value: Serializable, options?: { ttl?: number }): Promise<bigint | undefined> {
|
|
218
|
+
return this.#adapter.add(this.#prefix + key, ...toBuffer(value, options))
|
|
141
219
|
}
|
|
142
220
|
|
|
143
|
-
async replace(key: string, value: Serializable, options?: { cas?: bigint, ttl?: number }): Promise<bigint |
|
|
144
|
-
return this.#adapter.replace(key, ...toBuffer(value, options))
|
|
221
|
+
async replace(key: string, value: Serializable, options?: { cas?: bigint, ttl?: number }): Promise<bigint | undefined> {
|
|
222
|
+
return this.#adapter.replace(this.#prefix + key, ...toBuffer(value, options))
|
|
145
223
|
}
|
|
146
224
|
|
|
147
225
|
append(
|
|
@@ -149,7 +227,7 @@ export class Client {
|
|
|
149
227
|
value: Appendable,
|
|
150
228
|
options?: { cas?: bigint },
|
|
151
229
|
): Promise<boolean> {
|
|
152
|
-
return this.#adapter.append(key, ...toBuffer(value, options))
|
|
230
|
+
return this.#adapter.append(this.#prefix + key, ...toBuffer(value, options))
|
|
153
231
|
}
|
|
154
232
|
|
|
155
233
|
prepend(
|
|
@@ -157,19 +235,17 @@ export class Client {
|
|
|
157
235
|
value: Appendable,
|
|
158
236
|
options?: { cas?: bigint },
|
|
159
237
|
): Promise<boolean> {
|
|
160
|
-
return this.#adapter.prepend(key, ...toBuffer(value, options))
|
|
238
|
+
return this.#adapter.prepend(this.#prefix + key, ...toBuffer(value, options))
|
|
161
239
|
}
|
|
162
240
|
|
|
163
241
|
async increment(
|
|
164
242
|
key: string,
|
|
165
243
|
delta?: bigint | number,
|
|
166
244
|
options?: { initial?: bigint | number, cas?: bigint, ttl?: number },
|
|
167
|
-
): Promise<Counter |
|
|
168
|
-
const counter = await this.#adapter.increment(key, delta, options)
|
|
245
|
+
): Promise<Counter | undefined> {
|
|
246
|
+
const counter = await this.#adapter.increment(this.#prefix + key, delta, options)
|
|
169
247
|
|
|
170
|
-
if (
|
|
171
|
-
(options.initial !== undefined) &&
|
|
172
|
-
(counter.value === BigInt(options.initial))) {
|
|
248
|
+
if ((options?.initial !== undefined) && (counter?.value === BigInt(options.initial))) {
|
|
173
249
|
const cas = await this.replace(key, counter.value, { cas: counter.cas, ttl: options.ttl })
|
|
174
250
|
counter.cas = cas ?? counter.cas
|
|
175
251
|
}
|
|
@@ -180,12 +256,10 @@ export class Client {
|
|
|
180
256
|
key: string,
|
|
181
257
|
delta?: bigint | number,
|
|
182
258
|
options?: { initial?: bigint | number, cas?: bigint, ttl?: number },
|
|
183
|
-
): Promise<Counter |
|
|
184
|
-
const counter = await this.#adapter.decrement(key, delta, options)
|
|
259
|
+
): Promise<Counter | undefined> {
|
|
260
|
+
const counter = await this.#adapter.decrement(this.#prefix + key, delta, options)
|
|
185
261
|
|
|
186
|
-
if (
|
|
187
|
-
(options.initial !== undefined) &&
|
|
188
|
-
(counter.value === BigInt(options.initial))) {
|
|
262
|
+
if ((options?.initial !== undefined) && (counter?.value === BigInt(options.initial))) {
|
|
189
263
|
const cas = await this.replace(key, counter.value, { cas: counter.cas, ttl: options.ttl })
|
|
190
264
|
counter.cas = cas ?? counter.cas
|
|
191
265
|
}
|
|
@@ -194,16 +268,16 @@ export class Client {
|
|
|
194
268
|
|
|
195
269
|
touch(
|
|
196
270
|
key: string,
|
|
197
|
-
|
|
271
|
+
ttl?: number,
|
|
198
272
|
): Promise<boolean> {
|
|
199
|
-
return this.#adapter.touch(key,
|
|
273
|
+
return this.#adapter.touch(this.#prefix + key, ttl)
|
|
200
274
|
}
|
|
201
275
|
|
|
202
276
|
delete(
|
|
203
277
|
key: string,
|
|
204
278
|
options?: { cas?: bigint },
|
|
205
279
|
): Promise<boolean> {
|
|
206
|
-
return this.#adapter.delete(key, options)
|
|
280
|
+
return this.#adapter.delete(this.#prefix + key, options)
|
|
207
281
|
}
|
|
208
282
|
|
|
209
283
|
flush(ttl?: number): Promise<void> {
|
package/src/cluster.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Adapter, Counter, AdapterResult, Stats } from './types'
|
|
2
2
|
import { ServerAdapter, ServerOptions } from './server'
|
|
3
|
+
import assert from 'assert'
|
|
3
4
|
|
|
4
5
|
function parseHosts(hosts?: string): ServerOptions[] {
|
|
5
6
|
const result: { host: string, port?: number }[] = []
|
|
@@ -23,6 +24,7 @@ export interface ClusterOptions {
|
|
|
23
24
|
|
|
24
25
|
export class ClusterAdapter implements Adapter {
|
|
25
26
|
readonly servers: readonly ServerAdapter[]
|
|
27
|
+
readonly ttl: number
|
|
26
28
|
|
|
27
29
|
constructor()
|
|
28
30
|
constructor(servers: ServerAdapter[])
|
|
@@ -62,6 +64,12 @@ export class ClusterAdapter implements Adapter {
|
|
|
62
64
|
if (this.servers.length < 1) throw new Error('No hosts configured')
|
|
63
65
|
if (this.servers.length === 1) this.server = (): ServerAdapter => this.servers[0]
|
|
64
66
|
|
|
67
|
+
// Check TTLs are all the same
|
|
68
|
+
this.ttl = this.servers[0].ttl
|
|
69
|
+
this.servers.slice(1).forEach((server) => {
|
|
70
|
+
assert.equal(server.ttl, this.ttl, `TTL Mismatch (${server.ttl} != ${this.ttl})`)
|
|
71
|
+
})
|
|
72
|
+
|
|
65
73
|
// Freeze our lists of servers
|
|
66
74
|
Object.freeze(this.servers)
|
|
67
75
|
}
|
|
@@ -75,43 +83,47 @@ export class ClusterAdapter implements Adapter {
|
|
|
75
83
|
return this.servers[hash % this.servers.length]
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
get(key: string
|
|
79
|
-
return this.server(key).get(key
|
|
86
|
+
get(key: string): Promise<AdapterResult | undefined> {
|
|
87
|
+
return this.server(key).get(key)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
gat(key: string, ttl: number): Promise<AdapterResult | undefined> {
|
|
91
|
+
return this.server(key).gat(key, ttl)
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
touch(key: string,
|
|
83
|
-
return this.server(key).touch(key,
|
|
94
|
+
touch(key: string, ttl?: number): Promise<boolean> {
|
|
95
|
+
return this.server(key).touch(key, ttl)
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
set(key: string, value: Buffer, options?: { flags?: number
|
|
98
|
+
set(key: string, value: Buffer, options?: { flags?: number; cas?: bigint; ttl?: number }): Promise<bigint | undefined> {
|
|
87
99
|
return this.server(key).set(key, value, options)
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
add(key: string, value: Buffer, options?: { flags?: number
|
|
102
|
+
add(key: string, value: Buffer, options?: { flags?: number; ttl?: number }): Promise<bigint | undefined> {
|
|
91
103
|
return this.server(key).add(key, value, options)
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
replace(key: string, value: Buffer, options?: { flags?: number
|
|
106
|
+
replace(key: string, value: Buffer, options?: { flags?: number; cas?: bigint; ttl?: number }): Promise<bigint | undefined> {
|
|
95
107
|
return this.server(key).replace(key, value, options)
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
append(key: string, value: Buffer, options?: { cas?: bigint
|
|
110
|
+
append(key: string, value: Buffer, options?: { cas?: bigint }): Promise<boolean> {
|
|
99
111
|
return this.server(key).append(key, value, options)
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
prepend(key: string, value: Buffer, options?: { cas?: bigint
|
|
114
|
+
prepend(key: string, value: Buffer, options?: { cas?: bigint }): Promise<boolean> {
|
|
103
115
|
return this.server(key).prepend(key, value, options)
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
increment(key: string, delta?: number | bigint, options?: { initial?: number | bigint
|
|
118
|
+
increment(key: string, delta?: number | bigint, options?: { initial?: number | bigint; cas?: bigint; ttl?: number; create?: boolean }): Promise<Counter | undefined> {
|
|
107
119
|
return this.server(key).increment(key, delta, options)
|
|
108
120
|
}
|
|
109
121
|
|
|
110
|
-
decrement(key: string, delta?: number | bigint, options?: { initial?: number | bigint
|
|
122
|
+
decrement(key: string, delta?: number | bigint, options?: { initial?: number | bigint; cas?: bigint; ttl?: number; create?: boolean }): Promise<Counter | undefined> {
|
|
111
123
|
return this.server(key).decrement(key, delta, options)
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
delete(key: string, options?: { cas?: bigint
|
|
126
|
+
delete(key: string, options?: { cas?: bigint }): Promise<boolean> {
|
|
115
127
|
return this.server(key).delete(key, options)
|
|
116
128
|
}
|
|
117
129
|
|
package/src/connection.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Decoder, RawIncomingPacket } from './decode'
|
|
|
6
6
|
import { BUFFERS, OPCODE } from './constants'
|
|
7
7
|
import { socketFinalizationRegistry } from './internals'
|
|
8
8
|
|
|
9
|
-
type RawIncomingPackets = [ RawIncomingPacket, ...RawIncomingPacket[] ]
|
|
9
|
+
export type RawIncomingPackets = [ RawIncomingPacket, ...RawIncomingPacket[] ]
|
|
10
10
|
|
|
11
11
|
class Deferred {
|
|
12
12
|
#resolve!: (value: RawIncomingPackets) => void
|
package/src/encode.ts
CHANGED
|
@@ -58,7 +58,7 @@ export class Encoder {
|
|
|
58
58
|
const bodyLength = extrasLength + keyLength + valueLength
|
|
59
59
|
const length = bodyLength + CONSTANTS.HEADER_SIZE
|
|
60
60
|
|
|
61
|
-
const buffer = allocateBuffer(length)
|
|
61
|
+
const buffer = allocateBuffer(length)
|
|
62
62
|
|
|
63
63
|
buffer.writeUInt8(MAGIC.REQUEST, OFFSETS.MAGIC_$8)
|
|
64
64
|
buffer.writeUInt8(opcode, OFFSETS.OPCODE_$8)
|
package/src/fake.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Adapter, AdapterResult, Counter, Stats } from './types'
|
|
2
|
+
|
|
3
|
+
interface Entry {
|
|
4
|
+
value: Buffer,
|
|
5
|
+
flags: number,
|
|
6
|
+
cas: bigint,
|
|
7
|
+
exp: number,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function toExp(ttl: number = 0): number {
|
|
11
|
+
if (ttl === 0) return Number.MAX_SAFE_INTEGER
|
|
12
|
+
return Date.now() + (ttl * 1000)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class FakeAdapter implements Adapter {
|
|
16
|
+
#cache = new Map<string, Entry>()
|
|
17
|
+
#cas = 1n
|
|
18
|
+
|
|
19
|
+
readonly ttl = 0
|
|
20
|
+
|
|
21
|
+
#get(key: string): Entry | undefined {
|
|
22
|
+
if (key.length > 250) throw new TypeError(`Key too long (len=${key.length})`)
|
|
23
|
+
const entry = this.#cache.get(key)
|
|
24
|
+
if (! entry) return
|
|
25
|
+
|
|
26
|
+
if (Date.now() > entry.exp) {
|
|
27
|
+
this.#cache.delete(key)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return entry
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#set(key: string, value: Buffer, flags?: number, ttl?: number): bigint {
|
|
35
|
+
this.#cache.set(key, {
|
|
36
|
+
value: value,
|
|
37
|
+
flags: flags || 0,
|
|
38
|
+
cas: ++this.#cas,
|
|
39
|
+
exp: toExp(ttl),
|
|
40
|
+
})
|
|
41
|
+
return this.#cas
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async get(
|
|
45
|
+
key: string,
|
|
46
|
+
): Promise<AdapterResult | undefined> {
|
|
47
|
+
const entry = this.#get(key)
|
|
48
|
+
if (! entry) return
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
value: entry.value,
|
|
52
|
+
flags: entry.flags,
|
|
53
|
+
cas: entry.cas,
|
|
54
|
+
recycle: () => void 0,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async gat(
|
|
59
|
+
key: string,
|
|
60
|
+
ttl: number,
|
|
61
|
+
): Promise<AdapterResult | undefined> {
|
|
62
|
+
const entry = this.#get(key)
|
|
63
|
+
if (! entry) return
|
|
64
|
+
|
|
65
|
+
entry.exp = toExp(ttl)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
value: entry.value,
|
|
69
|
+
flags: entry.flags,
|
|
70
|
+
cas: entry.cas,
|
|
71
|
+
recycle: () => void 0,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async touch(
|
|
76
|
+
key: string,
|
|
77
|
+
ttl: number,
|
|
78
|
+
): Promise<boolean> {
|
|
79
|
+
const entry = this.#get(key)
|
|
80
|
+
if (entry) entry.exp = toExp(ttl)
|
|
81
|
+
return !! entry
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async set(
|
|
85
|
+
key: string,
|
|
86
|
+
value: Buffer,
|
|
87
|
+
options: { flags?: number; cas?: bigint; ttl?: number } = {},
|
|
88
|
+
): Promise<bigint | undefined> {
|
|
89
|
+
const entry = this.#get(key)
|
|
90
|
+
if (entry && (options.cas !== undefined) && (entry.cas !== options.cas)) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this.#set(key, value, options.flags, options.ttl)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async add(
|
|
98
|
+
key: string,
|
|
99
|
+
value: Buffer,
|
|
100
|
+
options: { flags?: number; ttl?: number } = {},
|
|
101
|
+
): Promise<bigint | undefined> {
|
|
102
|
+
if (this.#get(key)) return
|
|
103
|
+
return this.#set(key, value, options.flags, options.ttl)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async replace(
|
|
107
|
+
key: string,
|
|
108
|
+
value: Buffer,
|
|
109
|
+
options: { flags?: number; cas?: bigint; ttl?: number } = {},
|
|
110
|
+
): Promise<bigint | undefined> {
|
|
111
|
+
if (! this.#get(key)) return
|
|
112
|
+
return this.#set(key, value, options.flags, options.ttl)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async append(
|
|
116
|
+
key: string,
|
|
117
|
+
value: Buffer,
|
|
118
|
+
options: { cas?: bigint } = {},
|
|
119
|
+
): Promise<boolean> {
|
|
120
|
+
const entry = this.#get(key)
|
|
121
|
+
if (! entry) return false
|
|
122
|
+
|
|
123
|
+
if ((options.cas !== undefined) && (options.cas !== entry.cas)) return false
|
|
124
|
+
|
|
125
|
+
entry.value = Buffer.concat([ entry.value, value ])
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async prepend(
|
|
130
|
+
key: string,
|
|
131
|
+
value: Buffer,
|
|
132
|
+
options: { cas?: bigint } = {},
|
|
133
|
+
): Promise<boolean> {
|
|
134
|
+
const entry = this.#get(key)
|
|
135
|
+
if (! entry) return false
|
|
136
|
+
|
|
137
|
+
if ((options.cas !== undefined) && (options.cas !== entry.cas)) return false
|
|
138
|
+
|
|
139
|
+
entry.value = Buffer.concat([ value, entry.value ])
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async #counter(
|
|
144
|
+
key: string,
|
|
145
|
+
delta: number | bigint,
|
|
146
|
+
options: { initial?: number | bigint; cas?: bigint; ttl?: number; create?: boolean },
|
|
147
|
+
): Promise<Counter | undefined> {
|
|
148
|
+
const entry = this.#get(key)
|
|
149
|
+
|
|
150
|
+
if (! entry) {
|
|
151
|
+
if (options.initial !== undefined) {
|
|
152
|
+
const value = Buffer.from(options.initial.toString())
|
|
153
|
+
this.#set(key, value, undefined, options.ttl)
|
|
154
|
+
return { value: BigInt(options.initial), cas: this.#cas }
|
|
155
|
+
} else {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if ((options.cas !== undefined) && (options.cas !== entry.cas)) return
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const value = BigInt(entry.value.toString('utf-8')) + BigInt(delta)
|
|
164
|
+
this.#set(key, Buffer.from(value.toString()), undefined, options.ttl)
|
|
165
|
+
return { value, cas: this.#cas }
|
|
166
|
+
} catch (error: any) {
|
|
167
|
+
throw new TypeError(`${error.message} (status=NON_NUMERIC_VALUE, key=${key})`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
increment(
|
|
172
|
+
key: string,
|
|
173
|
+
delta: number | bigint = 1n,
|
|
174
|
+
options: { initial?: number | bigint; cas?: bigint; ttl?: number; create?: boolean } = {},
|
|
175
|
+
): Promise<Counter | undefined> {
|
|
176
|
+
return this.#counter(key, delta, options)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
decrement(
|
|
180
|
+
key: string,
|
|
181
|
+
delta: number | bigint = 1n,
|
|
182
|
+
options: { initial?: number | bigint; cas?: bigint; ttl?: number; create?: boolean } = {},
|
|
183
|
+
): Promise<Counter | undefined> {
|
|
184
|
+
return this.#counter(key, -delta, options)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async delete(
|
|
188
|
+
key: string,
|
|
189
|
+
options: { cas?: bigint } = {},
|
|
190
|
+
): Promise<boolean> {
|
|
191
|
+
const entry = this.#get(key)
|
|
192
|
+
if (entry && (options.cas !== undefined) && (entry.cas !== options.cas)) {
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this.#cache.delete(key)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async flush(
|
|
200
|
+
ttl?: number,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
if (! ttl) return this.#cache.clear()
|
|
203
|
+
|
|
204
|
+
const wait = toExp(ttl) - Date.now()
|
|
205
|
+
setTimeout(() => this.#cache.clear(), wait)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async noop(): Promise<void> {
|
|
209
|
+
// noop!
|
|
210
|
+
}
|
|
211
|
+
async quit(): Promise<void> {
|
|
212
|
+
// noop!
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async version(): Promise<Record<string, string>> {
|
|
216
|
+
return { fake: '0.0.0-fake' }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async stats(): Promise<Record<string, Stats>> {
|
|
220
|
+
return { fake: { version: '0.0.0-fake' } as Stats }
|
|
221
|
+
}
|
|
222
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { RecyclableBuffer } from './buffers'
|
|
2
|
+
|
|
1
3
|
import * as decode from './decode'
|
|
2
4
|
import * as encode from './encode'
|
|
3
5
|
import * as constants from './constants'
|
|
@@ -10,7 +12,9 @@ export {
|
|
|
10
12
|
connection,
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
export * from './fake'
|
|
13
16
|
export * from './types'
|
|
14
17
|
export * from './cluster'
|
|
15
18
|
export * from './server'
|
|
16
19
|
export * from './client'
|
|
20
|
+
export * from './utils'
|
package/src/internals.ts
CHANGED
|
@@ -39,3 +39,8 @@ export function typedArrayFlags(value: NodeJS.TypedArray): FLAGS {
|
|
|
39
39
|
assert.fail('Unsupported kind of TypedArray')
|
|
40
40
|
return flags
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
export function logPromiseError(promise: Promise<any>, message: string): Promise<void> {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
return promise.catch((error) => console.log(message, error)).then(() => void 0)
|
|
46
|
+
}
|