@dvmkit/sdk 0.14.1 → 0.15.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.
Files changed (53) hide show
  1. package/dist/{chunk-ITRSITFP.js → chunk-2I6NXIPG.js} +1 -1
  2. package/dist/{chunk-TKJBUFYY.js → chunk-33J3NQDZ.js} +1 -1
  3. package/dist/{chunk-7KQLPVV7.js → chunk-7NZRJPEP.js} +1 -1
  4. package/dist/{chunk-Z4FOPROB.js → chunk-BAHZK334.js} +2 -0
  5. package/dist/{chunk-D4FFS3IS.js → chunk-CRH6BMMG.js} +1 -1
  6. package/dist/{chunk-LDKV4UHI.js → chunk-CVT5LTAK.js} +1 -1
  7. package/dist/{chunk-EP7OVWI5.js → chunk-FOXGN4KQ.js} +1 -1
  8. package/dist/{chunk-VWIMQ5H5.js → chunk-G47UY3BK.js} +1 -1
  9. package/dist/{chunk-JLT2YIBS.js → chunk-GB2N4NG5.js} +2 -2
  10. package/dist/{chunk-E7ZBCT5U.js → chunk-I2H2XYQV.js} +1 -1
  11. package/dist/{chunk-3YFFZLW7.js → chunk-IUODXRQN.js} +2 -2
  12. package/dist/{chunk-566CFXGU.js → chunk-IVW5UMD3.js} +1 -1
  13. package/dist/{chunk-AFV77S4C.js → chunk-KVKJ7R5V.js} +81 -2
  14. package/dist/{chunk-A2YESDQM.js → chunk-LM6E4YHQ.js} +2 -2
  15. package/dist/{chunk-5JSPTVIK.js → chunk-LZFZGY7L.js} +1 -1
  16. package/dist/{chunk-FFRTAQUW.js → chunk-P5KQKLWL.js} +1 -1
  17. package/dist/{chunk-KJZVTWMX.js → chunk-PKLNMMKF.js} +52 -15
  18. package/dist/{chunk-M4NFYEEY.js → chunk-QRYGBKCR.js} +1 -1
  19. package/dist/{chunk-HNXMWHLU.js → chunk-R7FH7SHX.js} +1 -1
  20. package/dist/{chunk-NS2OMIU7.js → chunk-RQK7OFJO.js} +1 -1
  21. package/dist/{chunk-TWKQFIOW.js → chunk-YMMCRQ2H.js} +84 -14
  22. package/dist/{chunk-JFH34P3M.js → chunk-ZJRGORJ3.js} +359 -169
  23. package/dist/{chunk-HULU2UVM.js → chunk-ZO2ZAPG3.js} +1 -1
  24. package/dist/{content-retention-BcHNpP2F.d.ts → content-retention-BASsjgKw.d.ts} +1 -1
  25. package/dist/{credit-ledger-D3UUR2GW.js → credit-ledger-EZDZYLSK.js} +3 -3
  26. package/dist/{credit-menu-C-kmf61K.d.ts → credit-menu-B239nIY5.d.ts} +23 -9
  27. package/dist/{fx-hFlF4Cn8.d.ts → fx-BmvWGISH.d.ts} +1 -1
  28. package/dist/index.d.ts +6 -5
  29. package/dist/index.js +12 -5
  30. package/dist/internal/caller.d.ts +11 -7
  31. package/dist/internal/caller.js +7 -7
  32. package/dist/internal/server.d.ts +7 -7
  33. package/dist/internal/server.js +21 -21
  34. package/dist/{job-store-DVCATwhU.d.ts → job-store-CA7u1nHV.d.ts} +22 -4
  35. package/dist/{lightning-backend-DXNVlVs9.d.ts → lightning-backend-BOcopzXk.d.ts} +1 -1
  36. package/dist/{memory-credit-ledger-SCXQO4HZ.js → memory-credit-ledger-REZGHPJP.js} +4 -4
  37. package/dist/{mpp-setup-JU3MT7QX.js → mpp-setup-RG5JDMIO.js} +3 -3
  38. package/dist/{payout-reporter-NXWQVKXW.js → payout-reporter-DDQ7FGRC.js} +2 -2
  39. package/dist/{postgres-consumed-credential-store-WXJLM6J7.js → postgres-consumed-credential-store-I4WTNVQZ.js} +1 -1
  40. package/dist/{postgres-job-store-ZVMAN6L2.js → postgres-job-store-IXTNWT4K.js} +3 -3
  41. package/dist/{postgres-replay-store-QJ4FG6AR.js → postgres-replay-store-3F5FCUXN.js} +2 -2
  42. package/dist/{pricing-KCLENT2C.js → pricing-HLYEGPXD.js} +3 -3
  43. package/dist/{revenue-reporter-HD4GMXGD.js → revenue-reporter-RIEHBKRA.js} +2 -2
  44. package/dist/server/index.d.ts +15 -12
  45. package/dist/server/index.js +28 -28
  46. package/dist/{step-cache-CoORQQ_Y.d.ts → step-cache-BCRHimiY.d.ts} +48 -9
  47. package/dist/{tempo-charge-store-7LTUAQHQ.js → tempo-charge-store-B5SYDCBK.js} +2 -2
  48. package/dist/{tempo-session-store-LYAIDR2B.js → tempo-session-store-FHW7N22I.js} +3 -3
  49. package/dist/testing/index.d.ts +7 -4
  50. package/dist/testing/index.js +7 -7
  51. package/dist/{usd-CeXGiyTC.d.ts → usd-B8_WMfHT.d.ts} +1 -1
  52. package/dist/{x402-RUJ4LUKF.js → x402-AY6P5KV4.js} +3 -3
  53. package/package.json +1 -1
