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/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
- function toBuffer<T>(value: any, options: T): [ Buffer, T & { flags: number } ] {
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
- return [ Buffer.from(JSON.stringify(value), 'utf-8'), { ...options, flags: FLAGS.JSON } ]
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: new (buffer: ArrayBuffer, offset?: number, length?: number) => T,
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 / bytesPerValue)
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
- async get<T extends Serializable>(key: string, options?: { ttl?: number }): Promise<ClientResult<T> | void> {
83
- const result = await this.#adapter.get(key, options)
84
- if (! result) return
85
-
86
- try {
87
- const { flags, value, cas } = result
88
- switch (flags) {
89
- case FLAGS.BIGINT:
90
- return { value: BigInt(value.toString('utf-8')) as T, cas }
91
- case FLAGS.BOOLEAN:
92
- return { value: !!value[0] as T, cas }
93
- case FLAGS.NUMBER:
94
- return { value: Number(value.toString('utf-8' )) as T, cas }
95
- case FLAGS.STRING:
96
- return { value: value.toString('utf-8' ) as T, cas }
97
-
98
- case FLAGS.NULL:
99
- return { value: null as T, cas }
100
- case FLAGS.JSON:
101
- return { value: JSON.parse(value.toString('utf-8' )) as T, cas }
102
-
103
- case FLAGS.UINT8ARRAY:
104
- return { value: makeTypedArray(Uint8Array, value, 1) as T, cas }
105
- case FLAGS.UINT8CLAMPEDARRAY:
106
- return { value: makeTypedArray(Uint8ClampedArray, value, 1) as T, cas }
107
- case FLAGS.UINT16ARRAY:
108
- return { value: makeTypedArray(Uint16Array, value, 2) as T, cas }
109
- case FLAGS.UINT32ARRAY:
110
- return { value: makeTypedArray(Uint32Array, value, 4) as T, cas }
111
- case FLAGS.INT8ARRAY:
112
- return { value: makeTypedArray(Int8Array, value, 1) as T, cas }
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 | void> {
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?: { cas?: bigint, ttl?: number }): Promise<bigint | void> {
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 | void> {
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 | void> {
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 (counter && options &&
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 | void> {
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 (counter && options &&
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
- options?: { ttl?: number },
271
+ ttl?: number,
198
272
  ): Promise<boolean> {
199
- return this.#adapter.touch(key, options)
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, options?: { ttl?: number | undefined }): Promise<void | AdapterResult> {
79
- return this.server(key).get(key, options)
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, options?: { ttl?: number | undefined }): Promise<boolean> {
83
- return this.server(key).touch(key, options)
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 | undefined; cas?: bigint | undefined; ttl?: number | undefined }): Promise<bigint | void> {
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 | undefined; cas?: bigint | undefined; ttl?: number | undefined }): Promise<bigint | void> {
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 | undefined; cas?: bigint | undefined; ttl?: number | undefined }): Promise<bigint | void> {
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 | undefined }): Promise<boolean> {
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 | undefined }): Promise<boolean> {
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 | undefined; cas?: bigint | undefined; ttl?: number | undefined; create?: boolean | undefined }): Promise<void | Counter> {
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 | undefined; cas?: bigint | undefined; ttl?: number | undefined; create?: boolean | undefined }): Promise<void | Counter> {
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 | undefined }): Promise<boolean> {
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) // /* length <= buffer.length ? buffer : */ Buffer.allocUnsafe(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
+ }