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/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 } = {},
113
- ): Promise<AdapterResult | void> {
114
- const { ttl } = options
115
-
112
+ ttl?: number,
113
+ ): Promise<AdapterResult | undefined> {
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,13 +140,26 @@ 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
- options: { ttl?: number } = {},
158
+ ttl?: number,
148
159
  ): Promise<boolean> {
149
- const { ttl = this.#ttl } = options
160
+ const timeToLive = ttl ?? this.#ttl
150
161
 
151
- const keyOffset = this.#buffer.writeUInt32BE(ttl)
162
+ const keyOffset = this.#buffer.writeUInt32BE(timeToLive)
152
163
  const keyLength = this.#writeKey(key, keyOffset)
153
164
 
154
165
  const [ response ] = await this.#connection.send({
@@ -182,7 +193,7 @@ export class ServerAdapter implements Adapter {
182
193
  key: string,
183
194
  value: Buffer,
184
195
  options: { flags?: number, cas?: bigint, ttl?: number },
185
- ): Promise<bigint | void> {
196
+ ): Promise<bigint | undefined> {
186
197
  const { flags = 0, cas = 0n, ttl = this.#ttl } = options
187
198
 
188
199
  let keyOffset: number
@@ -221,15 +232,15 @@ export class ServerAdapter implements Adapter {
221
232
  key: string,
222
233
  value: Buffer,
223
234
  options: { flags?: number, cas?: bigint, ttl?: number } = {},
224
- ): Promise<bigint | void> {
235
+ ): Promise<bigint | undefined> {
225
236
  return this.#sar(OPCODE.SET, key, value, options)
226
237
  }
227
238
 
228
239
  add(
229
240
  key: string,
230
241
  value: Buffer,
231
- options: { flags?: number, cas?: bigint, ttl?: number } = {},
232
- ): Promise<bigint | void> {
242
+ options: { flags?: number, ttl?: number } = {},
243
+ ): Promise<bigint | undefined> {
233
244
  return this.#sar(OPCODE.ADD, key, value, options)
234
245
  }
235
246
 
@@ -237,7 +248,7 @@ export class ServerAdapter implements Adapter {
237
248
  key: string,
238
249
  value: Buffer,
239
250
  options: { flags?: number, cas?: bigint, ttl?: number } = {},
240
- ): Promise<bigint | void> {
251
+ ): Promise<bigint | undefined> {
241
252
  return this.#sar(OPCODE.REPLACE, key, value, options)
242
253
  }
243
254
 
@@ -300,7 +311,7 @@ export class ServerAdapter implements Adapter {
300
311
  key: string,
301
312
  delta: bigint | number,
302
313
  options: { initial?: bigint | number, cas?: bigint, ttl?: number },
303
- ): Promise<Counter | void> {
314
+ ): Promise<Counter | undefined> {
304
315
  const {
305
316
  initial,
306
317
  cas = 0n,
@@ -346,7 +357,7 @@ export class ServerAdapter implements Adapter {
346
357
  key: string,
347
358
  delta: bigint | number = 1,
348
359
  options: { initial?: bigint | number, cas?: bigint, ttl?: number, create?: boolean } = {},
349
- ): Promise<Counter | void> {
360
+ ): Promise<Counter | undefined> {
350
361
  return this.#counter(OPCODE.INCREMENT, key, delta, options)
351
362
  }
352
363
 
@@ -354,7 +365,7 @@ export class ServerAdapter implements Adapter {
354
365
  key: string,
355
366
  delta: bigint | number = 1,
356
367
  options: { initial?: bigint | number, cas?: bigint, ttl?: number, create?: boolean } = {},
357
- ): Promise<Counter | void> {
368
+ ): Promise<Counter | undefined> {
358
369
  return this.#counter(OPCODE.DECREMENT, key, delta, options)
359
370
  }
360
371
 
package/src/types.ts CHANGED
@@ -194,33 +194,39 @@ 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 },
200
- ): Promise<AdapterResult | void>
201
+ ): Promise<AdapterResult | undefined>
202
+
203
+ gat(
204
+ key: string,
205
+ ttl: number,
206
+ ): Promise<AdapterResult | undefined>
201
207
 
202
208
  touch(
203
209
  key: string,
204
- options?: { ttl?: number },
210
+ ttl?: number,
205
211
  ): Promise<boolean>
206
212
 
207
213
  set(
208
214
  key: string,
209
215
  value: Buffer,
210
216
  options?: { flags?: number, cas?: bigint, ttl?: number },
211
- ): Promise<bigint | void>
217
+ ): Promise<bigint | undefined>
212
218
 
213
219
  add(
214
220
  key: string,
215
221
  value: Buffer,
216
- options?: { flags?: number, cas?: bigint, ttl?: number },
217
- ): Promise<bigint | void>
222
+ options?: { flags?: number, ttl?: number },
223
+ ): Promise<bigint | undefined>
218
224
 
219
225
  replace(
220
226
  key: string,
221
227
  value: Buffer,
222
228
  options?: { flags?: number, cas?: bigint, ttl?: number },
223
- ): Promise<bigint | void>
229
+ ): Promise<bigint | undefined>
224
230
 
