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/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
|
-
|
|
113
|
-
): Promise<AdapterResult |
|
|
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
|
-
|
|
158
|
+
ttl?: number,
|
|
148
159
|
): Promise<boolean> {
|
|
149
|
-
const
|
|
160
|
+
const timeToLive = ttl ?? this.#ttl
|
|
150
161
|
|
|
151
|
-
const keyOffset = this.#buffer.writeUInt32BE(
|
|
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 |
|
|
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 |
|
|
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,
|
|
232
|
-
): Promise<bigint |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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 |
|
|
217
|
+
): Promise<bigint | undefined>
|
|
212
218
|
|
|
213
219
|
add(
|
|
214
220
|
key: string,
|
|
215
221
|
value: Buffer,
|
|
216
|
-
options?: { flags?: number,
|
|
217
|
-
): Promise<bigint |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
+
}
|