@@ -1,13 +1,15 @@
1
1
  import {
2
2
  ContentHoldError,
3
- applyContentHoldMutation
4
- } from "./chunk-AFV77S4C.js";
3
+ applyContentHoldMutation,
4
+ callerDeadline,
5
+ hasCallerDeadline
6
+ } from "./chunk-KVKJ7R5V.js";
5
7
  import {
6
8
  withSdkInitLock
7
9
  } from "./chunk-COYKJRMM.js";
8
10
  import {
9
11
  diagnosticConsole
10
- } from "./chunk-Z4FOPROB.js";
12
+ } from "./chunk-BAHZK334.js";
11
13
 
12
14
  // src/sdk/server/postgres-job-store.ts
13
15
  import { createHash } from "crypto";
@@ -261,6 +263,7 @@ var PostgresJobStore = class {
261
263
  -- with a fund-and-draw response. INSERT-only for replay stability.
262
264
  ALTER TABLE jobs ADD COLUMN IF NOT EXISTS funding_receipt JSONB;
263
265
  ALTER TABLE jobs ADD COLUMN IF NOT EXISTS funding_credit JSONB;
266
+ ALTER TABLE jobs ADD COLUMN IF NOT EXISTS upfront_payment JSONB;
264
267
  -- the outstanding mid-job ask, denominated in fiat micro at the
265
268
  -- instant it was emitted. The top-up's ledger leg funds and grows the
266
269
  -- draw in this figure, so it must not move with the BTC/USD rate between
@@ -490,6 +493,18 @@ var PostgresJobStore = class {
490
493
  }
491
494
  held.client.release();
492
495
  }
