mcp-scraper 0.21.2 → 0.21.4

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 (57) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +1061 -308
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +101 -34
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +5 -5
  14. package/dist/bin/paa-harvest.cjs +22 -0
  15. package/dist/bin/paa-harvest.cjs.map +1 -1
  16. package/dist/bin/paa-harvest.js +3 -3
  17. package/dist/{chunk-ZJDANZTQ.js → chunk-E5J4HJBO.js} +3 -2
  18. package/dist/chunk-E5J4HJBO.js.map +1 -0
  19. package/dist/{chunk-DZBGULWW.js → chunk-ICT7DDHL.js} +399 -3
  20. package/dist/chunk-ICT7DDHL.js.map +1 -0
  21. package/dist/{chunk-2FF4GBQ4.js → chunk-JHI373VB.js} +49 -7
  22. package/dist/chunk-JHI373VB.js.map +1 -0
  23. package/dist/{chunk-GMZFQRH2.js → chunk-MTSBI7ZH.js} +23 -6
  24. package/dist/chunk-MTSBI7ZH.js.map +1 -0
  25. package/dist/{chunk-ZNNYPE2U.js → chunk-N4JHXPW6.js} +89 -27
  26. package/dist/chunk-N4JHXPW6.js.map +1 -0
  27. package/dist/{chunk-X3NIXHHG.js → chunk-PNBYYAEK.js} +3 -3
  28. package/dist/{chunk-JBME5WJR.js → chunk-SDHPQTFM.js} +2 -2
  29. package/dist/chunk-WQI7JIIN.js +7 -0
  30. package/dist/chunk-WQI7JIIN.js.map +1 -0
  31. package/dist/{db-EONGTNJM.js → db-WRILVOFY.js} +6 -2
  32. package/dist/{extract-bundle-HMCYUVBJ.js → extract-bundle-NFFVMUMT.js} +2 -2
  33. package/dist/index.cjs +22 -0
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.js +3 -3
  36. package/dist/{server-CHTX6YQA.js → server-OLOOFN5W.js} +499 -271
  37. package/dist/server-OLOOFN5W.js.map +1 -0
  38. package/dist/{site-extract-repository-5V5EEQ72.js → site-extract-repository-2QQSL2QC.js} +4 -4
  39. package/dist/{worker-GE5JMHE6.js → worker-RV2TYXE3.js} +15 -8
  40. package/dist/worker-RV2TYXE3.js.map +1 -0
  41. package/docs/mcp-tool-manifest.generated.json +90 -11
  42. package/docs/specs/oauth-mcp-spec.md +2 -2
  43. package/package.json +1 -1
  44. package/dist/chunk-2FF4GBQ4.js.map +0 -1
  45. package/dist/chunk-DZBGULWW.js.map +0 -1
  46. package/dist/chunk-GMZFQRH2.js.map +0 -1
  47. package/dist/chunk-NBF4F4UT.js +0 -7
  48. package/dist/chunk-NBF4F4UT.js.map +0 -1
  49. package/dist/chunk-ZJDANZTQ.js.map +0 -1
  50. package/dist/chunk-ZNNYPE2U.js.map +0 -1
  51. package/dist/server-CHTX6YQA.js.map +0 -1
  52. package/dist/worker-GE5JMHE6.js.map +0 -1
  53. /package/dist/{chunk-X3NIXHHG.js.map → chunk-PNBYYAEK.js.map} +0 -0
  54. /package/dist/{chunk-JBME5WJR.js.map → chunk-SDHPQTFM.js.map} +0 -0
  55. /package/dist/{db-EONGTNJM.js.map → db-WRILVOFY.js.map} +0 -0
  56. /package/dist/{extract-bundle-HMCYUVBJ.js.map → extract-bundle-NFFVMUMT.js.map} +0 -0
  57. /package/dist/{site-extract-repository-5V5EEQ72.js.map → site-extract-repository-2QQSL2QC.js.map} +0 -0
@@ -6,6 +6,108 @@ var DB_URL = process.env.TURSO_DATABASE_URL ?? "file:./paa-api.db";
6
6
  var DB_TOKEN = process.env.TURSO_AUTH_TOKEN;
7
7
  var _db = null;
8
8
  var _rateLimitSchemaReady = false;