225
231
  append(
226
232
  key: string,
@@ -238,13 +244,13 @@ export interface Adapter {
238
244
  key: string,
239
245
  delta?: bigint | number,
240
246
  options?: { initial?: bigint | number, cas?: bigint, ttl?: number, create?: boolean },
241
- ): Promise<Counter | void>
247
+ ): Promise<Counter | undefined>
242
248
 
243
249
  decrement(
244
250
  key: string,
245
251
  delta?: bigint | number,
246
252
  options?: { initial?: bigint | number, cas?: bigint, ttl?: number, create?: boolean },
247
- ): Promise<Counter | void>
253
+ ): Promise<Counter | undefined>
248
254
 
249
255
  delete(
250
256
  key: string,
package/src/utils.ts ADDED
@@ -0,0 +1,161 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import assert from 'assert'
4
+ import { Client, Serializable } from './client'
5
+ import { logPromiseError } from './internals'
6
+
7
+ export class Factory<T extends Serializable> {
8
+ #factory: (key: string) => T | Promise<T>
9
+ #client: Client
10
+ #ttl?: number
11
+
12
+ constructor(client: Client, factory: (key: string) => T | Promise<T>, ttl?: number) {
13
+ assert(typeof factory === 'function', 'Invalid or no factory specified')
14
+ assert(client, 'No client specified')
15
+
16
+ this.#factory = factory
17
+ this.#client = client
18
+ this.#ttl = ttl
19
+ }
20
+
21
+ async get(key: string): Promise<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
+ }
29
+
30
+ const created = await this.#factory(key)
31
+ if (created) {
32
+ void logPromiseError(
33
+ this.#client.set(key, created, { ttl: this.#ttl }),
34
+ `Factory error setting key "${this.#client.prefix}${key}"`)
35
+ }
36
+ return created
37
+ }
38
+ }
39
+
40
+ export class Bundle<T extends Serializable = Serializable> {
41
+ #client: Client
42
+ #name: string
43
+ #ttl: number
44
+
45
+ constructor(client: Client, name: string, ttl?: number) {
46
+ assert(client, 'No client specified')
47
+ assert(name, 'No bundle name specified')
48
+
49
+ this.#client = client
50
+ this.#name = name
51
+ this.#ttl = ttl || 0
52
+ }
53
+
54
+ async #appendKey(key: string): Promise<void> {
55
+ await logPromiseError((async (): Promise<void> => {
56
+ const added = await this.#client.add(this.#name, key, { ttl: this.#ttl })
57
+ if (!added) {
58
+ await this.#client.append(this.#name, `\0${key}`)
59
+ await this.#client.touch(this.#name, this.#ttl)
60
+ }
61
+ })(), `Bundle "${this.#client.prefix}${this.#name}" error recording key "${key}"`)
62
+ }
63
+
64
+ async #removeKey(key: string): Promise<void> {
65
+ await logPromiseError((async (): Promise<void> => {
66
+ const result = await this.#client.getc<string>(this.#name)
67
+ if (! result) return
68
+ const keys = result.value.split('\0').filter((k) => k !== key).join('\0')
69
+ await this.#client.set(this.#name, keys, { cas: result.cas, ttl: this.#ttl })
70
+ })(), `Bundle "${this.#client.prefix}${this.#name}" error clearing key "${key}"`)
71
+ }
72
+
73
+ async add(key: string, value: T): Promise<void> {
74
+ await this.#client.set(`${this.#name}:${key}`, value, { ttl: this.#ttl })
75
+ await this.#appendKey(key)
76
+ }
77
+
78
+ async get(key: string): Promise<T | undefined> {
79
+ const result = await this.#client.getc<T>(`${this.#name}:${key}`)
80
+ if (result) return result.value
81
+ await this.#removeKey(key)
82
+ }
83
+
84
+ async delete(key: string): Promise<void> {
85
+ await this.#client.delete(`${this.#name}:${key}`)
86
+ await this.#removeKey(key)
87
+ }
88
+
89
+ async list(): Promise<Record<string, T>> {
90
+ const result = await this.#client.getc<string>(this.#name)
91
+ if (! result) return {}
92
+
93
+ const results: Record<string, T> = {}
94
+ const promises: Promise<void>[] = []
95
+
96
+ for (const key of new Set(result.value.split('\0'))) {
97
+ promises.push(this.#client.getc<T>(`${this.#name}:${key}`).then((result) => {
98
+ if (result) results[key] = result.value
99
+ }))
100
+ }
101
+
102
+ await Promise.all(promises)
103
+
104
+ await logPromiseError(
105
+ this.#client.set(this.#name, Object.keys(results).join('\0'), { cas: result.cas, ttl: this.#ttl }),
106
+ `Bundle "${this.#client.prefix}${this.#name}" error compacting keys`)
107
+
108
+ return results
109
+ }
110
+ }
111
+
112
+ export class PoorManLock {
113
+ #client: Client
114
+ #name: string
115
+
116
+ constructor(client: Client, name: string) {
117
+ assert(client, 'No client specified')
118
+ assert(name, 'No lock name specified')
119
+
120
+ this.#client = client
121
+ this.#name = name
122
+ }
123
+
124
+ async execute<T>(
125
+ executor: () => T | Promise<T>,
126
+ options?: { timeout?: number, owner?: string },
127
+ ): Promise<T> {
128
+ const { timeout = 5000, owner = false } = options || {}
129
+ const end = Date.now() + timeout
130
+
131
+ let cas: bigint | undefined
132
+ do {
133
+ cas = await this.#client.add(this.#name, owner, { ttl: 2 })
134
+ if (cas !== undefined) break
135
+ await new Promise((resolve) => setTimeout(resolve, 100))
136
+ } while (Date.now() < end)
137
+
138
+ if (cas === undefined) {
139
+ const other = await this.#client.getc(this.#name)
140
+ const owner = (other && other.value) ? `"${other.value}"` : 'anonymous'
141
+ throw new Error(`Lock "${this.#client.prefix}${this.#name}" timeout (owner=${owner})`)
142
+ }
143
+
144
+ const interval = setInterval(() => {
145
+ void logPromiseError((async (): Promise<void> => {
146
+ const replaced = await this.#client.replace(this.#name, owner, { ttl: 2, cas })
147
+ assert(replaced !== undefined, `Lock "${this.#client.prefix}${this.#name}" not replaced`)
148
+ cas = replaced
149
+ })(), `Error extending lock "${this.#client.prefix}${this.#name}"`)
150
+ }, 100)
151
+
152
+ try {
153
+ return await executor()
154
+ } finally {
155
+ clearInterval(interval)
156
+ await logPromiseError(
157
+ this.#client.delete(this.#name, { cas }),
158
+ `Error deleting lock "${this.#client.prefix}${this.#name}"`)
159
+ }
160
+ }
161
+ }