@dvmkit/sdk 0.0.0 → 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +2 -0
- package/README.md +38 -2
- package/dist/chunk-27V2ILSR.js +291 -0
- package/dist/chunk-5GFED3GJ.js +955 -0
- package/dist/chunk-6JZIX5WW.js +1155 -0
- package/dist/chunk-7IH5SG2A.js +1038 -0
- package/dist/chunk-AT6V3SY7.js +102 -0
- package/dist/chunk-DCNT4PJS.js +733 -0
- package/dist/chunk-DMNLFNTW.js +135 -0
- package/dist/chunk-FROTD5XQ.js +70 -0
- package/dist/chunk-H25M54MI.js +149 -0
- package/dist/chunk-KQAJVVZT.js +712 -0
- package/dist/chunk-KXWROQGK.js +74 -0
- package/dist/chunk-L4OYF4DQ.js +67 -0
- package/dist/chunk-NTK5DJ6R.js +1256 -0
- package/dist/chunk-RPXHKMYE.js +3808 -0
- package/dist/chunk-S3XAHZQY.js +63 -0
- package/dist/chunk-YG7G4DPZ.js +25 -0
- package/dist/credit-ledger-EDMEZSA2.js +28 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +303 -0
- package/dist/job-store-C5n6bhap.d.ts +5090 -0
- package/dist/memory-credit-ledger-7TTZDSRS.js +9 -0
- package/dist/mpp-secret-state-WNAQQ6K4.js +127 -0
- package/dist/mpp-setup-MOBWGTWJ.js +30 -0
- package/dist/postgres-consumed-credential-store-VHBT4KEA.js +72 -0
- package/dist/postgres-job-store-J5F4GUWU.js +7 -0
- package/dist/postgres-kv-store-JFBDP5IP.js +7 -0
- package/dist/postgres-replay-store-UJXRT6VO.js +7 -0
- package/dist/pricing-4CEB34RM.js +48 -0
- package/dist/processed-payment-store-HAA4SFNK.js +11 -0
- package/dist/revenue-reporter-M35KP6V7.js +435 -0
- package/dist/server/index.d.ts +4108 -0
- package/dist/server/index.js +22538 -0
- package/dist/ssrf-BdHsrrIb.d.ts +325 -0
- package/dist/tempo-charge-store-6GJEMNUU.js +130 -0
- package/dist/tempo-session-store-FTEEGZXA.js +467 -0
- package/dist/testing/index.d.ts +135 -0
- package/dist/testing/index.js +151 -0
- package/dist/x402-35VLYFKZ.js +1272 -0
- package/package.json +89 -6
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initCallerLoggers,
|
|
3
|
+
redactUrl
|
|
4
|
+
} from "./chunk-KXWROQGK.js";
|
|
5
|
+
|
|
6
|
+
// src/sdk/server/job-store.ts
|
|
7
|
+
function accumulateAskedTopUp(prior, ask) {
|
|
8
|
+
const poisoned = (reason) => ({
|
|
9
|
+
micro: UNCAPPABLE_ASK_TOTAL,
|
|
10
|
+
currency: prior.currency,
|
|
11
|
+
reason: prior.reason ?? reason
|
|
12
|
+
});
|
|
13
|
+
if (prior.micro === UNCAPPABLE_ASK_TOTAL) {
|
|
14
|
+
return { micro: prior.micro, currency: prior.currency, reason: prior.reason };
|
|
15
|
+
}
|
|
16
|
+
if (ask.micro === void 0) return poisoned("ask_unpriceable");
|
|
17
|
+
if (prior.micro === void 0) {
|
|
18
|
+
return { micro: ask.micro, currency: ask.currency, reason: prior.reason };
|
|
19
|
+
}
|
|
20
|
+
if (prior.currency !== ask.currency) return poisoned("ask_currency_switch");
|
|
21
|
+
return { micro: prior.micro + ask.micro, currency: prior.currency, reason: prior.reason };
|
|
22
|
+
}
|
|
23
|
+
var UNCAPPABLE_ASK_TOTAL = -1;
|
|
24
|
+
function isReceiptIssuingStore(store) {
|
|
25
|
+
return "claimReceiptSeq" in store && "saveReceipt" in store;
|
|
26
|
+
}
|
|
27
|
+
function isStreamableJobStore(store) {
|
|
28
|
+
return "appendOutgoing" in store && "recordInbound" in store && "verifyAndCredit" in store && "getVerifiedInbound" in store && "subscribeMessages" in store && "subscribeNotifications" in store && "getCounters" in store && "getMessages" in store && "cancelJob" in store && "initStreaming" in store;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/sdk/step-cache.ts
|
|
32
|
+
var StepCache = class _StepCache {
|
|
33
|
+
cache = /* @__PURE__ */ new Map();
|
|
34
|
+
/** Check if a step result is cached. */
|
|
35
|
+
has(id) {
|
|
36
|
+
return this.cache.has(id);
|
|
37
|
+
}
|
|
38
|
+
/** Get a cached step result. Throws if not present. */
|
|
39
|
+
get(id) {
|
|
40
|
+
if (!this.cache.has(id)) {
|
|
41
|
+
throw new Error(`StepCache: no cached result for step "${id}"`);
|
|
42
|
+
}
|
|
43
|
+
return this.cache.get(id);
|
|
44
|
+
}
|
|
45
|
+
/** Cache a step result. */
|
|
46
|
+
set(id, value) {
|
|
47
|
+
this.cache.set(id, value);
|
|
48
|
+
}
|
|
49
|
+
/** Export all cached steps for persistence. */
|
|
50
|
+
serialize() {
|
|
51
|
+
return [...this.cache.entries()].map(([id, value]) => ({ id, value }));
|
|
52
|
+
}
|
|
53
|
+
/** Restore a StepCache from persisted records. */
|
|
54
|
+
static deserialize(records) {
|
|
55
|
+
const cache = new _StepCache();
|
|
56
|
+
for (const { id, value } of records) {
|
|
57
|
+
cache.set(id, value);
|
|
58
|
+
}
|
|
59
|
+
return cache;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/sdk/server/job.ts
|
|
64
|
+
function toJobRecord(job) {
|
|
65
|
+
return {
|
|
66
|
+
id: job.id,
|
|
67
|
+
tags: job.tags,
|
|
68
|
+
capability: job.capability,
|
|
69
|
+
input: job.input,
|
|
70
|
+
params: job.params,
|
|
71
|
+
requesterId: job.requesterId,
|
|
72
|
+
requesterTokenHash: job.requesterTokenHash,
|
|
73
|
+
requesterToken: job.requesterToken,
|
|
74
|
+
requestFingerprint: job.requestFingerprint,
|
|
75
|
+
requesterPubkey: job.requesterPubkey,
|
|
76
|
+
requestId: job.requestId,
|
|
77
|
+
authRequestPath: job.authRequestPath,
|
|
78
|
+
status: job.status,
|
|
79
|
+
summary: job.summary,
|
|
80
|
+
messages: job.messages,
|
|
81
|
+
seq: job.seq,
|
|
82
|
+
paidMsats: job.paidMsats,
|
|
83
|
+
paymentMint: job.paymentMint,
|
|
84
|
+
paymentRail: job.paymentRail,
|
|
85
|
+
paymentTxHash: job.paymentTxHash,
|
|
86
|
+
paymentTransactionHash: job.paymentTransactionHash,
|
|
87
|
+
nativeAmount: job.nativeAmount,
|
|
88
|
+
nativeAsset: job.nativeAsset,
|
|
89
|
+
cashuFlow: job.cashuFlow,
|
|
90
|
+
creditId: job.creditId,
|
|
91
|
+
drawId: job.drawId,
|
|
92
|
+
fundingReceipt: job.fundingReceipt,
|
|
93
|
+
fundingCredit: job.fundingCredit,
|
|
94
|
+
receivedProofs: job.receivedProofs,
|
|
95
|
+
pendingPaymentMsats: job.pendingPaymentMsats,
|
|
96
|
+
pendingMppChallengeIds: job.pendingMppChallengeIds,
|
|
97
|
+
pendingX402Nonce: job.pendingX402Nonce,
|
|
98
|
+
pendingX402AmountUsdcMicro: job.pendingX402AmountUsdcMicro,
|
|
99
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
100
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency,
|
|
101
|
+
askedTopUpMicro: job.askedTopUpMicro,
|
|
102
|
+
askedTopUpCurrency: job.askedTopUpCurrency,
|
|
103
|
+
topUpCapUnenforcedReason: job.topUpCapUnenforcedReason,
|
|
104
|
+
requiredMsats: job.requiredMsats,
|
|
105
|
+
receipt: job.receipt,
|
|
106
|
+
stepCache: job.stepCache.serialize(),
|
|
107
|
+
state: job.state,
|
|
108
|
+
createdAt: job.createdAt,
|
|
109
|
+
lastActivityAt: job.lastActivityAt
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function fromJobRecord(record) {
|
|
113
|
+
return {
|
|
114
|
+
id: record.id,
|
|
115
|
+
tags: record.tags,
|
|
116
|
+
capability: record.capability,
|
|
117
|
+
input: record.input,
|
|
118
|
+
params: record.params,
|
|
119
|
+
requesterId: record.requesterId,
|
|
120
|
+
requesterTokenHash: record.requesterTokenHash,
|
|
121
|
+
status: record.status,
|
|
122
|
+
summary: record.summary,
|
|
123
|
+
messages: [...record.messages],
|
|
124
|
+
seq: record.seq,
|
|
125
|
+
paidMsats: record.paidMsats,
|
|
126
|
+
paymentMint: record.paymentMint,
|
|
127
|
+
paymentRail: record.paymentRail,
|
|
128
|
+
paymentTxHash: record.paymentTxHash,
|
|
129
|
+
paymentTransactionHash: record.paymentTransactionHash,
|
|
130
|
+
nativeAmount: record.nativeAmount,
|
|
131
|
+
nativeAsset: record.nativeAsset,
|
|
132
|
+
cashuFlow: record.cashuFlow,
|
|
133
|
+
creditId: record.creditId,
|
|
134
|
+
drawId: record.drawId,
|
|
135
|
+
fundingReceipt: record.fundingReceipt,
|
|
136
|
+
fundingCredit: record.fundingCredit,
|
|
137
|
+
receivedProofs: [...record.receivedProofs],
|
|
138
|
+
pendingPaymentMsats: record.pendingPaymentMsats,
|
|
139
|
+
pendingMppChallengeIds: record.pendingMppChallengeIds ? [...record.pendingMppChallengeIds] : void 0,
|
|
140
|
+
requesterToken: record.requesterToken,
|
|
141
|
+
requestFingerprint: record.requestFingerprint,
|
|
142
|
+
requesterPubkey: record.requesterPubkey,
|
|
143
|
+
requestId: record.requestId,
|
|
144
|
+
authRequestPath: record.authRequestPath,
|
|
145
|
+
pendingX402Nonce: record.pendingX402Nonce,
|
|
146
|
+
pendingX402AmountUsdcMicro: record.pendingX402AmountUsdcMicro,
|
|
147
|
+
pendingPaymentFiatMicro: record.pendingPaymentFiatMicro,
|
|
148
|
+
pendingPaymentFiatCurrency: record.pendingPaymentFiatCurrency,
|
|
149
|
+
askedTopUpMicro: record.askedTopUpMicro,
|
|
150
|
+
askedTopUpCurrency: record.askedTopUpCurrency,
|
|
151
|
+
topUpCapUnenforcedReason: record.topUpCapUnenforcedReason,
|
|
152
|
+
requiredMsats: record.requiredMsats,
|
|
153
|
+
receipt: record.receipt,
|
|
154
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
155
|
+
abort: new AbortController(),
|
|
156
|
+
messageAppender: void 0,
|
|
157
|
+
messageAppenderTail: void 0,
|
|
158
|
+
stepCache: StepCache.deserialize(record.stepCache),
|
|
159
|
+
state: record.state,
|
|
160
|
+
pendingPrompts: /* @__PURE__ */ new Map(),
|
|
161
|
+
pendingPayment: null,
|
|
162
|
+
replayHighSeq: null,
|
|
163
|
+
replayProviderSkip: 0,
|
|
164
|
+
replayPaymentIndex: 0,
|
|
165
|
+
createdAt: record.createdAt,
|
|
166
|
+
lastActivityAt: record.lastActivityAt
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function abortJob(job, reason) {
|
|
170
|
+
job.abort.abort(new JobCancelledError(reason));
|
|
171
|
+
}
|
|
172
|
+
var JobCancelledError = class extends Error {
|
|
173
|
+
constructor(message = "Job cancelled") {
|
|
174
|
+
super(message);
|
|
175
|
+
this.name = "JobCancelledError";
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
function providerMessage(job, type, content) {
|
|
179
|
+
if (job.replayProviderSkip > 0) {
|
|
180
|
+
job.replayProviderSkip--;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const msg = localMessage(job, "provider", type, content);
|
|
184
|
+
if (type === "payment-request") {
|
|
185
|
+
const paymentRequest = content;
|
|
186
|
+
const pendingPaymentDelta = typeof paymentRequest.amount_msats === "number" ? paymentRequest.amount_msats : void 0;
|
|
187
|
+
const pendingMppChallengeIds = paymentRequest.tempo?.challenges?.map((challenge) => challenge.id).filter((id) => typeof id === "string");
|
|
188
|
+
job.messageAppender?.(msg, {
|
|
189
|
+
pendingPaymentDelta,
|
|
190
|
+
pendingMppChallengeIds,
|
|
191
|
+
pendingX402Nonce: typeof paymentRequest.x402?.nonce === "string" ? paymentRequest.x402.nonce : void 0,
|
|
192
|
+
pendingX402AmountUsdcMicro: typeof paymentRequest.x402?.required_usdc_micro === "string" ? paymentRequest.x402.required_usdc_micro : void 0,
|
|
193
|
+
// internal-review: read off the job rather than the content — the fiat envelope
|
|
194
|
+
// is ledger state, not part of the wire shape, and `requestPayment` has
|
|
195
|
+
// already pinned it on the job by the time this fires (same ordering the
|
|
196
|
+
// `pendingPaymentMsats` comment above describes).
|
|
197
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
198
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
job.messageAppender?.(msg);
|
|
203
|
+
}
|
|
204
|
+
function localMessage(job, from, type, content) {
|
|
205
|
+
job.seq++;
|
|
206
|
+
const msg = {
|
|
207
|
+
seq: job.seq,
|
|
208
|
+
from,
|
|
209
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
210
|
+
type,
|
|
211
|
+
content
|
|
212
|
+
};
|
|
213
|
+
job.messages.push(msg);
|
|
214
|
+
for (const listener of job.listeners) {
|
|
215
|
+
listener(msg);
|
|
216
|
+
}
|
|
217
|
+
return msg;
|
|
218
|
+
}
|
|
219
|
+
function isTerminal(status) {
|
|
220
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
221
|
+
}
|
|
222
|
+
function isYieldMessage(msg) {
|
|
223
|
+
return msg.type === "prompt" || msg.type === "payment-request" || msg.type === "working" || msg.type === "complete" || msg.type === "cancel";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/sdk/server/memory-job-store.ts
|
|
227
|
+
var MemoryJobStore = class {
|
|
228
|
+
data = /* @__PURE__ */ new Map();
|
|
229
|
+
messages = /* @__PURE__ */ new Map();
|
|
230
|
+
nextSeq = /* @__PURE__ */ new Map();
|
|
231
|
+
messageSubscribers = /* @__PURE__ */ new Map();
|
|
232
|
+
notifySubscribers = /* @__PURE__ */ new Map();
|
|
233
|
+
reactivationLocks = /* @__PURE__ */ new Set();
|
|
234
|
+
requestIdClaims = /* @__PURE__ */ new Map();
|
|
235
|
+
/** Per-DVM monotonic receipt counter (internal-review) — the `receipt_counter` row. */
|
|
236
|
+
receiptCounter = 0;
|
|
237
|
+
/** Receipt sequence already allocated to a job, keyed by job id. */
|
|
238
|
+
receiptSeqs = /* @__PURE__ */ new Map();
|
|
239
|
+
get(id) {
|
|
240
|
+
const record = this.data.get(id);
|
|
241
|
+
return Promise.resolve(record ? structuredClone(record) : void 0);
|
|
242
|
+
}
|
|
243
|
+
findJobByPaymentTxHash(txHash) {
|
|
244
|
+
for (const record of this.data.values()) {
|
|
245
|
+
if (record.paymentTxHash === txHash) return Promise.resolve(structuredClone(record));
|
|
246
|
+
}
|
|
247
|
+
return Promise.resolve(void 0);
|
|
248
|
+
}
|
|
249
|
+
findJobByRequestId(requestId, requesterPubkey) {
|
|
250
|
+
for (const record of this.data.values()) {
|
|
251
|
+
if (record.requestId === requestId && record.requesterPubkey === requesterPubkey) {
|
|
252
|
+
return Promise.resolve(structuredClone(record));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return Promise.resolve(void 0);
|
|
256
|
+
}
|
|
257
|
+
claimRequestId(claim) {
|
|
258
|
+
const key = requestIdClaimKey(claim.requesterPubkey, claim.requestId);
|
|
259
|
+
const existing = this.requestIdClaims.get(key);
|
|
260
|
+
if (!existing) {
|
|
261
|
+
this.requestIdClaims.set(key, { ...claim, holderToken: claim.claimToken });
|
|
262
|
+
return Promise.resolve({ jobId: claim.jobId, replayed: false });
|
|
263
|
+
}
|
|
264
|
+
if (existing.holderToken !== void 0 || existing.requestFingerprint !== claim.requestFingerprint || existing.requesterId !== claim.requesterId) {
|
|
265
|
+
return Promise.resolve(void 0);
|
|
266
|
+
}
|
|
267
|
+
this.requestIdClaims.set(key, { ...existing, holderToken: claim.claimToken });
|
|
268
|
+
return Promise.resolve({ jobId: existing.jobId, replayed: true });
|
|
269
|
+
}
|
|
270
|
+
resumeRequestId(claim) {
|
|
271
|
+
const key = requestIdClaimKey(claim.requesterPubkey, claim.requestId);
|
|
272
|
+
const existing = this.requestIdClaims.get(key);
|
|
273
|
+
if (!existing || existing.holderToken !== void 0 || existing.requestFingerprint !== claim.requestFingerprint || existing.requesterId !== claim.requesterId) {
|
|
274
|
+
return Promise.resolve(void 0);
|
|
275
|
+
}
|
|
276
|
+
this.requestIdClaims.set(key, { ...existing, holderToken: claim.claimToken });
|
|
277
|
+
return Promise.resolve({ jobId: existing.jobId, replayed: true });
|
|
278
|
+
}
|
|
279
|
+
releaseRequestIdClaim(claim) {
|
|
280
|
+
const key = requestIdClaimKey(claim.requesterPubkey, claim.requestId);
|
|
281
|
+
const existing = this.requestIdClaims.get(key);
|
|
282
|
+
if (existing?.holderToken === claim.claimToken) {
|
|
283
|
+
this.requestIdClaims.set(key, { ...existing, holderToken: void 0 });
|
|
284
|
+
}
|
|
285
|
+
return Promise.resolve();
|
|
286
|
+
}
|
|
287
|
+
save(record) {
|
|
288
|
+
const existing = this.data.get(record.id);
|
|
289
|
+
if (!existing && record.requestId && record.requesterPubkey) {
|
|
290
|
+
for (const prior of this.data.values()) {
|
|
291
|
+
if (prior.requestId === record.requestId && prior.requesterPubkey === record.requesterPubkey) {
|
|
292
|
+
return Promise.reject(new Error("duplicate caller request id"));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (existing && isTerminal(existing.status) && existing.status !== record.status) {
|
|
297
|
+
return Promise.resolve();
|
|
298
|
+
}
|
|
299
|
+
const snapshot = structuredClone(record);
|
|
300
|
+
if (existing?.receipt) snapshot.receipt = existing.receipt;
|
|
301
|
+
this.data.set(record.id, snapshot);
|
|
302
|
+
const cur = this.nextSeq.get(record.id) ?? 1;
|
|
303
|
+
this.nextSeq.set(record.id, Math.max(cur, record.seq + 1));
|
|
304
|
+
if (isTerminal(record.status)) {
|
|
305
|
+
this.messages.delete(record.id);
|
|
306
|
+
}
|
|
307
|
+
return Promise.resolve();
|
|
308
|
+
}
|
|
309
|
+
delete(id) {
|
|
310
|
+
this.data.delete(id);
|
|
311
|
+
this.messages.delete(id);
|
|
312
|
+
this.nextSeq.delete(id);
|
|
313
|
+
this.receiptSeqs.delete(id);
|
|
314
|
+
return Promise.resolve();
|
|
315
|
+
}
|
|
316
|
+
claimReceiptSeq(jobId) {
|
|
317
|
+
if (!this.data.has(jobId)) {
|
|
318
|
+
return Promise.reject(new Error(`claimReceiptSeq: job ${jobId} not found`));
|
|
319
|
+
}
|
|
320
|
+
const existing = this.receiptSeqs.get(jobId);
|
|
321
|
+
if (existing !== void 0) return Promise.resolve(existing);
|
|
322
|
+
this.receiptCounter += 1;
|
|
323
|
+
this.receiptSeqs.set(jobId, this.receiptCounter);
|
|
324
|
+
return Promise.resolve(this.receiptCounter);
|
|
325
|
+
}
|
|
326
|
+
saveReceipt(jobId, receipt) {
|
|
327
|
+
const record = this.data.get(jobId);
|
|
328
|
+
if (!record) return Promise.resolve(void 0);
|
|
329
|
+
record.receipt ??= structuredClone(receipt);
|
|
330
|
+
return Promise.resolve(structuredClone(record.receipt));
|
|
331
|
+
}
|
|
332
|
+
appendOutgoing(jobId, message, opts) {
|
|
333
|
+
const record = this.data.get(jobId);
|
|
334
|
+
if (!record) return Promise.reject(new Error(`appendOutgoing: job ${jobId} not found`));
|
|
335
|
+
const seq = this.allocateSeq(jobId);
|
|
336
|
+
const fullMessage = { ...message, seq };
|
|
337
|
+
this.messagesFor(jobId).push({ message: fullMessage, status: "verified" });
|
|
338
|
+
if (opts?.pendingPaymentDelta !== void 0) {
|
|
339
|
+
if (opts.pendingPaymentDelta !== 0) {
|
|
340
|
+
record.pendingPaymentMsats = (record.pendingPaymentMsats ?? 0) + opts.pendingPaymentDelta;
|
|
341
|
+
}
|
|
342
|
+
record.pendingMppChallengeIds = opts.pendingMppChallengeIds;
|
|
343
|
+
record.pendingX402Nonce = opts.pendingX402Nonce;
|
|
344
|
+
record.pendingX402AmountUsdcMicro = opts.pendingX402AmountUsdcMicro;
|
|
345
|
+
if (opts.pendingPaymentFiatMicro === void 0) {
|
|
346
|
+
record.pendingPaymentFiatMicro = void 0;
|
|
347
|
+
record.pendingPaymentFiatCurrency = void 0;
|
|
348
|
+
} else {
|
|
349
|
+
record.pendingPaymentFiatMicro = record.pendingPaymentFiatCurrency === opts.pendingPaymentFiatCurrency ? (record.pendingPaymentFiatMicro ?? 0) + opts.pendingPaymentFiatMicro : opts.pendingPaymentFiatMicro;
|
|
350
|
+
record.pendingPaymentFiatCurrency = opts.pendingPaymentFiatCurrency;
|
|
351
|
+
}
|
|
352
|
+
const asked = accumulateAskedTopUp(
|
|
353
|
+
{
|
|
354
|
+
micro: record.askedTopUpMicro,
|
|
355
|
+
currency: record.askedTopUpCurrency,
|
|
356
|
+
reason: record.topUpCapUnenforcedReason
|
|
357
|
+
},
|
|
358
|
+
{ micro: opts.pendingPaymentFiatMicro, currency: opts.pendingPaymentFiatCurrency }
|
|
359
|
+
);
|
|
360
|
+
record.askedTopUpMicro = asked.micro;
|
|
361
|
+
record.askedTopUpCurrency = asked.currency;
|
|
362
|
+
record.topUpCapUnenforcedReason = asked.reason;
|
|
363
|
+
}
|
|
364
|
+
record.lastActivityAt = Date.now();
|
|
365
|
+
if (seq > record.seq) record.seq = seq;
|
|
366
|
+
this.notify(jobId, fullMessage);
|
|
367
|
+
return Promise.resolve(seq);
|
|
368
|
+
}
|
|
369
|
+
recordInbound(jobId, message) {
|
|
370
|
+
const record = this.data.get(jobId);
|
|
371
|
+
if (!record) return Promise.reject(new Error(`recordInbound: job ${jobId} not found`));
|
|
372
|
+
const seq = this.allocateSeq(jobId);
|
|
373
|
+
const fullMessage = { ...message, seq };
|
|
374
|
+
this.messagesFor(jobId).push({ message: fullMessage, status: "pending-verification" });
|
|
375
|
+
record.lastActivityAt = Date.now();
|
|
376
|
+
if (seq > record.seq) record.seq = seq;
|
|
377
|
+
this.fireNotifyOnly(jobId);
|
|
378
|
+
return Promise.resolve(seq);
|
|
379
|
+
}
|
|
380
|
+
verifyAndCredit(jobId, seq, credit) {
|
|
381
|
+
const record = this.data.get(jobId);
|
|
382
|
+
if (!record) return Promise.reject(new Error(`verifyAndCredit: job ${jobId} not found`));
|
|
383
|
+
const stored = this.messagesFor(jobId).find((m) => m.message.seq === seq);
|
|
384
|
+
if (!stored)
|
|
385
|
+
return Promise.reject(new Error(`verifyAndCredit: message ${jobId}/${seq} not found`));
|
|
386
|
+
if (stored.status !== "pending-verification") {
|
|
387
|
+
return Promise.resolve({
|
|
388
|
+
counters: snapshotCounters(record),
|
|
389
|
+
alreadyVerified: true,
|
|
390
|
+
...creditBinding(record)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
stored.status = "verified";
|
|
394
|
+
if (credit) {
|
|
395
|
+
record.paidMsats += credit.paidMsatsDelta;
|
|
396
|
+
if (credit.clearPending || (record.pendingPaymentMsats ?? 0) <= credit.paidMsatsDelta) {
|
|
397
|
+
record.pendingPaymentFiatMicro = void 0;
|
|
398
|
+
record.pendingPaymentFiatCurrency = void 0;
|
|
399
|
+
}
|
|
400
|
+
if (credit.clearPending) {
|
|
401
|
+
record.pendingPaymentMsats = void 0;
|
|
402
|
+
} else {
|
|
403
|
+
record.pendingPaymentMsats = Math.max(
|
|
404
|
+
0,
|
|
405
|
+
(record.pendingPaymentMsats ?? 0) - credit.paidMsatsDelta
|
|
406
|
+
);
|
|
407
|
+
if (record.pendingPaymentMsats === 0) record.pendingPaymentMsats = void 0;
|
|
408
|
+
}
|
|
409
|
+
record.creditId ??= credit.creditId;
|
|
410
|
+
record.drawId ??= credit.drawId;
|
|
411
|
+
if (credit.paymentMint) record.paymentMint = credit.paymentMint;
|
|
412
|
+
if (credit.paymentRail) record.paymentRail = credit.paymentRail;
|
|
413
|
+
if (credit.paymentTxHash) record.paymentTxHash = credit.paymentTxHash;
|
|
414
|
+
if (credit.nativeAmount !== void 0) {
|
|
415
|
+
record.nativeAmount = credit.accumulateNative ? (record.nativeAmount ?? 0) + credit.nativeAmount : credit.nativeAmount;
|
|
416
|
+
}
|
|
417
|
+
if (credit.nativeAsset) record.nativeAsset = credit.nativeAsset;
|
|
418
|
+
if (credit.cashuFlow) record.cashuFlow = credit.cashuFlow;
|
|
419
|
+
if (credit.clearPendingX402Binding) {
|
|
420
|
+
record.pendingX402Nonce = void 0;
|
|
421
|
+
record.pendingX402AmountUsdcMicro = void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
record.lastActivityAt = Date.now();
|
|
425
|
+
this.notify(jobId, stored.message);
|
|
426
|
+
return Promise.resolve({
|
|
427
|
+
counters: snapshotCounters(record),
|
|
428
|
+
alreadyVerified: false,
|
|
429
|
+
...creditBinding(record)
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
getVerifiedInbound(jobId, seq) {
|
|
433
|
+
const record = this.data.get(jobId);
|
|
434
|
+
const stored = this.messagesFor(jobId).find((m) => m.message.seq === seq);
|
|
435
|
+
if (!record || stored?.status !== "verified") return Promise.resolve(void 0);
|
|
436
|
+
return Promise.resolve({
|
|
437
|
+
counters: snapshotCounters(record),
|
|
438
|
+
alreadyVerified: true,
|
|
439
|
+
...creditBinding(record)
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
markInboundFailed(jobId, seq, _reason) {
|
|
443
|
+
const stored = this.messagesFor(jobId).find((m) => m.message.seq === seq);
|
|
444
|
+
if (stored?.status === "pending-verification") {
|
|
445
|
+
stored.status = "failed-verify";
|
|
446
|
+
}
|
|
447
|
+
return Promise.resolve();
|
|
448
|
+
}
|
|
449
|
+
findStaleJobs(processingThresholdMs, awaitingThresholdMs, limit, capabilities) {
|
|
450
|
+
const capabilitySet = capabilities === null ? null : new Set(capabilities);
|
|
451
|
+
const matches = [];
|
|
452
|
+
for (const record of this.data.values()) {
|
|
453
|
+
const isProcessing = record.status === "processing" || record.status === "working";
|
|
454
|
+
const isAwaiting = record.status === "awaiting-input";
|
|
455
|
+
const eligibleStatus = isProcessing && record.lastActivityAt <= processingThresholdMs || isAwaiting && record.lastActivityAt <= awaitingThresholdMs;
|
|
456
|
+
const eligibleCapability = capabilitySet === null || capabilitySet.has(record.capability);
|
|
457
|
+
if (eligibleStatus && eligibleCapability) {
|
|
458
|
+
matches.push(structuredClone(record));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
matches.sort((a, b) => a.lastActivityAt - b.lastActivityAt);
|
|
462
|
+
return Promise.resolve(matches.slice(0, limit));
|
|
463
|
+
}
|
|
464
|
+
heartbeatActiveJobs(jobIds, now) {
|
|
465
|
+
for (const id of jobIds) {
|
|
466
|
+
const record = this.data.get(id);
|
|
467
|
+
if (record && (record.status === "processing" || record.status === "working")) {
|
|
468
|
+
record.lastActivityAt = now;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return Promise.resolve();
|
|
472
|
+
}
|
|
473
|
+
cancelStaleJob(jobId, expectedActivityBefore, reason, terminalStatus) {
|
|
474
|
+
return this.terminateJob(jobId, reason, terminalStatus, expectedActivityBefore);
|
|
475
|
+
}
|
|
476
|
+
cancelJob(jobId, reason) {
|
|
477
|
+
return this.terminateJob(jobId, reason, "cancelled", null);
|
|
478
|
+
}
|
|
479
|
+
getCounters(jobId) {
|
|
480
|
+
const record = this.data.get(jobId);
|
|
481
|
+
if (!record) return Promise.resolve(void 0);
|
|
482
|
+
return Promise.resolve(snapshotCounters(record));
|
|
483
|
+
}
|
|
484
|
+
claimForProcessing(jobId) {
|
|
485
|
+
const record = this.data.get(jobId);
|
|
486
|
+
if (record?.status !== "awaiting-input") return Promise.resolve(false);
|
|
487
|
+
record.status = "processing";
|
|
488
|
+
record.lastActivityAt = Date.now();
|
|
489
|
+
return Promise.resolve(true);
|
|
490
|
+
}
|
|
491
|
+
async tryReactivationLock(jobId, fn) {
|
|
492
|
+
if (this.reactivationLocks.has(jobId)) return { acquired: false };
|
|
493
|
+
this.reactivationLocks.add(jobId);
|
|
494
|
+
try {
|
|
495
|
+
const result = await fn();
|
|
496
|
+
return { acquired: true, result };
|
|
497
|
+
} finally {
|
|
498
|
+
this.reactivationLocks.delete(jobId);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async subscribeMessages(jobId, afterSeq, onMessage) {
|
|
502
|
+
const sub = { afterSeq, cb: onMessage };
|
|
503
|
+
let subs = this.messageSubscribers.get(jobId);
|
|
504
|
+
if (!subs) {
|
|
505
|
+
subs = /* @__PURE__ */ new Set();
|
|
506
|
+
this.messageSubscribers.set(jobId, subs);
|
|
507
|
+
}
|
|
508
|
+
subs.add(sub);
|
|
509
|
+
const existing = await this.getMessages(jobId, afterSeq);
|
|
510
|
+
for (const msg of existing) {
|
|
511
|
+
if (msg.seq > sub.afterSeq) {
|
|
512
|
+
sub.afterSeq = msg.seq;
|
|
513
|
+
onMessage(msg);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const subSet = subs;
|
|
517
|
+
return () => {
|
|
518
|
+
subSet.delete(sub);
|
|
519
|
+
if (subSet.size === 0) this.messageSubscribers.delete(jobId);
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
subscribeNotifications(jobId, onNotify) {
|
|
523
|
+
let subs = this.notifySubscribers.get(jobId);
|
|
524
|
+
if (!subs) {
|
|
525
|
+
subs = /* @__PURE__ */ new Set();
|
|
526
|
+
this.notifySubscribers.set(jobId, subs);
|
|
527
|
+
}
|
|
528
|
+
subs.add(onNotify);
|
|
529
|
+
const subSet = subs;
|
|
530
|
+
return Promise.resolve(() => {
|
|
531
|
+
subSet.delete(onNotify);
|
|
532
|
+
if (subSet.size === 0) this.notifySubscribers.delete(jobId);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
getMessages(jobId, afterSeq) {
|
|
536
|
+
const stored = this.messagesFor(jobId);
|
|
537
|
+
return Promise.resolve(
|
|
538
|
+
stored.filter((m) => m.status === "verified" && m.message.seq > afterSeq).map((m) => m.message)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
initStreaming() {
|
|
542
|
+
return Promise.resolve();
|
|
543
|
+
}
|
|
544
|
+
shutdownStreaming() {
|
|
545
|
+
this.messageSubscribers.clear();
|
|
546
|
+
this.notifySubscribers.clear();
|
|
547
|
+
return Promise.resolve();
|
|
548
|
+
}
|
|
549
|
+
// ── Internal helpers ──────────────────────────────────────────────────
|
|
550
|
+
/**
|
|
551
|
+
* Shared body of `cancelStaleJob` (activity cutoff supplied) and `cancelJob`
|
|
552
|
+
* (`null` cutoff — unconditional). Mirrors `PostgresJobStore.terminateJob`:
|
|
553
|
+
* the terminal status and the final `cancel` message land together and the
|
|
554
|
+
* notify fires before the incremental log is cleaned up.
|
|
555
|
+
*/
|
|
556
|
+
terminateJob(jobId, reason, terminalStatus, expectedActivityBefore) {
|
|
557
|
+
const record = this.data.get(jobId);
|
|
558
|
+
if (!record) return Promise.resolve(false);
|
|
559
|
+
const eligible = (record.status === "processing" || record.status === "working" || record.status === "awaiting-input") && (expectedActivityBefore === null || record.lastActivityAt <= expectedActivityBefore);
|
|
560
|
+
if (!eligible) return Promise.resolve(false);
|
|
561
|
+
const seq = this.allocateSeq(jobId);
|
|
562
|
+
const cancelMsg = {
|
|
563
|
+
seq,
|
|
564
|
+
from: "provider",
|
|
565
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
566
|
+
type: "cancel",
|
|
567
|
+
content: { reason }
|
|
568
|
+
};
|
|
569
|
+
this.messagesFor(jobId).push({ message: cancelMsg, status: "verified" });
|
|
570
|
+
record.status = terminalStatus;
|
|
571
|
+
record.summary = record.summary ?? reason;
|
|
572
|
+
record.messages = [...record.messages, cancelMsg];
|
|
573
|
+
record.seq = seq;
|
|
574
|
+
record.lastActivityAt = Date.now();
|
|
575
|
+
this.notify(jobId, cancelMsg);
|
|
576
|
+
this.messages.delete(jobId);
|
|
577
|
+
return Promise.resolve(true);
|
|
578
|
+
}
|
|
579
|
+
allocateSeq(jobId) {
|
|
580
|
+
const seq = this.nextSeq.get(jobId) ?? 1;
|
|
581
|
+
this.nextSeq.set(jobId, seq + 1);
|
|
582
|
+
return seq;
|
|
583
|
+
}
|
|
584
|
+
messagesFor(jobId) {
|
|
585
|
+
let list = this.messages.get(jobId);
|
|
586
|
+
if (!list) {
|
|
587
|
+
list = [];
|
|
588
|
+
this.messages.set(jobId, list);
|
|
589
|
+
}
|
|
590
|
+
return list;
|
|
591
|
+
}
|
|
592
|
+
notify(jobId, message) {
|
|
593
|
+
const subs = this.messageSubscribers.get(jobId);
|
|
594
|
+
if (subs) {
|
|
595
|
+
for (const sub of subs) {
|
|
596
|
+
if (message.seq > sub.afterSeq) {
|
|
597
|
+
sub.afterSeq = message.seq;
|
|
598
|
+
sub.cb(message);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
this.fireNotifyOnly(jobId);
|
|
603
|
+
}
|
|
604
|
+
fireNotifyOnly(jobId) {
|
|
605
|
+
const subs = this.notifySubscribers.get(jobId);
|
|
606
|
+
if (subs) for (const cb of subs) cb();
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
function requestIdClaimKey(requesterPubkey, requestId) {
|
|
610
|
+
return `${requesterPubkey}\0${requestId}`;
|
|
611
|
+
}
|
|
612
|
+
function creditBinding(record) {
|
|
613
|
+
return {
|
|
614
|
+
...record.creditId !== void 0 && { creditId: record.creditId },
|
|
615
|
+
...record.drawId !== void 0 && { drawId: record.drawId }
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function snapshotCounters(record) {
|
|
619
|
+
return {
|
|
620
|
+
status: record.status,
|
|
621
|
+
paidMsats: record.paidMsats,
|
|
622
|
+
pendingPaymentMsats: record.pendingPaymentMsats,
|
|
623
|
+
summary: record.summary
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/sdk/logger.ts
|
|
628
|
+
function createConsoleLogger(jobId) {
|
|
629
|
+
const fmt = (message, data) => {
|
|
630
|
+
const parts = [`[${jobId}] ${message}`];
|
|
631
|
+
if (data) parts.push(data);
|
|
632
|
+
return parts;
|
|
633
|
+
};
|
|
634
|
+
return {
|
|
635
|
+
debug(message, data) {
|
|
636
|
+
console.debug(...fmt(message, data));
|
|
637
|
+
},
|
|
638
|
+
info(message, data) {
|
|
639
|
+
console.info(...fmt(message, data));
|
|
640
|
+
},
|
|
641
|
+
warn(message, data) {
|
|
642
|
+
console.warn(...fmt(message, data));
|
|
643
|
+
},
|
|
644
|
+
error(message, data) {
|
|
645
|
+
console.error(...fmt(message, data));
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function createNoopLogger() {
|
|
650
|
+
const noop2 = () => {
|
|
651
|
+
};
|
|
652
|
+
return { debug: noop2, info: noop2, warn: noop2, error: noop2 };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/observability/context.ts
|
|
656
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
657
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
658
|
+
var storage = new AsyncLocalStorage();
|
|
659
|
+
var defaultService = "unknown";
|
|
660
|
+
function setDefaultService(service) {
|
|
661
|
+
defaultService = service;
|
|
662
|
+
}
|
|
663
|
+
function randomTraceId() {
|
|
664
|
+
const buf = randomBytes(16);
|
|
665
|
+
const now = Date.now();
|
|
666
|
+
buf.writeUInt16BE(Math.floor(now / 4294967296), 0);
|
|
667
|
+
buf.writeUInt32BE(now % 4294967296, 2);
|
|
668
|
+
buf[6] = buf[6] & 15 | 112;
|
|
669
|
+
buf[8] = buf[8] & 63 | 128;
|
|
670
|
+
return buf.toString("hex");
|
|
671
|
+
}
|
|
672
|
+
function randomSpanId() {
|
|
673
|
+
return randomUUID().replace(/-/g, "").slice(0, 16);
|
|
674
|
+
}
|
|
675
|
+
function withTraceContext(ctx, fn) {
|
|
676
|
+
return storage.run(ctx, fn);
|
|
677
|
+
}
|
|
678
|
+
function currentContext(service) {
|
|
679
|
+
const ctx = storage.getStore();
|
|
680
|
+
if (ctx) return ctx;
|
|
681
|
+
return {
|
|
682
|
+
traceId: randomTraceId(),
|
|
683
|
+
spanId: null,
|
|
684
|
+
dimensions: {},
|
|
685
|
+
service: service ?? defaultService
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function pinDimension(key, value) {
|
|
689
|
+
const ctx = storage.getStore();
|
|
690
|
+
if (ctx) {
|
|
691
|
+
ctx.dimensions = { ...ctx.dimensions, [key]: value };
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function rawContext() {
|
|
695
|
+
return storage.getStore();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/observability/datasets.ts
|
|
699
|
+
var SPANS_DATASETS = [
|
|
700
|
+
{ name: "platform", retentionDays: 90 },
|
|
701
|
+
{ name: "cashu", retentionDays: 90 },
|
|
702
|
+
{ name: "lightning", retentionDays: 90 },
|
|
703
|
+
{ name: "stripe", retentionDays: 90 },
|
|
704
|
+
{ name: "x402", retentionDays: 90 },
|
|
705
|
+
{ name: "tempo", retentionDays: 90 },
|
|
706
|
+
{ name: "isolate", retentionDays: 90 },
|
|
707
|
+
{ name: "audit", retentionDays: 365 },
|
|
708
|
+
{ name: "cron", retentionDays: 30 }
|
|
709
|
+
];
|
|
710
|
+
var DATASET_NAMES = new Set(SPANS_DATASETS.map((d) => d.name));
|
|
711
|
+
|
|
712
|
+
// src/observability/with-span.ts
|
|
713
|
+
import { hostname } from "os";
|
|
714
|
+
|
|
715
|
+
// src/observability/stdout-sink.ts
|
|
716
|
+
var SENSITIVE_KEY_RE = /secret|token|password|private|bearer|seed|preimage|api[_-]?key|signing[_-]?key|priv[_-]?key/i;
|
|
717
|
+
var isTTY = typeof process.stdout.isTTY === "boolean" && process.stdout.isTTY;
|
|
718
|
+
var RED = isTTY ? "\x1B[31m" : "";
|
|
719
|
+
var YELLOW = isTTY ? "\x1B[33m" : "";
|
|
720
|
+
var RESET = isTTY ? "\x1B[0m" : "";
|
|
721
|
+
function formatSpanLine(span) {
|
|
722
|
+
const ts = new Date(span.started_at_ms).toISOString();
|
|
723
|
+
const levelStr = colorLevel(span.level);
|
|
724
|
+
const parts = [`[${ts}]`, span.dataset, `${levelStr}:`, span.name];
|
|
725
|
+
if (span.trace_id) {
|
|
726
|
+
parts.push(`trace=${span.trace_id}`);
|
|
727
|
+
}
|
|
728
|
+
for (const [k, v] of Object.entries(span.dimensions)) {
|
|
729
|
+
parts.push(formatKV(k, v));
|
|
730
|
+
}
|
|
731
|
+
for (const [k, v] of Object.entries(span.attributes)) {
|
|
732
|
+
parts.push(formatKV(k, v));
|
|
733
|
+
}
|
|
734
|
+
const durationMs = span.ended_at_ms - span.started_at_ms;
|
|
735
|
+
if (durationMs > 0) {
|
|
736
|
+
parts.push(`duration=${durationMs}ms`);
|
|
737
|
+
}
|
|
738
|
+
if (span.error_message) {
|
|
739
|
+
parts.push(`error=${truncate(span.error_message, 120)}`);
|
|
740
|
+
}
|
|
741
|
+
return parts.join(" ");
|
|
742
|
+
}
|
|
743
|
+
function writeSpanLine(span) {
|
|
744
|
+
const line = formatSpanLine(span);
|
|
745
|
+
process.stdout.write(line + "\n");
|
|
746
|
+
}
|
|
747
|
+
function colorLevel(level) {
|
|
748
|
+
if (level === "error") return `${RED}error${RESET}`;
|
|
749
|
+
if (level === "warn") return `${YELLOW}warn${RESET}`;
|
|
750
|
+
return level;
|
|
751
|
+
}
|
|
752
|
+
function formatKV(key, value) {
|
|
753
|
+
if (SENSITIVE_KEY_RE.test(key)) return `${key}=[REDACTED]`;
|
|
754
|
+
return `${key}=${truncate(stringify(value), 80)}`;
|
|
755
|
+
}
|
|
756
|
+
function stringify(value) {
|
|
757
|
+
if (typeof value === "string") return value;
|
|
758
|
+
if (value === void 0) return "undefined";
|
|
759
|
+
if (value === null) return "null";
|
|
760
|
+
if (typeof value === "function") return "[function]";
|
|
761
|
+
if (typeof value === "symbol") return value.toString();
|
|
762
|
+
if (typeof value === "bigint") return value.toString();
|
|
763
|
+
return JSON.stringify(value);
|
|
764
|
+
}
|
|
765
|
+
function truncate(s, max) {
|
|
766
|
+
if (s.length <= max) return s;
|
|
767
|
+
return s.slice(0, max - 3) + "...";
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/observability/with-span.ts
|
|
771
|
+
var emitter = null;
|
|
772
|
+
function setEmitter(fn) {
|
|
773
|
+
emitter = fn;
|
|
774
|
+
}
|
|
775
|
+
var cachedHost = hostname();
|
|
776
|
+
var serviceVersion = process.env.GIT_SHA ?? process.env.FLY_IMAGE_REF ?? "dev";
|
|
777
|
+
async function withSpan(opts, fn) {
|
|
778
|
+
const spanId = randomSpanId();
|
|
779
|
+
const existingCtx = rawContext();
|
|
780
|
+
const ctx = existingCtx ?? currentContext();
|
|
781
|
+
const startMs = opts.startMs ?? Date.now();
|
|
782
|
+
let status = "ok";
|
|
783
|
+
let level = opts.level;
|
|
784
|
+
let errorMessage = null;
|
|
785
|
+
let errorStack = null;
|
|
786
|
+
if (opts.err) {
|
|
787
|
+
status = "error";
|
|
788
|
+
errorMessage = opts.err.message;
|
|
789
|
+
errorStack = opts.err.stack ?? null;
|
|
790
|
+
}
|
|
791
|
+
const childCtx = {
|
|
792
|
+
traceId: ctx.traceId,
|
|
793
|
+
spanId,
|
|
794
|
+
dimensions: { ...ctx.dimensions, ...opts.dims },
|
|
795
|
+
service: ctx.service,
|
|
796
|
+
tracestate: ctx.tracestate
|
|
797
|
+
};
|
|
798
|
+
const emitRow = () => {
|
|
799
|
+
const endMs = opts.endMs ?? Date.now();
|
|
800
|
+
const row = {
|
|
801
|
+
span_id: spanId,
|
|
802
|
+
trace_id: ctx.traceId,
|
|
803
|
+
parent_span_id: ctx.spanId,
|
|
804
|
+
dataset: opts.dataset,
|
|
805
|
+
name: opts.name,
|
|
806
|
+
level: level ?? (status === "error" ? "error" : "info"),
|
|
807
|
+
status,
|
|
808
|
+
started_at_ms: startMs,
|
|
809
|
+
ended_at_ms: endMs,
|
|
810
|
+
service: ctx.service,
|
|
811
|
+
service_version: serviceVersion,
|
|
812
|
+
host: cachedHost,
|
|
813
|
+
pid: process.pid,
|
|
814
|
+
dimensions: childCtx.dimensions,
|
|
815
|
+
attributes: opts.attrs ?? {},
|
|
816
|
+
error_message: errorMessage,
|
|
817
|
+
error_stack: errorStack,
|
|
818
|
+
sample_rate: 1
|
|
819
|
+
};
|
|
820
|
+
writeSpanLine(row);
|
|
821
|
+
if (emitter) emitter(row);
|
|
822
|
+
};
|
|
823
|
+
try {
|
|
824
|
+
const result = await withTraceContext(childCtx, () => fn());
|
|
825
|
+
if (opts.inspect) {
|
|
826
|
+
const overrides = opts.inspect(result);
|
|
827
|
+
if (overrides) {
|
|
828
|
+
if (overrides.status) status = overrides.status;
|
|
829
|
+
if (overrides.level) level = overrides.level;
|
|
830
|
+
if (overrides.errorMessage !== void 0) errorMessage = overrides.errorMessage;
|
|
831
|
+
if (overrides.attrs) Object.assign(opts.attrs ??= {}, overrides.attrs);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
emitRow();
|
|
835
|
+
return result;
|
|
836
|
+
} catch (err) {
|
|
837
|
+
status = "error";
|
|
838
|
+
if (err instanceof Error) {
|
|
839
|
+
errorMessage = err.message;
|
|
840
|
+
errorStack = err.stack ?? null;
|
|
841
|
+
} else {
|
|
842
|
+
errorMessage = String(err);
|
|
843
|
+
}
|
|
844
|
+
if (opts.onError) {
|
|
845
|
+
const overrides = opts.onError(err);
|
|
846
|
+
if (overrides) Object.assign(opts.attrs ??= {}, overrides);
|
|
847
|
+
}
|
|
848
|
+
emitRow();
|
|
849
|
+
throw err;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/observability/loggers.ts
|
|
854
|
+
var noop = () => {
|
|
855
|
+
};
|
|
856
|
+
var loggers = buildNoopLoggers();
|
|
857
|
+
function initLoggers(writer, service) {
|
|
858
|
+
setEmitter((row) => {
|
|
859
|
+
writer.enqueue(row);
|
|
860
|
+
});
|
|
861
|
+
setDefaultService(service);
|
|
862
|
+
const result = {};
|
|
863
|
+
for (const ds of SPANS_DATASETS) {
|
|
864
|
+
result[ds.name] = createLogger(ds.name, {});
|
|
865
|
+
}
|
|
866
|
+
loggers = result;
|
|
867
|
+
initCallerLoggers(result);
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
function createLogger(dataset, bakedDims) {
|
|
871
|
+
const logger = {
|
|
872
|
+
info(name, attrs) {
|
|
873
|
+
void withSpan({ dataset, name, level: "info", attrs, dims: bakedDims }, noop).catch(noop);
|
|
874
|
+
},
|
|
875
|
+
warn(name, attrs) {
|
|
876
|
+
void withSpan({ dataset, name, level: "warn", attrs, dims: bakedDims }, noop).catch(noop);
|
|
877
|
+
},
|
|
878
|
+
error(name, attrs) {
|
|
879
|
+
const err = attrs?.err instanceof Error ? attrs.err : void 0;
|
|
880
|
+
const cleanAttrs = attrs ? { ...attrs } : void 0;
|
|
881
|
+
if (cleanAttrs) delete cleanAttrs.err;
|
|
882
|
+
void withSpan(
|
|
883
|
+
{ dataset, name, level: "error", attrs: cleanAttrs, dims: bakedDims, err },
|
|
884
|
+
noop
|
|
885
|
+
).catch(noop);
|
|
886
|
+
},
|
|
887
|
+
async span(name, attrs, fn, opts) {
|
|
888
|
+
return withSpan({ dataset, name, attrs, dims: bakedDims, onError: opts?.onError }, fn);
|
|
889
|
+
},
|
|
890
|
+
timer(name, attrs) {
|
|
891
|
+
const startMs = Date.now();
|
|
892
|
+
return {
|
|
893
|
+
[Symbol.dispose]() {
|
|
894
|
+
const endMs = Date.now();
|
|
895
|
+
void withSpan({ dataset, name, attrs, dims: bakedDims, startMs, endMs }, noop).catch(
|
|
896
|
+
noop
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
},
|
|
901
|
+
set(dims) {
|
|
902
|
+
for (const [k, v] of Object.entries(dims)) {
|
|
903
|
+
pinDimension(k, v);
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
extend(dims) {
|
|
907
|
+
return createLogger(dataset, { ...bakedDims, ...dims });
|
|
908
|
+
},
|
|
909
|
+
tracked(name, fn) {
|
|
910
|
+
return ((...args) => logger.span(name, {}, () => fn(...args)));
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
return logger;
|
|
914
|
+
}
|
|
915
|
+
function buildNoopLoggers() {
|
|
916
|
+
const noopLogger = {
|
|
917
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
918
|
+
info() {
|
|
919
|
+
},
|
|
920
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
921
|
+
warn() {
|
|
922
|
+
},
|
|
923
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
924
|
+
error() {
|
|
925
|
+
},
|
|
926
|
+
async span(_name, _attrs, fn, _opts) {
|
|
927
|
+
return fn();
|
|
928
|
+
},
|
|
929
|
+
timer() {
|
|
930
|
+
return { [Symbol.dispose]() {
|
|
931
|
+
} };
|
|
932
|
+
},
|
|
933
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
934
|
+
set() {
|
|
935
|
+
},
|
|
936
|
+
extend() {
|
|
937
|
+
return noopLogger;
|
|
938
|
+
},
|
|
939
|
+
tracked(_name, fn) {
|
|
940
|
+
return fn;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
const result = {};
|
|
944
|
+
for (const ds of SPANS_DATASETS) {
|
|
945
|
+
result[ds.name] = noopLogger;
|
|
946
|
+
}
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/sdk/server/instrumented-fetch.ts
|
|
951
|
+
function createInstrumentedFetch(signal) {
|
|
952
|
+
return async (input, init) => {
|
|
953
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
954
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
955
|
+
const requestSignal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
956
|
+
const merged = mergeSignals(signal, requestSignal);
|
|
957
|
+
return loggers.isolate.span(
|
|
958
|
+
"dvm.fetch",
|
|
959
|
+
{ url: redactUrl(url), method: method.toUpperCase() },
|
|
960
|
+
async () => {
|
|
961
|
+
const response = await globalThis.fetch(input, merged ? { ...init, signal: merged } : init);
|
|
962
|
+
return response;
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function mergeSignals(a, b) {
|
|
968
|
+
if (!a) return b ?? void 0;
|
|
969
|
+
if (!b) return a;
|
|
970
|
+
return AbortSignal.any([a, b]);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/sdk/store.ts
|
|
974
|
+
var MemoryKVStore = class {
|
|
975
|
+
data = /* @__PURE__ */ new Map();
|
|
976
|
+
/** Retrieve a value by key. Returns undefined if not found or expired. */
|
|
977
|
+
get(key) {
|
|
978
|
+
const entry = this.data.get(key);
|
|
979
|
+
if (!entry) return Promise.resolve(void 0);
|
|
980
|
+
if (entry.expiry !== void 0 && Date.now() > entry.expiry) {
|
|
981
|
+
this.data.delete(key);
|
|
982
|
+
return Promise.resolve(void 0);
|
|
983
|
+
}
|
|
984
|
+
return Promise.resolve(entry.value);
|
|
985
|
+
}
|
|
986
|
+
/** Store a value. Optionally set a TTL in seconds. */
|
|
987
|
+
set(key, value, opts) {
|
|
988
|
+
const expiry = opts?.ttl !== void 0 ? Date.now() + opts.ttl * 1e3 : void 0;
|
|
989
|
+
this.data.set(key, { value, expiry });
|
|
990
|
+
return Promise.resolve();
|
|
991
|
+
}
|
|
992
|
+
/** Delete a key. */
|
|
993
|
+
delete(key) {
|
|
994
|
+
this.data.delete(key);
|
|
995
|
+
return Promise.resolve();
|
|
996
|
+
}
|
|
997
|
+
/** List keys, optionally filtered by prefix. Excludes expired entries. */
|
|
998
|
+
list(prefix) {
|
|
999
|
+
const now = Date.now();
|
|
1000
|
+
const keys = [];
|
|
1001
|
+
for (const [key, entry] of this.data) {
|
|
1002
|
+
if (entry.expiry !== void 0 && now > entry.expiry) {
|
|
1003
|
+
this.data.delete(key);
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (prefix === void 0 || key.startsWith(prefix)) {
|
|
1007
|
+
keys.push(key);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return Promise.resolve(keys);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
export {
|
|
1015
|
+
StepCache,
|
|
1016
|
+
toJobRecord,
|
|
1017
|
+
fromJobRecord,
|
|
1018
|
+
abortJob,
|
|
1019
|
+
JobCancelledError,
|
|
1020
|
+
providerMessage,
|
|
1021
|
+
localMessage,
|
|
1022
|
+
isTerminal,
|
|
1023
|
+
isYieldMessage,
|
|
1024
|
+
accumulateAskedTopUp,
|
|
1025
|
+
UNCAPPABLE_ASK_TOTAL,
|
|
1026
|
+
isReceiptIssuingStore,
|
|
1027
|
+
isStreamableJobStore,
|
|
1028
|
+
MemoryJobStore,
|
|
1029
|
+
createConsoleLogger,
|
|
1030
|
+
createNoopLogger,
|
|
1031
|
+
withTraceContext,
|
|
1032
|
+
pinDimension,
|
|
1033
|
+
withSpan,
|
|
1034
|
+
loggers,
|
|
1035
|
+
initLoggers,
|
|
1036
|
+
createInstrumentedFetch,
|
|
1037
|
+
MemoryKVStore
|
|
1038
|
+
};
|