@stellar-agent-services/provider-kit 0.0.0-stage → 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 +506 -2
- package/dist/approve/assets/index-BazIJAI2.css +1 -0
- package/dist/approve/assets/index-CL28cSJy.js +420 -0
- package/dist/approve/favicon.svg +1 -0
- package/dist/approve/index.html +18 -0
- package/dist/cli/approval-view.d.ts +80 -0
- package/dist/cli/approval-view.d.ts.map +1 -0
- package/dist/cli/approval-view.js +5 -0
- package/dist/cli/approval-view.js.map +1 -0
- package/dist/cli/approval.d.ts +132 -0
- package/dist/cli/approval.d.ts.map +1 -0
- package/dist/cli/approval.js +542 -0
- package/dist/cli/approval.js.map +1 -0
- package/dist/cli/check.d.ts +3 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +137 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/dev-account.d.ts +28 -0
- package/dist/cli/dev-account.d.ts.map +1 -0
- package/dist/cli/dev-account.js +62 -0
- package/dist/cli/dev-account.js.map +1 -0
- package/dist/cli/dev.d.ts +27 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +226 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/discovery.d.ts +20 -0
- package/dist/cli/discovery.d.ts.map +1 -0
- package/dist/cli/discovery.js +202 -0
- package/dist/cli/discovery.js.map +1 -0
- package/dist/cli/env-file.d.ts +16 -0
- package/dist/cli/env-file.d.ts.map +1 -0
- package/dist/cli/env-file.js +77 -0
- package/dist/cli/env-file.js.map +1 -0
- package/dist/cli/fly.d.ts +28 -0
- package/dist/cli/fly.d.ts.map +1 -0
- package/dist/cli/fly.js +92 -0
- package/dist/cli/fly.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +170 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launch.d.ts +21 -0
- package/dist/cli/launch.d.ts.map +1 -0
- package/dist/cli/launch.js +627 -0
- package/dist/cli/launch.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +158 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/owner-chain.d.ts +88 -0
- package/dist/cli/owner-chain.d.ts.map +1 -0
- package/dist/cli/owner-chain.js +284 -0
- package/dist/cli/owner-chain.js.map +1 -0
- package/dist/cli/owner-steps.d.ts +33 -0
- package/dist/cli/owner-steps.d.ts.map +1 -0
- package/dist/cli/owner-steps.js +121 -0
- package/dist/cli/owner-steps.js.map +1 -0
- package/dist/cli/processes.d.ts +47 -0
- package/dist/cli/processes.d.ts.map +1 -0
- package/dist/cli/processes.js +129 -0
- package/dist/cli/processes.js.map +1 -0
- package/dist/cli/reconcile.d.ts +3 -0
- package/dist/cli/reconcile.d.ts.map +1 -0
- package/dist/cli/reconcile.js +76 -0
- package/dist/cli/reconcile.js.map +1 -0
- package/dist/cli/register.d.ts +9 -0
- package/dist/cli/register.d.ts.map +1 -0
- package/dist/cli/register.js +209 -0
- package/dist/cli/register.js.map +1 -0
- package/dist/cli/request-validation.d.ts +5 -0
- package/dist/cli/request-validation.d.ts.map +1 -0
- package/dist/cli/request-validation.js +144 -0
- package/dist/cli/request-validation.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +73 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/shared.d.ts +142 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +110 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/status.d.ts +32 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +248 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/target.d.ts +14 -0
- package/dist/cli/target.d.ts.map +1 -0
- package/dist/cli/target.js +43 -0
- package/dist/cli/target.js.map +1 -0
- package/dist/cli/trustline.d.ts +3 -0
- package/dist/cli/trustline.d.ts.map +1 -0
- package/dist/cli/trustline.js +114 -0
- package/dist/cli/trustline.js.map +1 -0
- package/dist/cli/try.d.ts +54 -0
- package/dist/cli/try.d.ts.map +1 -0
- package/dist/cli/try.js +448 -0
- package/dist/cli/try.js.map +1 -0
- package/dist/cli/tunnel.d.ts +13 -0
- package/dist/cli/tunnel.d.ts.map +1 -0
- package/dist/cli/tunnel.js +43 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/validators.d.ts +6 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +14 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/define-service.d.ts +71 -0
- package/dist/define-service.d.ts.map +1 -0
- package/dist/define-service.js +86 -0
- package/dist/define-service.js.map +1 -0
- package/dist/gateway/config.d.ts +88 -0
- package/dist/gateway/config.d.ts.map +1 -0
- package/dist/gateway/config.js +201 -0
- package/dist/gateway/config.js.map +1 -0
- package/dist/gateway/openapi.d.ts +69 -0
- package/dist/gateway/openapi.d.ts.map +1 -0
- package/dist/gateway/openapi.js +677 -0
- package/dist/gateway/openapi.js.map +1 -0
- package/dist/gateway/upstream.d.ts +15 -0
- package/dist/gateway/upstream.d.ts.map +1 -0
- package/dist/gateway/upstream.js +95 -0
- package/dist/gateway/upstream.js.map +1 -0
- package/dist/http/bounded.d.ts +3 -0
- package/dist/http/bounded.d.ts.map +1 -0
- package/dist/http/bounded.js +23 -0
- package/dist/http/bounded.js.map +1 -0
- package/dist/http/node-server.d.ts +15 -0
- package/dist/http/node-server.d.ts.map +1 -0
- package/dist/http/node-server.js +120 -0
- package/dist/http/node-server.js.map +1 -0
- package/dist/http/rate-limit.d.ts +11 -0
- package/dist/http/rate-limit.d.ts.map +1 -0
- package/dist/http/rate-limit.js +35 -0
- package/dist/http/rate-limit.js.map +1 -0
- package/dist/http/router.d.ts +29 -0
- package/dist/http/router.d.ts.map +1 -0
- package/dist/http/router.js +329 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/memory-store.d.ts +3 -0
- package/dist/jobs/memory-store.d.ts.map +1 -0
- package/dist/jobs/memory-store.js +121 -0
- package/dist/jobs/memory-store.js.map +1 -0
- package/dist/jobs/reconcile.d.ts +22 -0
- package/dist/jobs/reconcile.d.ts.map +1 -0
- package/dist/jobs/reconcile.js +82 -0
- package/dist/jobs/reconcile.js.map +1 -0
- package/dist/jobs/recovery.d.ts +19 -0
- package/dist/jobs/recovery.d.ts.map +1 -0
- package/dist/jobs/recovery.js +65 -0
- package/dist/jobs/recovery.js.map +1 -0
- package/dist/jobs/runner.d.ts +38 -0
- package/dist/jobs/runner.d.ts.map +1 -0
- package/dist/jobs/runner.js +194 -0
- package/dist/jobs/runner.js.map +1 -0
- package/dist/jobs/scheduler.d.ts +66 -0
- package/dist/jobs/scheduler.d.ts.map +1 -0
- package/dist/jobs/scheduler.js +197 -0
- package/dist/jobs/scheduler.js.map +1 -0
- package/dist/jobs/sqlite-store.d.ts +8 -0
- package/dist/jobs/sqlite-store.d.ts.map +1 -0
- package/dist/jobs/sqlite-store.js +231 -0
- package/dist/jobs/sqlite-store.js.map +1 -0
- package/dist/jobs/store.d.ts +118 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +36 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/manifest.d.ts +11 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +99 -0
- package/dist/manifest.js.map +1 -0
- package/dist/payments/chain.d.ts +23 -0
- package/dist/payments/chain.d.ts.map +1 -0
- package/dist/payments/chain.js +108 -0
- package/dist/payments/chain.js.map +1 -0
- package/dist/payments/facilitator.d.ts +44 -0
- package/dist/payments/facilitator.d.ts.map +1 -0
- package/dist/payments/facilitator.js +125 -0
- package/dist/payments/facilitator.js.map +1 -0
- package/dist/payments/onchain.d.ts +36 -0
- package/dist/payments/onchain.d.ts.map +1 -0
- package/dist/payments/onchain.js +191 -0
- package/dist/payments/onchain.js.map +1 -0
- package/dist/payments/payload-checks.d.ts +22 -0
- package/dist/payments/payload-checks.d.ts.map +1 -0
- package/dist/payments/payload-checks.js +113 -0
- package/dist/payments/payload-checks.js.map +1 -0
- package/dist/payments/settlement.d.ts +22 -0
- package/dist/payments/settlement.d.ts.map +1 -0
- package/dist/payments/settlement.js +65 -0
- package/dist/payments/settlement.js.map +1 -0
- package/dist/payments/x402-server.d.ts +8 -0
- package/dist/payments/x402-server.d.ts.map +1 -0
- package/dist/payments/x402-server.js +74 -0
- package/dist/payments/x402-server.js.map +1 -0
- package/dist/provider.d.ts +63 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +177 -0
- package/dist/provider.js.map +1 -0
- package/dist/registration.d.ts +86 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +162 -0
- package/dist/registration.js.map +1 -0
- package/dist/testing.d.ts +13 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +48 -0
- package/dist/testing.js.map +1 -0
- package/dist/validation-request.d.ts +36 -0
- package/dist/validation-request.d.ts.map +1 -0
- package/dist/validation-request.js +101 -0
- package/dist/validation-request.js.map +1 -0
- package/package.json +55 -4
- package/schemas/gateway-config.schema.json +140 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Capacity: how many jobs run at once, how many wait, and when to turn a request away
|
|
2
|
+
// (docs/provider-standard.md §5.8).
|
|
3
|
+
//
|
|
4
|
+
// Each service has a lane: `concurrency` jobs run at once and the others wait in queue order. The
|
|
5
|
+
// queue itself is in the JobStore (store.ts), so it is shared by every process using the store and is
|
|
6
|
+
// as durable as the store. This module drives it from one process:
|
|
7
|
+
// - admit(): at step 5 of §5.4, before the 402 and before the payment is verified, so a full lane
|
|
8
|
+
// answers 503 while nothing is signed or charged [Q1], [Q2]. It counts the store's pending, queued
|
|
9
|
+
// and running jobs plus this process's reservations: requests admitted and not yet claimed. The
|
|
10
|
+
// claim runs under the same lane lock (exclusive()), so a request is never counted twice.
|
|
11
|
+
// - on-success jobs wait for their turn here (waitForTurn): they have to run in the process that
|
|
12
|
+
// verified their payment, because that process settles it after the work.
|
|
13
|
+
// - upfront jobs are paid before they queue, so any process may run them: workers lease them from the
|
|
14
|
+
// store, and a job whose worker died is taken over when the lease runs out [Q4].
|
|
15
|
+
// The lane wakes on local events (a job queued or finished) and polls the store for other processes'.
|
|
16
|
+
//
|
|
17
|
+
// Waits are estimated from the lane's average job duration, seeded with expectedDurationSeconds, times
|
|
18
|
+
// the number of rounds ahead. They are hints [Q5]. With several processes, each averages its own jobs.
|
|
19
|
+
/** Used when a service sets no expectedDurationSeconds. */
|
|
20
|
+
const DEFAULT_EXPECTED_SECONDS = 10;
|
|
21
|
+
/** Bounds of the Retry-After a busy lane sends [Q1]. */
|
|
22
|
+
const RETRY_AFTER_MIN_SECONDS = 1;
|
|
23
|
+
const RETRY_AFTER_MAX_SECONDS = 60;
|
|
24
|
+
/** Weight of the newest job in the average duration. */
|
|
25
|
+
const AVERAGE_WEIGHT = 0.2;
|
|
26
|
+
/** Room left, after an "on-success" job's work, to settle before the signed payment expires [S2]. */
|
|
27
|
+
export const SETTLE_MARGIN_SECONDS = 10;
|
|
28
|
+
/**
|
|
29
|
+
* The lane for a service. An "on-success" job is settled after its work, before the payment expires
|
|
30
|
+
* [S2], so it may wait only as long as leaves room for the work and the settlement.
|
|
31
|
+
*/
|
|
32
|
+
export function laneFor(service, maxTimeoutSeconds) {
|
|
33
|
+
const { concurrency, maxQueued, maxQueueWaitSeconds } = service.capacity;
|
|
34
|
+
const expectedSeconds = service.expectedDurationSeconds ?? DEFAULT_EXPECTED_SECONDS;
|
|
35
|
+
const maxWaitSeconds = service.charge === "on-success"
|
|
36
|
+
? Math.min(maxQueueWaitSeconds, Math.max(0, maxTimeoutSeconds - SETTLE_MARGIN_SECONDS - expectedSeconds))
|
|
37
|
+
: maxQueueWaitSeconds;
|
|
38
|
+
return { service: service.name, charge: service.charge, concurrency, maxQueued, maxWaitSeconds, expectedSeconds };
|
|
39
|
+
}
|
|
40
|
+
export function scheduler(options) {
|
|
41
|
+
const { store, work, owner, leaseSeconds } = options;
|
|
42
|
+
const lanes = new Map(options.lanes.map((config) => [
|
|
43
|
+
config.service,
|
|
44
|
+
{ ...config, reserved: 0, waiters: [], averageMs: config.expectedSeconds * 1000, lock: Promise.resolve(), pumping: false, again: false },
|
|
45
|
+
]));
|
|
46
|
+
let poll;
|
|
47
|
+
let change = deferred();
|
|
48
|
+
const signal = () => {
|
|
49
|
+
change.resolve();
|
|
50
|
+
change = deferred();
|
|
51
|
+
};
|
|
52
|
+
const laneOf = (service) => {
|
|
53
|
+
const lane = lanes.get(service);
|
|
54
|
+
if (lane === undefined)
|
|
55
|
+
throw new Error(`no lane for service "${service}"`);
|
|
56
|
+
return lane;
|
|
57
|
+
};
|
|
58
|
+
/** Seconds until the job at `position` (1 = next) is expected to start. */
|
|
59
|
+
const waitSeconds = (lane, position) => Math.ceil(position / lane.concurrency) * (lane.averageMs / 1000);
|
|
60
|
+
const exclusive = (lane, fn) => {
|
|
61
|
+
const run = lane.lock.then(fn);
|
|
62
|
+
lane.lock = run.catch(() => undefined);
|
|
63
|
+
return run;
|
|
64
|
+
};
|
|
65
|
+
/** Starts what can start: waiting on-success jobs in order, then upfront jobs for this process's workers. */
|
|
66
|
+
const pump = (lane) => {
|
|
67
|
+
if (lane.pumping) {
|
|
68
|
+
lane.again = true;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
lane.pumping = true;
|
|
72
|
+
void (async () => {
|
|
73
|
+
try {
|
|
74
|
+
do {
|
|
75
|
+
lane.again = false;
|
|
76
|
+
while (lane.waiters.length > 0) {
|
|
77
|
+
const waiter = lane.waiters[0];
|
|
78
|
+
const started = await store.start(waiter.jobId, lane.concurrency);
|
|
79
|
+
// It timed out while the store answered. Its runner fails the job, from running if it started.
|
|
80
|
+
const index = lane.waiters.indexOf(waiter);
|
|
81
|
+
if (index === -1)
|
|
82
|
+
continue;
|
|
83
|
+
// FIFO: if the first waiter's turn hasn't come, no later one's has.
|
|
84
|
+
if (started === undefined)
|
|
85
|
+
break;
|
|
86
|
+
lane.waiters.splice(index, 1);
|
|
87
|
+
clearTimeout(waiter.timer);
|
|
88
|
+
waiter.resolve(started);
|
|
89
|
+
}
|
|
90
|
+
if (work !== undefined && lane.charge === "upfront") {
|
|
91
|
+
for (;;) {
|
|
92
|
+
const job = await store.lease(lane.service, lane.concurrency, owner, leaseSeconds);
|
|
93
|
+
if (job === undefined)
|
|
94
|
+
break;
|
|
95
|
+
void work(job)
|
|
96
|
+
.catch((error) => console.error(`[provider-kit] job ${job.jobId} crashed:`, error))
|
|
97
|
+
.finally(() => {
|
|
98
|
+
signal();
|
|
99
|
+
pump(lane);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} while (lane.again);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(`[provider-kit] the "${lane.service}" queue could not be read:`, error);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
lane.pumping = false;
|
|
110
|
+
}
|
|
111
|
+
})();
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
admit(service) {
|
|
115
|
+
const lane = laneOf(service);
|
|
116
|
+
return exclusive(lane, async () => {
|
|
117
|
+
const load = await store.load(service);
|
|
118
|
+
const ahead = load.pending + load.queued + load.running + lane.reserved;
|
|
119
|
+
const position = ahead - lane.concurrency + 1;
|
|
120
|
+
if (position > 0) {
|
|
121
|
+
const wait = waitSeconds(lane, position);
|
|
122
|
+
if (position > lane.maxQueued || wait > lane.maxWaitSeconds) {
|
|
123
|
+
// About when enough of the queue has drained for this request to fit.
|
|
124
|
+
const seconds = Math.ceil(Math.max(wait - lane.maxWaitSeconds, lane.averageMs / 1000 / lane.concurrency));
|
|
125
|
+
return { accepted: false, retryAfterSeconds: Math.min(RETRY_AFTER_MAX_SECONDS, Math.max(RETRY_AFTER_MIN_SECONDS, seconds)) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
lane.reserved++;
|
|
129
|
+
let held = true;
|
|
130
|
+
return {
|
|
131
|
+
accepted: true,
|
|
132
|
+
reservation: {
|
|
133
|
+
release() {
|
|
134
|
+
if (held)
|
|
135
|
+
lane.reserved--;
|
|
136
|
+
held = false;
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
exclusive: (service, fn) => exclusive(laneOf(service), fn),
|
|
143
|
+
waitForTurn(job, deadline) {
|
|
144
|
+
const lane = laneOf(job.service);
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
lane.waiters = lane.waiters.filter((waiter) => waiter.jobId !== job.jobId);
|
|
148
|
+
resolve(undefined);
|
|
149
|
+
}, Math.max(0, deadline - Date.now()));
|
|
150
|
+
timer.unref();
|
|
151
|
+
lane.waiters.push({ jobId: job.jobId, resolve, timer });
|
|
152
|
+
pump(lane);
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
wake(service) {
|
|
156
|
+
signal();
|
|
157
|
+
pump(laneOf(service));
|
|
158
|
+
},
|
|
159
|
+
changed: () => change.promise,
|
|
160
|
+
async position(job) {
|
|
161
|
+
// A job of a service this provider no longer offers has no lane, and no estimate.
|
|
162
|
+
const lane = lanes.get(job.service);
|
|
163
|
+
if (lane === undefined)
|
|
164
|
+
return undefined;
|
|
165
|
+
const position = await store.position(job.jobId);
|
|
166
|
+
if (position === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
return { position, estimatedStartAt: new Date(Date.now() + waitSeconds(lane, position) * 1000).toISOString() };
|
|
169
|
+
},
|
|
170
|
+
record(service, durationMs) {
|
|
171
|
+
const lane = laneOf(service);
|
|
172
|
+
lane.averageMs += AVERAGE_WEIGHT * (durationMs - lane.averageMs);
|
|
173
|
+
},
|
|
174
|
+
start() {
|
|
175
|
+
if (poll !== undefined)
|
|
176
|
+
return;
|
|
177
|
+
for (const lane of lanes.values())
|
|
178
|
+
pump(lane);
|
|
179
|
+
poll = setInterval(() => {
|
|
180
|
+
for (const lane of lanes.values())
|
|
181
|
+
if (lane.waiters.length > 0 || (work !== undefined && lane.charge === "upfront"))
|
|
182
|
+
pump(lane);
|
|
183
|
+
}, options.pollMs);
|
|
184
|
+
poll.unref();
|
|
185
|
+
},
|
|
186
|
+
stop() {
|
|
187
|
+
clearInterval(poll);
|
|
188
|
+
poll = undefined;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function deferred() {
|
|
193
|
+
let resolve;
|
|
194
|
+
const promise = new Promise((done) => (resolve = done));
|
|
195
|
+
return { promise, resolve };
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type JobStore } from "./store.js";
|
|
2
|
+
export interface SqliteStoreOptions {
|
|
3
|
+
/** How long a write waits for another process's transaction, in ms. Default 5000. */
|
|
4
|
+
busyTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/** A JobStore in the SQLite file at `path` (created if missing). ":memory:" is a private, non-durable one. */
|
|
7
|
+
export declare function sqliteStore(path: string, options?: SqliteStoreOptions): JobStore;
|
|
8
|
+
//# sourceMappingURL=sqlite-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/jobs/sqlite-store.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgD,KAAK,QAAQ,EAAkB,MAAM,YAAY,CAAC;AA0BzG,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8GAA8G;AAC9G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,QAAQ,CAsLpF"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Durable JobStore on the built-in node:sqlite (Node >= 22.13, no native dependency).
|
|
2
|
+
//
|
|
3
|
+
// One file holds jobs and payment fingerprints. Several processes on one machine may share it (an
|
|
4
|
+
// HTTP server and separate workers, say): every check-and-write runs in a BEGIN IMMEDIATE
|
|
5
|
+
// transaction, which SQLite serializes across processes, and WAL mode lets readers proceed meanwhile.
|
|
6
|
+
// Commits are fsynced (synchronous = FULL): a paid job is on disk before the client hears about it.
|
|
7
|
+
//
|
|
8
|
+
// Each job is one row: the columns the queue and sweeps query, plus the whole StoredJob as JSON.
|
|
9
|
+
import { DatabaseSync } from "node:sqlite";
|
|
10
|
+
import { applyPatch, FINAL_STATUSES } from "./store.js";
|
|
11
|
+
const SCHEMA_VERSION = 1;
|
|
12
|
+
const SCHEMA = `
|
|
13
|
+
CREATE TABLE jobs (
|
|
14
|
+
job_id TEXT PRIMARY KEY,
|
|
15
|
+
service TEXT NOT NULL,
|
|
16
|
+
charge TEXT NOT NULL,
|
|
17
|
+
status TEXT NOT NULL,
|
|
18
|
+
created_at TEXT NOT NULL,
|
|
19
|
+
finished_at TEXT,
|
|
20
|
+
queue_seq INTEGER,
|
|
21
|
+
lease_owner TEXT,
|
|
22
|
+
lease_expires_at INTEGER,
|
|
23
|
+
data TEXT NOT NULL
|
|
24
|
+
) STRICT;
|
|
25
|
+
CREATE INDEX jobs_queue ON jobs (service, status, queue_seq);
|
|
26
|
+
CREATE INDEX jobs_sweep ON jobs (status, created_at);
|
|
27
|
+
CREATE INDEX jobs_finished ON jobs (finished_at) WHERE finished_at IS NOT NULL;
|
|
28
|
+
CREATE TABLE payments (
|
|
29
|
+
fingerprint TEXT PRIMARY KEY,
|
|
30
|
+
job_id TEXT NOT NULL
|
|
31
|
+
) STRICT;
|
|
32
|
+
`;
|
|
33
|
+
/** A JobStore in the SQLite file at `path` (created if missing). ":memory:" is a private, non-durable one. */
|
|
34
|
+
export function sqliteStore(path, options = {}) {
|
|
35
|
+
const db = new DatabaseSync(path);
|
|
36
|
+
db.exec(`PRAGMA busy_timeout = ${Math.trunc(options.busyTimeoutMs ?? 5000)}`);
|
|
37
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
38
|
+
db.exec("PRAGMA synchronous = FULL");
|
|
39
|
+
migrate(db);
|
|
40
|
+
const statement = (sql) => {
|
|
41
|
+
const prepared = db.prepare(sql);
|
|
42
|
+
return {
|
|
43
|
+
get: (...params) => prepared.get(...params),
|
|
44
|
+
all: (...params) => prepared.all(...params),
|
|
45
|
+
run: (...params) => prepared.run(...params),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const sql = {
|
|
49
|
+
get: statement("SELECT data FROM jobs WHERE job_id = ?"),
|
|
50
|
+
insert: statement(`INSERT INTO jobs (job_id, service, charge, status, created_at, finished_at, queue_seq, lease_owner, lease_expires_at, data)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
52
|
+
write: statement("UPDATE jobs SET status = ?, finished_at = ?, queue_seq = ?, lease_owner = ?, lease_expires_at = ?, data = ? WHERE job_id = ?"),
|
|
53
|
+
paymentUsed: statement("SELECT 1 FROM payments WHERE fingerprint = ?"),
|
|
54
|
+
reservePayment: statement("INSERT INTO payments (fingerprint, job_id) VALUES (?, ?)"),
|
|
55
|
+
nextSeq: statement("SELECT COALESCE(MAX(queue_seq), 0) + 1 AS seq FROM jobs"),
|
|
56
|
+
load: statement("SELECT status, COUNT(*) AS n FROM jobs WHERE service = ? AND status IN ('pending', 'queued', 'running') GROUP BY status"),
|
|
57
|
+
running: statement("SELECT COUNT(*) AS n FROM jobs WHERE service = ? AND status = 'running'"),
|
|
58
|
+
ahead: statement("SELECT COUNT(*) AS n FROM jobs WHERE service = ? AND status = 'queued' AND queue_seq < ?"),
|
|
59
|
+
orphan: statement(`SELECT data FROM jobs WHERE service = ? AND charge = 'upfront' AND status = 'running' AND lease_expires_at < ? AND lease_owner != ?
|
|
60
|
+
ORDER BY queue_seq LIMIT 1`),
|
|
61
|
+
nextQueued: statement("SELECT data FROM jobs WHERE service = ? AND charge = 'upfront' AND status = 'queued' ORDER BY queue_seq LIMIT 1"),
|
|
62
|
+
list: (count) => statement(`SELECT data FROM jobs WHERE status IN (${Array(count).fill("?").join(", ")}) AND created_at < ? ORDER BY created_at LIMIT ?`),
|
|
63
|
+
expired: statement("SELECT job_id, data FROM jobs WHERE finished_at IS NOT NULL AND finished_at < ? AND status IN ('succeeded', 'failed')"),
|
|
64
|
+
deleteJob: statement("DELETE FROM jobs WHERE job_id = ?"),
|
|
65
|
+
deletePayment: statement("DELETE FROM payments WHERE fingerprint = ?"),
|
|
66
|
+
};
|
|
67
|
+
const read = (row) => row === undefined ? undefined : JSON.parse(String(row["data"]));
|
|
68
|
+
const columns = (job) => [
|
|
69
|
+
job.status,
|
|
70
|
+
job.finishedAt ?? null,
|
|
71
|
+
job.queueSeq ?? null,
|
|
72
|
+
job.lease?.owner ?? null,
|
|
73
|
+
job.lease?.expiresAt ?? null,
|
|
74
|
+
JSON.stringify(job),
|
|
75
|
+
];
|
|
76
|
+
const write = (job) => {
|
|
77
|
+
sql.write.run(...columns(job), job.jobId);
|
|
78
|
+
return job;
|
|
79
|
+
};
|
|
80
|
+
const enqueue = (job) => {
|
|
81
|
+
job.queueSeq = Number(sql.nextSeq.get()["seq"]);
|
|
82
|
+
};
|
|
83
|
+
const count = (row) => Number(row?.["n"] ?? 0);
|
|
84
|
+
/** Runs fn in a write transaction, so a check and the write after it are atomic across processes. */
|
|
85
|
+
const transaction = (fn) => {
|
|
86
|
+
db.exec("BEGIN IMMEDIATE");
|
|
87
|
+
try {
|
|
88
|
+
const result = fn();
|
|
89
|
+
db.exec("COMMIT");
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
db.exec("ROLLBACK");
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
async get(jobId) {
|
|
99
|
+
return read(sql.get.get(jobId));
|
|
100
|
+
},
|
|
101
|
+
async claim(job) {
|
|
102
|
+
return transaction(() => {
|
|
103
|
+
const existing = read(sql.get.get(job.jobId));
|
|
104
|
+
if (existing !== undefined) {
|
|
105
|
+
const same = existing.bindingHash === job.bindingHash && existing.tokenHash === job.tokenHash;
|
|
106
|
+
return same ? { outcome: "existing", job: existing } : { outcome: "conflict" };
|
|
107
|
+
}
|
|
108
|
+
if (sql.paymentUsed.get(job.claim.fingerprint) !== undefined)
|
|
109
|
+
return { outcome: "payment_used" };
|
|
110
|
+
const stored = structuredClone(job);
|
|
111
|
+
if (stored.status === "queued")
|
|
112
|
+
enqueue(stored);
|
|
113
|
+
sql.reservePayment.run(job.claim.fingerprint, job.jobId);
|
|
114
|
+
const [status, ...rest] = columns(stored);
|
|
115
|
+
sql.insert.run(stored.jobId, stored.service, stored.charge, status, stored.createdAt, ...rest);
|
|
116
|
+
return { outcome: "created", job: stored };
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
async update(jobId, from, patch, owner) {
|
|
120
|
+
return transaction(() => {
|
|
121
|
+
const job = read(sql.get.get(jobId));
|
|
122
|
+
if (job === undefined || job.status !== from || FINAL_STATUSES.includes(job.status))
|
|
123
|
+
return undefined;
|
|
124
|
+
if (owner !== undefined && job.lease?.owner !== owner)
|
|
125
|
+
return undefined;
|
|
126
|
+
applyPatch(job, patch, new Date());
|
|
127
|
+
if (patch.status === "queued" && from !== "queued")
|
|
128
|
+
enqueue(job);
|
|
129
|
+
return write(job);
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
async load(service) {
|
|
133
|
+
const load = { pending: 0, queued: 0, running: 0 };
|
|
134
|
+
for (const row of sql.load.all(service))
|
|
135
|
+
load[row["status"]] = Number(row["n"]);
|
|
136
|
+
return load;
|
|
137
|
+
},
|
|
138
|
+
async position(jobId) {
|
|
139
|
+
const job = read(sql.get.get(jobId));
|
|
140
|
+
if (job?.status !== "queued")
|
|
141
|
+
return undefined;
|
|
142
|
+
return count(sql.ahead.get(job.service, job.queueSeq)) + 1;
|
|
143
|
+
},
|
|
144
|
+
async start(jobId, concurrency) {
|
|
145
|
+
return transaction(() => {
|
|
146
|
+
const job = read(sql.get.get(jobId));
|
|
147
|
+
if (job?.status !== "queued")
|
|
148
|
+
return undefined;
|
|
149
|
+
const busy = count(sql.running.get(job.service)) + count(sql.ahead.get(job.service, job.queueSeq));
|
|
150
|
+
return busy < concurrency ? write(applyPatch(job, { status: "running" }, new Date())) : undefined;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
async lease(service, concurrency, owner, leaseSeconds) {
|
|
154
|
+
return transaction(() => {
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
const lease = { owner, expiresAt: now + leaseSeconds * 1000 };
|
|
157
|
+
const orphan = read(sql.orphan.get(service, now, owner));
|
|
158
|
+
if (orphan !== undefined) {
|
|
159
|
+
orphan.lease = lease;
|
|
160
|
+
orphan.attempts = (orphan.attempts ?? 0) + 1;
|
|
161
|
+
orphan.updatedAt = new Date(now).toISOString();
|
|
162
|
+
return write(orphan);
|
|
163
|
+
}
|
|
164
|
+
if (count(sql.running.get(service)) >= concurrency)
|
|
165
|
+
return undefined;
|
|
166
|
+
const next = read(sql.nextQueued.get(service));
|
|
167
|
+
if (next === undefined)
|
|
168
|
+
return undefined;
|
|
169
|
+
applyPatch(next, { status: "running" }, new Date(now));
|
|
170
|
+
next.lease = lease;
|
|
171
|
+
next.attempts = (next.attempts ?? 0) + 1;
|
|
172
|
+
return write(next);
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
async renew(jobId, owner, leaseSeconds) {
|
|
176
|
+
return transaction(() => {
|
|
177
|
+
const job = read(sql.get.get(jobId));
|
|
178
|
+
if (job?.status !== "running" || job.lease?.owner !== owner)
|
|
179
|
+
return false;
|
|
180
|
+
job.lease.expiresAt = Date.now() + leaseSeconds * 1000;
|
|
181
|
+
write(job);
|
|
182
|
+
return true;
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
async list({ statuses, createdBefore, limit }) {
|
|
186
|
+
if (statuses.length === 0)
|
|
187
|
+
return [];
|
|
188
|
+
return sql
|
|
189
|
+
.list(statuses.length)
|
|
190
|
+
.all(...statuses, createdBefore, limit)
|
|
191
|
+
.map((row) => read(row));
|
|
192
|
+
},
|
|
193
|
+
async deleteFinishedBefore(before) {
|
|
194
|
+
return transaction(() => {
|
|
195
|
+
const rows = sql.expired.all(before);
|
|
196
|
+
for (const row of rows) {
|
|
197
|
+
const job = read(row);
|
|
198
|
+
if (!FINAL_STATUSES.includes(job.status))
|
|
199
|
+
continue;
|
|
200
|
+
sql.deleteJob.run(job.jobId);
|
|
201
|
+
sql.deletePayment.run(job.claim.fingerprint);
|
|
202
|
+
}
|
|
203
|
+
return rows.length;
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
close() {
|
|
207
|
+
db.close();
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function migrate(db) {
|
|
212
|
+
const version = Number(db.prepare("PRAGMA user_version").get().user_version);
|
|
213
|
+
if (version === SCHEMA_VERSION)
|
|
214
|
+
return;
|
|
215
|
+
if (version > SCHEMA_VERSION)
|
|
216
|
+
throw new Error(`the job database has schema version ${version}; this provider-kit knows ${SCHEMA_VERSION}`);
|
|
217
|
+
db.exec("BEGIN IMMEDIATE");
|
|
218
|
+
try {
|
|
219
|
+
// Another process may have migrated while this one waited for the lock.
|
|
220
|
+
const now = Number(db.prepare("PRAGMA user_version").get().user_version);
|
|
221
|
+
if (now === 0)
|
|
222
|
+
db.exec(SCHEMA);
|
|
223
|
+
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
224
|
+
db.exec("COMMIT");
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
db.exec("ROLLBACK");
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=sqlite-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../../src/jobs/sqlite-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Charge, JobFailureCode, JobStatus, JsonObject, PaymentReceipt, Problem } from "@stellar-agent-services/protocol";
|
|
2
|
+
/**
|
|
3
|
+
* "pending" is internal: an upfront job whose id and payment are claimed and whose settlement has not
|
|
4
|
+
* answered yet. Requests for a pending job get 409 request_in_progress [J7].
|
|
5
|
+
*/
|
|
6
|
+
export type StoredStatus = JobStatus | "pending";
|
|
7
|
+
/** The payment a job was claimed with. Reconciliation finds it on chain from these fields. */
|
|
8
|
+
export interface PaymentClaim {
|
|
9
|
+
/** sha256(JCS({ network, payer, nonce })): unique across the store [P9]. */
|
|
10
|
+
fingerprint: string;
|
|
11
|
+
payer: string;
|
|
12
|
+
nonce: string;
|
|
13
|
+
/** The last ledger the payer's signature is valid for: after it, the payment can never land. */
|
|
14
|
+
expirationLedger: number;
|
|
15
|
+
}
|
|
16
|
+
export interface StoredJob {
|
|
17
|
+
jobId: string;
|
|
18
|
+
service: string;
|
|
19
|
+
charge: Charge;
|
|
20
|
+
bindingHash: string;
|
|
21
|
+
tokenHash: string;
|
|
22
|
+
status: StoredStatus;
|
|
23
|
+
claim: PaymentClaim;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
finishedAt?: string;
|
|
27
|
+
/** Kept until the job is final: a queued upfront job runs from it, in any process, after a restart. */
|
|
28
|
+
input?: JsonObject;
|
|
29
|
+
output?: JsonObject;
|
|
30
|
+
error?: Problem<JobFailureCode>;
|
|
31
|
+
/** The settled payment. */
|
|
32
|
+
payment?: PaymentReceipt;
|
|
33
|
+
/** When settlement was asked for. A job still settling long after it is decided as payment_uncertain. */
|
|
34
|
+
settlingSince?: string;
|
|
35
|
+
/** Why the settlement is uncertain, and the transaction the facilitator named, if any. */
|
|
36
|
+
settlement?: {
|
|
37
|
+
reason: string;
|
|
38
|
+
transaction?: string;
|
|
39
|
+
};
|
|
40
|
+
/** Queue order, set by the store when the job becomes queued. */
|
|
41
|
+
queueSeq?: number;
|
|
42
|
+
/** The worker running an upfront job, and until when (epoch ms) its lease holds. */
|
|
43
|
+
lease?: {
|
|
44
|
+
owner: string;
|
|
45
|
+
expiresAt: number;
|
|
46
|
+
};
|
|
47
|
+
/** How many times a worker started the job. */
|
|
48
|
+
attempts?: number;
|
|
49
|
+
}
|
|
50
|
+
/** What update() may change. The store sets updatedAt, and clears what a final job no longer needs. */
|
|
51
|
+
export type JobPatch = Partial<Omit<StoredJob, "jobId" | "service" | "charge" | "bindingHash" | "tokenHash" | "claim" | "createdAt" | "queueSeq">> & {
|
|
52
|
+
status: StoredStatus;
|
|
53
|
+
};
|
|
54
|
+
export type ClaimResult = {
|
|
55
|
+
outcome: "created";
|
|
56
|
+
job: StoredJob;
|
|
57
|
+
}
|
|
58
|
+
/** Same binding and token: the existing job [J5]. */
|
|
59
|
+
| {
|
|
60
|
+
outcome: "existing";
|
|
61
|
+
job: StoredJob;
|
|
62
|
+
}
|
|
63
|
+
/** Same job id, different binding or token [J6]. */
|
|
64
|
+
| {
|
|
65
|
+
outcome: "conflict";
|
|
66
|
+
}
|
|
67
|
+
/** The payment already bought another job [P9]. */
|
|
68
|
+
| {
|
|
69
|
+
outcome: "payment_used";
|
|
70
|
+
};
|
|
71
|
+
export interface ServiceLoad {
|
|
72
|
+
pending: number;
|
|
73
|
+
queued: number;
|
|
74
|
+
running: number;
|
|
75
|
+
}
|
|
76
|
+
export interface JobStore {
|
|
77
|
+
get(jobId: string): Promise<StoredJob | undefined>;
|
|
78
|
+
/** Inserts the job and reserves job.claim.fingerprint, atomically. */
|
|
79
|
+
claim(job: StoredJob): Promise<ClaimResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Applies the patch only while the job is still in `from` and, with `owner`, still leased by that
|
|
82
|
+
* worker. Returns the updated job, or undefined. On a final status the store drops the input, the
|
|
83
|
+
* lease and a failed job's output, and sets finishedAt if the patch did not; settlingSince is kept
|
|
84
|
+
* only while pending or running.
|
|
85
|
+
*/
|
|
86
|
+
update(jobId: string, from: StoredStatus, patch: JobPatch, owner?: string): Promise<StoredJob | undefined>;
|
|
87
|
+
/** How many of a service's jobs are being settled, are queued and are running: admission counts (§5.8). */
|
|
88
|
+
load(service: string): Promise<ServiceLoad>;
|
|
89
|
+
/** 1 for a service's next queued job; undefined when the job is not queued. */
|
|
90
|
+
position(jobId: string): Promise<number | undefined>;
|
|
91
|
+
/**
|
|
92
|
+
* Moves a queued job to running if its turn has come: fewer than `concurrency` jobs of its service
|
|
93
|
+
* are running and queued ahead of it. Returns the running job, or undefined.
|
|
94
|
+
*/
|
|
95
|
+
start(jobId: string, concurrency: number): Promise<StoredJob | undefined>;
|
|
96
|
+
/**
|
|
97
|
+
* Gives a worker the next upfront job of a service, leased until now + leaseSeconds: first a running
|
|
98
|
+
* job whose lease ran out (its worker died), else the oldest queued job if fewer than `concurrency`
|
|
99
|
+
* are running. Counts the attempt. A lease held by `owner` itself is never taken over: an owner is
|
|
100
|
+
* one process, which still runs its jobs even when a stalled event loop let a lease run out.
|
|
101
|
+
*/
|
|
102
|
+
lease(service: string, concurrency: number, owner: string, leaseSeconds: number): Promise<StoredJob | undefined>;
|
|
103
|
+
/** Extends a running job's lease. False when the worker no longer holds it. */
|
|
104
|
+
renew(jobId: string, owner: string, leaseSeconds: number): Promise<boolean>;
|
|
105
|
+
/** Jobs in the given statuses created before `createdBefore`, oldest first: for recovery sweeps. */
|
|
106
|
+
list(filter: {
|
|
107
|
+
statuses: StoredStatus[];
|
|
108
|
+
createdBefore: string;
|
|
109
|
+
limit: number;
|
|
110
|
+
}): Promise<StoredJob[]>;
|
|
111
|
+
/** Deletes final jobs that finished before `before`, with their payment fingerprints. Returns how many. */
|
|
112
|
+
deleteFinishedBefore(before: string): Promise<number>;
|
|
113
|
+
close?(): Promise<void> | void;
|
|
114
|
+
}
|
|
115
|
+
export declare const FINAL_STATUSES: readonly StoredStatus[];
|
|
116
|
+
/** The patch rules every store applies (see update()). Mutates and returns `job`. */
|
|
117
|
+
export declare function applyPatch(job: StoredJob, patch: JobPatch, now: Date): StoredJob;
|
|
118
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// JobStore: the persistence contract behind Job Recovery and the queue.
|
|
2
|
+
//
|
|
3
|
+
// - claim(): an atomic insert keyed by jobId that also reserves the payment fingerprint, so one
|
|
4
|
+
// signed payment can buy at most one job [P9].
|
|
5
|
+
// - update(): compare-and-set on the status (and, for a leased job, on the worker). It never moves a
|
|
6
|
+
// job out of a final state [J12].
|
|
7
|
+
// - Only sha256(job token) is stored, never the token itself [J3]. The input is kept only until the
|
|
8
|
+
// job is final.
|
|
9
|
+
// - The queue lives here too, so it is as durable as the store (docs/provider-standard.md §5.8):
|
|
10
|
+
// a service's jobs start in the order they were queued, at most `concurrency` at a time, counted
|
|
11
|
+
// across every process that shares the store. Workers lease upfront jobs; a job whose worker died
|
|
12
|
+
// is taken over when its lease runs out, so a paid job survives a restart [Q4].
|
|
13
|
+
//
|
|
14
|
+
// memoryStore() and sqliteStore() implement it; test/job-store.test.ts is the contract every store
|
|
15
|
+
// must pass.
|
|
16
|
+
export const FINAL_STATUSES = ["succeeded", "failed"];
|
|
17
|
+
/** The patch rules every store applies (see update()). Mutates and returns `job`. */
|
|
18
|
+
export function applyPatch(job, patch, now) {
|
|
19
|
+
Object.assign(job, structuredClone(patch), { updatedAt: now.toISOString() });
|
|
20
|
+
if (FINAL_STATUSES.includes(job.status)) {
|
|
21
|
+
delete job.input;
|
|
22
|
+
delete job.lease;
|
|
23
|
+
delete job.settlingSince;
|
|
24
|
+
if (job.status === "failed")
|
|
25
|
+
delete job.output;
|
|
26
|
+
job.finishedAt ??= now.toISOString();
|
|
27
|
+
}
|
|
28
|
+
if (job.status === "payment_uncertain")
|
|
29
|
+
delete job.lease;
|
|
30
|
+
// settlingSince marks a settlement still in flight: pending (upfront) or running (on-success). Any
|
|
31
|
+
// other status means the settlement was decided, so a long upfront job is never mistaken for one.
|
|
32
|
+
if (job.status !== "pending" && job.status !== "running")
|
|
33
|
+
delete job.settlingSince;
|
|
34
|
+
return job;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,gGAAgG;AAChG,iDAAiD;AACjD,qGAAqG;AACrG,oCAAoC;AACpC,oGAAoG;AACpG,kBAAkB;AAClB,iGAAiG;AACjG,mGAAmG;AACnG,oGAAoG;AACpG,kFAAkF;AAClF,EAAE;AACF,mGAAmG;AACnG,aAAa;AAyGb,MAAM,CAAC,MAAM,cAAc,GAA4B,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAE/E,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,GAAc,EAAE,KAAe,EAAE,GAAS;IACnE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC7E,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,KAAK,CAAC;QACjB,OAAO,GAAG,CAAC,KAAK,CAAC;QACjB,OAAO,GAAG,CAAC,aAAa,CAAC;QACzB,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC;QAC/C,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC;IACvC,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,mBAAmB;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC;IACzD,mGAAmG;IACnG,kGAAkG;IAClG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC,aAAa,CAAC;IACnF,OAAO,GAAG,CAAC;AACb,CAAC"}
|