memx 0.0.4 → 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,7 +4,7 @@ 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
8
 
9
9
  /** JSON replacere function serializing `bigint`, {@link Date}, {@link Set} and {@link Map}. */
10
10
  function replacer(this: any, key: string, value: any): any {
@@ -68,6 +68,56 @@ type TypedArrayConstructor<T extends NodeJS.TypedArray> = {
68
68
  BYTES_PER_ELEMENT: number
69
69
  }
70
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
+
71
121
  /** Create a {@link NodeJS.TypedArray} copying the contents of its source {@link Buffer} */
72
122
  function makeTypedArray<T extends NodeJS.TypedArray>(
73
123
  constructor: TypedArrayConstructor<T>,
@@ -140,57 +190,24 @@ export class Client {
140
190
  return client
141
191
  }
142
192
 
143
- async get<T extends Serializable>(key: string, options?: { ttl?: number }): Promise<ClientResult<T> | undefined> {
144
- const result = await this.#adapter.get(this.#prefix + key, options)
145
- if (! result) return
146
-
147
- try {
148
- const { flags, value, cas } = result
149
- switch (flags) {
150
- case FLAGS.BIGINT:
151
- return { value: BigInt(value.toString('utf-8')) as T, cas }
152
- case FLAGS.BOOLEAN:
153
- return { value: !!value[0] as T, cas }
154
- case FLAGS.NUMBER:
155
- return { value: Number(value.toString('utf-8' )) as T, cas }
156
- case FLAGS.STRING:
157
- return { value: value.toString('utf-8' ) as T, cas }
158
-
159
- case FLAGS.NULL:
160
- return { value: null as T, cas }
161
- case FLAGS.JSON:
162
- return { value: JSON.parse(value.toString('utf-8' ), reviver) as T, cas }
163
-
164
- case FLAGS.UINT8ARRAY:
165
- return { value: makeTypedArray(Uint8Array, value) as T, cas }
166
- case FLAGS.UINT8CLAMPEDARRAY:
167
- return { value: makeTypedArray(Uint8ClampedArray, value) as T, cas }
168
- case FLAGS.UINT16ARRAY:
169
- return { value: makeTypedArray(Uint16Array, value) as T, cas }
170
- case FLAGS.UINT32ARRAY:
171
- return { value: makeTypedArray(Uint32Array, value) as T, cas }
172
- case FLAGS.INT8ARRAY:
173
- return { value: makeTypedArray(Int8Array, value) as T, cas }
174
- case FLAGS.INT16ARRAY:
175
- return { value: makeTypedArray(Int16Array, value) as T, cas }
176
- case FLAGS.INT32ARRAY:
177
- return { value: makeTypedArray(Int32Array, value) as T, cas }
178
- case FLAGS.BIGUINT64ARRAY:
179
- return { value: makeTypedArray(BigUint64Array, value) as T, cas }
180
- case FLAGS.BIGINT64ARRAY:
181
- return { value: makeTypedArray(BigInt64Array, value) as T, cas }
182
- case FLAGS.FLOAT32ARRAY:
183
- return { value: makeTypedArray(Float32Array, value) as T, cas }
184
- case FLAGS.FLOAT64ARRAY:
185
- return { value: makeTypedArray(Float64Array, value) as T, cas }
186
-
187
- case FLAGS.BUFFER:
188
- default:
189
- return { value: Buffer.from(value) as T, cas }
190
- }
191
- } finally {
192
- result.recycle()
193
- }
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)
194
211
  }
195
212
 
196
213
  async set(key: string, value: Serializable, options?: { cas?: bigint, ttl?: number }): Promise<bigint | undefined> {
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,8 +83,12 @@ 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 }): Promise<AdapterResult | undefined> {
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
94
  touch(key: string, ttl?: number): Promise<boolean> {
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/fake.ts CHANGED
@@ -16,6 +16,8 @@ export class FakeAdapter implements Adapter {
16
16
  #cache = new Map<string, Entry>()
17
17
  #cas = 1n
18
18
 
19
+ readonly ttl = 0
20
+
19
21
  #get(key: string): Entry | undefined {
20
22
  if (key.length > 250) throw new TypeError(`Key too long (len=${key.length})`)
21
23
  const entry = this.#cache.get(key)
@@ -41,15 +43,26 @@ export class FakeAdapter implements Adapter {
41
43
 
42
44
  async get(
43
45
  key: string,
44
- options: { ttl?: number } = {},
45
46
  ): Promise<AdapterResult | undefined> {
46
47
  const entry = this.#get(key)
47
48
  if (! entry) return
48
49
 
49
- if (options.ttl !== undefined) {
50
- const exp = toExp(options.ttl)
51
- entry.exp = exp
50
+ return {
51
+ value: entry.value,
52
+ flags: entry.flags,
53
+ cas: entry.cas,
54
+ recycle: () => void 0,
52
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)
53
66
 
54
67
  return {
55
68
  value: entry.value,
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'
package/src/server.ts CHANGED
@@ -107,14 +107,12 @@ export class ServerAdapter implements Adapter {
107
107
 
108
108
  /* ======================================================================== */
109
109
 
110
- async get(
110
+ async #get(
111
111
  key: string,
112
- options: { ttl?: number } = {},
112
+ ttl?: number,
113
113
  ): Promise<AdapterResult | undefined> {
114
- const { ttl } = options
115
-
116
114
  let keyOffset = 0
117
- if (ttl) keyOffset = this.#buffer.writeUInt32BE(ttl)
115
+ if (ttl !== undefined) keyOffset = this.#buffer.writeUInt32BE(ttl)
118
116
  const keyLength = this.#writeKey(key, keyOffset)
119
117
 
120
118
  const [ response ] = await this.#connection.send({
@@ -142,11 +140,24 @@ export class ServerAdapter implements Adapter {
142
140
  }
143
141
  }
144
142
 
143
+ async get(
144
+ key: string,
145
+ ): Promise<AdapterResult | undefined> {
146
+ return this.#get(key)
147
+ }
148
+
149
+ async gat(
150
+ key: string,
151
+ ttl: number,
152
+ ): Promise<AdapterResult | undefined> {
153
+ return this.#get(key, ttl || 2592000) // TTL 0 is "invalid arg"
154
+ }
155
+
145
156
  async touch(
146
157
  key: string,
147
158
  ttl?: number,
148
159
  ): Promise<boolean> {
149
- const timeToLive = ttl === undefined ? this.#ttl : ttl
160
+ const timeToLive = ttl ?? this.#ttl
150
161
 
151
162
  const keyOffset = this.#buffer.writeUInt32BE(timeToLive)
152
163
  const keyLength = this.#writeKey(key, keyOffset)
package/src/types.ts CHANGED
@@ -194,9 +194,15 @@ export interface Stats {
194
194
  }
195
195
 
196
196
  export interface Adapter {
197
+ readonly ttl: number
198
+
197
199
  get(
198
200
  key: string,
199
- options?: { ttl?: number },
201
+ ): Promise<AdapterResult | undefined>
202
+
203
+ gat(
204
+ key: string,
205
+ ttl: number,
200
206
  ): Promise<AdapterResult | undefined>
201
207
 
202
208
  touch(
package/src/utils.ts CHANGED
@@ -19,8 +19,13 @@ export class Factory<T extends Serializable> {
19
19
  }
20
20
 
21
21
  async get(key: string): Promise<T> {
22
- const cached = await this.#client.get(key, { ttl: this.#ttl })
23
- if (cached) return cached.value as T
22
+ const cached = await this.#client.getc(key)
23
+ if (cached) {
24
+ void logPromiseError(
25
+ this.#client.touch(key),
26
+ `Factory error touching key "${this.#client.prefix}${key}"`)
27
+ return cached.value as T
28
+ }
24
29
 
25
30
  const created = await this.#factory(key)
26
31
  if (created) {
@@ -58,7 +63,7 @@ export class Bundle<T extends Serializable = Serializable> {
58
63
 
59
64
  async #removeKey(key: string): Promise<void> {
60
65
  await logPromiseError((async (): Promise<void> => {
61
- const result = await this.#client.get<string>(this.#name)
66
+ const result = await this.#client.getc<string>(this.#name)
62
67
  if (! result) return
63
68
  const keys = result.value.split('\0').filter((k) => k !== key).join('\0')
64
69
  await this.#client.set(this.#name, keys, { cas: result.cas, ttl: this.#ttl })
@@ -71,7 +76,7 @@ export class Bundle<T extends Serializable = Serializable> {
71
76
  }
72
77
 
73
78
  async get(key: string): Promise<T | undefined> {
74
- const result = await this.#client.get<T>(`${this.#name}:${key}`)
79
+ const result = await this.#client.getc<T>(`${this.#name}:${key}`)
75
80
  if (result) return result.value
76
81
  await this.#removeKey(key)
77
82
  }
@@ -82,14 +87,14 @@ export class Bundle<T extends Serializable = Serializable> {
82
87
  }
83
88
 
84
89
  async list(): Promise<Record<string, T>> {
85
- const result = await this.#client.get<string>(this.#name)
90
+ const result = await this.#client.getc<string>(this.#name)
86
91
  if (! result) return {}
87
92
 
88
93
  const results: Record<string, T> = {}
89
94
  const promises: Promise<void>[] = []
90
95
 
91
96
  for (const key of new Set(result.value.split('\0'))) {
92
- promises.push(this.#client.get<T>(`${this.#name}:${key}`).then((result) => {
97
+ promises.push(this.#client.getc<T>(`${this.#name}:${key}`).then((result) => {
93
98
  if (result) results[key] = result.value
94
99
  }))
95
100
  }
@@ -131,7 +136,7 @@ export class PoorManLock {
131
136
  } while (Date.now() < end)
132
137
 
133
138
  if (cas === undefined) {
134
- const other = await this.#client.get(this.#name)
139
+ const other = await this.#client.getc(this.#name)
135
140
  const owner = (other && other.value) ? `"${other.value}"` : 'anonymous'
136
141
  throw new Error(`Lock "${this.#client.prefix}${this.#name}" timeout (owner=${owner})`)
137
142
  }