9
+ var CORE_SCHEMA_VERSION = "2026-07-14.1";
10
+ var CORE_SCHEMA_TABLES = [
11
+ "billing_authorizations",
12
+ "billing_debits",
13
+ "billing_events",
14
+ "concurrency_locks",
15
+ "credit_lots",
16
+ "free_credit_refreshes",
17
+ "harvest_attempts",
18
+ "jobs",
19
+ "kpo_jobs",
20
+ "kpo_phase_log",
21
+ "ledger",
22
+ "oauth_clients",
23
+ "oauth_codes",
24
+ "oauth_tokens",
25
+ "page_snapshots",
26
+ "password_reset_tokens",
27
+ "rate_limits",
28
+ "request_events",
29
+ "site_audit_jobs",
30
+ "site_audit_phase_log",
31
+ "site_extract_jobs",
32
+ "site_extract_pages",
33
+ "stripe_events",
34
+ "users",
35
+ "workflow_artifacts",
36
+ "workflow_runs",
37
+ "workflow_schedules",
38
+ "workflow_webhook_deliveries"
39
+ ];
40
+ var CORE_SCHEMA_INDEXES = [
41
+ "billing_authorizations_user_status",
42
+ "billing_debits_user_status",
43
+ "billing_events_authorization",
44
+ "billing_events_user_created_at",
45
+ "concurrency_locks_operation",
46
+ "concurrency_locks_user_status",
47
+ "credit_lots_user_active",
48
+ "harvest_attempts_job_id",
49
+ "harvest_attempts_user_created_at",
50
+ "jobs_status",
51
+ "jobs_user_id",
52
+ "kpo_jobs_status",
53
+ "kpo_jobs_user_id",
54
+ "kpo_phase_log_job_id",
55
+ "ledger_stripe_pi_unique",
56
+ "ledger_user_id",
57
+ "oauth_tokens_identity",
58
+ "page_snapshots_user_checked_at",
59
+ "prt_user_id",
60
+ "request_events_user_created_at",
61
+ "site_audit_jobs_status",
62
+ "site_audit_jobs_user_id",
63
+ "site_audit_phase_log_job_id",
64
+ "site_extract_jobs_status",
65
+ "site_extract_jobs_user_id",
66
+ "site_extract_pages_job_id",
67
+ "workflow_artifacts_run",
68
+ "workflow_runs_idempotency",
69
+ "workflow_runs_schedule",
70
+ "workflow_runs_user_created",
71
+ "workflow_schedules_due",
72
+ "workflow_schedules_user_status",
73
+ "workflow_webhook_deliveries_run"
74
+ ];
75
+ var CORE_SCHEMA_COLUMNS = {
76
+ harvest_attempts: ["debug_json"],
77
+ oauth_tokens: ["replaced_by", "rotated_at"],
78
+ users: [
79
+ "balance_mc",
80
+ "concurrency_stripe_sub_id",
81
+ "extra_concurrency_slots",
82
+ "key_active",
83
+ "memory_identity",
84
+ "memory_key",
85
+ "memory_plan",
86
+ "memory_provisioned",
87
+ "memory_subscription_id",
88
+ "password_hash",
89
+ "stripe_customer_id",
90
+ "subscription_concurrency",
91
+ "subscription_id",
92
+ "subscription_tier"
93
+ ],
94
+ workflow_artifacts: ["content"],
95
+ workflow_runs: ["step_index", "step_log_json", "step_state_json", "total_steps", "webhook_url"]
96
+ };
97
+ function sqlStringList(values) {
98
+ return values.map((value) => `'${value.replaceAll("'", "''")}'`).join(", ");
99
+ }
100
+ async function hasCurrentCoreSchema(db) {
101
+ const checks = [
102
+ `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN (${sqlStringList(CORE_SCHEMA_TABLES)})) = ${CORE_SCHEMA_TABLES.length}`,
103
+ `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name IN (${sqlStringList(CORE_SCHEMA_INDEXES)})) = ${CORE_SCHEMA_INDEXES.length}`,
104
+ ...Object.entries(CORE_SCHEMA_COLUMNS).map(
105
+ ([table, columns]) => `(SELECT COUNT(*) FROM pragma_table_info('${table}') WHERE name IN (${sqlStringList(columns)})) = ${columns.length}`
106
+ )
107
+ ];
108
+ const result = await db.execute(`SELECT ${checks.join(" AND ")} AS ready`);
109
+ return Number(result.rows[0]?.ready ?? 0) === 1;
110
+ }
9
111
  function getDb() {
10
112
  if (!_db) _db = createClient({ url: DB_URL, authToken: DB_TOKEN });
11
113
  return _db;
@@ -15,6 +117,31 @@ function setDbForTesting(client) {
15
117
  }
16
118
  async function migrate() {
17
119
  const db = getDb();
120
+ let applied;
121
+ try {
122
+ applied = await db.execute({
123
+ sql: "SELECT 1 AS applied FROM schema_migrations WHERE version = ? LIMIT 1",
124
+ args: [CORE_SCHEMA_VERSION]
125
+ });
126
+ } catch (error) {
127
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
128
+ if (!message.includes("no such table") || !message.includes("schema_migrations")) throw error;
129
+ await db.execute(`
130
+ CREATE TABLE IF NOT EXISTS schema_migrations (
131
+ version TEXT PRIMARY KEY,
132
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
133
+ )
134
+ `);
135
+ applied = { rows: [] };
136
+ }
137
+ if (applied.rows.length > 0) return;
138
+ if (await hasCurrentCoreSchema(db)) {
139
+ await db.execute({
140
+ sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
141
+ args: [CORE_SCHEMA_VERSION]
142
+ });
143
+ return;
144
+ }
18
145
  await db.execute(`
19
146
  CREATE TABLE IF NOT EXISTS users (
20
147
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -248,6 +375,19 @@ async function migrate() {
248
375
  `);
249
376
  await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_user_created_at ON billing_events(user_id, created_at DESC)`);
250
377
  await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_authorization ON billing_events(authorization_id)`);
378
+ await db.execute(`
379
+ CREATE TABLE IF NOT EXISTS billing_debits (
380
+ idempotency_key TEXT PRIMARY KEY,
381
+ user_id INTEGER NOT NULL REFERENCES users(id),
382
+ amount_mc INTEGER NOT NULL,
383
+ operation TEXT NOT NULL,
384
+ description TEXT,
385
+ status TEXT NOT NULL DEFAULT 'claimed',
386
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
387
+ applied_at TEXT
388
+ )
389
+ `);
390
+ await db.execute(`CREATE INDEX IF NOT EXISTS billing_debits_user_status ON billing_debits(user_id, status)`);
251
391
  await db.execute(`
252
392
  CREATE TABLE IF NOT EXISTS site_audit_jobs (
253
393
  id TEXT PRIMARY KEY,
@@ -495,6 +635,13 @@ async function migrate() {
495
635
  )
496
636
  `);
497
637
  await db.execute(`CREATE INDEX IF NOT EXISTS page_snapshots_user_checked_at ON page_snapshots(user_id, checked_at DESC)`);
638
+ if (!await hasCurrentCoreSchema(db)) {
639
+ throw new Error("core schema migration finished without all required tables, indexes, and columns");
640
+ }
641
+ await db.execute({
642
+ sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
643
+ args: [CORE_SCHEMA_VERSION]
644
+ });
498
645
  }
