unikvs 0.0.2 → 0.0.3

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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/core/_assign-context.d.ts +3 -0
  3. package/dist/src/core/_assign-context.d.ts.map +1 -0
  4. package/dist/src/core/_assign-context.js +3 -0
  5. package/dist/src/core/_combine-signals.d.ts +3 -0
  6. package/dist/src/core/_combine-signals.d.ts.map +1 -0
  7. package/dist/src/core/_combine-signals.js +34 -0
  8. package/dist/src/core/_is-iterable.d.ts +2 -0
  9. package/dist/src/core/_is-iterable.d.ts.map +1 -0
  10. package/dist/src/core/_is-iterable.js +3 -0
  11. package/dist/src/core/_is-plain-object.d.ts +2 -0
  12. package/dist/src/core/_is-plain-object.d.ts.map +1 -0
  13. package/dist/src/core/_is-plain-object.js +7 -0
  14. package/dist/src/core/_logger.d.ts +3 -0
  15. package/dist/src/core/_logger.d.ts.map +1 -0
  16. package/dist/src/core/_logger.js +3 -0
  17. package/dist/src/core/_merge-context.d.ts +3 -0
  18. package/dist/src/core/_merge-context.d.ts.map +1 -0
  19. package/dist/src/core/_merge-context.js +4 -0
  20. package/dist/src/core/_to-context.d.ts +7 -0
  21. package/dist/src/core/_to-context.d.ts.map +1 -0
  22. package/dist/src/core/_to-context.js +42 -0
  23. package/dist/src/core/_to-value-stream.d.ts +4 -0
  24. package/dist/src/core/_to-value-stream.d.ts.map +1 -0
  25. package/dist/src/core/_to-value-stream.js +38 -0
  26. package/dist/src/core/_with-readable-stream-from.d.ts +6 -0
  27. package/dist/src/core/_with-readable-stream-from.d.ts.map +1 -0
  28. package/dist/src/core/_with-readable-stream-from.js +58 -0
  29. package/dist/src/core/context.types.d.ts +5 -0
  30. package/dist/src/core/context.types.d.ts.map +1 -0
  31. package/dist/src/core/context.types.js +1 -0
  32. package/dist/src/core/errors.d.ts +13 -0
  33. package/dist/src/core/errors.d.ts.map +1 -0
  34. package/dist/src/core/errors.js +20 -0
  35. package/dist/src/core/index.d.ts +15 -0
  36. package/dist/src/core/index.d.ts.map +1 -0
  37. package/dist/src/core/index.js +4 -0
  38. package/dist/src/core/plugin.types.d.ts +8 -0
  39. package/dist/src/core/plugin.types.d.ts.map +1 -0
  40. package/dist/src/core/plugin.types.js +1 -0
  41. package/dist/src/core/storage.types.d.ts +22 -0
  42. package/dist/src/core/storage.types.d.ts.map +1 -0
  43. package/dist/src/core/storage.types.js +1 -0
  44. package/dist/src/core/to-readable-stream.d.ts +2 -0
  45. package/dist/src/core/to-readable-stream.d.ts.map +1 -0
  46. package/dist/src/core/to-readable-stream.js +4 -0
  47. package/dist/src/core/transformer.types.d.ts +18 -0
  48. package/dist/src/core/transformer.types.d.ts.map +1 -0
  49. package/dist/src/core/transformer.types.js +1 -0
  50. package/dist/src/core/unikvs-config.d.ts +82 -0
  51. package/dist/src/core/unikvs-config.d.ts.map +1 -0
  52. package/dist/src/core/unikvs-config.js +49 -0
  53. package/dist/src/core/unikvs.d.ts +73 -0
  54. package/dist/src/core/unikvs.d.ts.map +1 -0
  55. package/dist/src/core/unikvs.js +678 -0
  56. package/dist/src/core/utils.types.d.ts +3 -0
  57. package/dist/src/core/utils.types.d.ts.map +1 -0
  58. package/dist/src/core/utils.types.js +1 -0
  59. package/dist/src/core/value-stream.types.d.ts +4 -0
  60. package/dist/src/core/value-stream.types.d.ts.map +1 -0
  61. package/dist/src/core/value-stream.types.js +1 -0
  62. package/dist/src/destinations/memory-storage.d.ts +17 -0
  63. package/dist/src/destinations/memory-storage.d.ts.map +1 -0
  64. package/dist/src/destinations/memory-storage.js +113 -0
  65. package/dist/src/types/storage.d.ts +5 -0
  66. package/dist/src/types/storage.d.ts.map +1 -0
  67. package/dist/src/types/storage.js +1 -0
  68. package/dist/src/types/transformer.d.ts +5 -0
  69. package/dist/src/types/transformer.d.ts.map +1 -0
  70. package/dist/src/types/transformer.js +1 -0
  71. package/package.json +69 -6
  72. package/src/core/_assign-context.ts +5 -0
  73. package/src/core/_combine-signals.ts +40 -0
  74. package/src/core/_is-iterable.ts +3 -0
  75. package/src/core/_is-plain-object.ts +11 -0
  76. package/src/core/_logger.ts +5 -0
  77. package/src/core/_merge-context.ts +9 -0
  78. package/src/core/_to-context.ts +59 -0
  79. package/src/core/_to-value-stream.ts +41 -0
  80. package/src/core/_with-readable-stream-from.ts +70 -0
  81. package/src/core/context.types.ts +7 -0
  82. package/src/core/errors.ts +37 -0
  83. package/src/core/index.ts +23 -0
  84. package/src/core/plugin.types.ts +10 -0
  85. package/src/core/storage.types.ts +53 -0
  86. package/src/core/to-readable-stream.ts +7 -0
  87. package/src/core/transformer.types.ts +110 -0
  88. package/src/core/unikvs-config.ts +230 -0
  89. package/src/core/unikvs.ts +740 -0
  90. package/src/core/utils.types.ts +3 -0
  91. package/src/core/value-stream.types.ts +3 -0
  92. package/src/destinations/memory-storage.ts +160 -0
  93. package/src/types/storage.ts +5 -0
  94. package/src/types/transformer.ts +5 -0
  95. package/README.md +0 -45
