durable-x 0.1.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/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/adapters/windmill.d.ts +3 -0
- package/dist/adapters/windmill.d.ts.map +1 -0
- package/dist/api.d.ts +17 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/checkpoint.d.ts +18 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/cleanup.d.ts +3 -0
- package/dist/cleanup.d.ts.map +1 -0
- package/dist/hash.d.ts +3 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +506 -0
- package/dist/index.js.map +15 -0
- package/dist/log.d.ts +10 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Windmill Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Durable Execution
|
|
2
|
+
|
|
3
|
+
**Lightweight durable execution with checkpoint memoization and saga cleanup—no sidecar, no orchestration engine, works anywhere.**
|
|
4
|
+
|
|
5
|
+
Add Temporal/Dapr-style durability and idempotency to **Windmill workflows, APIs, background jobs, CLI tools**—anywhere you run async code.
|
|
6
|
+
|
|
7
|
+
## Why This Exists
|
|
8
|
+
|
|
9
|
+
Temporal and Dapr are powerful but heavyweight: they require sidecars, generators, replay logic, and determinism constraints. **Most teams just want crash recovery and step memoization without the infrastructure overhead.**
|
|
10
|
+
|
|
11
|
+
This library brings durability to your **existing workflows and code**:
|
|
12
|
+
|
|
13
|
+
- Running a Windmill job that fetches → transforms → uploads? Add crash recovery.
|
|
14
|
+
- Building an API endpoint that calls 3 services? Make it idempotent with checkpoints.
|
|
15
|
+
- Processing background jobs? Memoize expensive steps, skip on retry.
|
|
16
|
+
- Running a CLI migration? Resume from last checkpoint if it crashes.
|
|
17
|
+
|
|
18
|
+
**No sidecar. No generators. No determinism requirements. Just checkpoints.**
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Checkpoint-based progress tracking**: Survives crashes, retries resume from last checkpoint
|
|
23
|
+
- **Step memoization**: Expensive/side-effectful steps cached by input hash, skipped on retry
|
|
24
|
+
- **Saga-style cleanup**: Register compensations before risky operations, auto-executed on crash
|
|
25
|
+
- **Platform-agnostic core**: Works with any async runtime (Node, Bun, Deno)
|
|
26
|
+
- **Bring your own storage**: Postgres, Redis, SQLite, S3, Windmill datatable—anything works
|
|
27
|
+
- **Functional architecture**: Pure functions, Result-based error handling, no exceptions
|
|
28
|
+
- **Zero infrastructure**: Just a table and a storage adapter—no sidecars, daemons, or special runtimes
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun add @tommdavidson/durable-x
|
|
34
|
+
npm install @tommdavidson/durable-x
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
***
|
|
38
|
+
|
|
39
|
+
## Landscape & Decision Guide
|
|
40
|
+
|
|
41
|
+
### What's Out There
|
|
42
|
+
|
|
43
|
+
The durability space is fragmented. Here's what exists and what doesn't:
|
|
44
|
+
|
|
45
|
+
| Tool | Scope | Sidecar | Step Memoization | Saga Cleanup | Works Anywhere | Primary Use Case |
|
|
46
|
+
|------|-------|---------|------------------|--------------|---|---|
|
|
47
|
+
| **Temporal** | Full orchestration | ✅ Required | ✅ (via replay) | ❌ Manual | ❌ Temporal-only | Distributed systems |
|
|
48
|
+
| **Dapr** | Full orchestration | ✅ Required | ✅ (via replay) | ❌ Manual | ❌ Dapr-only | Microservices |
|
|
49
|
+
| **Restate** | Full orchestration | ✅ Required | ✅ (via replay) | ❌ Manual | ❌ Restate-only | Event-driven |
|
|
50
|
+
| **AWS Durable SDK** | Lambda orchestration | ❌ | ✅ (via replay) | ❌ Manual | ❌ Lambda-only | Serverless |
|
|
51
|
+
| **gpahal/durable-execution** | Task workflows | ❌ | ✅ (via replay) | ❌ Manual | ✅ | AI workflows |
|
|
52
|
+
| **micro-memoize** | In-memory caching | ❌ | ✅ | ❌ | ✅ (in-memory only) | Pure functions |
|
|
53
|
+
| **@windmill-labs** | Multi-step resilience | ❌ | ✅ (input-based) | ✅ Built-in | ✅ | Windmill + general |
|
|
54
|
+
|
|
55
|
+
***
|
|
56
|
+
|
|
57
|
+
### The Gap This Library Fills
|
|
58
|
+
|
|
59
|
+
**You're solving a unique combination:**
|
|
60
|
+
|
|
61
|
+
1. **Checkpoint + memoization + saga cleanup** bundled together (only library with all three)
|
|
62
|
+
2. **No infrastructure** (no sidecar, no special runtime, no determinism constraints)
|
|
63
|
+
3. **Windmill-native** (works with Windmill's datatable out-of-box, plus Postgres/Redis/etc.)
|
|
64
|
+
4. **Saga cleanup built-in** (not manual like Temporal/Dapr/Restate)
|
|
65
|
+
5. **Input-based memoization** (not replay-based like AWS/Temporal)
|
|
66
|
+
|
|
67
|
+
### vs `gpahal/durable-execution`
|
|
68
|
+
|
|
69
|
+
| Feature | gpahal/durable-execution | @tommdavidson/durable-execution |
|
|
70
|
+
|---------|--------------------------|----------------------------------|
|
|
71
|
+
| **Primary focus** | AI workflows [1][2] | Windmill + general workflows |
|
|
72
|
+
| **Saga cleanup** | ❌ Manual | ✅ Built-in (`beforeRisky`/`afterSafe`) |
|
|
73
|
+
| **Memoization approach** | Replay-based | Input-hash based (skip unchanged steps) |
|
|
74
|
+
| **Storage adapters** | Built-in | Pluggable (Windmill, Postgres, Redis, S3) |
|
|
75
|
+
| **Windmill integration** | ❌ | ✅ Native datatable support |
|
|
76
|
+
| **Error handling** | Unknown | neverthrow Result types |
|
|
77
|
+
| **Best for** | Generic task workflows | Windmill-first + multi-platform |
|
|
78
|
+
|
|
79
|
+
**Bottom line**: `gpahal/durable-execution` is task-focused and AI-oriented. This library is Windmill-native with built-in saga cleanup and pluggable storage.[2][1]
|
|
80
|
+
|
|
81
|
+
***
|
|
82
|
+
|
|
83
|
+
### Decision Tree
|
|
84
|
+
|
|
85
|
+
**Building AI workflows with generic task engine?** → Consider `gpahal/durable-execution`
|
|
86
|
+
**Using Windmill or need saga cleanup?** → Use this library
|
|
87
|
+
**Need distributed multi-service orchestration?** → Temporal/Dapr
|
|
88
|
+
**AWS Lambda only?** → AWS Durable SDK
|
|
89
|
+
**Just need request idempotency?** → Stripe keys or custom
|
|
90
|
+
**Need to skip expensive steps on retry with cleanup?** → This library
|
|
91
|
+
|
|
92
|
+
***
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
### Windmill Workflow
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import {
|
|
100
|
+
windmillStorage,
|
|
101
|
+
load,
|
|
102
|
+
durable,
|
|
103
|
+
beforeRisky,
|
|
104
|
+
afterSafe,
|
|
105
|
+
complete,
|
|
106
|
+
type CleanupRegistry,
|
|
107
|
+
} from '@tommdavidson/durable-x';
|
|
108
|
+
|
|
109
|
+
const cleanupRunners: CleanupRegistry = {
|
|
110
|
+
delete_temp: async (params) => {
|
|
111
|
+
await Bun.remove(params.path as string).catch(() => {});
|
|
112
|
+
},
|
|
113
|
+
rollback_upload: async (params) => {
|
|
114
|
+
await deleteFromCdn(params.url as string);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export async function main(fileId: string) {
|
|
119
|
+
const cp = await load(windmillStorage)(cleanupRunners)(fileId);
|
|
120
|
+
const step = durable(windmillStorage)(cp);
|
|
121
|
+
|
|
122
|
+
const downloaded = await step('download')({ fileId })(() =>
|
|
123
|
+
fetch(`https://api.example.com/files/${fileId}`).then((r) => r.json()),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const processed = await step('process')({ path: downloaded.path })(() =>
|
|
127
|
+
processFile(downloaded.path),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const uploadUrl = `s3://bucket/${fileId}.json`;
|
|
131
|
+
await beforeRisky(windmillStorage)(cp)('rollback_upload')({ url: uploadUrl });
|
|
132
|
+
|
|
133
|
+
const uploaded = await step('upload')({ url: uploadUrl })(() =>
|
|
134
|
+
uploadToCdn(processed, uploadUrl),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await afterSafe(windmillStorage)(cp)('rollback_upload');
|
|
138
|
+
await complete(windmillStorage)(cp);
|
|
139
|
+
|
|
140
|
+
return { fileId, uploadedUrl: uploadUrl };
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
***
|
|
145
|
+
|
|
146
|
+
## Why `@tommdavidson/durable-x`?
|
|
147
|
+
|
|
148
|
+
**The name justifies itself:**
|
|
149
|
+
|
|
150
|
+
1. **Scoped package** (`@windmill-labs/`) = clear differentiation from `gpahal/durable-execution`
|
|
151
|
+
2. **Windmill-first** = Native integration with Windmill's ecosystem
|
|
152
|
+
3. **Different target** = Not competing with `gpahal` (AI workflows vs Windmill + general)
|
|
153
|
+
4. **Unique features** = Built-in saga cleanup + pluggable storage
|
|
154
|
+
|
|
155
|
+
**You're NOT stepping on toes.** Different niches, different users, complementary tools.
|
|
156
|
+
|
|
157
|
+
***
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT – Free to use, modify, distribute.
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
Issues and PRs welcome. Run `bun run prepublish` before submitting.
|
|
166
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windmill.d.ts","sourceRoot":"","sources":["../../src/adapters/windmill.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,UAAU,CAAC;AAwB3D,eAAO,MAAM,eAAe,EAAE,cAsD7B,CAAC"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Checkpoint, CleanupRegistry, StorageAdapter } from './types';
|
|
2
|
+
export declare const load: (storage: StorageAdapter) => (runners: CleanupRegistry) => (fileId: string) => Promise<Checkpoint>;
|
|
3
|
+
export declare const beforeRisky: (storage: StorageAdapter) => (cp: Checkpoint) => (type: string) => (params: Record<string, unknown>) => Promise<Checkpoint>;
|
|
4
|
+
export declare const afterSafe: (storage: StorageAdapter) => (cp: Checkpoint) => (type: string) => Promise<Checkpoint>;
|
|
5
|
+
export declare const durable: (storage: StorageAdapter) => (cp: Checkpoint) => (name: string) => <I, T>(inputs: I) => (fn: () => Promise<T>) => Promise<T>;
|
|
6
|
+
export declare const complete: (storage: StorageAdapter) => (cp: Checkpoint) => Promise<Checkpoint>;
|
|
7
|
+
export declare const fail: (storage: StorageAdapter) => (cp: Checkpoint) => Promise<Checkpoint>;
|
|
8
|
+
export declare const clear: (storage: StorageAdapter) => (fileId: string) => Promise<void>;
|
|
9
|
+
export declare const clearStep: (storage: StorageAdapter) => (cp: Checkpoint) => (name: string) => Promise<Checkpoint>;
|
|
10
|
+
export declare const sweep: (storage: StorageAdapter) => (runners: CleanupRegistry) => (staleThresholdMs?: number) => Promise<{
|
|
11
|
+
cleaned: number;
|
|
12
|
+
details: string[];
|
|
13
|
+
}>;
|
|
14
|
+
export declare const sweepAllCleanups: (storage: StorageAdapter) => (runners: CleanupRegistry) => () => Promise<{
|
|
15
|
+
cleaned: number;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAuB3E,eAAO,MAAM,IAAI,GACd,SAAS,cAAc,MACrB,SAAS,eAAe,MACtB,QAAQ,MAAM,KAAG,OAAO,CAAC,UAAU,CAgBX,CAAC;AAEhC,eAAO,MAAM,WAAW,GACrB,SAAS,cAAc,MACrB,IAAI,UAAU,MACZ,MAAM,MAAM,MACV,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,UAAU,CAO/C,CAAC;AAEf,eAAO,MAAM,SAAS,GACnB,SAAS,cAAc,MACrB,IAAI,UAAU,MACZ,MAAM,MAAM,KAAG,OAAO,CAAC,UAAU,CAO5B,CAAC;AAEb,eAAO,MAAM,OAAO,GACjB,SAAS,cAAc,MACrB,IAAI,UAAU,MACZ,MAAM,MAAM,MACV,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,MACb,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAkBhC,CAAC;AAEZ,eAAO,MAAM,QAAQ,GAClB,SAAS,cAAc,MACrB,IAAI,UAAU,KAAG,OAAO,CAAC,UAAU,CAM9B,CAAC;AAEX,eAAO,MAAM,IAAI,GACd,SAAS,cAAc,MACrB,IAAI,UAAU,KAAG,OAAO,CAAC,UAAU,CAM9B,CAAC;AAEX,eAAO,MAAM,KAAK,GACf,SAAS,cAAc,MACrB,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CACH,CAAC;AAEhC,eAAO,MAAM,SAAS,GACnB,SAAS,cAAc,MACrB,IAAI,UAAU,MACZ,MAAM,MAAM,KAAG,OAAO,CAAC,UAAU,CAM5B,CAAC;AAEb,eAAO,MAAM,KAAK,GACf,SAAS,cAAc,MACrB,SAAS,eAAe,MAErB,mBAAkB,MAAuB,KACxC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAU9C,CAAC;AAEV,eAAO,MAAM,gBAAgB,GAC1B,SAAS,cAAc,MACrB,SAAS,eAAe,WACnB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAS5B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Checkpoint, CheckpointStatus, CleanupSpec, StepRecord, CacheCheck } from './types';
|
|
2
|
+
export declare const emptyCheckpoint: (fileId: string) => Checkpoint;
|
|
3
|
+
export declare const checkCache: <T>(step: StepRecord | undefined, inputHash: string) => CacheCheck<T>;
|
|
4
|
+
export declare const withStep: (cp: Checkpoint, name: string, result: unknown, inputHash: string) => Checkpoint;
|
|
5
|
+
export declare const withCleanup: (cp: Checkpoint, action: CleanupSpec) => Checkpoint;
|
|
6
|
+
export declare const withoutCleanup: (cp: Checkpoint, type: string) => Checkpoint;
|
|
7
|
+
export declare const withStatus: (cp: Checkpoint, status: CheckpointStatus) => Checkpoint;
|
|
8
|
+
export declare const withoutStep: (cp: Checkpoint, name: string) => Checkpoint;
|
|
9
|
+
export declare const rowToCheckpoint: (row: Record<string, unknown>) => Checkpoint;
|
|
10
|
+
export declare const checkpointToRow: (cp: Checkpoint) => {
|
|
11
|
+
file_id: string;
|
|
12
|
+
started_at: number;
|
|
13
|
+
completed_at: number | null;
|
|
14
|
+
status: CheckpointStatus;
|
|
15
|
+
steps: string;
|
|
16
|
+
cleanup: string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=checkpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAEhB,WAAW,EACX,UAAU,EACV,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,UAO/C,CAAC;AAEH,eAAO,MAAM,UAAU,GAAI,CAAC,EAC1B,MAAM,UAAU,GAAG,SAAS,EAC5B,WAAW,MAAM,KAChB,UAAU,CAAC,CAAC,CAGK,CAAC;AAErB,eAAO,MAAM,QAAQ,GACnB,IAAI,UAAU,EACd,MAAM,MAAM,EACZ,QAAQ,OAAO,EACf,WAAW,MAAM,KAChB,UAMD,CAAC;AAEH,eAAO,MAAM,WAAW,GACtB,IAAI,UAAU,EACd,QAAQ,WAAW,KAClB,UAUD,CAAC;AAEH,eAAO,MAAM,cAAc,GAAI,IAAI,UAAU,EAAE,MAAM,MAAM,KAAG,UAG5D,CAAC;AAEH,eAAO,MAAM,UAAU,GACrB,IAAI,UAAU,EACd,QAAQ,gBAAgB,KACvB,UAID,CAAC;AAEH,eAAO,MAAM,WAAW,GAAI,IAAI,UAAU,EAAE,MAAM,MAAM,KAAG,UAG1D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,UAa7D,CAAC;AAEH,eAAO,MAAM,eAAe,GAAI,IAAI,UAAU;;;;;;;CAO5C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../src/cleanup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA4B9D,eAAO,MAAM,kBAAkB,GAC5B,SAAS,eAAe,MACtB,SAAS,aAAa,EAAE,KAAG,OAAO,CAAC,IAAI,CACiC,CAAC"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,GAAI,KAAK,OAAO,KAAG,OAa7B,CAAC;AAEZ,eAAO,MAAM,IAAI,GAAI,OAAO,OAAO,KAAG,MAGrB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAGzH,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/checkpoint.ts
|
|
3
|
+
var emptyCheckpoint = (fileId) => ({
|
|
4
|
+
fileId,
|
|
5
|
+
startedAt: Date.now(),
|
|
6
|
+
completedAt: null,
|
|
7
|
+
status: "running",
|
|
8
|
+
steps: {},
|
|
9
|
+
cleanup: []
|
|
10
|
+
});
|
|
11
|
+
var checkCache = (step, inputHash) => step?.inputHash === inputHash ? { hit: true, result: step.result } : { hit: false };
|
|
12
|
+
var withStep = (cp, name, result, inputHash) => ({
|
|
13
|
+
...cp,
|
|
14
|
+
steps: {
|
|
15
|
+
...cp.steps,
|
|
16
|
+
[name]: { result, inputHash, completedAt: Date.now() }
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
var withCleanup = (cp, action) => ({
|
|
20
|
+
...cp,
|
|
21
|
+
cleanup: [
|
|
22
|
+
...cp.cleanup,
|
|
23
|
+
{
|
|
24
|
+
...action,
|
|
25
|
+
id: `${action.type}-${crypto.randomUUID()}`,
|
|
26
|
+
registeredAt: Date.now()
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
var withoutCleanup = (cp, type) => ({
|
|
31
|
+
...cp,
|
|
32
|
+
cleanup: cp.cleanup.filter((c) => c.type !== type)
|
|
33
|
+
});
|
|
34
|
+
var withStatus = (cp, status) => ({
|
|
35
|
+
...cp,
|
|
36
|
+
status,
|
|
37
|
+
completedAt: status === "running" ? null : Date.now()
|
|
38
|
+
});
|
|
39
|
+
var withoutStep = (cp, name) => {
|
|
40
|
+
const { [name]: _removed, ...rest } = cp.steps;
|
|
41
|
+
return { ...cp, steps: rest };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/hash.ts
|
|
45
|
+
var sortKeys = (obj) => Array.isArray(obj) ? obj.map(sortKeys) : obj && typeof obj === "object" ? Object.keys(obj).sort().reduce((acc, k) => ({
|
|
46
|
+
...acc,
|
|
47
|
+
[k]: sortKeys(obj[k])
|
|
48
|
+
}), {}) : obj;
|
|
49
|
+
var hash = (input) => Array.from(JSON.stringify(sortKeys(input))).reduce((h, c) => (h << 5) - h + c.charCodeAt(0) | 0, 0).toString(36);
|
|
50
|
+
|
|
51
|
+
// node_modules/neverthrow/dist/index.es.js
|
|
52
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
53
|
+
function adopt(value) {
|
|
54
|
+
return value instanceof P ? value : new P(function(resolve) {
|
|
55
|
+
resolve(value);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return new (P || (P = Promise))(function(resolve, reject) {
|
|
59
|
+
function fulfilled(value) {
|
|
60
|
+
try {
|
|
61
|
+
step(generator.next(value));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
reject(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function rejected(value) {
|
|
67
|
+
try {
|
|
68
|
+
step(generator["throw"](value));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
reject(e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function step(result) {
|
|
74
|
+
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
75
|
+
}
|
|
76
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function __values(o) {
|
|
80
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
81
|
+
if (m)
|
|
82
|
+
return m.call(o);
|
|
83
|
+
if (o && typeof o.length === "number")
|
|
84
|
+
return {
|
|
85
|
+
next: function() {
|
|
86
|
+
if (o && i >= o.length)
|
|
87
|
+
o = undefined;
|
|
88
|
+
return { value: o && o[i++], done: !o };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
92
|
+
}
|
|
93
|
+
function __await(v) {
|
|
94
|
+
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
95
|
+
}
|
|
96
|
+
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
97
|
+
if (!Symbol.asyncIterator)
|
|
98
|
+
throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
99
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
100
|
+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
|
|
101
|
+
return this;
|
|
102
|
+
}, i;
|
|
103
|
+
function verb(n) {
|
|
104
|
+
if (g[n])
|
|
105
|
+
i[n] = function(v) {
|
|
106
|
+
return new Promise(function(a, b) {
|
|
107
|
+
q.push([n, v, a, b]) > 1 || resume(n, v);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function resume(n, v) {
|
|
112
|
+
try {
|
|
113
|
+
step(g[n](v));
|
|
114
|
+
} catch (e) {
|
|
115
|
+
settle(q[0][3], e);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function step(r) {
|
|
119
|
+
r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
|
|
120
|
+
}
|
|
121
|
+
function fulfill(value) {
|
|
122
|
+
resume("next", value);
|
|
123
|
+
}
|
|
124
|
+
function reject(value) {
|
|
125
|
+
resume("throw", value);
|
|
126
|
+
}
|
|
127
|
+
function settle(f, v) {
|
|
128
|
+
if (f(v), q.shift(), q.length)
|
|
129
|
+
resume(q[0][0], q[0][1]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function __asyncDelegator(o) {
|
|
133
|
+
var i, p;
|
|
134
|
+
return i = {}, verb("next"), verb("throw", function(e) {
|
|
135
|
+
throw e;
|
|
136
|
+
}), verb("return"), i[Symbol.iterator] = function() {
|
|
137
|
+
return this;
|
|
138
|
+
}, i;
|
|
139
|
+
function verb(n, f) {
|
|
140
|
+
i[n] = o[n] ? function(v) {
|
|
141
|
+
return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v;
|
|
142
|
+
} : f;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function __asyncValues(o) {
|
|
146
|
+
if (!Symbol.asyncIterator)
|
|
147
|
+
throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
148
|
+
var m = o[Symbol.asyncIterator], i;
|
|
149
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
|
|
150
|
+
return this;
|
|
151
|
+
}, i);
|
|
152
|
+
function verb(n) {
|
|
153
|
+
i[n] = o[n] && function(v) {
|
|
154
|
+
return new Promise(function(resolve, reject) {
|
|
155
|
+
v = o[n](v), settle(resolve, reject, v.done, v.value);
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function settle(resolve, reject, d, v) {
|
|
160
|
+
Promise.resolve(v).then(function(v2) {
|
|
161
|
+
resolve({ value: v2, done: d });
|
|
162
|
+
}, reject);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
var defaultErrorConfig = {
|
|
166
|
+
withStackTrace: false
|
|
167
|
+
};
|
|
168
|
+
var createNeverThrowError = (message, result, config = defaultErrorConfig) => {
|
|
169
|
+
const data = result.isOk() ? { type: "Ok", value: result.value } : { type: "Err", value: result.error };
|
|
170
|
+
const maybeStack = config.withStackTrace ? new Error().stack : undefined;
|
|
171
|
+
return {
|
|
172
|
+
data,
|
|
173
|
+
message,
|
|
174
|
+
stack: maybeStack
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
class ResultAsync {
|
|
179
|
+
constructor(res) {
|
|
180
|
+
this._promise = res;
|
|
181
|
+
}
|
|
182
|
+
static fromSafePromise(promise) {
|
|
183
|
+
const newPromise = promise.then((value) => new Ok(value));
|
|
184
|
+
return new ResultAsync(newPromise);
|
|
185
|
+
}
|
|
186
|
+
static fromPromise(promise, errorFn) {
|
|
187
|
+
const newPromise = promise.then((value) => new Ok(value)).catch((e) => new Err(errorFn(e)));
|
|
188
|
+
return new ResultAsync(newPromise);
|
|
189
|
+
}
|
|
190
|
+
static fromThrowable(fn, errorFn) {
|
|
191
|
+
return (...args) => {
|
|
192
|
+
return new ResultAsync((() => __awaiter(this, undefined, undefined, function* () {
|
|
193
|
+
try {
|
|
194
|
+
return new Ok(yield fn(...args));
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return new Err(errorFn ? errorFn(error) : error);
|
|
197
|
+
}
|
|
198
|
+
}))());
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
static combine(asyncResultList) {
|
|
202
|
+
return combineResultAsyncList(asyncResultList);
|
|
203
|
+
}
|
|
204
|
+
static combineWithAllErrors(asyncResultList) {
|
|
205
|
+
return combineResultAsyncListWithAllErrors(asyncResultList);
|
|
206
|
+
}
|
|
207
|
+
map(f) {
|
|
208
|
+
return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
|
|
209
|
+
if (res.isErr()) {
|
|
210
|
+
return new Err(res.error);
|
|
211
|
+
}
|
|
212
|
+
return new Ok(yield f(res.value));
|
|
213
|
+
})));
|
|
214
|
+
}
|
|
215
|
+
mapErr(f) {
|
|
216
|
+
return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
|
|
217
|
+
if (res.isOk()) {
|
|
218
|
+
return new Ok(res.value);
|
|
219
|
+
}
|
|
220
|
+
return new Err(yield f(res.error));
|
|
221
|
+
})));
|
|
222
|
+
}
|
|
223
|
+
andThen(f) {
|
|
224
|
+
return new ResultAsync(this._promise.then((res) => {
|
|
225
|
+
if (res.isErr()) {
|
|
226
|
+
return new Err(res.error);
|
|
227
|
+
}
|
|
228
|
+
const newValue = f(res.value);
|
|
229
|
+
return newValue instanceof ResultAsync ? newValue._promise : newValue;
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
orElse(f) {
|
|
233
|
+
return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
|
|
234
|
+
if (res.isErr()) {
|
|
235
|
+
return f(res.error);
|
|
236
|
+
}
|
|
237
|
+
return new Ok(res.value);
|
|
238
|
+
})));
|
|
239
|
+
}
|
|
240
|
+
match(ok, _err) {
|
|
241
|
+
return this._promise.then((res) => res.match(ok, _err));
|
|
242
|
+
}
|
|
243
|
+
unwrapOr(t) {
|
|
244
|
+
return this._promise.then((res) => res.unwrapOr(t));
|
|
245
|
+
}
|
|
246
|
+
safeUnwrap() {
|
|
247
|
+
return __asyncGenerator(this, arguments, function* safeUnwrap_1() {
|
|
248
|
+
return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
then(successCallback, failureCallback) {
|
|
252
|
+
return this._promise.then(successCallback, failureCallback);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
var errAsync = (err) => new ResultAsync(Promise.resolve(new Err(err)));
|
|
256
|
+
var fromPromise = ResultAsync.fromPromise;
|
|
257
|
+
var fromSafePromise = ResultAsync.fromSafePromise;
|
|
258
|
+
var fromAsyncThrowable = ResultAsync.fromThrowable;
|
|
259
|
+
var combineResultList = (resultList) => {
|
|
260
|
+
let acc = ok([]);
|
|
261
|
+
for (const result of resultList) {
|
|
262
|
+
if (result.isErr()) {
|
|
263
|
+
acc = err(result.error);
|
|
264
|
+
break;
|
|
265
|
+
} else {
|
|
266
|
+
acc.map((list) => list.push(result.value));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return acc;
|
|
270
|
+
};
|
|
271
|
+
var combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
|
|
272
|
+
var combineResultListWithAllErrors = (resultList) => {
|
|
273
|
+
let acc = ok([]);
|
|
274
|
+
for (const result of resultList) {
|
|
275
|
+
if (result.isErr() && acc.isErr()) {
|
|
276
|
+
acc.error.push(result.error);
|
|
277
|
+
} else if (result.isErr() && acc.isOk()) {
|
|
278
|
+
acc = err([result.error]);
|
|
279
|
+
} else if (result.isOk() && acc.isOk()) {
|
|
280
|
+
acc.value.push(result.value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return acc;
|
|
284
|
+
};
|
|
285
|
+
var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
|
|
286
|
+
var Result;
|
|
287
|
+
(function(Result2) {
|
|
288
|
+
function fromThrowable(fn, errorFn) {
|
|
289
|
+
return (...args) => {
|
|
290
|
+
try {
|
|
291
|
+
const result = fn(...args);
|
|
292
|
+
return ok(result);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
return err(errorFn ? errorFn(e) : e);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
Result2.fromThrowable = fromThrowable;
|
|
299
|
+
function combine(resultList) {
|
|
300
|
+
return combineResultList(resultList);
|
|
301
|
+
}
|
|
302
|
+
Result2.combine = combine;
|
|
303
|
+
function combineWithAllErrors(resultList) {
|
|
304
|
+
return combineResultListWithAllErrors(resultList);
|
|
305
|
+
}
|
|
306
|
+
Result2.combineWithAllErrors = combineWithAllErrors;
|
|
307
|
+
})(Result || (Result = {}));
|
|
308
|
+
var ok = (value) => new Ok(value);
|
|
309
|
+
var err = (err2) => new Err(err2);
|
|
310
|
+
|
|
311
|
+
class Ok {
|
|
312
|
+
constructor(value) {
|
|
313
|
+
this.value = value;
|
|
314
|
+
}
|
|
315
|
+
isOk() {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
isErr() {
|
|
319
|
+
return !this.isOk();
|
|
320
|
+
}
|
|
321
|
+
map(f) {
|
|
322
|
+
return ok(f(this.value));
|
|
323
|
+
}
|
|
324
|
+
mapErr(_f) {
|
|
325
|
+
return ok(this.value);
|
|
326
|
+
}
|
|
327
|
+
andThen(f) {
|
|
328
|
+
return f(this.value);
|
|
329
|
+
}
|
|
330
|
+
orElse(_f) {
|
|
331
|
+
return ok(this.value);
|
|
332
|
+
}
|
|
333
|
+
asyncAndThen(f) {
|
|
334
|
+
return f(this.value);
|
|
335
|
+
}
|
|
336
|
+
asyncMap(f) {
|
|
337
|
+
return ResultAsync.fromSafePromise(f(this.value));
|
|
338
|
+
}
|
|
339
|
+
unwrapOr(_v) {
|
|
340
|
+
return this.value;
|
|
341
|
+
}
|
|
342
|
+
match(ok2, _err) {
|
|
343
|
+
return ok2(this.value);
|
|
344
|
+
}
|
|
345
|
+
safeUnwrap() {
|
|
346
|
+
const value = this.value;
|
|
347
|
+
return function* () {
|
|
348
|
+
return value;
|
|
349
|
+
}();
|
|
350
|
+
}
|
|
351
|
+
_unsafeUnwrap(_) {
|
|
352
|
+
return this.value;
|
|
353
|
+
}
|
|
354
|
+
_unsafeUnwrapErr(config) {
|
|
355
|
+
throw createNeverThrowError("Called `_unsafeUnwrapErr` on an Ok", this, config);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
class Err {
|
|
360
|
+
constructor(error) {
|
|
361
|
+
this.error = error;
|
|
362
|
+
}
|
|
363
|
+
isOk() {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
isErr() {
|
|
367
|
+
return !this.isOk();
|
|
368
|
+
}
|
|
369
|
+
map(_f) {
|
|
370
|
+
return err(this.error);
|
|
371
|
+
}
|
|
372
|
+
mapErr(f) {
|
|
373
|
+
return err(f(this.error));
|
|
374
|
+
}
|
|
375
|
+
andThen(_f) {
|
|
376
|
+
return err(this.error);
|
|
377
|
+
}
|
|
378
|
+
orElse(f) {
|
|
379
|
+
return f(this.error);
|
|
380
|
+
}
|
|
381
|
+
asyncAndThen(_f) {
|
|
382
|
+
return errAsync(this.error);
|
|
383
|
+
}
|
|
384
|
+
asyncMap(_f) {
|
|
385
|
+
return errAsync(this.error);
|
|
386
|
+
}
|
|
387
|
+
unwrapOr(v) {
|
|
388
|
+
return v;
|
|
389
|
+
}
|
|
390
|
+
match(_ok, err2) {
|
|
391
|
+
return err2(this.error);
|
|
392
|
+
}
|
|
393
|
+
safeUnwrap() {
|
|
394
|
+
const error = this.error;
|
|
395
|
+
return function* () {
|
|
396
|
+
yield err(error);
|
|
397
|
+
throw new Error("Do not use this generator out of `safeTry`");
|
|
398
|
+
}();
|
|
399
|
+
}
|
|
400
|
+
_unsafeUnwrap(config) {
|
|
401
|
+
throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
|
|
402
|
+
}
|
|
403
|
+
_unsafeUnwrapErr(_) {
|
|
404
|
+
return this.error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
var fromThrowable = Result.fromThrowable;
|
|
408
|
+
|
|
409
|
+
// src/log.ts
|
|
410
|
+
var log = (emoji, tag, msg) => (value) => {
|
|
411
|
+
console.log(`${emoji} [${tag}] ${msg}`);
|
|
412
|
+
return value;
|
|
413
|
+
};
|
|
414
|
+
var logHit = (name, h) => log("\u23ED\uFE0F", name, `cached (${h})`);
|
|
415
|
+
var logMiss = (name, h) => log("\u25B6\uFE0F", name, `exec (${h})`);
|
|
416
|
+
var logSave = (name) => log("\u2705", name, "saved");
|
|
417
|
+
var logCleanup = (type) => log("\uD83E\uDDF9", type, "cleanup executed");
|
|
418
|
+
var logCleanupReg = (type) => log("\uD83D\uDCCB", type, "cleanup registered");
|
|
419
|
+
var logCleanupClear = (type) => log("\u2713", type, "cleanup cleared");
|
|
420
|
+
var logDone = (id) => log("\uD83C\uDFC1", id, "complete");
|
|
421
|
+
var logFailed = (id) => log("\uD83D\uDCA5", id, "failed");
|
|
422
|
+
var logRecovery = (id, count) => log("\uD83D\uDD27", id, `recovering ${count} pending cleanups`);
|
|
423
|
+
|
|
424
|
+
// src/cleanup.ts
|
|
425
|
+
var executeCleanup = (runners) => async (action) => {
|
|
426
|
+
const runner = runners[action.type];
|
|
427
|
+
if (!runner) {
|
|
428
|
+
console.warn(`\u26A0\uFE0F [${action.type}] no cleanup runner registered`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const result = await ResultAsync.fromPromise(runner(action.params), (e) => new Error(`[${action.type}] cleanup failed: ${String(e)}`));
|
|
432
|
+
result.match(() => {
|
|
433
|
+
logCleanup(action.type)(undefined);
|
|
434
|
+
}, (err2) => {
|
|
435
|
+
console.error(`\u26A0\uFE0F ${err2.message}`);
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
var executeAllCleanups = (runners) => (actions) => Promise.all(actions.map(executeCleanup(runners))).then(() => {
|
|
439
|
+
return;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// src/api.ts
|
|
443
|
+
var load = (storage) => (runners) => (fileId) => storage.fetchOne(fileId).then((existing) => existing ?? emptyCheckpoint(fileId)).then((cp) => {
|
|
444
|
+
if (cp.cleanup.length === 0)
|
|
445
|
+
return cp;
|
|
446
|
+
logRecovery(fileId, cp.cleanup.length)(cp);
|
|
447
|
+
return executeAllCleanups(runners)(cp.cleanup).then(() => ({
|
|
448
|
+
...cp,
|
|
449
|
+
cleanup: [],
|
|
450
|
+
startedAt: Date.now(),
|
|
451
|
+
status: "running"
|
|
452
|
+
}));
|
|
453
|
+
}).then(storage.upsert);
|
|
454
|
+
var beforeRisky = (storage) => (cp) => (type) => (params) => Promise.resolve(withCleanup(cp, { type, params })).then(storage.upsert).then((updated) => {
|
|
455
|
+
logCleanupReg(type)(updated);
|
|
456
|
+
Object.assign(cp, updated);
|
|
457
|
+
return updated;
|
|
458
|
+
});
|
|
459
|
+
var afterSafe = (storage) => (cp) => (type) => Promise.resolve(withoutCleanup(cp, type)).then(storage.upsert).then((updated) => {
|
|
460
|
+
logCleanupClear(type)(updated);
|
|
461
|
+
Object.assign(cp, updated);
|
|
462
|
+
return updated;
|
|
463
|
+
});
|
|
464
|
+
var durable = (storage) => (cp) => (name) => (inputs) => (fn) => {
|
|
465
|
+
const h = hash(inputs);
|
|
466
|
+
const cached = checkCache(cp.steps[name], h);
|
|
467
|
+
if (cached.hit) {
|
|
468
|
+
logHit(name, h)(cached.result);
|
|
469
|
+
return Promise.resolve(cached.result);
|
|
470
|
+
}
|
|
471
|
+
logMiss(name, h)(undefined);
|
|
472
|
+
return fn().then((result) => withStep(cp, name, result, h)).then(storage.upsert).then((updated) => {
|
|
473
|
+
logSave(name)(updated);
|
|
474
|
+
Object.assign(cp, updated);
|
|
475
|
+
return updated.steps[name].result;
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
var complete = (storage) => (cp) => Promise.resolve(withStatus(cp, "completed")).then(storage.upsert).then((updated) => {
|
|
479
|
+
logDone(cp.fileId)(updated);
|
|
480
|
+
return updated;
|
|
481
|
+
});
|
|
482
|
+
var fail = (storage) => (cp) => Promise.resolve(withStatus(cp, "failed")).then(storage.upsert).then((updated) => {
|
|
483
|
+
logFailed(cp.fileId)(updated);
|
|
484
|
+
return updated;
|
|
485
|
+
});
|
|
486
|
+
var clear = (storage) => (fileId) => storage.deleteOne(fileId);
|
|
487
|
+
var clearStep = (storage) => (cp) => (name) => Promise.resolve(withoutStep(cp, name)).then(storage.upsert).then((updated) => {
|
|
488
|
+
Object.assign(cp, updated);
|
|
489
|
+
return updated;
|
|
490
|
+
});
|
|
491
|
+
var sweep = (storage) => (runners) => (staleThresholdMs = 60 * 60 * 1000) => storage.fetchStale(staleThresholdMs).then((stale) => Promise.all(stale.map((cp) => executeAllCleanups(runners)(cp.cleanup).then(() => withStatus({ ...cp, cleanup: [] }, "failed")).then(storage.upsert).then(() => cp.fileId))).then((fileIds) => ({ cleaned: fileIds.length, details: fileIds })));
|
|
492
|
+
var sweepAllCleanups = (storage) => (runners) => () => storage.fetchPendingCleanups().then((cps) => Promise.all(cps.map((cp) => executeAllCleanups(runners)(cp.cleanup).then(() => storage.upsert({ ...cp, cleanup: [] })))).then((results) => ({ cleaned: results.length })));
|
|
493
|
+
export {
|
|
494
|
+
sweepAllCleanups,
|
|
495
|
+
sweep,
|
|
496
|
+
load,
|
|
497
|
+
fail,
|
|
498
|
+
durable,
|
|
499
|
+
complete,
|
|
500
|
+
clearStep,
|
|
501
|
+
clear,
|
|
502
|
+
beforeRisky,
|
|
503
|
+
afterSafe
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
//# debugId=0964CEB4F2EA8B6464756E2164756E21
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/checkpoint.ts", "../src/hash.ts", "../node_modules/neverthrow/dist/index.es.js", "../src/log.ts", "../src/cleanup.ts", "../src/api.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// src/checkpoint.ts\n\nimport type {\n Checkpoint,\n CheckpointStatus,\n CleanupAction,\n CleanupSpec,\n StepRecord,\n CacheCheck,\n} from './types';\n\nexport const emptyCheckpoint = (fileId: string): Checkpoint => ({\n fileId,\n startedAt: Date.now(),\n completedAt: null,\n status: 'running',\n steps: {},\n cleanup: [],\n});\n\nexport const checkCache = <T>(\n step: StepRecord | undefined,\n inputHash: string,\n): CacheCheck<T> =>\n step?.inputHash === inputHash\n ? { hit: true, result: step.result as T }\n : { hit: false };\n\nexport const withStep = (\n cp: Checkpoint,\n name: string,\n result: unknown,\n inputHash: string,\n): Checkpoint => ({\n ...cp,\n steps: {\n ...cp.steps,\n [name]: { result, inputHash, completedAt: Date.now() },\n },\n});\n\nexport const withCleanup = (\n cp: Checkpoint,\n action: CleanupSpec,\n): Checkpoint => ({\n ...cp,\n cleanup: [\n ...cp.cleanup,\n {\n ...action,\n id: `${action.type}-${crypto.randomUUID()}`,\n registeredAt: Date.now(),\n },\n ],\n});\n\nexport const withoutCleanup = (cp: Checkpoint, type: string): Checkpoint => ({\n ...cp,\n cleanup: cp.cleanup.filter((c) => c.type !== type),\n});\n\nexport const withStatus = (\n cp: Checkpoint,\n status: CheckpointStatus,\n): Checkpoint => ({\n ...cp,\n status,\n completedAt: status === 'running' ? null : Date.now(),\n});\n\nexport const withoutStep = (cp: Checkpoint, name: string): Checkpoint => {\n const { [name]: _removed, ...rest } = cp.steps;\n return { ...cp, steps: rest };\n};\n\nexport const rowToCheckpoint = (row: Record<string, unknown>): Checkpoint => ({\n fileId: row.file_id as string,\n startedAt: row.started_at as number,\n completedAt: (row.completed_at as number | null) ?? null,\n status: row.status as CheckpointStatus,\n steps:\n typeof row.steps === 'string'\n ? JSON.parse(row.steps)\n : ((row.steps as Record<string, StepRecord> | undefined) ?? {}),\n cleanup:\n typeof row.cleanup === 'string'\n ? JSON.parse(row.cleanup)\n : ((row.cleanup as CleanupAction[] | undefined) ?? []),\n});\n\nexport const checkpointToRow = (cp: Checkpoint) => ({\n file_id: cp.fileId,\n started_at: cp.startedAt,\n completed_at: cp.completedAt,\n status: cp.status,\n steps: JSON.stringify(cp.steps),\n cleanup: JSON.stringify(cp.cleanup),\n});\n\n",
|
|
6
|
+
"// src/hash.ts\n\nexport const sortKeys = (obj: unknown): unknown =>\n Array.isArray(obj)\n ? obj.map(sortKeys)\n : obj && typeof obj === 'object'\n ? Object.keys(obj as Record<string, unknown>)\n .sort()\n .reduce<Record<string, unknown>>(\n (acc, k) => ({\n ...acc,\n [k]: sortKeys((obj as Record<string, unknown>)[k]),\n }),\n {},\n )\n : obj;\n\nexport const hash = (input: unknown): string =>\n Array.from(JSON.stringify(sortKeys(input)))\n .reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0)\n .toString(36);\n\n",
|
|
7
|
+
"const defaultErrorConfig = {\r\n withStackTrace: false,\r\n};\r\n// Custom error object\r\n// Context / discussion: https://github.com/supermacro/neverthrow/pull/215\r\nconst createNeverThrowError = (message, result, config = defaultErrorConfig) => {\r\n const data = result.isOk()\r\n ? { type: 'Ok', value: result.value }\r\n : { type: 'Err', value: result.error };\r\n const maybeStack = config.withStackTrace ? new Error().stack : undefined;\r\n return {\r\n data,\r\n message,\r\n stack: maybeStack,\r\n };\r\n};\n\n/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n\r\nfunction __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nfunction __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nfunction __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nfunction __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nfunction __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nfunction __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\n\nclass ResultAsync {\r\n constructor(res) {\r\n this._promise = res;\r\n }\r\n static fromSafePromise(promise) {\r\n const newPromise = promise.then((value) => new Ok(value));\r\n return new ResultAsync(newPromise);\r\n }\r\n static fromPromise(promise, errorFn) {\r\n const newPromise = promise\r\n .then((value) => new Ok(value))\r\n .catch((e) => new Err(errorFn(e)));\r\n return new ResultAsync(newPromise);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n static fromThrowable(fn, errorFn) {\r\n return (...args) => {\r\n return new ResultAsync((() => __awaiter(this, void 0, void 0, function* () {\r\n try {\r\n return new Ok(yield fn(...args));\r\n }\r\n catch (error) {\r\n return new Err(errorFn ? errorFn(error) : error);\r\n }\r\n }))());\r\n };\r\n }\r\n static combine(asyncResultList) {\r\n return combineResultAsyncList(asyncResultList);\r\n }\r\n static combineWithAllErrors(asyncResultList) {\r\n return combineResultAsyncListWithAllErrors(asyncResultList);\r\n }\r\n map(f) {\r\n return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {\r\n if (res.isErr()) {\r\n return new Err(res.error);\r\n }\r\n return new Ok(yield f(res.value));\r\n })));\r\n }\r\n mapErr(f) {\r\n return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {\r\n if (res.isOk()) {\r\n return new Ok(res.value);\r\n }\r\n return new Err(yield f(res.error));\r\n })));\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n andThen(f) {\r\n return new ResultAsync(this._promise.then((res) => {\r\n if (res.isErr()) {\r\n return new Err(res.error);\r\n }\r\n const newValue = f(res.value);\r\n return newValue instanceof ResultAsync ? newValue._promise : newValue;\r\n }));\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n orElse(f) {\r\n return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {\r\n if (res.isErr()) {\r\n return f(res.error);\r\n }\r\n return new Ok(res.value);\r\n })));\r\n }\r\n match(ok, _err) {\r\n return this._promise.then((res) => res.match(ok, _err));\r\n }\r\n unwrapOr(t) {\r\n return this._promise.then((res) => res.unwrapOr(t));\r\n }\r\n /**\r\n * Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`.\r\n */\r\n safeUnwrap() {\r\n return __asyncGenerator(this, arguments, function* safeUnwrap_1() {\r\n return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));\r\n });\r\n }\r\n // Makes ResultAsync implement PromiseLike<Result>\r\n then(successCallback, failureCallback) {\r\n return this._promise.then(successCallback, failureCallback);\r\n }\r\n}\r\nconst okAsync = (value) => new ResultAsync(Promise.resolve(new Ok(value)));\r\nconst errAsync = (err) => new ResultAsync(Promise.resolve(new Err(err)));\r\nconst fromPromise = ResultAsync.fromPromise;\r\nconst fromSafePromise = ResultAsync.fromSafePromise;\r\nconst fromAsyncThrowable = ResultAsync.fromThrowable;\n\n/**\r\n * Short circuits on the FIRST Err value that we find\r\n */\r\nconst combineResultList = (resultList) => {\r\n let acc = ok([]);\r\n for (const result of resultList) {\r\n if (result.isErr()) {\r\n acc = err(result.error);\r\n break;\r\n }\r\n else {\r\n acc.map((list) => list.push(result.value));\r\n }\r\n }\r\n return acc;\r\n};\r\n/* This is the typesafe version of Promise.all\r\n *\r\n * Takes a list of ResultAsync<T, E> and success if all inner results are Ok values\r\n * or fails if one (or more) of the inner results are Err values\r\n */\r\nconst combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);\r\n/**\r\n * Give a list of all the errors we find\r\n */\r\nconst combineResultListWithAllErrors = (resultList) => {\r\n let acc = ok([]);\r\n for (const result of resultList) {\r\n if (result.isErr() && acc.isErr()) {\r\n acc.error.push(result.error);\r\n }\r\n else if (result.isErr() && acc.isOk()) {\r\n acc = err([result.error]);\r\n }\r\n else if (result.isOk() && acc.isOk()) {\r\n acc.value.push(result.value);\r\n }\r\n // do nothing when result.isOk() && acc.isErr()\r\n }\r\n return acc;\r\n};\r\nconst combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\r\nvar Result;\r\n(function (Result) {\r\n /**\r\n * Wraps a function with a try catch, creating a new function with the same\r\n * arguments but returning `Ok` if successful, `Err` if the function throws\r\n *\r\n * @param fn function to wrap with ok on success or err on failure\r\n * @param errorFn when an error is thrown, this will wrap the error result if provided\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n function fromThrowable(fn, errorFn) {\r\n return (...args) => {\r\n try {\r\n const result = fn(...args);\r\n return ok(result);\r\n }\r\n catch (e) {\r\n return err(errorFn ? errorFn(e) : e);\r\n }\r\n };\r\n }\r\n Result.fromThrowable = fromThrowable;\r\n function combine(resultList) {\r\n return combineResultList(resultList);\r\n }\r\n Result.combine = combine;\r\n function combineWithAllErrors(resultList) {\r\n return combineResultListWithAllErrors(resultList);\r\n }\r\n Result.combineWithAllErrors = combineWithAllErrors;\r\n})(Result || (Result = {}));\r\nconst ok = (value) => new Ok(value);\r\nconst err = (err) => new Err(err);\r\nfunction safeTry(body) {\r\n const n = body().next();\r\n if (n instanceof Promise) {\r\n return n.then((r) => r.value);\r\n }\r\n return n.value;\r\n}\r\nclass Ok {\r\n constructor(value) {\r\n this.value = value;\r\n }\r\n isOk() {\r\n return true;\r\n }\r\n isErr() {\r\n return !this.isOk();\r\n }\r\n map(f) {\r\n return ok(f(this.value));\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n mapErr(_f) {\r\n return ok(this.value);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n andThen(f) {\r\n return f(this.value);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n orElse(_f) {\r\n return ok(this.value);\r\n }\r\n asyncAndThen(f) {\r\n return f(this.value);\r\n }\r\n asyncMap(f) {\r\n return ResultAsync.fromSafePromise(f(this.value));\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n unwrapOr(_v) {\r\n return this.value;\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n match(ok, _err) {\r\n return ok(this.value);\r\n }\r\n safeUnwrap() {\r\n const value = this.value;\r\n /* eslint-disable-next-line require-yield */\r\n return (function* () {\r\n return value;\r\n })();\r\n }\r\n _unsafeUnwrap(_) {\r\n return this.value;\r\n }\r\n _unsafeUnwrapErr(config) {\r\n throw createNeverThrowError('Called `_unsafeUnwrapErr` on an Ok', this, config);\r\n }\r\n}\r\nclass Err {\r\n constructor(error) {\r\n this.error = error;\r\n }\r\n isOk() {\r\n return false;\r\n }\r\n isErr() {\r\n return !this.isOk();\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n map(_f) {\r\n return err(this.error);\r\n }\r\n mapErr(f) {\r\n return err(f(this.error));\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n andThen(_f) {\r\n return err(this.error);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types\r\n orElse(f) {\r\n return f(this.error);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n asyncAndThen(_f) {\r\n return errAsync(this.error);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n asyncMap(_f) {\r\n return errAsync(this.error);\r\n }\r\n unwrapOr(v) {\r\n return v;\r\n }\r\n match(_ok, err) {\r\n return err(this.error);\r\n }\r\n safeUnwrap() {\r\n const error = this.error;\r\n return (function* () {\r\n yield err(error);\r\n throw new Error('Do not use this generator out of `safeTry`');\r\n })();\r\n }\r\n _unsafeUnwrap(config) {\r\n throw createNeverThrowError('Called `_unsafeUnwrap` on an Err', this, config);\r\n }\r\n _unsafeUnwrapErr(_) {\r\n return this.error;\r\n }\r\n}\r\nconst fromThrowable = Result.fromThrowable;\r\n//#endregion\n\nexport { Err, Ok, Result, ResultAsync, err, errAsync, fromAsyncThrowable, fromPromise, fromSafePromise, fromThrowable, ok, okAsync, safeTry };\n",
|
|
8
|
+
"// src/log.ts\n\nconst log =\n <T>(emoji: string, tag: string, msg: string) =>\n (value: T): T => {\n console.log(`${emoji} [${tag}] ${msg}`);\n return value;\n };\n\nexport const logHit = (name: string, h: string) =>\n log('⏭️', name, `cached (${h})`);\n\nexport const logMiss = (name: string, h: string) =>\n log('▶️', name, `exec (${h})`);\n\nexport const logSave = (name: string) => log('✅', name, 'saved');\n\nexport const logCleanup = (type: string) => log('🧹', type, 'cleanup executed');\n\nexport const logCleanupReg = (type: string) =>\n log('📋', type, 'cleanup registered');\n\nexport const logCleanupClear = (type: string) =>\n log('✓', type, 'cleanup cleared');\n\nexport const logDone = (id: string) => log('🏁', id, 'complete');\n\nexport const logFailed = (id: string) => log('💥', id, 'failed');\n\nexport const logRecovery = (id: string, count: number) =>\n log('🔧', id, `recovering ${count} pending cleanups`);\n",
|
|
9
|
+
"// src/cleanup.ts\n\nimport { ResultAsync } from 'neverthrow';\nimport type { CleanupAction, CleanupRegistry } from './types';\nimport { logCleanup } from './log';\n\nconst executeCleanup =\n (runners: CleanupRegistry) =>\n async (action: CleanupAction): Promise<void> => {\n const runner = runners[action.type];\n\n if (!runner) {\n console.warn(`⚠️ [${action.type}] no cleanup runner registered`);\n return;\n }\n\n const result = await ResultAsync.fromPromise(\n runner(action.params),\n (e: unknown) => new Error(`[${action.type}] cleanup failed: ${String(e)}`),\n );\n\n result.match(\n () => {\n logCleanup(action.type)(undefined as unknown as void);\n },\n (err: Error) => {\n console.error(`⚠️ ${err.message}`);\n },\n );\n };\n\nexport const executeAllCleanups =\n (runners: CleanupRegistry) =>\n (actions: CleanupAction[]): Promise<void> =>\n Promise.all(actions.map(executeCleanup(runners))).then(() => undefined);\n",
|
|
10
|
+
"// src/api.ts\n\nimport type { Checkpoint, CleanupRegistry, StorageAdapter } from './types';\nimport {\n emptyCheckpoint,\n checkCache,\n withStep,\n withCleanup,\n withoutCleanup,\n withStatus,\n withoutStep,\n} from './checkpoint';\nimport { hash } from './hash';\nimport { executeAllCleanups } from './cleanup';\nimport {\n logHit,\n logMiss,\n logSave,\n logCleanupReg,\n logCleanupClear,\n logDone,\n logFailed,\n logRecovery,\n} from './log';\n\nexport const load =\n (storage: StorageAdapter) =>\n (runners: CleanupRegistry) =>\n (fileId: string): Promise<Checkpoint> =>\n storage\n .fetchOne(fileId)\n .then((existing) => existing ?? emptyCheckpoint(fileId))\n .then((cp) => {\n if (cp.cleanup.length === 0) return cp;\n\n logRecovery(fileId, cp.cleanup.length)(cp);\n\n return executeAllCleanups(runners)(cp.cleanup).then(() => ({\n ...cp,\n cleanup: [],\n startedAt: Date.now(),\n status: 'running' as const,\n }));\n })\n .then(storage.upsert);\n\nexport const beforeRisky =\n (storage: StorageAdapter) =>\n (cp: Checkpoint) =>\n (type: string) =>\n (params: Record<string, unknown>): Promise<Checkpoint> =>\n Promise.resolve(withCleanup(cp, { type, params }))\n .then(storage.upsert)\n .then((updated) => {\n logCleanupReg(type)(updated);\n Object.assign(cp, updated);\n return updated;\n });\n\nexport const afterSafe =\n (storage: StorageAdapter) =>\n (cp: Checkpoint) =>\n (type: string): Promise<Checkpoint> =>\n Promise.resolve(withoutCleanup(cp, type))\n .then(storage.upsert)\n .then((updated) => {\n logCleanupClear(type)(updated);\n Object.assign(cp, updated);\n return updated;\n });\n\nexport const durable =\n (storage: StorageAdapter) =>\n (cp: Checkpoint) =>\n (name: string) =>\n <I, T>(inputs: I) =>\n (fn: () => Promise<T>): Promise<T> => {\n const h = hash(inputs);\n const cached = checkCache<T>(cp.steps[name], h);\n\n if (cached.hit) {\n logHit(name, h)(cached.result);\n return Promise.resolve(cached.result);\n }\n\n logMiss(name, h)(undefined);\n return fn()\n .then((result) => withStep(cp, name, result, h))\n .then(storage.upsert)\n .then((updated) => {\n logSave(name)(updated);\n Object.assign(cp, updated);\n return updated.steps[name].result as T;\n });\n };\n\nexport const complete =\n (storage: StorageAdapter) =>\n (cp: Checkpoint): Promise<Checkpoint> =>\n Promise.resolve(withStatus(cp, 'completed'))\n .then(storage.upsert)\n .then((updated) => {\n logDone(cp.fileId)(updated);\n return updated;\n });\n\nexport const fail =\n (storage: StorageAdapter) =>\n (cp: Checkpoint): Promise<Checkpoint> =>\n Promise.resolve(withStatus(cp, 'failed'))\n .then(storage.upsert)\n .then((updated) => {\n logFailed(cp.fileId)(updated);\n return updated;\n });\n\nexport const clear =\n (storage: StorageAdapter) =>\n (fileId: string): Promise<void> =>\n storage.deleteOne(fileId);\n\nexport const clearStep =\n (storage: StorageAdapter) =>\n (cp: Checkpoint) =>\n (name: string): Promise<Checkpoint> =>\n Promise.resolve(withoutStep(cp, name))\n .then(storage.upsert)\n .then((updated) => {\n Object.assign(cp, updated);\n return updated;\n });\n\nexport const sweep =\n (storage: StorageAdapter) =>\n (runners: CleanupRegistry) =>\n (\n staleThresholdMs: number = 60 * 60 * 1000,\n ): Promise<{ cleaned: number; details: string[] }> =>\n storage.fetchStale(staleThresholdMs).then((stale) =>\n Promise.all(\n stale.map((cp) =>\n executeAllCleanups(runners)(cp.cleanup)\n .then(() => withStatus({ ...cp, cleanup: [] }, 'failed'))\n .then(storage.upsert)\n .then(() => cp.fileId),\n ),\n ).then((fileIds) => ({ cleaned: fileIds.length, details: fileIds })),\n );\n\nexport const sweepAllCleanups =\n (storage: StorageAdapter) =>\n (runners: CleanupRegistry) =>\n (): Promise<{ cleaned: number }> =>\n storage.fetchPendingCleanups().then((cps) =>\n Promise.all(\n cps.map((cp) =>\n executeAllCleanups(runners)(cp.cleanup).then(() =>\n storage.upsert({ ...cp, cleanup: [] }),\n ),\n ),\n ).then((results) => ({ cleaned: results.length })),\n );\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "0964CEB4F2EA8B6464756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const logHit: (name: string, h: string) => (value: unknown) => unknown;
|
|
2
|
+
export declare const logMiss: (name: string, h: string) => (value: unknown) => unknown;
|
|
3
|
+
export declare const logSave: (name: string) => (value: unknown) => unknown;
|
|
4
|
+
export declare const logCleanup: (type: string) => (value: unknown) => unknown;
|
|
5
|
+
export declare const logCleanupReg: (type: string) => (value: unknown) => unknown;
|
|
6
|
+
export declare const logCleanupClear: (type: string) => (value: unknown) => unknown;
|
|
7
|
+
export declare const logDone: (id: string) => (value: unknown) => unknown;
|
|
8
|
+
export declare const logFailed: (id: string) => (value: unknown) => unknown;
|
|
9
|
+
export declare const logRecovery: (id: string, count: number) => (value: unknown) => unknown;
|
|
10
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,EAAE,GAAG,MAAM,gCACZ,CAAC;AAEnC,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,GAAG,MAAM,gCACf,CAAC;AAEjC,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,gCAA4B,CAAC;AAEjE,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,gCAAwC,CAAC;AAEhF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,gCACH,CAAC;AAExC,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,gCACT,CAAC;AAEpC,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,gCAA8B,CAAC;AAEjE,eAAO,MAAM,SAAS,GAAI,IAAI,MAAM,gCAA4B,CAAC;AAEjE,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,EAAE,OAAO,MAAM,gCACE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type StepRecord = {
|
|
2
|
+
result: unknown;
|
|
3
|
+
inputHash: string;
|
|
4
|
+
completedAt: number;
|
|
5
|
+
};
|
|
6
|
+
export type CleanupAction = {
|
|
7
|
+
id: string;
|
|
8
|
+
type: string;
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
registeredAt: number;
|
|
11
|
+
};
|
|
12
|
+
export type CheckpointStatus = 'running' | 'completed' | 'failed';
|
|
13
|
+
export type Checkpoint = {
|
|
14
|
+
fileId: string;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
completedAt: number | null;
|
|
17
|
+
status: CheckpointStatus;
|
|
18
|
+
steps: Record<string, StepRecord>;
|
|
19
|
+
cleanup: CleanupAction[];
|
|
20
|
+
};
|
|
21
|
+
export type CleanupRunner = (params: Record<string, unknown>) => Promise<void>;
|
|
22
|
+
export type CleanupRegistry = Record<string, CleanupRunner>;
|
|
23
|
+
export type CacheHit<T> = {
|
|
24
|
+
hit: true;
|
|
25
|
+
result: T;
|
|
26
|
+
};
|
|
27
|
+
export type CacheMiss = {
|
|
28
|
+
hit: false;
|
|
29
|
+
};
|
|
30
|
+
export type CacheCheck<T> = CacheHit<T> | CacheMiss;
|
|
31
|
+
export type CleanupSpec = {
|
|
32
|
+
type: string;
|
|
33
|
+
params: Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
export type StorageAdapter = {
|
|
36
|
+
fetchOne: (fileId: string) => Promise<Checkpoint | null>;
|
|
37
|
+
upsert: (cp: Checkpoint) => Promise<Checkpoint>;
|
|
38
|
+
deleteOne: (fileId: string) => Promise<void>;
|
|
39
|
+
fetchStale: (thresholdMs: number) => Promise<Checkpoint[]>;
|
|
40
|
+
fetchPendingCleanups: () => Promise<Checkpoint[]>;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAElE,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AACnD,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,KAAK,CAAA;CAAE,CAAC;AACvC,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC,EAAE,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAChD,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3D,oBAAoB,EAAE,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;CACnD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "durable-x",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight durable execution with checkpoint memoization and saga cleanup—no sidecar required. Add Temporal/Dapr-style durability to Windmill workflows, APIs, background jobs, and CLI tools.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./adapters/windmill": {
|
|
14
|
+
"import": "./dist/adapters/windmill.js",
|
|
15
|
+
"types": "./dist/adapters/windmill.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/**",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "bun run build:types && bun run build:esm",
|
|
25
|
+
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
26
|
+
"build:esm": "bun build ./src/index.ts --outdir dist --format esm --target bun --sourcemap=external",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"test:integration": "bun test tests/**/*.test.ts",
|
|
29
|
+
"test:watch": "bun test --watch",
|
|
30
|
+
"lint": "tsc --noEmit",
|
|
31
|
+
"clean": "rm -rf dist",
|
|
32
|
+
"prepublish": "bun run clean && bun run build && bun run lint && bun run test"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"windmill",
|
|
36
|
+
"workflow",
|
|
37
|
+
"durable-execution",
|
|
38
|
+
"checkpoint",
|
|
39
|
+
"temporal",
|
|
40
|
+
"dapr",
|
|
41
|
+
"idempotency",
|
|
42
|
+
"saga",
|
|
43
|
+
"memoization",
|
|
44
|
+
"fault-tolerance",
|
|
45
|
+
"resilience",
|
|
46
|
+
"crash-recovery",
|
|
47
|
+
"compensation",
|
|
48
|
+
"n8n"
|
|
49
|
+
],
|
|
50
|
+
"author": {
|
|
51
|
+
"name": "Tom Davidson",
|
|
52
|
+
"email": "tom@tomdavidson.org",
|
|
53
|
+
"url": "https://github.com/tomdavidson/"
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"homepage": "https://github.com/tommdavidson/durable-x#readme",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/tommdavidson/durable-x.git"
|
|
60
|
+
},
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/tommdavidson/durable-x/issues"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"neverthrow": "^6.0.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"windmill-client": "^1.0.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/bun": "latest",
|
|
72
|
+
"typescript": "^5.3.0"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"bun": ">=1.0.0"
|
|
76
|
+
}
|
|
77
|
+
}
|