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/dist/index.js +116 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -78
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +18 -17
- package/package.json +1 -1
- package/src/buffers.ts +1 -1
- package/src/client.ts +69 -52
- package/src/cluster.ts +14 -2
- package/src/connection.ts +1 -1
- package/src/fake.ts +17 -4
- package/src/index.ts +2 -0
- package/src/server.ts +17 -6
- package/src/types.ts +7 -1
- package/src/utils.ts +12 -7
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
|
|
144
|
-
const result = await this.#adapter.get(this.#prefix + key
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
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
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
|
-
|
|
50
|
-
|
|
51
|
-
entry.
|
|
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
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
23
|
-
if (cached)
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|