@u17g/deferrable 1.0.0 → 2.0.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.
- package/README.md +29 -2
- package/dist/index.cjs +10 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +40 -5
package/README.md
CHANGED
|
@@ -59,9 +59,29 @@ await deferrable(async (defer): Promise<number> => {
|
|
|
59
59
|
- **Result passed to deferred callbacks**: each deferred callback receives a tuple `[value, error]`:
|
|
60
60
|
- `[value, undefined]` when the callback resolved
|
|
61
61
|
- `[undefined, error]` when the callback threw / rejected
|
|
62
|
-
- **Error propagation**: if the callback fails, deferred callbacks still run,
|
|
62
|
+
- **Error propagation (main callback)**: if the callback fails, deferred callbacks still run; if none of them fails, the original error is thrown. (If a deferred callback fails, see below.)
|
|
63
63
|
- **Awaited sequentially**: deferred callbacks are awaited one by one (no parallel execution).
|
|
64
|
-
- **Deferred callback errors
|
|
64
|
+
- **Deferred callback errors**: if a deferred callback throws/rejects, `deferrable` fails fast with that error (remaining deferred callbacks are not executed). If the main callback already failed, `deferrable` throws an `AggregateError` containing both errors.
|
|
65
|
+
- If you want **all** deferred callbacks to run even if one of them fails, catch errors inside the deferred callback itself.
|
|
66
|
+
|
|
67
|
+
### Example: always run all deferred callbacks (catch inside defer)
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
await deferrable(async (defer): Promise<void> => {
|
|
71
|
+
defer(async () => {
|
|
72
|
+
await cleanupA().catch((err) => {
|
|
73
|
+
// ignore / log
|
|
74
|
+
console.error("cleanupA failed:", err);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
defer(async () => {
|
|
79
|
+
await cleanupB().catch((err) => {
|
|
80
|
+
console.error("cleanupB failed:", err);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
```
|
|
65
85
|
|
|
66
86
|
### Example: LIFO order
|
|
67
87
|
|
|
@@ -145,3 +165,10 @@ async function reserveInventory(_orderId: string): Promise<void> {
|
|
|
145
165
|
}
|
|
146
166
|
async function releaseInventory(_orderId: string): Promise<void> {}
|
|
147
167
|
```
|
|
168
|
+
|
|
169
|
+
### Durable execution frameworks (Inngest / Temporal, etc.)
|
|
170
|
+
|
|
171
|
+
`deferrable` can be used with durable execution frameworks such as Inngest or Temporal.
|
|
172
|
+
Because deferred callbacks are **fail-fast**, a failure in cleanup/rollback will fail the run immediately, which helps avoid continuing a workflow in an inconsistent state.
|
|
173
|
+
|
|
174
|
+
If you want a particular cleanup to be best-effort (i.e. not fail the run), catch errors inside the deferred callback (e.g. `await cleanup().catch(...)`).
|
package/dist/index.cjs
CHANGED
|
@@ -46,16 +46,22 @@ async function deferrable(fn) {
|
|
|
46
46
|
stack.push(exec);
|
|
47
47
|
};
|
|
48
48
|
const result = await wrap(fn(defer));
|
|
49
|
+
const [value, mainError] = result;
|
|
49
50
|
while (stack.length) {
|
|
50
51
|
const exec = stack.pop();
|
|
51
52
|
try {
|
|
52
53
|
await exec(result);
|
|
53
54
|
}
|
|
54
|
-
catch {
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (mainError !== undefined) {
|
|
57
|
+
throw new AggregateError([mainError, error], "deferrable: multiple errors");
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (mainError !== undefined) {
|
|
63
|
+
throw mainError;
|
|
55
64
|
}
|
|
56
|
-
const [value, error] = result;
|
|
57
|
-
if (error !== undefined)
|
|
58
|
-
throw error;
|
|
59
65
|
return value;
|
|
60
66
|
}
|
|
61
67
|
;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAqEA,gCAwBC;AA/DD,SAAS,EAAE,CAAI,KAAQ;IACrB,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AAC5B,CAAC;AAAA,CAAC;AAEF,SAAS,OAAO,CAAY,KAAQ;IAClC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC5B,CAAC;AAAA,CAAC;AAEF,KAAK,UAAU,IAAI,CAAe,OAAmB;IACnD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC;QAC5B,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,KAAU,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,UAAU,CAC9B,EAAsC;IAEtC,MAAM,KAAK,GAA8C,EAAE,CAAC;IAC5D,MAAM,KAAK,GAA6B,CAAC,IAAI,EAAE,EAAE;QAC/C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC;IAClC,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,cAAc,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,6BAA6B,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAAA,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deferred callback registered via `defer(...)`.
|
|
3
|
+
*
|
|
4
|
+
* It will be executed after the main callback finishes (either resolved or rejected),
|
|
5
|
+
* in **LIFO** order. If it throws/rejects, `deferrable` fails fast with that error.
|
|
6
|
+
*/
|
|
7
|
+
export type DeferredCallback<T, E = Error> = (
|
|
8
|
+
/**
|
|
9
|
+
* The result of the main callback:
|
|
10
|
+
* - `[value, undefined]` on success
|
|
11
|
+
* - `[undefined, error]` on failure
|
|
12
|
+
*/
|
|
13
|
+
result: [value: T, error: undefined] | [value: undefined, error: E]) => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Register a deferred callback to be executed later (LIFO).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* await deferrable(async (defer): Promise<number> => {
|
|
19
|
+
* defer(async ([value, err]) => {
|
|
20
|
+
* // cleanup / rollback
|
|
21
|
+
* });
|
|
22
|
+
* return 42;
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export type Defer<T, E = Error> = (exec: DeferredCallback<T, E>) => void;
|
|
1
26
|
/**
|
|
2
27
|
* Deferrable execution.
|
|
3
28
|
*
|
|
@@ -20,5 +45,5 @@
|
|
|
20
45
|
* return "Hello, world!";
|
|
21
46
|
* });
|
|
22
47
|
*/
|
|
23
|
-
export declare function deferrable<T, E = Error>(fn: (defer:
|
|
48
|
+
export declare function deferrable<T, E = Error>(fn: (defer: Defer<T, E>) => Promise<T>): Promise<T>;
|
|
24
49
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI;AAC3C;;;;GAIG;AACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,KAChE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;;;;;;GAUG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;AAmBzE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAC3C,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,OAAO,CAAC,CAAC,CAAC,CAsBZ"}
|
package/dist/index.js
CHANGED
|
@@ -43,16 +43,22 @@ export async function deferrable(fn) {
|
|
|
43
43
|
stack.push(exec);
|
|
44
44
|
};
|
|
45
45
|
const result = await wrap(fn(defer));
|
|
46
|
+
const [value, mainError] = result;
|
|
46
47
|
while (stack.length) {
|
|
47
48
|
const exec = stack.pop();
|
|
48
49
|
try {
|
|
49
50
|
await exec(result);
|
|
50
51
|
}
|
|
51
|
-
catch {
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (mainError !== undefined) {
|
|
54
|
+
throw new AggregateError([mainError, error], "deferrable: multiple errors");
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (mainError !== undefined) {
|
|
60
|
+
throw mainError;
|
|
52
61
|
}
|
|
53
|
-
const [value, error] = result;
|
|
54
|
-
if (error !== undefined)
|
|
55
|
-
throw error;
|
|
56
62
|
return value;
|
|
57
63
|
}
|
|
58
64
|
;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8BA,SAAS,EAAE,CAAI,KAAQ;IACrB,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AAC5B,CAAC;AAAA,CAAC;AAEF,SAAS,OAAO,CAAY,KAAQ;IAClC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC5B,CAAC;AAAA,CAAC;AAEF,KAAK,UAAU,IAAI,CAAe,OAAmB;IACnD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC;QAC5B,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,KAAU,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAsC;IAEtC,MAAM,KAAK,GAA8C,EAAE,CAAC;IAC5D,MAAM,KAAK,GAA6B,CAAC,IAAI,EAAE,EAAE;QAC/C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC;IAClC,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,MAAM,IAAI,cAAc,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,6BAA6B,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,KAAU,CAAC;AACpB,CAAC;AAAA,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@u17g/deferrable",
|
|
3
3
|
"description": "A tiny defer utility for JavaScript/TypeScript: register cleanup/rollback callbacks that run in LIFO order. Works in any runtime (Node.js, Bun, Deno, browsers).",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/u17g/deferrable.git"
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
type Result<T, E = Error> = [value: T, error: undefined] | [value: undefined, error: E];
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A deferred callback registered via `defer(...)`.
|
|
5
|
+
*
|
|
6
|
+
* It will be executed after the main callback finishes (either resolved or rejected),
|
|
7
|
+
* in **LIFO** order. If it throws/rejects, `deferrable` fails fast with that error.
|
|
8
|
+
*/
|
|
9
|
+
export type DeferredCallback<T, E = Error> = (
|
|
10
|
+
/**
|
|
11
|
+
* The result of the main callback:
|
|
12
|
+
* - `[value, undefined]` on success
|
|
13
|
+
* - `[undefined, error]` on failure
|
|
14
|
+
*/
|
|
15
|
+
result: [value: T, error: undefined] | [value: undefined, error: E],
|
|
16
|
+
) => void | Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register a deferred callback to be executed later (LIFO).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* await deferrable(async (defer): Promise<number> => {
|
|
23
|
+
* defer(async ([value, err]) => {
|
|
24
|
+
* // cleanup / rollback
|
|
25
|
+
* });
|
|
26
|
+
* return 42;
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export type Defer<T, E = Error> = (exec: DeferredCallback<T, E>) => void;
|
|
30
|
+
|
|
3
31
|
function ok<T>(value: T): Result<T, never> {
|
|
4
32
|
return [value, undefined];
|
|
5
33
|
};
|
|
@@ -40,20 +68,27 @@ async function wrap<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> {
|
|
|
40
68
|
* });
|
|
41
69
|
*/
|
|
42
70
|
export async function deferrable<T, E = Error>(
|
|
43
|
-
fn: (defer:
|
|
71
|
+
fn: (defer: Defer<T, E>) => Promise<T>,
|
|
44
72
|
): Promise<T> {
|
|
45
73
|
const stack: Parameters<Parameters<typeof fn>[0]>[0][] = [];
|
|
46
74
|
const defer: Parameters<typeof fn>[0] = (exec) => {
|
|
47
75
|
stack.push(exec);
|
|
48
76
|
};
|
|
49
77
|
const result = await wrap<T, E>(fn(defer));
|
|
78
|
+
const [value, mainError] = result;
|
|
50
79
|
while (stack.length) {
|
|
51
80
|
const exec = stack.pop()!;
|
|
52
81
|
try {
|
|
53
82
|
await exec(result);
|
|
54
|
-
} catch {
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (mainError !== undefined) {
|
|
85
|
+
throw new AggregateError([mainError, error], "deferrable: multiple errors");
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (mainError !== undefined) {
|
|
91
|
+
throw mainError;
|
|
55
92
|
}
|
|
56
|
-
const [value, error] = result;
|
|
57
|
-
if (error !== undefined) throw error;
|
|
58
93
|
return value as T;
|
|
59
|
-
};
|
|
94
|
+
};
|