encore.dev 1.54.2 → 1.55.0

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.
Files changed (41) hide show
  1. package/config/secrets.ts +7 -1
  2. package/dist/config/secrets.js +4 -0
  3. package/dist/config/secrets.js.map +1 -1
  4. package/dist/internal/runtime/napi/napi.cjs +3 -1
  5. package/dist/internal/runtime/napi/napi.d.cts +114 -1
  6. package/dist/storage/cache/basic.d.ts +268 -0
  7. package/dist/storage/cache/basic.js +383 -0
  8. package/dist/storage/cache/basic.js.map +1 -0
  9. package/dist/storage/cache/cluster.d.ts +48 -0
  10. package/dist/storage/cache/cluster.js +40 -0
  11. package/dist/storage/cache/cluster.js.map +1 -0
  12. package/dist/storage/cache/errors.d.ts +19 -0
  13. package/dist/storage/cache/errors.js +59 -0
  14. package/dist/storage/cache/errors.js.map +1 -0
  15. package/dist/storage/cache/expiry.d.ts +55 -0
  16. package/dist/storage/cache/expiry.js +74 -0
  17. package/dist/storage/cache/expiry.js.map +1 -0
  18. package/dist/storage/cache/keyspace.d.ts +77 -0
  19. package/dist/storage/cache/keyspace.js +100 -0
  20. package/dist/storage/cache/keyspace.js.map +1 -0
  21. package/dist/storage/cache/list.d.ts +249 -0
  22. package/dist/storage/cache/list.js +376 -0
  23. package/dist/storage/cache/list.js.map +1 -0
  24. package/dist/storage/cache/mod.d.ts +10 -0
  25. package/dist/storage/cache/mod.js +13 -0
  26. package/dist/storage/cache/mod.js.map +1 -0
  27. package/dist/storage/cache/set.d.ts +258 -0
  28. package/dist/storage/cache/set.js +411 -0
  29. package/dist/storage/cache/set.js.map +1 -0
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/internal/runtime/napi/napi.cjs +3 -1
  32. package/internal/runtime/napi/napi.d.cts +114 -1
  33. package/package.json +6 -1
  34. package/storage/cache/basic.ts +511 -0
  35. package/storage/cache/cluster.ts +67 -0
  36. package/storage/cache/errors.ts +66 -0
  37. package/storage/cache/expiry.ts +98 -0
  38. package/storage/cache/keyspace.ts +142 -0
  39. package/storage/cache/list.ts +496 -0
  40. package/storage/cache/mod.ts +36 -0
  41. package/storage/cache/set.ts +491 -0