499
646
  async function registerClient(clientId, redirectUris, name) {
500
647
  await getDb().execute({
@@ -957,6 +1104,18 @@ async function upsertPageSnapshot(userId, url, snapshot) {
957
1104
  args: [userId, url, snapshot.contentHash, snapshot.content, snapshot.title, snapshot.contentBytes, snapshot.truncated ? 1 : 0]
958
1105
  });
959
1106
  }
1107
+ var REQUEST_EVENT_RESULT_MAX_BYTES = 32 * 1024;
1108
+ function serializeRequestEventResult(result) {
1109
+ if (result == null) return null;
1110
+ const serialized = JSON.stringify(result);
1111
+ const bytes = Buffer.byteLength(serialized, "utf8");
1112
+ if (bytes <= REQUEST_EVENT_RESULT_MAX_BYTES) return serialized;
1113
+ return JSON.stringify({
1114
+ truncated: true,
1115
+ originalBytes: bytes,
1116
+ sha256: createHash("sha256").update(serialized).digest("hex")
1117
+ });
1118
+ }
960
1119
  function rowToRequestEvent(row) {
961
1120
  const rawResult = row.result != null ? String(row.result) : null;
962
1121
  return {
@@ -987,7 +1146,7 @@ async function logRequestEvent(input) {
987
1146
  input.query,
988
1147
  input.location ?? null,
989
1148
  input.resultCount ?? null,
990
- input.result != null ? JSON.stringify(input.result) : null,
1149
+ serializeRequestEventResult(input.result),
991
1150
  input.error ?? null
992
1151
  ]
993
1152
  });
@@ -996,7 +1155,7 @@ async function logRequestEvent(input) {
996
1155
  async function listRequestEvents(userId, limit = 100) {
997
1156
  const res = await getDb().execute({
998
1157
  sql: `
999
- SELECT id, user_id, source, status, query, location, result_count, result, error, created_at
1158
+ SELECT id, user_id, source, status, query, location, result_count, NULL AS result, error, created_at
1000
1159
  FROM request_events
1001
1160
  WHERE user_id = ?
1002
1161
  ORDER BY created_at DESC
@@ -1620,6 +1779,241 @@ async function debitMc(userId, mc, operation, description) {
1620
1779
  const res = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
1621
1780
  return { ok: true, balance_mc: Number(res.rows[0]?.balance_mc ?? 0) };
1622
1781
  }
1782
+ async function debitMcIdempotent(userId, mc, operation, description, idempotencyKey) {
1783
+ if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error("idempotent debit amount must be a positive integer");
1784
+ if (!idempotencyKey || idempotencyKey.length > 500) throw new Error("idempotent debit key is required and must be bounded");
1785
+ const db = getDb();
1786
+ await ensureMigrated(userId);
1787
+ let results;
1788
+ try {
1789
+ results = await db.batch([
1790
+ {
1791
+ sql: "UPDATE billing_debits SET amount_mc = ? WHERE idempotency_key = ? AND user_id = ? AND operation = ? AND description = ? AND status = 'claimed'",
1792
+ args: [mc, idempotencyKey, userId, operation, description]
1793
+ },
1794
+ {
1795
+ sql: "INSERT OR IGNORE INTO billing_debits (idempotency_key, user_id, amount_mc, operation, description, status) VALUES (?, ?, ?, ?, ?, 'claimed')",
1796
+ args: [idempotencyKey, userId, mc, operation, description]
1797
+ },
1798
+ {
1799
+ sql: "UPDATE users SET balance_mc = balance_mc - ? WHERE id = ? AND balance_mc >= ? AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed')",
1800
+ args: [mc, userId, mc, idempotencyKey, userId, mc, operation, description]
1801
+ },
1802
+ {
1803
+ sql: "UPDATE billing_debits SET status = 'applying' WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed' AND changes() = 1",
1804
+ args: [idempotencyKey, userId, mc, operation, description]
1805
+ },
1806
+ {
1807
+ sql: `WITH ordered AS MATERIALIZED (
1808
+ SELECT id,
1809
+ remaining_mc,
1810
+ COALESCE(SUM(remaining_mc) OVER (
1811
+ ORDER BY expires_at ASC, granted_at ASC, id ASC
1812
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
1813
+ ), 0) AS prior_mc
1814
+ FROM credit_lots
1815
+ WHERE user_id = ? AND remaining_mc > 0 AND expires_at > datetime('now')
1816
+ ), allocation AS (
1817
+ SELECT id, MIN(remaining_mc, MAX(0, ? - prior_mc)) AS take_mc
1818
+ FROM ordered
1819
+ )
1820
+ UPDATE credit_lots
1821
+ SET remaining_mc = remaining_mc - COALESCE((SELECT take_mc FROM allocation WHERE allocation.id = credit_lots.id), 0)
1822
+ WHERE id IN (SELECT id FROM allocation WHERE take_mc > 0)
1823
+ AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,
1824
+ args: [userId, mc, idempotencyKey]
1825
+ },
1826
+ {
1827
+ sql: "INSERT INTO ledger (user_id, amount_mc, operation, description) SELECT user_id, -amount_mc, operation, description FROM billing_debits WHERE idempotency_key = ? AND status = 'applying'",
1828
+ args: [idempotencyKey]
1829
+ },
1830
+ {
1831
+ sql: "UPDATE billing_debits SET status = 'applied', applied_at = datetime('now') WHERE idempotency_key = ? AND status = 'applying'",
1832
+ args: [idempotencyKey]
1833
+ }
1834
+ ], "write");
1835
+ } catch (error) {
1836
+ try {
1837
+ const recovered = await db.execute({
1838
+ sql: "SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? AND status = 'applied' LIMIT 1",
1839
+ args: [idempotencyKey]
1840
+ });
1841
+ const row2 = recovered.rows[0];
1842
+ if (row2 && Number(row2.user_id) === userId && Number(row2.amount_mc) === mc && String(row2.operation) === operation && String(row2.description) === description) {
1843
+ const balance2 = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
1844
+ return { ok: true, balance_mc: Number(balance2.rows[0]?.balance_mc ?? 0), duplicate: true };
1845
+ }
1846
+ } catch {
1847
+ }
1848
+ throw error;
1849
+ }
1850
+ const claim = await db.execute({
1851
+ sql: "SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? LIMIT 1",
1852
+ args: [idempotencyKey]
1853
+ });
1854
+ const row = claim.rows[0];
1855
+ if (!row || Number(row.user_id) !== userId || Number(row.amount_mc) !== mc || String(row.operation) !== operation || String(row.description) !== description) {
1856
+ throw new Error("idempotent debit key conflicts with another debit");
1857
+ }
1858
+ const balance = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
1859
+ return {
1860
+ ok: String(row.status) === "applied",
1861
+ balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),
1862
+ duplicate: results[2]?.rowsAffected === 0 && String(row.status) === "applied"
1863
+ };
1864
+ }
1865
+ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountMc, refundOperation, description, sourceSurface) {
1866
+ if (!Number.isSafeInteger(finalAmountMc) || finalAmountMc < 0) {
1867
+ throw new Error("idempotent debit settlement amount must be a non-negative integer");
1868
+ }
1869
+ if (!debitIdempotencyKey || debitIdempotencyKey.length > 500) {
1870
+ throw new Error("idempotent debit settlement key is required and must be bounded");
1871
+ }
1872
+ if (!refundOperation || !sourceSurface) {
1873
+ throw new Error("idempotent debit settlement operation and source are required");
1874
+ }
1875
+ const db = getDb();
1876
+ await ensureMigrated(userId);
1877
+ const settlementKey = `debit-settlement:${createHash("sha256").update(debitIdempotencyKey).digest("hex")}`;
1878
+ const billingClass = "wallet_hold_settlement";
1879
+ const eventId = randomUUID();
1880
+ const metadata = JSON.stringify({ debitIdempotencyKey, refundOperation, description });
1881
+ const readReceipt = async () => {
1882
+ const receipt2 = await db.execute({
1883
+ sql: `SELECT e.user_id,
1884
+ e.status,
1885
+ e.amount_mc AS final_amount_mc,
1886
+ e.billing_class,
1887
+ e.source_surface,
1888
+ d.amount_mc AS held_amount_mc
1889
+ FROM billing_events e
1890
+ JOIN billing_debits d ON d.idempotency_key = ?
1891
+ WHERE e.idempotency_key = ?
1892
+ AND d.user_id = e.user_id
1893
+ AND d.status = 'applied'
1894
+ LIMIT 1`,
1895
+ args: [debitIdempotencyKey, settlementKey]
1896
+ });
1897
+ const row = receipt2.rows[0];
1898
+ if (!row) return null;
1899
+ if (Number(row.user_id) !== userId || String(row.billing_class) !== billingClass || String(row.source_surface) !== sourceSurface) {
1900
+ throw new Error("idempotent debit settlement key conflicts with another settlement");
1901
+ }
1902
+ if (String(row.status) !== "settled") return null;
1903
+ const heldAmountMc = Number(row.held_amount_mc);
1904
+ const durableFinalAmountMc = Number(row.final_amount_mc);
1905
+ if (!Number.isSafeInteger(heldAmountMc) || !Number.isSafeInteger(durableFinalAmountMc) || durableFinalAmountMc < 0 || durableFinalAmountMc > heldAmountMc) {
1906
+ throw new Error("idempotent debit settlement receipt is invalid");
1907
+ }
1908
+ const balance = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
1909
+ return {
1910
+ balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),
1911
+ final_amount_mc: durableFinalAmountMc,
1912
+ refund_mc: heldAmountMc - durableFinalAmountMc,
1913
+ duplicate: true
1914
+ };
1915
+ };
1916
+ let results;
1917
+ try {
1918
+ results = await db.batch([
1919
+ {
1920
+ sql: `INSERT OR IGNORE INTO billing_events
1921
+ (id, user_id, idempotency_key, billing_class, source_surface, status, amount_mc, metadata)
1922
+ VALUES (?, ?, ?, ?, ?, 'settling', ?, ?)`,
1923
+ args: [eventId, userId, settlementKey, billingClass, sourceSurface, finalAmountMc, metadata]
1924
+ },
1925
+ {
1926
+ sql: `UPDATE users
1927
+ SET balance_mc = balance_mc + (
1928
+ SELECT d.amount_mc - e.amount_mc
1929
+ FROM billing_events e
1930
+ JOIN billing_debits d ON d.idempotency_key = ?
1931
+ WHERE e.idempotency_key = ?
1932
+ )
1933
+ WHERE id = ?
1934
+ AND EXISTS (
1935
+ SELECT 1
1936
+ FROM billing_events e
1937
+ JOIN billing_debits d ON d.idempotency_key = ?
1938
+ WHERE e.idempotency_key = ?
1939
+ AND e.user_id = ?
1940
+ AND e.billing_class = ?
1941
+ AND e.source_surface = ?
1942
+ AND e.status = 'settling'
1943
+ AND d.user_id = e.user_id
1944
+ AND d.status = 'applied'
1945
+ AND e.amount_mc BETWEEN 0 AND d.amount_mc
1946
+ )`,
1947
+ args: [
1948
+ debitIdempotencyKey,
1949
+ settlementKey,
1950
+ userId,
1951
+ debitIdempotencyKey,
1952
+ settlementKey,
1953
+ userId,
1954
+ billingClass,
1955
+ sourceSurface
1956
+ ]
1957
+ },
1958
+ {
1959
+ sql: `UPDATE billing_events
1960
+ SET status = 'applying'
1961
+ WHERE idempotency_key = ?
1962
+ AND user_id = ?
1963
+ AND billing_class = ?
1964
+ AND source_surface = ?
1965
+ AND status = 'settling'
1966
+ AND changes() = 1`,
1967
+ args: [settlementKey, userId, billingClass, sourceSurface]
1968
+ },
1969
+ {
1970
+ sql: `INSERT INTO credit_lots
1971
+ (user_id, amount_mc, remaining_mc, source, expires_at)
1972
+ SELECT e.user_id,
1973
+ d.amount_mc - e.amount_mc,
1974
+ d.amount_mc - e.amount_mc,
1975
+ ?,
1976
+ datetime('now', ?)
1977
+ FROM billing_events e
1978
+ JOIN billing_debits d ON d.idempotency_key = ?
1979
+ WHERE e.idempotency_key = ?
1980
+ AND e.status = 'applying'
1981
+ AND d.amount_mc > e.amount_mc`,
1982
+ args: [refundOperation, CREDIT_LOT_TTL, debitIdempotencyKey, settlementKey]
1983
+ },
1984
+ {
1985
+ sql: `INSERT INTO ledger (user_id, amount_mc, operation, description)
1986
+ SELECT e.user_id,
1987
+ d.amount_mc - e.amount_mc,
1988
+ ?,
1989
+ ?
1990
+ FROM billing_events e
1991
+ JOIN billing_debits d ON d.idempotency_key = ?
1992
+ WHERE e.idempotency_key = ?
1993
+ AND e.status = 'applying'
1994
+ AND d.amount_mc > e.amount_mc`,
1995
+ args: [refundOperation, description, debitIdempotencyKey, settlementKey]
1996
+ },
1997
+ {
1998
+ sql: `UPDATE billing_events
1999
+ SET status = 'settled', settled_at = datetime('now')
2000
+ WHERE idempotency_key = ?
2001
+ AND status = 'applying'`,
2002
+ args: [settlementKey]
2003
+ }
2004
+ ], "write");
2005
+ } catch (error) {
2006
+ try {
2007
+ const recovered = await readReceipt();
2008
+ if (recovered) return recovered;
2009
+ } catch {
2010
+ }
2011
+ throw error;
2012
+ }
2013
+ const receipt = await readReceipt();
2014
+ if (!receipt) throw new Error("idempotent debit settlement did not reach a durable settled state");
2015
+ return { ...receipt, duplicate: results[0]?.rowsAffected === 0 };
2016
+ }
1623
2017
  async function getLedger(userId, limit = 50) {
1624
2018
  const res = await getDb().execute({
1625
2019
  sql: "SELECT * FROM ledger WHERE user_id = ? ORDER BY created_at DESC LIMIT ?",
@@ -1784,10 +2178,12 @@ export {
1784
2178
  ledgerExistsForOperation,
1785
2179
  claimMonthlyFreeRefresh,
1786
2180
  debitMc,
2181
+ debitMcIdempotent,
2182
+ settleDebitMcIdempotent,
1787
2183
  getLedger,
1788
2184
  ledgerExistsForStripePI,
1789
2185
  reconcileBalanceMc,
1790
2186
  SiteAuditJobRowSchema,
1791
2187
  SiteAuditPhaseLogRowSchema
1792
2188
  };
1793
- //# sourceMappingURL=chunk-DZBGULWW.js.map
2189
+ //# sourceMappingURL=chunk-ICT7DDHL.js.map