496
+ async saveStep(jobId, record) {
497
+ const result = await this.pool.query(
498
+ `UPDATE jobs SET step_cache = CASE
499
+ WHEN EXISTS (SELECT 1 FROM jsonb_array_elements(step_cache) AS step
500
+ WHERE step->>'id' = $2::jsonb->>'id') THEN step_cache
501
+ ELSE step_cache || jsonb_build_array($2::jsonb) END
502
+ WHERE id = $1 AND status NOT IN ('completed', 'failed', 'cancelled')
503
+ AND redacted_at IS NULL`,
504
+ [jobId, JSON.stringify(record)]
505
+ );
506
+ if (result.rowCount !== 1) throw new Error("Step persistence requires a live job");
507
+ }
493
508
  async save(record) {
494
509
  const dvmId = record[JOB_DVM_ID];
495
510
  for (let attempt = 0; ; attempt++) {
@@ -536,8 +551,8 @@ var PostgresJobStore = class {
536
551
  last_activity_at, next_seq, requester_token_hash, requester_token,
537
552
  request_fingerprint, requester_pubkey, request_id, credit_id, draw_id,
538
553
  pending_payment_fiat_micro, pending_payment_fiat_currency, auth_request_path,
539
- funding_receipt, funding_credit, dvm_id, terminal_at_ms, caller_wait_ms, awaiting_input_at_ms, ended_by, timing_known
540
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$51)
554
+ funding_receipt, funding_credit, dvm_id, terminal_at_ms, caller_wait_ms, awaiting_input_at_ms, ended_by, timing_known, upfront_payment
555
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$51,$52)
541
556
  ON CONFLICT (id) DO UPDATE SET
542
557
  terminal_at_ms = COALESCE(jobs.terminal_at_ms, EXCLUDED.terminal_at_ms),
543
558
  ended_by = CASE WHEN jobs.status IN ('completed', 'failed', 'cancelled') THEN jobs.ended_by ELSE EXCLUDED.ended_by END,
@@ -588,7 +603,17 @@ var PostgresJobStore = class {
588
603
  pending_x402_nonce = EXCLUDED.pending_x402_nonce,
589
604
  pending_x402_amount_usdc_micro = EXCLUDED.pending_x402_amount_usdc_micro,
590
605
  required_msats = EXCLUDED.required_msats,
591
- step_cache = EXCLUDED.step_cache,
606
+ -- A stale snapshot must not erase a step committed by saveStep.
607
+ -- First stored result wins for each id, matching cached replay.
608
+ step_cache = (
609
+ SELECT COALESCE(jsonb_agg(entry ORDER BY position), '[]'::jsonb)
610
+ FROM (
611
+ SELECT DISTINCT ON (entry->>'id') entry, position
612
+ FROM jsonb_array_elements(jobs.step_cache || EXCLUDED.step_cache)
613
+ WITH ORDINALITY AS steps(entry, position)
614
+ ORDER BY entry->>'id', position
615
+ ) AS merged_steps
616
+ ),
592
617
  state = EXCLUDED.state,
593
618
  last_activity_at = CASE WHEN jobs.status IN ('completed', 'failed', 'cancelled') THEN jobs.last_activity_at ELSE EXCLUDED.last_activity_at END
594
619
  -- terminal is terminal. On a multi-machine DVM a DELETE can
@@ -666,7 +691,8 @@ var PostgresJobStore = class {
666
691
  record.status === "awaiting-input" ? record.awaitingInputAtMs ?? now : null,
667
692
  isTerminalStatus(record.status) && record.status !== "completed" ? record.endedBy ?? "provider" : null,
668
693
  now,
669
- record.timingKnown ?? true
694
+ record.timingKnown ?? true,
695
+ record.upfrontPayment ? JSON.stringify(record.upfrontPayment) : null
670
696
  ]
671
697
  );
672
698
  let rowCount;
@@ -705,6 +731,7 @@ var PostgresJobStore = class {
705
731
  return;
706
732
  }
707
733
  if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") {
734
+ await this.pool.query("SELECT pg_notify('job_messages', $1)", [record.id]).catch(() => void 0);
708
735
  await this.deleteResolvedMessages(record.id).catch(() => void 0);
709
736
  }
710
737
  }
@@ -1173,7 +1200,11 @@ var PostgresJobStore = class {
1173
1200
  }
1174
1201
  async markInboundFailed(jobId, seq, _reason) {
1175
1202
  await this.pool.query(
1176
- "UPDATE job_messages SET status='failed-verify' WHERE job_id = $1 AND seq = $2 AND status = 'pending-verification'",
1203
+ `WITH failed AS (
1204
+ UPDATE job_messages SET status='failed-verify'
1205
+ WHERE job_id = $1 AND seq = $2 AND status = 'pending-verification'
1206
+ RETURNING job_id
1207
+ ) SELECT pg_notify('job_messages', job_id) FROM failed`,
1177
1208
  [jobId, seq]
1178
1209
  );
1179
1210
  }
@@ -1292,10 +1323,24 @@ var PostgresJobStore = class {
1292
1323
  if (subSet.size === 0) this.notifySubscribers.delete(jobId);
1293
1324
  });
1294
1325
  }