@@ -0,0 +1,740 @@
1
+ import { asyncmux, Asyncmux } from "asyncmux";
2
+
3
+ import combineSignals from "./_combine-signals.js";
4
+ import log from "./_logger.js";
5
+ import mergeContext from "./_merge-context.js";
6
+ import toValueStream from "./_to-value-stream.js";
7
+ import type { Context, ContextSource } from "./context.types.js";
8
+ import type { IPlugin } from "./plugin.types.js";
9
+ import type { IReadableStream, IStorage, Key } from "./storage.types.js";
10
+ import type { ITransformer } from "./transformer.types.js";
11
+ import UniKvsConfig, {
12
+ type $InferPlainValueData,
13
+ type $InferStreamValueChunkData,
14
+ type IUniKvsConfigBuilder,
15
+ type KeyofKeyValueMapping,
16
+ type KeyValueMapping,
17
+ type PlainValue,
18
+ type StreamValue,
19
+ type Value,
20
+ } from "./unikvs-config.js";
21
+ import type { ValueOf } from "./utils.types.js";
22
+ import type { ValueStream } from "./value-stream.types.js";
23
+
24
+ export type InputValue<TValue extends Value> =
25
+ | $InferPlainValueData<TValue>
26
+ | (TValue extends StreamValue<infer TChunkData> ? ReadableStream<TChunkData> : never);
27
+
28
+ export type KeyofKeyValueMappingHasPlainValue<TKeyValueMapping extends KeyValueMapping> = ValueOf<{
29
+ [TKey in KeyofKeyValueMapping<TKeyValueMapping>]: PlainValue extends TKeyValueMapping[TKey]
30
+ ? TKey
31
+ : never;
32
+ }>;
33
+
34
+ export type KeyofKeyValueMappingHasStreamValue<TKeyValueMapping extends KeyValueMapping> = ValueOf<{
35
+ [TKey in KeyofKeyValueMapping<TKeyValueMapping>]: StreamValue extends TKeyValueMapping[TKey]
36
+ ? TKey
37
+ : never;
38
+ }>;
39
+
40
+ export type OpenOptions = {
41
+ readonly signal?: AbortSignal | undefined;
42
+ };
43
+
44
+ export type CloseOptions = {
45
+ readonly signal?: AbortSignal | undefined;
46
+ };
47
+
48
+ export type SetOptions<
49
+ TKeyValueMapping extends KeyValueMapping = KeyValueMapping,
50
+ TKey extends KeyofKeyValueMapping<TKeyValueMapping> = KeyofKeyValueMapping<TKeyValueMapping>,
51
+ > = {
52
+ readonly key: TKey;
53
+ readonly value: InputValue<TKeyValueMapping[TKey]>;
54
+ readonly signal?: AbortSignal | undefined;
55
+ readonly context?: Context | ContextSource | undefined;
56
+ };
57
+
58
+ export type GetOptions<TKey = Key> = {
59
+ readonly key: TKey;
60
+ readonly signal?: AbortSignal | undefined;
61
+ readonly context?: Context | ContextSource | undefined;
62
+ };
63
+
64
+ export type StreamOptions<TKey = Key> = {
65
+ readonly key: TKey;
66
+ readonly signal?: AbortSignal | undefined;
67
+ readonly context?: Context | ContextSource | undefined;
68
+ };
69
+
70
+ export type HasOptions<TKey = Key> = {
71
+ readonly key: TKey;
72
+ readonly signal?: AbortSignal | undefined;
73
+ readonly context?: Context | ContextSource | undefined;
74
+ };
75
+
76
+ // export type KeysOptions = {
77
+ // readonly signal?: AbortSignal | undefined;
78
+ // readonly context?: Context | ContextSource | undefined;
79
+ // };
80
+
81
+ export type DeleteOptions<TKey = Key> = {
82
+ readonly key: TKey;
83
+ readonly signal?: AbortSignal | undefined;
84
+ readonly context?: Context | ContextSource | undefined;
85
+ };
86
+
87
+ export type ClearOptions = {
88
+ readonly signal?: AbortSignal | undefined;
89
+ readonly context?: Context | ContextSource | undefined;
90
+ };
91
+
92
+ export default class UniKvs<TKeyValueMapping extends KeyValueMapping = KeyValueMapping> {
93
+ public static config<
94
+ TKeyValueMapping extends KeyValueMapping,
95
+ >(): IUniKvsConfigBuilder<TKeyValueMapping> {
96
+ return new UniKvsConfig(this);
97
+ }
98
+
99
+ #con: null | {
100
+ ac: AbortController;
101
+ ioMux: Asyncmux;
102
+ };
103
+
104
+ readonly #context: Readonly<Context>;
105
+
106
+ readonly #destinations: readonly [IStorage, ...IStorage[]];
107
+
108
+ readonly #transformers: readonly ITransformer[];
109
+
110
+ readonly #transformerSet: ReadonlySet<IPlugin>;
111
+
112
+ public constructor(
113
+ context: Readonly<Context>,
114
+ destinations: readonly [IStorage, ...IStorage[]],
115
+ transformers: readonly ITransformer[],
116
+ ) {
117
+ this.#con = null;
118
+ this.#context = context;
119
+ this.#destinations = destinations;
120
+ this.#transformers = transformers;
121
+ this.#transformerSet = new Set(transformers);
122
+ }
123
+
124
+ public get isOpen(): boolean {
125
+ return !!this.#con;
126
+ }
127
+
128
+ public open(signal?: AbortSignal): Promise<void>;
129
+
130
+ public open(options?: OpenOptions): Promise<void>;
131
+
132
+ public open(signalOrOptions?: AbortSignal | OpenOptions): Promise<void>;
133
+
134
+ public async open(arg0: AbortSignal | OpenOptions = {}): Promise<void> {
135
+ if (this.#con) {
136
+ throw new Error();
137
+ }
138
+
139
+ this.#con = {
140
+ ac: new AbortController(),
141
+ ioMux: new Asyncmux(),
142
+ };
143
+ const { ac } = this.#con;
144
+
145
+ const { signal } = (function parseOpenArguments() {
146
+ const { signal } = arg0 instanceof AbortSignal ? { signal: arg0 } : arg0;
147
+
148
+ return {
149
+ signal: combineSignals([signal, ac.signal]),
150
+ };
151
+ })();
152
+
153
+ using _ = await asyncmux(this, signal);
154
+
155
+ if (this.#con.ac !== ac) {
156
+ throw new Error();
157
+ }
158
+
159
+ const context: Context = { "unikvs:action": "open" };
160
+ const plugins: IPlugin[] = [...this.#destinations, ...this.#transformers];
161
+ const openFns: (() => Promise<void>)[] = [];
162
+ const dispose: (() => Promise<void>)[] = [];
163
+ for (const plugin of plugins) {
164
+ if (plugin.isOpen || !plugin.open) {
165
+ continue;
166
+ }
167
+
168
+ openFns.push(async () => {
169
+ await plugin.open!(signal, context);
170
+ if (!plugin.close) {
171
+ return;
172
+ }
173
+
174
+ dispose.push(async () => {
175
+ try {
176
+ await plugin.close!(signal, context);
177
+ } catch (ex) {
178
+ if (!this.#transformerSet.has(plugin)) {
179
+ log.warn`Failed to close storage: ${ex}`;
180
+ } else {
181
+ log.warn`Failed to close transformer: ${ex}`;
182
+ }
183
+ }
184
+ });
185
+ });
186
+ }
187
+
188
+ try {
189
+ if (openFns.length > 0) {
190
+ const errors: unknown[] = [];
191
+ await Promise.all(
192
+ openFns.map(async (f) => {
193
+ try {
194
+ await f();
195
+ } catch (ex) {
196
+ errors.push(ex);
197
+ }
198
+ }),
199
+ );
200
+ switch (errors.length) {
201
+ case 0:
202
+ break;
203
+ case 1:
204
+ throw errors[0];
205
+ default:
206
+ throw new AggregateError(errors);
207
+ }
208
+ }
209
+ } catch (ex) {
210
+ if (dispose.length > 0) {
211
+ await Promise.all(dispose.map((f) => f()));
212
+ }
213
+
214
+ this.#con = null;
215
+
216
+ throw ex;
217
+ }
218
+ }
219
+
220
+ public close(signal?: AbortSignal): Promise<void>;
221
+
222
+ public close(options?: CloseOptions): Promise<void>;
223
+
224
+ public close(signalOrOptions?: AbortSignal | CloseOptions): Promise<void>;
225
+
226
+ public async close(arg0: AbortSignal | CloseOptions = {}): Promise<void> {
227
+ if (!this.#con) {
228
+ throw new Error();
229
+ }
230
+
231
+ const { signal } = (function parseCloseArguments() {
232
+ const { signal } = arg0 instanceof AbortSignal ? { signal: arg0 } : arg0;
233
+
234
+ return {
235
+ signal: combineSignals([signal, AbortSignal.timeout(10e3)]),
236
+ };
237
+ })();
238
+
239
+ const { ac, ioMux } = this.#con;
240
+ ac.abort();
241
+
242
+ using _lock = await asyncmux(this, signal);
243
+ using _ioLock = await ioMux.lock({ signal });
244
+
245
+ if (!this.#con) {
246
+ throw new Error();
247
+ }
248
+
249
+ const context: Context = { "unikvs:action": "close" };
250
+ const plugins: IPlugin[] = [...this.#destinations, ...this.#transformers];
251
+ const closeFns: (() => Promise<void>)[] = [];
252
+ for (const plugin of plugins) {
253
+ if (plugin.isOpen || !plugin.close) {
254
+ continue;
255
+ }
256
+
257
+ closeFns.push(async () => {
258
+ await plugin.close!(signal, context);
259
+ });
260
+ }
261
+
262
+ if (closeFns.length > 0) {
263
+ const errors: unknown[] = [];
264
+ await Promise.all(
265
+ closeFns.map(async (f) => {
266
+ try {
267
+ await f();
268
+ } catch (ex) {
269
+ errors.push(ex);
270
+ }
271
+ }),
272
+ );
273
+ switch (errors.length) {
274
+ case 0:
275
+ break;
276
+ case 1:
277
+ throw errors[0];
278
+ default:
279
+ throw new AggregateError(errors);
280
+ }
281
+ }
282
+ }
283
+
284
+ async #open(signal: AbortSignal, context: Context): Promise<AsyncDisposable> {
285
+ const plugins: IPlugin[] = [...this.#destinations, ...this.#transformers];
286
+ const openFns: (() => Promise<void>)[] = [];
287
+ const dispose: (() => Promise<void>)[] = [];
288
+ for (const plugin of plugins) {
289
+ if (plugin.isOpen || !plugin.open) {
290
+ continue;
291
+ }
292
+
293
+ openFns.push(async () => {
294
+ await plugin.open!(signal, context);
295
+ if (!plugin.close) {
296
+ return;
297
+ }
298
+
299
+ dispose.push(async () => {
300
+ try {
301
+ await plugin.close!(signal, context);
302
+ } catch (ex) {
303
+ if (!this.#transformerSet.has(plugin)) {
304
+ log.warn`Failed to close storage: ${ex}`;
305
+ } else {
306
+ log.warn`Failed to close transformer: ${ex}`;
307
+ }
308
+ }
309
+ });
310
+ });
311
+ }
312
+
313
+ if (openFns.length > 0) {
314
+ const errors: unknown[] = [];
315
+ await Promise.all(
316
+ openFns.map(async (f) => {
317
+ try {
318
+ await f();
319
+ } catch (ex) {
320
+ errors.push(ex);
321
+ }
322
+ }),
323
+ );
324
+ switch (errors.length) {
325
+ case 0:
326
+ break;
327
+ case 1:
328
+ throw errors[0];
329
+ default:
330
+ throw new AggregateError(errors);
331
+ }
332
+ }
333
+
334
+ return {
335
+ async [Symbol.asyncDispose]() {
336
+ if (dispose.length > 0) {
337
+ await Promise.all(dispose.map((f) => f()));
338
+ }
339
+ },
340
+ };
341
+ }
342
+
343
+ public set<const TKey extends KeyofKeyValueMapping<TKeyValueMapping>>(
344
+ key: TKey,
345
+ value: InputValue<TKeyValueMapping[TKey]>,
346
+ options?: Omit<SetOptions, "key" | "value">,
347
+ ): Promise<void>;
348
+
349
+ public set<const TKey extends KeyofKeyValueMapping<TKeyValueMapping>>(
350
+ options: SetOptions<TKeyValueMapping, TKey>,
351
+ ): Promise<void>;
352
+
353
+ public async set(
354
+ arg0: Key | SetOptions,
355
+ arg1?: unknown,
356
+ arg2?: Omit<SetOptions, "key" | "value">,
357
+ ): Promise<void> {
358
+ if (!this.#con) {
359
+ throw new Error();
360
+ }
361
+
362
+ const { ac, ioMux } = this.#con;
363
+ const { key, value, signal, context } = (function parseSetArguments(ctx) {
364
+ const { key, value, signal, context } =
365
+ typeof arg0 === "object"
366
+ ? arg0
367
+ : {
368
+ ...arg2,
369
+ key: arg0,
370
+ value: arg1,
371
+ };
372
+
373
+ return {
374
+ key,
375
+ value,
376
+ signal: combineSignals([signal, ac.signal]),
377
+ context: mergeContext(ctx, context),
378
+ };
379
+ })(this.#context);
380
+
381
+ using _lock = await asyncmux.readonly(this, signal);
382
+
383
+ if (!this.#con) {
384
+ throw new Error();
385
+ }
386
+
387
+ context["unikvs:action"] = "set";
388
+
389
+ await using _close = await this.#open(signal, context);
390
+
391
+ const errors: unknown[] = [];
392
+ if (value instanceof ReadableStream) {
393
+ let data = value;
394
+ for (const transformer of this.#transformers) {
395
+ const e = await transformer.getEncodable!(signal, context);
396
+ data = data.pipeThrough(e);
397
+ }
398
+
399
+ using _ioLock = await ioMux.lock({ key, signal });
400
+ await Promise.all(
401
+ this.#destinations.map(async (storage, i) => {
402
+ let r = data;
403
+ if (i !== this.#destinations.length - 1) {
404
+ [r, data] = data.tee();
405
+ }
406
+
407
+ try {
408
+ const w = await storage.getWritable!(key, signal, context);
409
+ await r.pipeTo(w, { signal });
410
+ } catch (ex) {
411
+ errors.push(ex);
412
+ }
413
+ }),
414
+ );
415
+ } else {
416
+ let data = value;
417
+ for (const transformer of this.#transformers) {
418
+ data = await transformer.encode(data, signal, context);
419
+ }
420
+
421
+ using _ioLock = await ioMux.lock({ key, signal });
422
+ await Promise.all(
423
+ this.#destinations.map(async (storage) => {
424
+ try {
425
+ await storage.write(key, data, signal, context);
426
+ } catch (ex) {
427
+ errors.push(ex);
428
+ }
429
+ }),
430
+ );
431
+ }
432
+
433
+ switch (errors.length) {
434
+ case 0:
435
+ break;
436
+ case 1:
437
+ throw errors[0];
438
+ default:
439
+ throw new AggregateError(errors);
440
+ }
441
+ }
442
+
443
+ public get<const TKey extends KeyofKeyValueMappingHasPlainValue<TKeyValueMapping>>(
444
+ key: TKey,
445
+ options?: Omit<GetOptions, "key">,
446
+ ): Promise<$InferPlainValueData<TKeyValueMapping[TKey]>>;
447
+
448
+ public get<const TKey extends KeyofKeyValueMappingHasPlainValue<TKeyValueMapping>>(
449
+ options: GetOptions<TKey>,
450
+ ): Promise<$InferPlainValueData<TKeyValueMapping[TKey]>>;
451
+
452
+ public async get(arg0: Key | GetOptions, arg1: Omit<GetOptions, "key"> = {}): Promise<unknown> {
453
+ if (!this.#con) {
454
+ throw new Error();
455
+ }
456
+
457
+ const { ac, ioMux } = this.#con;
458
+ const { key, signal, context } = (function parseGetArguments(ctx) {
459
+ const { key, signal, context } =
460
+ typeof arg0 === "object"
461
+ ? arg0
462
+ : {
463
+ ...arg1,
464
+ key: arg0,
465
+ };
466
+
467
+ return {
468
+ key,
469
+ signal: combineSignals([signal, ac.signal]),
470
+ context: mergeContext(ctx, context),
471
+ };
472
+ })(this.#context);
473
+
474
+ using _lock = await asyncmux.readonly(this, signal);
475
+
476
+ if (!this.#con) {
477
+ throw new Error();
478
+ }
479
+
480
+ context["unikvs:action"] = "get";
481
+
482
+ await using _close = await this.#open(signal, context);
483
+
484
+ const NONE = {};
485
+ let data: unknown = NONE;
486
+
487
+ {
488
+ using _ioLock = await ioMux.rLock({ key, signal });
489
+ for (const storage of this.#destinations) {
490
+ if (await storage.exists(key, signal, context)) {
491
+ data = await storage.read(key, signal, context);
492
+ }
493
+ }
494
+ }
495
+
496
+ if (data === NONE) {
497
+ throw new Error();
498
+ }
499
+
500
+ if (this.#transformers.length > 0) {
501
+ for (const transformer of this.#transformers.toReversed()) {
502
+ data = await transformer.decode(data, signal, context);
503
+ }
504
+ }
505
+
506
+ return data;
507
+ }
508
+
509
+ public stream<const TKey extends KeyofKeyValueMappingHasStreamValue<TKeyValueMapping>>(
510
+ key: TKey,
511
+ options?: Omit<StreamOptions, "key">,
512
+ ): Promise<ValueStream<$InferStreamValueChunkData<TKeyValueMapping[TKey]>>>;
513
+
514
+ public stream<const TKey extends KeyofKeyValueMappingHasStreamValue<TKeyValueMapping>>(
515
+ options: StreamOptions<TKey>,
516
+ ): Promise<ValueStream<$InferStreamValueChunkData<TKeyValueMapping[TKey]>>>;
517
+
518
+ public async stream(
519
+ arg0: Key | StreamOptions,
520
+ arg1: Omit<GetOptions, "key"> = {},
521
+ ): Promise<ValueStream<unknown>> {
522
+ if (!this.#con) {
523
+ throw new Error();
524
+ }
525
+
526
+ const { ac, ioMux } = this.#con;
527
+ const { key, signal, context } = (function parseStreamArguments(ctx) {
528
+ const { key, signal, context } =
529
+ typeof arg0 === "object"
530
+ ? arg0
531
+ : {
532
+ ...arg1,
533
+ key: arg0,
534
+ };
535
+
536
+ return {
537
+ key,
538
+ signal: combineSignals([signal, ac.signal]),
539
+ context: mergeContext(ctx, context),
540
+ };
541
+ })(this.#context);
542
+
543
+ using _lock = await asyncmux.readonly(this, signal);
544
+
545
+ if (!this.#con) {
546
+ throw new Error();
547
+ }
548
+
549
+ context["unikvs:action"] = "stream";
550
+
551
+ await using _close = await this.#open(signal, context);
552
+
553
+ const NONE: any = {};
554
+ let r: IReadableStream = NONE;
555
+
556
+ {
557
+ using _ioLock = await ioMux.rLock({ key, signal });
558
+ for (const storage of this.#destinations) {
559
+ if (await storage.exists(key, signal, context)) {
560
+ r = await storage.getReadable!(key, signal, context);
561
+ }
562
+ }
563
+ }
564
+
565
+ if (r === NONE) {
566
+ throw new Error();
567
+ }
568
+
569
+ if (this.#transformers.length > 0) {
570
+ for (const transformer of this.#transformers.toReversed()) {
571
+ const d = await transformer.getDecodable!(signal, context);
572
+ r = r.pipeThrough(d);
573
+ }
574
+ }
575
+
576
+ return toValueStream(r);
577
+ }
578
+
579
+ public has(
580
+ key: KeyofKeyValueMapping<TKeyValueMapping>,
581
+ options?: Omit<HasOptions, "key">,
582
+ ): Promise<boolean>;
583
+
584
+ public has(options: HasOptions<KeyofKeyValueMapping<TKeyValueMapping>>): Promise<boolean>;
585
+
586
+ public async has(arg0: Key | HasOptions, arg1: Omit<HasOptions, "key"> = {}): Promise<boolean> {
587
+ if (!this.#con) {
588
+ throw new Error();
589
+ }
590
+
591
+ const { ac, ioMux } = this.#con;
592
+ const { key, signal, context } = (function parseHasArguments(ctx) {
593
+ const { key, signal, context } =
594
+ typeof arg0 === "object"
595
+ ? arg0
596
+ : {
597
+ ...arg1,
598
+ key: arg0,
599
+ };
600
+
601
+ return {
602
+ key,
603
+ signal: combineSignals([signal, ac.signal]),
604
+ context: mergeContext(ctx, context),
605
+ };
606
+ })(this.#context);
607
+
608
+ using _lock = await asyncmux.readonly(this, signal);
609
+
610
+ if (!this.#con) {
611
+ throw new Error();
612
+ }
613
+
614
+ context["unikvs:action"] = "has";
615
+
616
+ await using _close = await this.#open(signal, context);
617
+
618
+ using _ioLock = await ioMux.rLock({ key, signal });
619
+ for (const storage of this.#destinations) {
620
+ if (await storage.exists(key, signal, context)) {
621
+ return true;
622
+ }
623
+ }
624
+
625
+ return false;
626
+ }
627
+
628
+ public delete(
629
+ key: KeyofKeyValueMapping<TKeyValueMapping>,
630
+ options?: Omit<DeleteOptions, "key">,
631
+ ): Promise<void>;
632
+
633
+ public delete(options: DeleteOptions<KeyofKeyValueMapping<TKeyValueMapping>>): Promise<void>;
634
+
635
+ public async delete(
636
+ arg0: Key | DeleteOptions,
637
+ arg1: Omit<DeleteOptions, "key"> = {},
638
+ ): Promise<void> {
639
+ if (!this.#con) {
640
+ throw new Error();
641
+ }
642
+
643
+ const { ac, ioMux } = this.#con;
644
+ const { key, signal, context } = (function parseDeleteArguments(ctx) {
645
+ const { key, signal, context } =
646
+ typeof arg0 === "object"
647
+ ? arg0
648
+ : {
649
+ ...arg1,
650
+ key: arg0,
651
+ };
652
+
653
+ return {
654
+ key,
655
+ signal: combineSignals([signal, ac.signal]),
656
+ context: mergeContext(ctx, context),
657
+ };
658
+ })(this.#context);
659
+
660
+ using _lock = await asyncmux.readonly(this, signal);
661
+
662
+ if (!this.#con) {
663
+ throw new Error();
664
+ }
665
+
666
+ context["unikvs:action"] = "delete";
667
+
668
+ await using _close = await this.#open(signal, context);
669
+
670
+ const errors: unknown[] = [];
671
+ using _ioLock = await ioMux.lock({ key, signal });
672
+ await Promise.all(
673
+ this.#destinations.map(async (storage) => {
674
+ try {
675
+ if (await storage.exists(key, signal, context)) {
676
+ await storage.delete(key, signal, context);
677
+ }
678
+ } catch (ex) {
679
+ errors.push(ex);
680
+ }
681
+ }),
682
+ );
683
+
684
+ switch (errors.length) {
685
+ case 0:
686
+ break;
687
+ case 1:
688
+ throw errors[0];
689
+ default:
690
+ throw new AggregateError(errors);
691
+ }
692
+ }
693
+
694
+ public async clear(options: ClearOptions = {}): Promise<void> {
695
+ if (!this.#con) {
696
+ throw new Error();
697
+ }
698
+
699
+ const { ac, ioMux } = this.#con;
700
+ const { signal, context } = (function parseClearArguments(ctx) {
701
+ const { signal, context } = options;
702
+
703
+ return {
704
+ signal: combineSignals([signal, ac.signal]),
705
+ context: mergeContext(ctx, context),
706
+ };
707
+ })(this.#context);
708
+
709
+ using _lock = await asyncmux.readonly(this, signal);
710
+
711
+ if (!this.#con) {
712
+ throw new Error();
713
+ }
714
+
715
+ context["unikvs:action"] = "clear";
716
+
717
+ await using _close = await this.#open(signal, context);
718
+
719
+ const errors: unknown[] = [];
720
+ using _ioLock = await ioMux.lock({ signal });
721
+ await Promise.all(
722
+ this.#destinations.map(async (storage) => {
723
+ try {
724
+ await storage.clear(signal, context);
725
+ } catch (ex) {
726
+ errors.push(ex);
727
+ }
728
+ }),
729
+ );
730
+
731
+ switch (errors.length) {
732
+ case 0:
733
+ break;
734
+ case 1:
735
+ throw errors[0];
736
+ default:
737
+ throw new AggregateError(errors);
738
+ }
739
+ }
740
+ }