@@ -0,0 +1,491 @@
1
+ import { getCurrentRequest } from "../../internal/reqtrack/mod";
2
+ import { CacheCluster } from "./cluster";
3
+ import { Keyspace, KeyspaceConfig, WriteOptions } from "./keyspace";
4
+
5
+ /**
6
+ * Base class for set keyspaces with all set operations.
7
+ * Subclasses provide typed serialization/deserialization.
8
+ * @internal
9
+ */
10
+ abstract class SetKeyspace<K, V> extends Keyspace<K> {
11
+ constructor(cluster: CacheCluster, config: KeyspaceConfig<K>) {
12
+ super(cluster, config);
13
+ }
14
+
15
+ protected abstract serializeItem(value: V): Buffer;
16
+ protected abstract deserializeItem(data: Buffer): V;
17
+
18
+ /**
19
+ * Adds one or more values to the set stored at key.
20
+ * If the key does not already exist, it is first created as an empty set.
21
+ *
22
+ * @returns The number of values that were added to the set,
23
+ * not including values already present beforehand.
24
+ * @see https://redis.io/commands/sadd/
25
+ */
26
+ async add(key: K, ...members: V[]): Promise<number> {
27
+ const source = getCurrentRequest();
28
+ const mappedKey = this.mapKey(key);
29
+ const serialized = members.map((m) => this.serializeItem(m));
30
+ const ttlMs = this.resolveTtl();
31
+ const result = await this.cluster.impl.sadd(
32
+ mappedKey,
33
+ serialized,
34
+ ttlMs,
35
+ source
36
+ );
37
+ return Number(result);
38
+ }
39
+
40
+ /**
41
+ * Removes one or more values from the set stored at key.
42
+ * Values not present in the set are ignored.
43
+ * If the key does not already exist, it is a no-op.
44
+ *
45
+ * @returns The number of values that were removed from the set.
46
+ * @see https://redis.io/commands/srem/
47
+ */
48
+ async remove(key: K, ...members: V[]): Promise<number> {
49
+ const source = getCurrentRequest();
50
+ const mappedKey = this.mapKey(key);
51
+ const serialized = members.map((m) => this.serializeItem(m));
52
+ const ttlMs = this.resolveTtl();
53
+ const result = await this.cluster.impl.srem(
54
+ mappedKey,
55
+ serialized,
56
+ ttlMs,
57
+ source
58
+ );
59
+ return Number(result);
60
+ }
61
+
62
+ /**
63
+ * Removes a random element from the set stored at key and returns it.
64
+ *
65
+ * @returns The removed member, or `undefined` if the set is empty.
66
+ * @see https://redis.io/commands/spop/
67
+ */
68
+ async popOne(key: K, options?: WriteOptions): Promise<V | undefined> {
69
+ const source = getCurrentRequest();
70
+ const mappedKey = this.mapKey(key);
71
+ const ttlMs = this.resolveTtl(options);
72
+ const result = await this.cluster.impl.spop(mappedKey, ttlMs, source);
73
+ if (result === null || result === undefined) {
74
+ return undefined;
75
+ }
76
+ return this.deserializeItem(result);
77
+ }
78
+
79
+ /**
80
+ * Removes up to `count` random elements (bounded by the set's size)
81
+ * from the set stored at key and returns them.
82
+ *
83
+ * If the set is empty it returns an empty array.
84
+ *
85
+ * @param key - The cache key.
86
+ * @param count - Number of members to pop.
87
+ * @returns The removed members (may be fewer than `count` if the set is small).
88
+ * @see https://redis.io/commands/spop/
89
+ */
90
+ async pop(key: K, count: number, options?: WriteOptions): Promise<V[]> {
91
+ const source = getCurrentRequest();
92
+ const mappedKey = this.mapKey(key);
93
+ const ttlMs = this.resolveTtl(options);
94
+ const results = await this.cluster.impl.spopN(
95
+ mappedKey,
96
+ count,
97
+ ttlMs,
98
+ source
99
+ );
100
+ return results.map((r) => this.deserializeItem(r));
101
+ }
102
+
103
+ /**
104
+ * Reports whether the set stored at key contains the given value.
105
+ *
106
+ * If the key does not exist it returns `false`.
107
+ *
108
+ * @returns `true` if the member exists in the set, `false` otherwise.
109
+ * @see https://redis.io/commands/sismember/
110
+ */
111
+ async contains(key: K, member: V): Promise<boolean> {
112
+ const source = getCurrentRequest();
113
+ const mappedKey = this.mapKey(key);
114
+ const serialized = this.serializeItem(member);
115
+ return await this.cluster.impl.sismember(mappedKey, serialized, source);
116
+ }
117
+
118
+ /**
119
+ * Returns the number of elements in the set stored at key.
120
+ *
121
+ * If the key does not exist it returns 0.
122
+ *
123
+ * @returns The set cardinality.
124
+ * @see https://redis.io/commands/scard/
125
+ */
126
+ async len(key: K): Promise<number> {
127
+ const source = getCurrentRequest();
128
+ const mappedKey = this.mapKey(key);
129
+ const result = await this.cluster.impl.scard(mappedKey, source);
130
+ return Number(result);
131
+ }
132
+
133
+ /**
134
+ * Returns the elements in the set stored at key.
135
+ *
136
+ * If the key does not exist it returns an empty array.
137
+ *
138
+ * @returns All members of the set.
139
+ * @see https://redis.io/commands/smembers/
140
+ */
141
+ async items(key: K): Promise<V[]> {
142
+ const source = getCurrentRequest();
143
+ const mappedKey = this.mapKey(key);
144
+ const results = await this.cluster.impl.smembers(mappedKey, source);
145
+ return results.map((r) => this.deserializeItem(r));
146
+ }
147
+
148
+ /**
149
+ * Identical to {@link items} except it returns the values as a `Set`.
150
+ *
151
+ * If the key does not exist it returns an empty `Set`.
152
+ *
153
+ * @returns All members of the set as a `Set`.
154
+ * @see https://redis.io/commands/smembers/
155
+ */
156
+ async itemsSet(key: K): Promise<Set<V>> {
157
+ const members = await this.items(key);
158
+ return new Set(members);
159
+ }
160
+
161
+ /**
162
+ * Computes the set difference between the first set and all the consecutive sets.
163
+ *
164
+ * Set difference means the values present in the first set that are not present
165
+ * in any of the other sets.
166
+ *
167
+ * Keys that do not exist are considered as empty sets.
168
+ *
169
+ * @param keys - Keys of sets to compute difference for. At least one must be provided.
170
+ * @returns Members in the first set but not in any of the other sets.
171
+ * @throws {Error} If no keys are provided.
172
+ * @see https://redis.io/commands/sdiff/
173
+ */
174
+ async diff(...keys: K[]): Promise<V[]> {
175
+ if (keys.length === 0) {
176
+ throw new Error("at least one key must be provided");
177
+ }
178
+ const source = getCurrentRequest();
179
+ const mappedKeys = keys.map((k) => this.mapKey(k));
180
+ const results = await this.cluster.impl.sdiff(mappedKeys, source);
181
+ return results.map((r) => this.deserializeItem(r));
182
+ }
183
+
184
+ /**
185
+ * Identical to {@link diff} except it returns the values as a `Set`.
186
+ *
187
+ * @see https://redis.io/commands/sdiff/
188
+ */
189
+ async diffSet(...keys: K[]): Promise<Set<V>> {
190
+ const items = await this.diff(...keys);
191
+ return new Set(items);
192
+ }
193
+
194
+ /**
195
+ * Computes the set difference between keys (like {@link diff}) and stores the result
196
+ * in `destination`.
197
+ *
198
+ * @param destination - Key to store the result.
199
+ * @param keys - Keys of sets to compute difference for.
200
+ * @returns The size of the resulting set.
201
+ * @see https://redis.io/commands/sdiffstore/
202
+ */
203
+ async diffStore(destination: K, ...keys: K[]): Promise<number> {
204
+ if (keys.length === 0) {
205
+ throw new Error("at least one key must be provided");
206
+ }
207
+ const source = getCurrentRequest();
208
+ const destKey = this.mapKey(destination);
209
+ const mappedKeys = keys.map((k) => this.mapKey(k));
210
+ const ttlMs = this.resolveTtl();
211
+ const result = await this.cluster.impl.sdiffstore(
212
+ destKey,
213
+ mappedKeys,
214
+ ttlMs,
215
+ source
216
+ );
217
+ return Number(result);
218
+ }
219
+
220
+ /**
221
+ * Computes the set intersection between the sets stored at the given keys.
222
+ *
223
+ * Set intersection means the values common to all the provided sets.
224
+ *
225
+ * Keys that do not exist are considered to be empty sets.
226
+ * As a result, if any key is missing the final result is the empty set.
227
+ *
228
+ * @param keys - Keys of sets to compute intersection for. At least one must be provided.
229
+ * @returns Members common to all sets.
230
+ * @throws {Error} If no keys are provided.
231
+ * @see https://redis.io/commands/sinter/
232
+ */
233
+ async intersect(...keys: K[]): Promise<V[]> {
234
+ if (keys.length === 0) {
235
+ throw new Error("at least one key must be provided");
236
+ }
237
+ const source = getCurrentRequest();
238
+ const mappedKeys = keys.map((k) => this.mapKey(k));
239
+ const results = await this.cluster.impl.sinter(mappedKeys, source);
240
+ return results.map((r) => this.deserializeItem(r));
241
+ }
242
+
243
+ /**
244
+ * Identical to {@link intersect} except it returns the values as a `Set`.
245
+ *
246
+ * @see https://redis.io/commands/sinter/
247
+ */
248
+ async intersectSet(...keys: K[]): Promise<Set<V>> {
249
+ const items = await this.intersect(...keys);
250
+ return new Set(items);
251
+ }
252
+
253
+ /**
254
+ * Computes the set intersection between keys (like {@link intersect}) and stores the result
255
+ * in `destination`.
256
+ *
257
+ * @param destination - Key to store the result.
258
+ * @param keys - Keys of sets to compute intersection for.
259
+ * @returns The size of the resulting set.
260
+ * @see https://redis.io/commands/sinterstore/
261
+ */
262
+ async intersectStore(destination: K, ...keys: K[]): Promise<number> {
263
+ if (keys.length === 0) {
264
+ throw new Error("at least one key must be provided");
265
+ }
266
+ const source = getCurrentRequest();
267
+ const destKey = this.mapKey(destination);
268
+ const mappedKeys = keys.map((k) => this.mapKey(k));
269
+ const ttlMs = this.resolveTtl();
270
+ const result = await this.cluster.impl.sinterstore(
271
+ destKey,
272
+ mappedKeys,
273
+ ttlMs,
274
+ source
275
+ );
276
+ return Number(result);
277
+ }
278
+
279
+ /**
280
+ * Computes the set union between the sets stored at the given keys.
281
+ *
282
+ * Set union means the values present in at least one of the provided sets.
283
+ *
284
+ * Keys that do not exist are considered to be empty sets.
285
+ *
286
+ * @param keys - Keys of sets to compute union for. At least one must be provided.
287
+ * @returns Members in any of the provided sets.
288
+ * @throws {Error} If no keys are provided.
289
+ * @see https://redis.io/commands/sunion/
290
+ */
291
+ async union(...keys: K[]): Promise<V[]> {
292
+ if (keys.length === 0) {
293
+ throw new Error("at least one key must be provided");
294
+ }
295
+ const source = getCurrentRequest();
296
+ const mappedKeys = keys.map((k) => this.mapKey(k));
297
+ const results = await this.cluster.impl.sunion(mappedKeys, source);
298
+ return results.map((r) => this.deserializeItem(r));
299
+ }
300
+
301
+ /**
302
+ * Identical to {@link union} except it returns the values as a `Set`.
303
+ *
304
+ * @see https://redis.io/commands/sunion/
305
+ */
306
+ async unionSet(...keys: K[]): Promise<Set<V>> {
307
+ const items = await this.union(...keys);
308
+ return new Set(items);
309
+ }
310
+
311
+ /**
312
+ * Computes the set union between sets (like {@link union}) and stores the result
313
+ * in `destination`.
314
+ *
315
+ * @param destination - Key to store the result.
316
+ * @param keys - Keys of sets to compute union for.
317
+ * @returns The size of the resulting set.
318
+ * @see https://redis.io/commands/sunionstore/
319
+ */
320
+ async unionStore(destination: K, ...keys: K[]): Promise<number> {
321
+ if (keys.length === 0) {
322
+ throw new Error("at least one key must be provided");
323
+ }
324
+ const source = getCurrentRequest();
325
+ const destKey = this.mapKey(destination);
326
+ const mappedKeys = keys.map((k) => this.mapKey(k));
327
+ const ttlMs = this.resolveTtl();
328
+ const result = await this.cluster.impl.sunionstore(
329
+ destKey,
330
+ mappedKeys,
331
+ ttlMs,
332
+ source
333
+ );
334
+ return Number(result);
335
+ }
336
+
337
+ /**
338
+ * Returns a random member from the set stored at key without removing it.
339
+ *
340
+ * @returns A random member, or `undefined` if the key does not exist.
341
+ * @see https://redis.io/commands/srandmember/
342
+ */
343
+ async sampleOne(key: K): Promise<V | undefined> {
344
+ const source = getCurrentRequest();
345
+ const mappedKey = this.mapKey(key);
346
+ const result = await this.cluster.impl.srandmember(mappedKey, source);
347
+ if (result === null || result === undefined) {
348
+ return undefined;
349
+ }
350
+ return this.deserializeItem(result);
351
+ }
352
+
353
+ /**
354
+ * Returns up to `count` distinct random elements from the set stored at key.
355
+ * The same element is never returned multiple times.
356
+ *
357
+ * If the key does not exist it returns an empty array.
358
+ *
359
+ * @param key - The cache key.
360
+ * @param count - Number of distinct members to return.
361
+ * @returns Random members (may be fewer than `count` if the set is small).
362
+ * @see https://redis.io/commands/srandmember/
363
+ */
364
+ async sample(key: K, count: number): Promise<V[]> {
365
+ if (count < 0) {
366
+ throw new Error("count must be non-negative");
367
+ }
368
+ const source = getCurrentRequest();
369
+ const mappedKey = this.mapKey(key);
370
+ const results = await this.cluster.impl.srandmemberN(
371
+ mappedKey,
372
+ count,
373
+ source
374
+ );
375
+ return results.map((r) => this.deserializeItem(r));
376
+ }
377
+
378
+ /**
379
+ * Returns `count` random elements from the set stored at key.
380
+ * The same element may be returned multiple times.
381
+ *
382
+ * If the key does not exist it returns an empty array.
383
+ *
384
+ * @param key - The cache key.
385
+ * @param count - Number of members to return (may include duplicates).
386
+ * @returns Random members, possibly with duplicates.
387
+ * @see https://redis.io/commands/srandmember/
388
+ */
389
+ async sampleWithReplacement(key: K, count: number): Promise<V[]> {
390
+ if (count < 0) {
391
+ throw new Error("count must be non-negative");
392
+ }
393
+ const source = getCurrentRequest();
394
+ const mappedKey = this.mapKey(key);
395
+ // Negative count in Redis SRANDMEMBER allows duplicates
396
+ const results = await this.cluster.impl.srandmemberN(
397
+ mappedKey,
398
+ -count,
399
+ source
400
+ );
401
+ return results.map((r) => this.deserializeItem(r));
402
+ }
403
+
404
+ /**
405
+ * Atomically moves the given member from the set stored at `src`
406
+ * to the set stored at `dst`.
407
+ *
408
+ * If the element already exists in `dst` it is still removed from `src`.
409
+ *
410
+ * @param src - Source set key.
411
+ * @param dst - Destination set key.
412
+ * @param member - The member to move.
413
+ * @returns `true` if the member was moved, `false` if not found in `src`.
414
+ * @see https://redis.io/commands/smove/
415
+ */
416
+ async move(
417
+ src: K,
418
+ dst: K,
419
+ member: V,
420
+ options?: WriteOptions
421
+ ): Promise<boolean> {
422
+ const source = getCurrentRequest();
423
+ const srcKey = this.mapKey(src);
424
+ const dstKey = this.mapKey(dst);
425
+ const serialized = this.serializeItem(member);
426
+ const ttlMs = this.resolveTtl(options);
427
+ return await this.cluster.impl.smove(
428
+ srcKey,
429
+ dstKey,
430
+ serialized,
431
+ ttlMs,
432
+ source
433
+ );
434
+ }
435
+ }
436
+
437
+ /**
438
+ * StringSetKeyspace stores sets of unique string values.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * const tags = new StringSetKeyspace<string>(cluster, {
443
+ * keyPattern: "tags/:articleId",
444
+ * });
445
+ *
446
+ * await tags.add("article1", "typescript", "programming", "web");
447
+ * const hasTech = await tags.contains("article1", "typescript");
448
+ * const allTags = await tags.items("article1");
449
+ * const tagSet = await tags.itemsSet("article1");
450
+ * ```
451
+ */
452
+ export class StringSetKeyspace<K> extends SetKeyspace<K, string> {
453
+ constructor(cluster: CacheCluster, config: KeyspaceConfig<K>) {
454
+ super(cluster, config);
455
+ }
456
+
457
+ protected serializeItem(value: string): Buffer {
458
+ return Buffer.from(value, "utf-8");
459
+ }
460
+
461
+ protected deserializeItem(data: Buffer): string {
462
+ return data.toString("utf-8");
463
+ }
464
+ }
465
+
466
+ /**
467
+ * NumberSetKeyspace stores sets of unique numeric values.
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * const scores = new NumberSetKeyspace<string>(cluster, {
472
+ * keyPattern: "unique-scores/:gameId",
473
+ * });
474
+ *
475
+ * await scores.add("game1", 100, 200, 300);
476
+ * const hasScore = await scores.contains("game1", 100);
477
+ * ```
478
+ */
479
+ export class NumberSetKeyspace<K> extends SetKeyspace<K, number> {
480
+ constructor(cluster: CacheCluster, config: KeyspaceConfig<K>) {
481
+ super(cluster, config);
482
+ }
483
+
484
+ protected serializeItem(value: number): Buffer {
485
+ return Buffer.from(String(value), "utf-8");
486
+ }
487
+
488
+ protected deserializeItem(data: Buffer): number {
489
+ return Number(data.toString("utf-8"));
490
+ }
491
+ }