@cometloop/safe 0.0.1

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.cjs ADDED
@@ -0,0 +1,692 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ TimeoutError: () => TimeoutError,
24
+ createSafe: () => createSafe,
25
+ err: () => err,
26
+ errObj: () => errObj,
27
+ ok: () => ok,
28
+ okObj: () => okObj,
29
+ safe: () => safe,
30
+ withObjects: () => withObjects
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/safe/types.ts
35
+ function ok(value) {
36
+ const tuple = [value, null];
37
+ Object.defineProperties(tuple, {
38
+ ok: { value: true, enumerable: false },
39
+ value: { value, enumerable: false },
40
+ error: { value: null, enumerable: false }
41
+ });
42
+ return tuple;
43
+ }
44
+ function err(error) {
45
+ const tuple = [null, error];
46
+ Object.defineProperties(tuple, {
47
+ ok: { value: false, enumerable: false },
48
+ value: { value: null, enumerable: false },
49
+ error: { value: error, enumerable: false }
50
+ });
51
+ return tuple;
52
+ }
53
+ function okObj(data) {
54
+ return { ok: true, data, error: null };
55
+ }
56
+ function errObj(error) {
57
+ return { ok: false, data: null, error };
58
+ }
59
+ var TimeoutError = class extends Error {
60
+ constructor(ms) {
61
+ super(`Operation timed out after ${ms}ms`);
62
+ this.name = "TimeoutError";
63
+ }
64
+ };
65
+
66
+ // src/safe/safe.ts
67
+ var HOOK_KEY_MAP = {
68
+ parseResult: true,
69
+ onSuccess: true,
70
+ onError: true,
71
+ onSettled: true,
72
+ onHookError: true,
73
+ defaultError: true,
74
+ onRetry: true,
75
+ retry: true,
76
+ abortAfter: true
77
+ };
78
+ var HOOK_KEYS = new Set(Object.keys(HOOK_KEY_MAP));
79
+ var FUNCTION_HOOK_KEYS = /* @__PURE__ */ new Set([
80
+ "parseResult",
81
+ "onSuccess",
82
+ "onError",
83
+ "onSettled",
84
+ "onHookError",
85
+ "onRetry"
86
+ ]);
87
+ var isHooks = (arg) => {
88
+ if (typeof arg !== "object" || arg === null || Array.isArray(arg))
89
+ return false;
90
+ const keys = Object.keys(arg);
91
+ if (keys.length === 0) return false;
92
+ const obj = arg;
93
+ for (const key of keys) {
94
+ if (!HOOK_KEYS.has(key)) return false;
95
+ const val = obj[key];
96
+ if (val === void 0) continue;
97
+ if (FUNCTION_HOOK_KEYS.has(key)) {
98
+ if (typeof val !== "function") return false;
99
+ } else if (key === "retry") {
100
+ if (typeof val !== "object" || val === null) return false;
101
+ } else if (key === "abortAfter") {
102
+ if (typeof val !== "number") return false;
103
+ }
104
+ }
105
+ return true;
106
+ };
107
+ var callHook = (fn, onHookError, hookName) => {
108
+ try {
109
+ fn?.();
110
+ } catch (e) {
111
+ try {
112
+ onHookError?.(e, hookName ?? "unknown");
113
+ } catch {
114
+ }
115
+ }
116
+ };
117
+ var toError = (e) => {
118
+ if (e instanceof Error) return e;
119
+ const error = new Error(String(e));
120
+ error.cause = e;
121
+ return error;
122
+ };
123
+ var callParseError = (e, parseError, onHookError, defaultError) => {
124
+ if (!parseError) return toError(e);
125
+ try {
126
+ return parseError(e);
127
+ } catch (parseErrorException) {
128
+ try {
129
+ onHookError?.(parseErrorException, "parseError");
130
+ } catch {
131
+ }
132
+ return defaultError ?? toError(e);
133
+ }
134
+ };
135
+ var callParseResult = (parseResult, rawResult) => {
136
+ if (!parseResult) return rawResult;
137
+ return parseResult(rawResult);
138
+ };
139
+ var sanitiseRetryTimes = (times) => {
140
+ if (times === void 0) return 0;
141
+ const n = Math.floor(times);
142
+ if (!Number.isFinite(n) || n < 0) return 0;
143
+ return n;
144
+ };
145
+ var validateAbortAfter = (ms) => {
146
+ if (ms === void 0) return void 0;
147
+ if (!Number.isFinite(ms) || ms < 0) {
148
+ throw new RangeError(
149
+ `abortAfter must be a non-negative finite number, got ${ms}`
150
+ );
151
+ }
152
+ return ms;
153
+ };
154
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
155
+ var withTimeout = (promise, ms, controller) => {
156
+ let timeoutId;
157
+ const timeoutPromise = new Promise((_, reject) => {
158
+ timeoutId = setTimeout(() => {
159
+ controller.abort();
160
+ reject(new TimeoutError(ms));
161
+ }, ms);
162
+ });
163
+ return Promise.race([promise, timeoutPromise]).finally(() => {
164
+ clearTimeout(timeoutId);
165
+ });
166
+ };
167
+ function safeSync(fn, parseErrorOrHooks, hooks) {
168
+ const context = [];
169
+ let parseError;
170
+ let resolvedHooks;
171
+ if (isHooks(parseErrorOrHooks)) {
172
+ resolvedHooks = parseErrorOrHooks;
173
+ } else {
174
+ parseError = parseErrorOrHooks;
175
+ resolvedHooks = hooks;
176
+ }
177
+ const onHookError = resolvedHooks?.onHookError;
178
+ try {
179
+ const rawResult = fn();
180
+ const result = callParseResult(resolvedHooks?.parseResult, rawResult);
181
+ callHook(
182
+ () => resolvedHooks?.onSuccess?.(result, context),
183
+ onHookError,
184
+ "onSuccess"
185
+ );
186
+ callHook(
187
+ () => resolvedHooks?.onSettled?.(result, null, context),
188
+ onHookError,
189
+ "onSettled"
190
+ );
191
+ return ok(result);
192
+ } catch (e) {
193
+ const error = callParseError(
194
+ e,
195
+ parseError,
196
+ onHookError,
197
+ resolvedHooks?.defaultError
198
+ );
199
+ callHook(
200
+ () => resolvedHooks?.onError?.(error, context),
201
+ onHookError,
202
+ "onError"
203
+ );
204
+ callHook(
205
+ () => resolvedHooks?.onSettled?.(null, error, context),
206
+ onHookError,
207
+ "onSettled"
208
+ );
209
+ return err(error);
210
+ }
211
+ }
212
+ async function safeAsync(fn, parseErrorOrHooks, hooks) {
213
+ const context = [];
214
+ let parseError;
215
+ let resolvedHooks;
216
+ if (isHooks(parseErrorOrHooks)) {
217
+ resolvedHooks = parseErrorOrHooks;
218
+ } else {
219
+ parseError = parseErrorOrHooks;
220
+ resolvedHooks = hooks;
221
+ }
222
+ const maxAttempts = sanitiseRetryTimes(resolvedHooks?.retry?.times) + 1;
223
+ const abortAfter = validateAbortAfter(resolvedHooks?.abortAfter);
224
+ const onHookError = resolvedHooks?.onHookError;
225
+ let lastError;
226
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
227
+ const controller = abortAfter !== void 0 ? new AbortController() : void 0;
228
+ try {
229
+ let promise = fn(controller?.signal);
230
+ if (abortAfter !== void 0 && controller) {
231
+ promise = withTimeout(promise, abortAfter, controller);
232
+ }
233
+ const rawResult = await promise;
234
+ const result = callParseResult(resolvedHooks?.parseResult, rawResult);
235
+ callHook(
236
+ () => resolvedHooks?.onSuccess?.(result, context),
237
+ onHookError,
238
+ "onSuccess"
239
+ );
240
+ callHook(
241
+ () => resolvedHooks?.onSettled?.(result, null, context),
242
+ onHookError,
243
+ "onSettled"
244
+ );
245
+ return ok(result);
246
+ } catch (e) {
247
+ lastError = callParseError(
248
+ e,
249
+ parseError,
250
+ onHookError,
251
+ resolvedHooks?.defaultError
252
+ );
253
+ if (attempt < maxAttempts) {
254
+ callHook(
255
+ () => resolvedHooks?.onRetry?.(lastError, attempt, context),
256
+ onHookError,
257
+ "onRetry"
258
+ );
259
+ const waitMs = resolvedHooks?.retry?.waitBefore?.(attempt) ?? 0;
260
+ if (waitMs > 0 && Number.isFinite(waitMs)) {
261
+ await sleep(waitMs);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ callHook(
267
+ () => resolvedHooks?.onError?.(lastError, context),
268
+ onHookError,
269
+ "onError"
270
+ );
271
+ callHook(
272
+ () => resolvedHooks?.onSettled?.(null, lastError, context),
273
+ onHookError,
274
+ "onSettled"
275
+ );
276
+ return err(lastError);
277
+ }
278
+ function wrap(fn, parseErrorOrHooks, hooks) {
279
+ let parseError;
280
+ let resolvedHooks;
281
+ if (isHooks(parseErrorOrHooks)) {
282
+ resolvedHooks = parseErrorOrHooks;
283
+ } else {
284
+ parseError = parseErrorOrHooks;
285
+ resolvedHooks = hooks;
286
+ }
287
+ const onHookError = resolvedHooks?.onHookError;
288
+ return function(...args) {
289
+ try {
290
+ const rawResult = fn.call(this, ...args);
291
+ const result = callParseResult(resolvedHooks?.parseResult, rawResult);
292
+ callHook(
293
+ () => resolvedHooks?.onSuccess?.(result, args),
294
+ onHookError,
295
+ "onSuccess"
296
+ );
297
+ callHook(
298
+ () => resolvedHooks?.onSettled?.(result, null, args),
299
+ onHookError,
300
+ "onSettled"
301
+ );
302
+ return ok(result);
303
+ } catch (e) {
304
+ const error = callParseError(
305
+ e,
306
+ parseError,
307
+ onHookError,
308
+ resolvedHooks?.defaultError
309
+ );
310
+ callHook(
311
+ () => resolvedHooks?.onError?.(error, args),
312
+ onHookError,
313
+ "onError"
314
+ );
315
+ callHook(
316
+ () => resolvedHooks?.onSettled?.(null, error, args),
317
+ onHookError,
318
+ "onSettled"
319
+ );
320
+ return err(error);
321
+ }
322
+ };
323
+ }
324
+ function wrapAsync(fn, parseErrorOrHooks, hooks) {
325
+ let parseError;
326
+ let resolvedHooks;
327
+ if (isHooks(parseErrorOrHooks)) {
328
+ resolvedHooks = parseErrorOrHooks;
329
+ } else {
330
+ parseError = parseErrorOrHooks;
331
+ resolvedHooks = hooks;
332
+ }
333
+ const maxAttempts = sanitiseRetryTimes(resolvedHooks?.retry?.times) + 1;
334
+ const abortAfter = validateAbortAfter(resolvedHooks?.abortAfter);
335
+ const onHookError = resolvedHooks?.onHookError;
336
+ return async function(...args) {
337
+ let lastError;
338
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
339
+ const controller = abortAfter !== void 0 ? new AbortController() : void 0;
340
+ try {
341
+ let promise = fn.call(this, ...args);
342
+ if (abortAfter !== void 0 && controller) {
343
+ promise = withTimeout(promise, abortAfter, controller);
344
+ }
345
+ const rawResult = await promise;
346
+ const result = callParseResult(resolvedHooks?.parseResult, rawResult);
347
+ callHook(
348
+ () => resolvedHooks?.onSuccess?.(result, args),
349
+ onHookError,
350
+ "onSuccess"
351
+ );
352
+ callHook(
353
+ () => resolvedHooks?.onSettled?.(result, null, args),
354
+ onHookError,
355
+ "onSettled"
356
+ );
357
+ return ok(result);
358
+ } catch (e) {
359
+ lastError = callParseError(
360
+ e,
361
+ parseError,
362
+ onHookError,
363
+ resolvedHooks?.defaultError
364
+ );
365
+ if (attempt < maxAttempts) {
366
+ callHook(
367
+ () => resolvedHooks?.onRetry?.(lastError, attempt, args),
368
+ onHookError,
369
+ "onRetry"
370
+ );
371
+ const waitMs = resolvedHooks?.retry?.waitBefore?.(attempt) ?? 0;
372
+ if (waitMs > 0 && Number.isFinite(waitMs)) {
373
+ await sleep(waitMs);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ callHook(
379
+ () => resolvedHooks?.onError?.(lastError, args),
380
+ onHookError,
381
+ "onError"
382
+ );
383
+ callHook(
384
+ () => resolvedHooks?.onSettled?.(null, lastError, args),
385
+ onHookError,
386
+ "onSettled"
387
+ );
388
+ return err(lastError);
389
+ };
390
+ }
391
+ function safeAll(promises) {
392
+ const keys = Object.keys(promises);
393
+ const values = Object.values(promises);
394
+ if (values.length === 0) {
395
+ return Promise.resolve(ok({}));
396
+ }
397
+ return new Promise((resolve) => {
398
+ const results = new Array(values.length);
399
+ let remaining = values.length;
400
+ let done = false;
401
+ for (let i = 0; i < values.length; i++) {
402
+ values[i].then(
403
+ (result) => {
404
+ if (done) return;
405
+ if (!result.ok) {
406
+ done = true;
407
+ resolve(err(result.error));
408
+ return;
409
+ }
410
+ results[i] = result;
411
+ remaining--;
412
+ if (remaining === 0) {
413
+ done = true;
414
+ const obj = {};
415
+ for (let j = 0; j < keys.length; j++) {
416
+ obj[keys[j]] = results[j].value;
417
+ }
418
+ resolve(ok(obj));
419
+ }
420
+ },
421
+ // Safety net: safe-wrapped promises should never reject.
422
+ // No parseError is available in the standalone API, so toError is
423
+ // the best fallback. Use createSafe.all() if custom error mapping is needed.
424
+ (rejection) => {
425
+ if (done) return;
426
+ done = true;
427
+ resolve(err(toError(rejection)));
428
+ }
429
+ );
430
+ }
431
+ });
432
+ }
433
+ async function safeAllSettled(promises) {
434
+ const keys = Object.keys(promises);
435
+ const values = Object.values(promises);
436
+ const results = await Promise.all(
437
+ values.map(
438
+ (p) => p.catch(
439
+ (rejection) => err(toError(rejection))
440
+ )
441
+ )
442
+ );
443
+ const obj = {};
444
+ for (let i = 0; i < keys.length; i++) {
445
+ obj[keys[i]] = results[i];
446
+ }
447
+ return obj;
448
+ }
449
+ var safe = {
450
+ sync: safeSync,
451
+ async: safeAsync,
452
+ wrap,
453
+ wrapAsync,
454
+ all: safeAll,
455
+ allSettled: safeAllSettled
456
+ };
457
+
458
+ // src/safe/createSafe.ts
459
+ function createSafe(config) {
460
+ const {
461
+ parseError,
462
+ defaultError,
463
+ parseResult: defaultParseResult,
464
+ onSuccess: defaultOnSuccess,
465
+ onError: defaultOnError,
466
+ onSettled: defaultOnSettled,
467
+ onRetry: defaultOnRetry,
468
+ retry: defaultRetry,
469
+ abortAfter: defaultAbortAfter,
470
+ onHookError: defaultOnHookError
471
+ } = config;
472
+ const mergeHooks = (hooks) => {
473
+ const onHookError = hooks?.onHookError ?? defaultOnHookError;
474
+ return {
475
+ parseResult: hooks?.parseResult ?? defaultParseResult,
476
+ onSuccess: (result, context) => {
477
+ callHook(() => defaultOnSuccess?.(result), onHookError, "onSuccess");
478
+ hooks?.onSuccess?.(result, context);
479
+ },
480
+ onError: (error, context) => {
481
+ callHook(() => defaultOnError?.(error), onHookError, "onError");
482
+ hooks?.onError?.(error, context);
483
+ },
484
+ onSettled: (result, error, context) => {
485
+ callHook(
486
+ () => defaultOnSettled?.(result, error),
487
+ onHookError,
488
+ "onSettled"
489
+ );
490
+ hooks?.onSettled?.(result, error, context);
491
+ },
492
+ onHookError,
493
+ defaultError: hooks?.defaultError ?? defaultError
494
+ };
495
+ };
496
+ const mergeAsyncHooks = (hooks) => {
497
+ const onHookError = hooks?.onHookError ?? defaultOnHookError;
498
+ return {
499
+ parseResult: hooks?.parseResult ?? defaultParseResult,
500
+ onSuccess: (result, context) => {
501
+ callHook(() => defaultOnSuccess?.(result), onHookError, "onSuccess");
502
+ hooks?.onSuccess?.(result, context);
503
+ },
504
+ onError: (error, context) => {
505
+ callHook(() => defaultOnError?.(error), onHookError, "onError");
506
+ hooks?.onError?.(error, context);
507
+ },
508
+ onSettled: (result, error, context) => {
509
+ callHook(
510
+ () => defaultOnSettled?.(result, error),
511
+ onHookError,
512
+ "onSettled"
513
+ );
514
+ hooks?.onSettled?.(result, error, context);
515
+ },
516
+ onRetry: (error, attempt, context) => {
517
+ callHook(() => defaultOnRetry?.(error, attempt), onHookError, "onRetry");
518
+ hooks?.onRetry?.(error, attempt, context);
519
+ },
520
+ // Per-call retry completely overrides default retry
521
+ retry: hooks?.retry ?? defaultRetry,
522
+ // Per-call abortAfter overrides default abortAfter
523
+ abortAfter: hooks?.abortAfter ?? defaultAbortAfter,
524
+ onHookError,
525
+ defaultError: hooks?.defaultError ?? defaultError
526
+ };
527
+ };
528
+ return {
529
+ sync: (fn, hooks) => {
530
+ return safeSync(
531
+ fn,
532
+ parseError,
533
+ mergeHooks(hooks)
534
+ );
535
+ },
536
+ async: (fn, hooks) => {
537
+ return safeAsync(
538
+ fn,
539
+ parseError,
540
+ mergeAsyncHooks(hooks)
541
+ );
542
+ },
543
+ wrap: (fn, hooks) => {
544
+ return wrap(
545
+ fn,
546
+ parseError,
547
+ mergeHooks(hooks)
548
+ );
549
+ },
550
+ wrapAsync: (fn, hooks) => {
551
+ return wrapAsync(
552
+ fn,
553
+ parseError,
554
+ mergeAsyncHooks(hooks)
555
+ );
556
+ },
557
+ all: (fns) => {
558
+ const keys = Object.keys(fns);
559
+ const promises = keys.map(
560
+ (key) => safeAsync(
561
+ fns[key],
562
+ parseError,
563
+ mergeAsyncHooks()
564
+ )
565
+ );
566
+ if (promises.length === 0) {
567
+ return Promise.resolve(ok({}));
568
+ }
569
+ return new Promise((resolve) => {
570
+ const results = new Array(promises.length);
571
+ let remaining = promises.length;
572
+ let done = false;
573
+ for (let i = 0; i < promises.length; i++) {
574
+ promises[i].then(
575
+ (result) => {
576
+ if (done) return;
577
+ if (!result.ok) {
578
+ done = true;
579
+ resolve(err(result.error));
580
+ return;
581
+ }
582
+ results[i] = result;
583
+ remaining--;
584
+ if (remaining === 0) {
585
+ done = true;
586
+ const obj = {};
587
+ for (let j = 0; j < keys.length; j++) {
588
+ obj[keys[j]] = results[j].value;
589
+ }
590
+ resolve(ok(obj));
591
+ }
592
+ },
593
+ (rejection) => {
594
+ if (done) return;
595
+ done = true;
596
+ resolve(
597
+ err(
598
+ callParseError(
599
+ rejection,
600
+ parseError,
601
+ defaultOnHookError,
602
+ defaultError
603
+ )
604
+ )
605
+ );
606
+ }
607
+ );
608
+ }
609
+ });
610
+ },
611
+ allSettled: async (fns) => {
612
+ const keys = Object.keys(fns);
613
+ const promises = keys.map(
614
+ (key) => safeAsync(
615
+ fns[key],
616
+ parseError,
617
+ mergeAsyncHooks()
618
+ )
619
+ );
620
+ const results = await Promise.all(promises);
621
+ const obj = {};
622
+ for (let i = 0; i < keys.length; i++) {
623
+ obj[keys[i]] = results[i];
624
+ }
625
+ return obj;
626
+ }
627
+ };
628
+ }
629
+
630
+ // src/safe/withObjects.ts
631
+ function toObjectResult(result) {
632
+ if (result.ok) return okObj(result.value);
633
+ return errObj(result.error);
634
+ }
635
+ function withObjects(input) {
636
+ if (Array.isArray(input)) {
637
+ return toObjectResult(input);
638
+ }
639
+ if (input instanceof Promise) {
640
+ return input.then(
641
+ (result) => toObjectResult(result)
642
+ );
643
+ }
644
+ if (typeof input === "function") {
645
+ return (...args) => {
646
+ const result = input(...args);
647
+ if (result instanceof Promise) {
648
+ return result.then(
649
+ (r) => toObjectResult(r)
650
+ );
651
+ }
652
+ return toObjectResult(result);
653
+ };
654
+ }
655
+ if (typeof input === "object" && input !== null && "sync" in input && "async" in input) {
656
+ const instance = input;
657
+ return {
658
+ sync: (fn, hooks) => toObjectResult(instance.sync(fn, hooks)),
659
+ async: (fn, hooks) => instance.async(fn, hooks).then(toObjectResult),
660
+ wrap: (fn, hooks) => {
661
+ const wrapped = instance.wrap(fn, hooks);
662
+ return (...args) => toObjectResult(wrapped(...args));
663
+ },
664
+ wrapAsync: (fn, hooks) => {
665
+ const wrapped = instance.wrapAsync(fn, hooks);
666
+ return (...args) => wrapped(...args).then(toObjectResult);
667
+ },
668
+ all: (fns) => instance.all(fns).then(toObjectResult),
669
+ allSettled: async (fns) => {
670
+ const results = await instance.allSettled(fns);
671
+ const obj = {};
672
+ for (const key of Object.keys(results)) {
673
+ obj[key] = toObjectResult(results[key]);
674
+ }
675
+ return obj;
676
+ }
677
+ };
678
+ }
679
+ throw new TypeError("withObjects: unsupported input type");
680
+ }
681
+ // Annotate the CommonJS export names for ESM import in node:
682
+ 0 && (module.exports = {
683
+ TimeoutError,
684
+ createSafe,
685
+ err,
686
+ errObj,
687
+ ok,
688
+ okObj,
689
+ safe,
690
+ withObjects
691
+ });
692
+ //# sourceMappingURL=index.cjs.map