unikvs 0.0.8 → 0.0.10
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/src/_logger.d.ts.map +1 -1
- package/dist/src/_storage.d.ts.map +1 -1
- package/dist/src/_to-value-stream.d.ts.map +1 -1
- package/dist/src/_to-value-stream.js +44 -6
- package/dist/src/_transformer.d.ts.map +1 -1
- package/dist/src/_valibot.d.ts +4 -5
- package/dist/src/_valibot.d.ts.map +1 -1
- package/dist/src/errors.d.ts +249 -24
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +107 -36
- package/dist/src/unikvs-config.d.ts.map +1 -1
- package/dist/src/unikvs-config.js +2 -2
- package/dist/src/unikvs.d.ts +10 -3
- package/dist/src/unikvs.d.ts.map +1 -1
- package/dist/src/unikvs.js +174 -32
- package/dist/src/value-stream.types.d.ts +6 -1
- package/dist/src/value-stream.types.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/_to-value-stream.ts +47 -8
- package/src/_valibot.ts +4 -5
- package/src/errors.ts +316 -55
- package/src/unikvs-config.ts +2 -2
- package/src/unikvs.ts +199 -30
- package/src/value-stream.types.ts +6 -1
package/dist/src/unikvs.js
CHANGED
|
@@ -24,9 +24,11 @@ import UniKvsConfig from "./unikvs-config.js";
|
|
|
24
24
|
//
|
|
25
25
|
// -------------------------------------------------------------------------------------------------
|
|
26
26
|
const ContextKeySchema = v.string();
|
|
27
|
+
// 配列形式の context も正当な入力であるため、record スキーマより先に判定する必要があります。
|
|
28
|
+
// record スキーマは配列にもマッチして数値キーのオブジェクトに変換してしまうため、配列形式を先に処理しないと mergeContext での配列としてのマージが行われません。
|
|
27
29
|
const ContextSourceSchema = v.union([
|
|
28
|
-
v.record(v.any(), v.unknown()),
|
|
29
30
|
v.array(v.tuple([ContextKeySchema, v.unknown()])),
|
|
31
|
+
v.record(v.any(), v.unknown()),
|
|
30
32
|
]);
|
|
31
33
|
const OpenOptionsSchema = v.object({
|
|
32
34
|
/**
|
|
@@ -121,10 +123,16 @@ const ClearArgsSchema = v.tuple([v.optional(ClearOptionsSchema)]);
|
|
|
121
123
|
*/
|
|
122
124
|
const ioLockRegistry = typeof FinalizationRegistry !== "function"
|
|
123
125
|
? null
|
|
124
|
-
: new FinalizationRegistry((
|
|
125
|
-
if (!lock.released) {
|
|
126
|
-
lock.release();
|
|
126
|
+
: new FinalizationRegistry((held) => {
|
|
127
|
+
if (!held.lock.released) {
|
|
128
|
+
held.lock.release();
|
|
127
129
|
}
|
|
130
|
+
// valueStream が読み取られないまま GC されたとき、ソースストリームが保持するリソース (S3 レスポンスボディなど) を解放するために破棄します。
|
|
131
|
+
// dispose は valueStream 自身を参照しないため、これを保持しても valueStream のガベージコレクションを妨げません。
|
|
132
|
+
// 失敗しても対処できないので無視します。
|
|
133
|
+
held.dispose().catch((ex) => {
|
|
134
|
+
logger.error `Failed to dispose value stream: ${ex}`;
|
|
135
|
+
});
|
|
128
136
|
});
|
|
129
137
|
/**
|
|
130
138
|
* UniKvs メインクラスです。
|
|
@@ -267,8 +275,15 @@ export default class UniKvs {
|
|
|
267
275
|
}
|
|
268
276
|
}));
|
|
269
277
|
if (errors.length > 0) {
|
|
278
|
+
// 中断以外の失敗が混在しない場合は、abort 理由が集約エラーに埋もれないようにそのまま投げます。
|
|
279
|
+
if (signal.aborted && errors.every((error) => error.reason === signal.reason)) {
|
|
280
|
+
throw signal.reason;
|
|
281
|
+
}
|
|
270
282
|
throw new PluginOperationAggregateError({ action: "open", errors });
|
|
271
283
|
}
|
|
284
|
+
// すべてのプラグインのオープンが成功した後も、待機中に中断されていないか最終確認します。
|
|
285
|
+
// 中断済みのシグナルを持つ接続を作成すると、isOpen が true でありながら以降のすべての操作が即座に失敗する壊れた状態になります。
|
|
286
|
+
signal.throwIfAborted();
|
|
272
287
|
__classPrivateFieldSet(this, _UniKvs_con, {
|
|
273
288
|
ac,
|
|
274
289
|
io: new Asyncmux(),
|
|
@@ -303,7 +318,8 @@ export default class UniKvs {
|
|
|
303
318
|
}
|
|
304
319
|
throw new UniKvsIsNotOpenError();
|
|
305
320
|
}
|
|
306
|
-
const
|
|
321
|
+
const con = __classPrivateFieldGet(this, _UniKvs_con, "f");
|
|
322
|
+
const { ac } = con;
|
|
307
323
|
const acArr = [ac, ...__classPrivateFieldGet(this, _UniKvs_acSet, "f")];
|
|
308
324
|
__classPrivateFieldGet(this, _UniKvs_acSet, "f").clear();
|
|
309
325
|
for (const ac of acArr) {
|
|
@@ -311,12 +327,35 @@ export default class UniKvs {
|
|
|
311
327
|
ac.abort(new UniKvsIsNotOpenError());
|
|
312
328
|
}
|
|
313
329
|
}
|
|
314
|
-
return __classPrivateFieldGet(this, _UniKvs_instances, "m", _UniKvs_close).call(this, context, signal,
|
|
330
|
+
return __classPrivateFieldGet(this, _UniKvs_instances, "m", _UniKvs_close).call(this, context, signal, con).catch(async (ex) => {
|
|
331
|
+
// #close が失敗した場合、コネクションの AbortController は既に abort 済みであり、以降の操作がすべて即座に失敗する壊れた状態になります。
|
|
332
|
+
// そこで接続を破棄して isOpen=false の一貫した状態にし、ベストエフォートでプラグインのクローズ処理を実行します。
|
|
333
|
+
if (__classPrivateFieldGet(this, _UniKvs_con, "f") === con) {
|
|
334
|
+
__classPrivateFieldSet(this, _UniKvs_con, null, "f");
|
|
335
|
+
const disposeSignal = AbortSignal.timeout(10e3);
|
|
336
|
+
await Promise.all([...__classPrivateFieldGet(this, _UniKvs_destinations, "f"), ...__classPrivateFieldGet(this, _UniKvs_transformers, "f")].map(async (plugin) => {
|
|
337
|
+
try {
|
|
338
|
+
await plugin.close(context, disposeSignal);
|
|
339
|
+
}
|
|
340
|
+
catch (reason) {
|
|
341
|
+
logger.error `Failed to close plugin after close failure: ${reason}`;
|
|
342
|
+
}
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
throw ex;
|
|
346
|
+
});
|
|
315
347
|
}
|
|
316
348
|
catch (ex) {
|
|
317
349
|
return Promise.reject(ex);
|
|
318
350
|
}
|
|
319
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* UniKvs が開いている場合にクローズします。
|
|
354
|
+
*
|
|
355
|
+
* `await using` 構文による自動クローズに使用されます。
|
|
356
|
+
*
|
|
357
|
+
* @returns 完了を通知する Promise です。
|
|
358
|
+
*/
|
|
320
359
|
async [(_UniKvs_con = new WeakMap(), _UniKvs_acSet = new WeakMap(), _UniKvs_context = new WeakMap(), _UniKvs_destinations = new WeakMap(), _UniKvs_transformers = new WeakMap(), _UniKvs_instances = new WeakSet(), _UniKvs_close = async function _UniKvs_close(context, signal, con) {
|
|
321
360
|
const lock = await asyncmux(this, signal);
|
|
322
361
|
try {
|
|
@@ -398,23 +437,47 @@ export default class UniKvs {
|
|
|
398
437
|
if (value instanceof ReadableStream) {
|
|
399
438
|
// 各トランスフォーマーを通してデータをエンコードストリームへ変換します。
|
|
400
439
|
let data = value;
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
440
|
+
try {
|
|
441
|
+
for (const transformer of __classPrivateFieldGet(this, _UniKvs_transformers, "f")) {
|
|
442
|
+
const e = await transformer.getEncodable(context, signal);
|
|
443
|
+
data = data.pipeThrough(e);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (ex) {
|
|
447
|
+
// チェーンの構築中に失敗した場合、パイプが保持するソースストリームのリソースを解放するためにキャンセルします。
|
|
448
|
+
// キャンセルしないとソースストリームはロックされたままリークします。
|
|
449
|
+
try {
|
|
450
|
+
await data.cancel(ex);
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// キャンセルに失敗しても元の例外の伝播を優先します。
|
|
454
|
+
}
|
|
455
|
+
throw ex;
|
|
404
456
|
}
|
|
405
457
|
const lock = await io.lock({ key, signal });
|
|
406
458
|
try {
|
|
407
459
|
await Promise.all(__classPrivateFieldGet(this, _UniKvs_destinations, "f").map(async (storage, i) => {
|
|
460
|
+
// tee ブランチはキャンセルされるまで健康なブランチの読み取り分のチャンクを保持し続けるため、失敗時に放棄したブランチを確実にキャンセルできるよう参照を保持します。
|
|
461
|
+
let branch = null;
|
|
408
462
|
try {
|
|
409
463
|
// 複数のストレージがある場合は、tee メソッドを使用してストリームを分岐させます。
|
|
410
464
|
let r = data;
|
|
411
465
|
if (i !== __classPrivateFieldGet(this, _UniKvs_destinations, "f").length - 1) {
|
|
412
466
|
[r, data] = data.tee();
|
|
413
467
|
}
|
|
468
|
+
branch = r;
|
|
414
469
|
const w = await storage.getWritable(context, signal, key);
|
|
415
470
|
await r.pipeTo(w, { signal });
|
|
416
471
|
}
|
|
417
472
|
catch (reason) {
|
|
473
|
+
if (branch !== null) {
|
|
474
|
+
try {
|
|
475
|
+
await branch.cancel(reason);
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
// キャンセルに失敗してもエラー集約を優先します。
|
|
479
|
+
}
|
|
480
|
+
}
|
|
418
481
|
errors.push({ reason });
|
|
419
482
|
errorStorageSet.add(storage);
|
|
420
483
|
}
|
|
@@ -496,21 +559,47 @@ export default class UniKvs {
|
|
|
496
559
|
}
|
|
497
560
|
const NONE = {};
|
|
498
561
|
let data = NONE;
|
|
562
|
+
const errors = [];
|
|
499
563
|
const lock = await io.rLock({ key, signal });
|
|
500
564
|
try {
|
|
501
565
|
// 各ストレージを巡回し、最初に見つかったデータを取得します。
|
|
566
|
+
// あるストレージの読み取りに失敗しても、他のストレージからデータを取得できるようにフォールバックします。
|
|
502
567
|
for (const storage of __classPrivateFieldGet(this, _UniKvs_destinations, "f")) {
|
|
503
|
-
|
|
568
|
+
try {
|
|
569
|
+
if (!(await storage.exists(context, signal, key))) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
504
572
|
data = await storage.read(context, signal, key);
|
|
505
573
|
break;
|
|
506
574
|
}
|
|
575
|
+
catch (ex) {
|
|
576
|
+
if (signal.aborted) {
|
|
577
|
+
throw ex;
|
|
578
|
+
}
|
|
579
|
+
errors.push({ reason: ex });
|
|
580
|
+
logger.error `Failed to read from a storage: ${ex}`;
|
|
581
|
+
}
|
|
507
582
|
}
|
|
508
583
|
}
|
|
509
584
|
finally {
|
|
510
585
|
lock.release();
|
|
511
586
|
}
|
|
512
587
|
if (data === NONE) {
|
|
513
|
-
|
|
588
|
+
const args = { key };
|
|
589
|
+
switch (errors.length) {
|
|
590
|
+
case 0:
|
|
591
|
+
break;
|
|
592
|
+
case 1:
|
|
593
|
+
args.cause = errors[0].reason;
|
|
594
|
+
break;
|
|
595
|
+
default:
|
|
596
|
+
args.cause = new PluginOperationAggregateError({
|
|
597
|
+
plugin: "storage",
|
|
598
|
+
action: "read",
|
|
599
|
+
errors,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
throw new KeyNotFoundError(args);
|
|
514
603
|
}
|
|
515
604
|
// トランスフォーマーを逆順に適用してデータをデコードします。
|
|
516
605
|
if (__classPrivateFieldGet(this, _UniKvs_transformers, "f").length > 0) {
|
|
@@ -535,7 +624,7 @@ export default class UniKvs {
|
|
|
535
624
|
const context = mergeContext(__classPrivateFieldGet(this, _UniKvs_context, "f"), contextOption);
|
|
536
625
|
context["unikvs:action"] = "stream";
|
|
537
626
|
context["unikvs:key"] = key;
|
|
538
|
-
const lock = await asyncmux(this, signal);
|
|
627
|
+
const lock = await asyncmux.readonly(this, signal);
|
|
539
628
|
try {
|
|
540
629
|
// ロック待機中に接続状態が変更されていないか再確認します。
|
|
541
630
|
if (__classPrivateFieldGet(this, _UniKvs_con, "f") === null) {
|
|
@@ -543,31 +632,51 @@ export default class UniKvs {
|
|
|
543
632
|
}
|
|
544
633
|
const NONE = {};
|
|
545
634
|
let r = NONE;
|
|
635
|
+
const errors = [];
|
|
546
636
|
const lock = await io.rLock({ key, signal });
|
|
547
637
|
try {
|
|
548
638
|
// 各ストレージを巡回し、最初に見つかったデータを取得します。
|
|
639
|
+
// あるストレージの読み取りに失敗しても、他のストレージからデータを取得できるようにフォールバックします。
|
|
549
640
|
for (const storage of __classPrivateFieldGet(this, _UniKvs_destinations, "f")) {
|
|
550
|
-
|
|
641
|
+
try {
|
|
642
|
+
if (!(await storage.exists(context, signal, key))) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
551
645
|
r = await storage.getReadable(context, signal, key);
|
|
552
646
|
break;
|
|
553
647
|
}
|
|
648
|
+
catch (ex) {
|
|
649
|
+
if (signal.aborted) {
|
|
650
|
+
throw ex;
|
|
651
|
+
}
|
|
652
|
+
errors.push({ reason: ex });
|
|
653
|
+
logger.error `Failed to read from a storage: ${ex}`;
|
|
654
|
+
}
|
|
554
655
|
}
|
|
555
656
|
if (r === NONE) {
|
|
556
|
-
|
|
657
|
+
const args = { key };
|
|
658
|
+
switch (errors.length) {
|
|
659
|
+
case 0:
|
|
660
|
+
break;
|
|
661
|
+
case 1:
|
|
662
|
+
args.cause = errors[0].reason;
|
|
663
|
+
break;
|
|
664
|
+
default:
|
|
665
|
+
args.cause = new PluginOperationAggregateError({
|
|
666
|
+
plugin: "storage",
|
|
667
|
+
action: "read",
|
|
668
|
+
errors,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
throw new KeyNotFoundError(args);
|
|
557
672
|
}
|
|
558
673
|
// トランスフォーマーを逆順に適用し、デコード用トランスフォームを連結します。
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
r = r.pipeThrough(d);
|
|
563
|
-
}
|
|
674
|
+
for (const transformer of __classPrivateFieldGet(this, _UniKvs_transformers, "f").toReversed()) {
|
|
675
|
+
const d = await transformer.getDecodable(context, signal);
|
|
676
|
+
r = r.pipeThrough(d);
|
|
564
677
|
}
|
|
565
678
|
if (!ioLockRegistry) {
|
|
566
679
|
return toValueStream(r, async () => {
|
|
567
|
-
try {
|
|
568
|
-
await r.cancel();
|
|
569
|
-
}
|
|
570
|
-
catch { }
|
|
571
680
|
try {
|
|
572
681
|
lock.release();
|
|
573
682
|
}
|
|
@@ -580,20 +689,26 @@ export default class UniKvs {
|
|
|
580
689
|
ioLockRegistry.unregister(unregisterToken);
|
|
581
690
|
}
|
|
582
691
|
catch { }
|
|
583
|
-
try {
|
|
584
|
-
await r.cancel();
|
|
585
|
-
}
|
|
586
|
-
catch { }
|
|
587
692
|
try {
|
|
588
693
|
lock.release();
|
|
589
694
|
}
|
|
590
695
|
catch { }
|
|
591
696
|
});
|
|
592
|
-
// valueStream が GC
|
|
593
|
-
ioLockRegistry.register(valueStream,
|
|
697
|
+
// valueStream が GC されるタイミングでストリームが終了していなければロックを自動解放するとともに、ソースストリームが保持するリソース (S3 レスポンスボディなど) を解放できるように dispose を記録します。
|
|
698
|
+
ioLockRegistry.register(valueStream, {
|
|
699
|
+
lock,
|
|
700
|
+
dispose: valueStream.dispose,
|
|
701
|
+
}, unregisterToken);
|
|
594
702
|
return valueStream;
|
|
595
703
|
}
|
|
596
704
|
catch (ex) {
|
|
705
|
+
if (r !== NONE) {
|
|
706
|
+
// getReadable 成功後のセットアップ中に失敗した場合、ソースストリームが保持するリソース (S3 レスポンスボディなど) を解放するためにキャンセルします。
|
|
707
|
+
try {
|
|
708
|
+
await r.cancel(ex);
|
|
709
|
+
}
|
|
710
|
+
catch { }
|
|
711
|
+
}
|
|
597
712
|
lock.release();
|
|
598
713
|
throw ex;
|
|
599
714
|
}
|
|
@@ -613,7 +728,7 @@ export default class UniKvs {
|
|
|
613
728
|
const context = mergeContext(__classPrivateFieldGet(this, _UniKvs_context, "f"), contextOption);
|
|
614
729
|
context["unikvs:action"] = "has";
|
|
615
730
|
context["unikvs:key"] = key;
|
|
616
|
-
const lock = await asyncmux(this, signal);
|
|
731
|
+
const lock = await asyncmux.readonly(this, signal);
|
|
617
732
|
try {
|
|
618
733
|
// ロック待機中に接続状態が変更されていないか再確認します。
|
|
619
734
|
if (__classPrivateFieldGet(this, _UniKvs_con, "f") === null) {
|
|
@@ -622,10 +737,37 @@ export default class UniKvs {
|
|
|
622
737
|
const lock = await io.rLock({ key, signal });
|
|
623
738
|
try {
|
|
624
739
|
// いずれかのストレージに存在すれば true を返します。
|
|
740
|
+
// あるストレージの存在確認に失敗しても、他のストレージで存在を確認できるようにフォールバックします。
|
|
741
|
+
const errors = [];
|
|
625
742
|
for (const storage of __classPrivateFieldGet(this, _UniKvs_destinations, "f")) {
|
|
626
|
-
|
|
627
|
-
|
|
743
|
+
try {
|
|
744
|
+
if (await storage.exists(context, signal, key)) {
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch (ex) {
|
|
749
|
+
if (signal.aborted) {
|
|
750
|
+
throw ex;
|
|
751
|
+
}
|
|
752
|
+
errors.push({ reason: ex });
|
|
753
|
+
logger.error `Failed to check existence in a storage: ${ex}`;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
// すべてのストレージで存在を確認できなかった場合は結果が不明のため、get() と同様にエラーとして報告します。
|
|
757
|
+
if (errors.length > 0) {
|
|
758
|
+
const args = { key };
|
|
759
|
+
switch (errors.length) {
|
|
760
|
+
case 1:
|
|
761
|
+
args.cause = errors[0].reason;
|
|
762
|
+
break;
|
|
763
|
+
default:
|
|
764
|
+
args.cause = new PluginOperationAggregateError({
|
|
765
|
+
plugin: "storage",
|
|
766
|
+
action: "read",
|
|
767
|
+
errors,
|
|
768
|
+
});
|
|
628
769
|
}
|
|
770
|
+
throw new KeyNotFoundError(args);
|
|
629
771
|
}
|
|
630
772
|
return false;
|
|
631
773
|
}
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* @template T ストリームを流れるデータの型です。
|
|
7
7
|
*/
|
|
8
8
|
export interface ValueStream<T = any> extends Omit<ReadableStream, keyof AsyncDisposable | keyof AsyncIterable<unknown>>, AsyncDisposable, AsyncIterable<T, void, unknown> {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* ストリームを閉じ、関連するリソースを解放します。
|
|
11
|
+
*
|
|
12
|
+
* @returns リソースの解放が完了したときに解決される Promise です。
|
|
13
|
+
*/
|
|
14
|
+
dispose: (this: void) => Promise<void>;
|
|
10
15
|
}
|
|
11
16
|
//# sourceMappingURL=value-stream.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-stream.types.d.ts","sourceRoot":"","sources":["../../src/value-stream.types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAClC,SACE,IAAI,CAAC,cAAc,EAAE,MAAM,eAAe,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAC1E,eAAe,EACf,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IACjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"value-stream.types.d.ts","sourceRoot":"","sources":["../../src/value-stream.types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG,CAClC,SACE,IAAI,CAAC,cAAc,EAAE,MAAM,eAAe,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAC1E,eAAe,EACf,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unikvs",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"description": "unikvs は TypeScript で実装されたモジュラーでポータブルな KVS クライアントです。",
|
|
5
5
|
"homepage": "https://github.com/tai-kun/unikvs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "tai-kun",
|
|
@@ -25,33 +25,33 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@tai-kun/valibot-extra-lab": "
|
|
29
|
-
"@unikvs/core": "0.0.
|
|
30
|
-
"abort-signal-utils": "
|
|
31
|
-
"asyncmux": "
|
|
32
|
-
"call-fn-once": "
|
|
33
|
-
"try-capture-stack-trace": "
|
|
28
|
+
"@tai-kun/valibot-extra-lab": "0.0.5",
|
|
29
|
+
"@unikvs/core": "0.0.10",
|
|
30
|
+
"abort-signal-utils": "0.0.3",
|
|
31
|
+
"asyncmux": "0.0.8",
|
|
32
|
+
"call-fn-once": "0.0.6",
|
|
33
|
+
"try-capture-stack-trace": "0.0.3"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@tsconfig/node24": "
|
|
37
|
-
"@tsconfig/strictest": "
|
|
38
|
-
"@types/node": "
|
|
39
|
-
"@unikvs/checksum": "0.0.
|
|
40
|
-
"@unikvs/compression": "0.0.
|
|
41
|
-
"@unikvs/fs.node": "0.0.
|
|
42
|
-
"@unikvs/memory": "0.0.
|
|
43
|
-
"@unikvs/opfs": "0.0.
|
|
44
|
-
"@unikvs/utils": "0.0.
|
|
45
|
-
"@vitest/browser": "
|
|
46
|
-
"@vitest/browser-playwright": "
|
|
47
|
-
"npm-check-updates": "
|
|
48
|
-
"playwright": "
|
|
49
|
-
"vite": "
|
|
50
|
-
"vitest": "
|
|
36
|
+
"@tsconfig/node24": "24.0.5",
|
|
37
|
+
"@tsconfig/strictest": "2.0.8",
|
|
38
|
+
"@types/node": "24.13.3",
|
|
39
|
+
"@unikvs/checksum": "0.0.10",
|
|
40
|
+
"@unikvs/compression": "0.0.10",
|
|
41
|
+
"@unikvs/fs.node": "0.0.10",
|
|
42
|
+
"@unikvs/memory": "0.0.10",
|
|
43
|
+
"@unikvs/opfs": "0.0.10",
|
|
44
|
+
"@unikvs/utils": "0.0.10",
|
|
45
|
+
"@vitest/browser": "4.1.10",
|
|
46
|
+
"@vitest/browser-playwright": "4.1.10",
|
|
47
|
+
"npm-check-updates": "20.0.2",
|
|
48
|
+
"playwright": "1.62.1",
|
|
49
|
+
"vite": "8.2.1",
|
|
50
|
+
"vitest": "4.1.10"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@logtape/logtape": "
|
|
54
|
-
"valibot": "
|
|
53
|
+
"@logtape/logtape": "2.0.0",
|
|
54
|
+
"valibot": "1.0.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsc --project ./.config/tsconfig.build.json"
|
package/src/_to-value-stream.ts
CHANGED
|
@@ -17,17 +17,47 @@ export default function toValueStream<T>(
|
|
|
17
17
|
onAsyncDispose: () => Promise<void>,
|
|
18
18
|
): ValueStream<T> {
|
|
19
19
|
const cacheMap = new Map();
|
|
20
|
+
const reader = readableStream.getReader();
|
|
21
|
+
|
|
20
22
|
async function disposeValueStream(): Promise<void> {
|
|
21
|
-
await callAsyncableFnOnce(cacheMap, "dispose",
|
|
23
|
+
await callAsyncableFnOnce(cacheMap, "dispose", async () => {
|
|
24
|
+
try {
|
|
25
|
+
await reader.cancel();
|
|
26
|
+
} catch (ex) {
|
|
27
|
+
logger.error`Failed to cancel the source stream: ${ex}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await onAsyncDispose();
|
|
31
|
+
});
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
// cancel・エラー・早期 break であっても dispose されるように、readable 側のキャンセルを直接フックしたストリームを返します。
|
|
35
|
+
const valueStream = new ReadableStream<T>({
|
|
36
|
+
async pull(controller) {
|
|
37
|
+
try {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done) {
|
|
40
|
+
// ストリームが正常に完了したことも dispose 対象とし、完了を通知する前に破棄を完了させます。
|
|
41
|
+
await disposeValueStream();
|
|
42
|
+
controller.close();
|
|
43
|
+
} else {
|
|
44
|
+
controller.enqueue(value);
|
|
45
|
+
}
|
|
46
|
+
} catch (ex) {
|
|
47
|
+
// ソースエラー時も dispose (ロック解放など) を行わないと、ストリームを放棄した consumer に対してキーの I/O ロックがリークします。
|
|
48
|
+
// dispose 自体が失敗しても元の例外の伝播を壊さないように、失敗はログに残すだけにします。
|
|
49
|
+
await disposeValueStream().catch((e) => logger.error`Failed to dispose value stream: ${e}`);
|
|
50
|
+
controller.error(ex);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async cancel() {
|
|
55
|
+
await disposeValueStream();
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const stream = Object.assign(
|
|
60
|
+
valueStream,
|
|
31
61
|
|
|
32
62
|
// AsyncDisposable
|
|
33
63
|
{
|
|
@@ -63,6 +93,13 @@ export default function toValueStream<T>(
|
|
|
63
93
|
|
|
64
94
|
throw ex;
|
|
65
95
|
} finally {
|
|
96
|
+
// cancel・エラー・早期 break など完了方法に関係なくストリームを破棄し、キーの読み取りロックがリークしないようにします (2 回目以降は no-op)。
|
|
97
|
+
try {
|
|
98
|
+
await disposeValueStream();
|
|
99
|
+
} catch (ex) {
|
|
100
|
+
logger.error`Failed to dispose value stream: ${ex}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
66
103
|
// 最後に必ずリーダーのロックを解放し、ストリームを再利用可能な状態にします。
|
|
67
104
|
try {
|
|
68
105
|
r.releaseLock();
|
|
@@ -73,4 +110,6 @@ export default function toValueStream<T>(
|
|
|
73
110
|
},
|
|
74
111
|
},
|
|
75
112
|
);
|
|
113
|
+
|
|
114
|
+
return stream;
|
|
76
115
|
}
|
package/src/_valibot.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ErrorOptions } from "@unikvs/core";
|
|
2
1
|
import { tryCaptureStackTrace } from "try-capture-stack-trace";
|
|
3
2
|
import { type InferOutput, safeParse } from "valibot";
|
|
4
3
|
|
|
@@ -29,14 +28,14 @@ export { array, tuple, object, record, transform } from "@tai-kun/valibot-extra-
|
|
|
29
28
|
|
|
30
29
|
type BaseSchema = typeof safeParse extends (schema: infer S, ...args: any) => any ? S : never;
|
|
31
30
|
|
|
32
|
-
export interface
|
|
33
|
-
new (args: InvalidInputErrorArgs
|
|
31
|
+
export interface IParseInputErrorConstructor {
|
|
32
|
+
new (args: InvalidInputErrorArgs): Error;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
export function parseInput<const TSchema extends BaseSchema>(
|
|
37
36
|
schema: TSchema,
|
|
38
37
|
value: unknown,
|
|
39
|
-
Error:
|
|
38
|
+
Error: IParseInputErrorConstructor = InvalidInputError,
|
|
40
39
|
): InferOutput<TSchema> {
|
|
41
40
|
const result = safeParse(schema, value);
|
|
42
41
|
if (result.success) {
|
|
@@ -49,7 +48,7 @@ export function parseInput<const TSchema extends BaseSchema>(
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export interface ParseOutputErrorConstructor {
|
|
52
|
-
new (args: InvalidOutputErrorArgs
|
|
51
|
+
new (args: InvalidOutputErrorArgs): Error;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
export function parseOutput<const TSchema extends BaseSchema>(
|