1295
- async getMessages(jobId, afterSeq) {
1326
+ async getMessages(jobId, afterSeq, options) {
1296
1327
  const { rows } = await this.pool.query(
1297
- "SELECT data FROM job_messages WHERE job_id = $1 AND seq > $2 AND status = 'verified' ORDER BY seq",
1298
- [jobId, afterSeq]
1328
+ `WITH terminal AS (
1329
+ SELECT messages FROM jobs WHERE id = $1 AND status IN ('completed', 'failed', 'cancelled')
1330
+ ), history AS (
1331
+ SELECT seq, data, 0 AS origin FROM job_messages
1332
+ WHERE job_id = $1 AND seq > $2 AND status = 'verified'
1333
+ AND ($3::boolean OR EXISTS (SELECT 1 FROM terminal)
1334
+ OR EXISTS (SELECT 1 FROM job_messages WHERE job_id = $1 AND status = 'verified'
1335
+ AND data->>'from' = 'provider' AND data->>'type' IN ('complete', 'cancel'))
1336
+ OR seq < COALESCE((SELECT MIN(seq) FROM job_messages
1337
+ WHERE job_id = $1 AND status = 'pending-verification' AND data->>'type' = 'payment'), 9223372036854775807))
1338
+ UNION ALL
1339
+ SELECT (message->>'seq')::bigint, message, 1 AS origin
1340
+ FROM terminal CROSS JOIN LATERAL jsonb_array_elements(terminal.messages) AS snapshot(message)
1341
+ WHERE (message->>'seq')::bigint > $2
1342
+ ) SELECT DISTINCT ON (seq) data FROM history ORDER BY seq, origin`,
1343
+ [jobId, afterSeq, options?.includeHeld ?? false]
1299
1344
  );
1300
1345
  return rows.map((r) => typeof r.data === "string" ? JSON.parse(r.data) : r.data);
1301
1346
  }
@@ -1373,7 +1418,7 @@ var PostgresJobStore = class {
1373
1418
  try {
1374
1419
  await client.query("BEGIN");
1375
1420
  const { rows } = await client.query(
1376
- `SELECT next_seq FROM jobs
1421
+ `SELECT * FROM jobs
1377
1422
  WHERE id = $1
1378
1423
  AND status IN ('processing', 'working', 'awaiting-input')
1379
1424
  ${staleOnly ? "AND last_activity_at <= $2" : ""}
@@ -1384,6 +1429,22 @@ var PostgresJobStore = class {
1384
1429
  await client.query("ROLLBACK");
1385
1430
  return false;
1386
1431
  }
1432
+ let terminalMessages;
1433
+ if (staleOnly) {
1434
+ const record = rowToRecord(rows[0]);
1435
+ const { rows: messages } = await client.query(
1436
+ "SELECT data FROM job_messages WHERE job_id=$1 AND status='verified' ORDER BY seq",
1437
+ [jobId]
1438
+ );
1439
+ const history = messages.length ? messages.map((row) => row.data) : record.messages;
1440
+ terminalMessages = history;
1441
+ const deadline = callerDeadline({ ...record, messages: history });
1442
+ const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !hasCallerDeadline(history);
1443
+ if (terminalStatus === "cancelled" !== awaiting || deadline !== void 0 && Date.now() < deadline) {
1444
+ await client.query("ROLLBACK");
1445
+ return false;
1446
+ }
1447
+ }
1387
1448
  const nextSeq = rows[0].next_seq;
1388
1449
  const now = Date.now();
1389
1450
  const cancelMsg = {
@@ -1406,12 +1467,20 @@ var PostgresJobStore = class {
1406
1467
  awaiting_input_at_ms = NULL,
1407
1468
  status = $6,
1408
1469
  summary = COALESCE(summary, $2),
1409
- messages = COALESCE(messages, '[]'::jsonb) || $3::jsonb,
1470
+ messages = ${staleOnly ? "$3::jsonb" : "COALESCE(messages, '[]'::jsonb) || $3::jsonb"},
1410
1471
  seq = $4,
1411
1472
  next_seq = $4 + 1,
1412
1473
  last_activity_at = $5
1413
1474
  WHERE id = $1`,
1414
- [jobId, reason, JSON.stringify([cancelMsg]), nextSeq, now, terminalStatus, endedBy]
1475
+ [
1476
+ jobId,
1477
+ reason,
1478
+ JSON.stringify([...terminalMessages ?? [], cancelMsg]),
1479
+ nextSeq,
1480
+ now,
1481
+ terminalStatus,
1482
+ staleOnly ? terminalStatus === "cancelled" ? "caller" : "provider" : endedBy
1483
+ ]
1415
1484
  );
1416
1485
  await client.query("SELECT pg_notify('job_messages', $1)", [jobId]);
1417
1486
  await client.query("COMMIT");
@@ -1499,6 +1568,7 @@ function rowToRecord(row) {
1499
1568
  summary: row.summary,
1500
1569
  messages: row.messages,
1501
1570
  seq: row.seq,
1571
+ upfrontPayment: row.upfront_payment ?? void 0,
1502
1572
  paidMsats: Number(row.paid_msats),
1503
1573
  paymentMint: row.payment_mint,
1504
1574
  paymentRail: row.payment_rail ?? void 0,