mcp-scraper 0.35.1 → 0.36.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/README.md +9 -9
- package/dist/bin/api-server.cjs +26406 -21015
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +51 -7
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +48 -5
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +944 -215
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +8 -8
- package/dist/bin/paa-harvest.cjs +125 -70
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +4 -4
- package/dist/chunk-345BQXZH.js +712 -0
- package/dist/chunk-345BQXZH.js.map +1 -0
- package/dist/{chunk-M2S27J6Z.js → chunk-44HZLHDV.js} +10 -1
- package/dist/chunk-44HZLHDV.js.map +1 -0
- package/dist/chunk-4HO66323.js +7 -0
- package/dist/chunk-4HO66323.js.map +1 -0
- package/dist/{chunk-BWXLTWF7.js → chunk-4ZIJ3BKZ.js} +6 -4
- package/dist/chunk-4ZIJ3BKZ.js.map +1 -0
- package/dist/{chunk-NPMW5HUS.js → chunk-5RULXBJ7.js} +879 -237
- package/dist/chunk-5RULXBJ7.js.map +1 -0
- package/dist/chunk-AN3VQARU.js +684 -0
- package/dist/chunk-AN3VQARU.js.map +1 -0
- package/dist/{chunk-XVVNKASZ.js → chunk-ANCGXUQJ.js} +118 -73
- package/dist/chunk-ANCGXUQJ.js.map +1 -0
- package/dist/{chunk-3HBPKR5G.js → chunk-D7LM5QZN.js} +3 -3
- package/dist/{chunk-4ZB3X6BQ.js → chunk-E5UEELA7.js} +16 -2
- package/dist/{chunk-4ZB3X6BQ.js.map → chunk-E5UEELA7.js.map} +1 -1
- package/dist/{chunk-ZID3WQID.js → chunk-FQI5PFE7.js} +9 -71
- package/dist/chunk-FQI5PFE7.js.map +1 -0
- package/dist/chunk-G3P3ZDB4.js +69 -0
- package/dist/chunk-G3P3ZDB4.js.map +1 -0
- package/dist/{chunk-YRGSEY5L.js → chunk-G7KAVJ3F.js} +2 -2
- package/dist/{chunk-YRGSEY5L.js.map → chunk-G7KAVJ3F.js.map} +1 -1
- package/dist/{chunk-62DQAWPF.js → chunk-IFYER7O4.js} +367 -42
- package/dist/chunk-IFYER7O4.js.map +1 -0
- package/dist/chunk-O2MCWFXQ.js +499 -0
- package/dist/chunk-O2MCWFXQ.js.map +1 -0
- package/dist/chunk-QZXKQB7Y.js +414 -0
- package/dist/chunk-QZXKQB7Y.js.map +1 -0
- package/dist/{db-YAI5AQOI.js → db-N6MPVMEF.js} +8 -2
- package/dist/{extract-bundle-ONWZVV55.js → extract-bundle-M4SDJG3V.js} +284 -98
- package/dist/extract-bundle-M4SDJG3V.js.map +1 -0
- package/dist/index.cjs +129 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4 -4
- package/dist/location-data-repository-Z4NQOU5Y.js +35 -0
- package/dist/{server-GKUTC73B.js → server-ZIGAFKOL.js} +10791 -7629
- package/dist/server-ZIGAFKOL.js.map +1 -0
- package/dist/site-extract-repository-PGQZNW6V.js +62 -0
- package/dist/site-extract-repository-PGQZNW6V.js.map +1 -0
- package/dist/{worker-645BZPEK.js → worker-EBB6CTGW.js} +7 -7
- package/docs/hosted-location-data.md +108 -0
- package/docs/mcp-tool-craft-lint.generated.md +6 -3
- package/docs/mcp-tool-manifest.generated.json +1253 -222
- package/docs/mcp-tool-quality-spec.md +1 -1
- package/docs/specs/connected-services-control-plane-decoupling-spec.md +1044 -0
- package/docs/specs/kernel-stealth-captcha-test-matrix.md +278 -0
- package/docs/specs/multimodal-image-memory-architecture-spec.md +1022 -0
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +36 -27
- package/package.json +6 -5
- package/dist/chunk-62DQAWPF.js.map +0 -1
- package/dist/chunk-BWXLTWF7.js.map +0 -1
- package/dist/chunk-M2S27J6Z.js.map +0 -1
- package/dist/chunk-NPMW5HUS.js.map +0 -1
- package/dist/chunk-R7EETU7Z.js +0 -419
- package/dist/chunk-R7EETU7Z.js.map +0 -1
- package/dist/chunk-U44TPRST.js +0 -130
- package/dist/chunk-U44TPRST.js.map +0 -1
- package/dist/chunk-XVVNKASZ.js.map +0 -1
- package/dist/chunk-YR4LJ6AQ.js +0 -7
- package/dist/chunk-YR4LJ6AQ.js.map +0 -1
- package/dist/chunk-YV2FUEBX.js +0 -851
- package/dist/chunk-YV2FUEBX.js.map +0 -1
- package/dist/chunk-ZID3WQID.js.map +0 -1
- package/dist/extract-bundle-ONWZVV55.js.map +0 -1
- package/dist/server-GKUTC73B.js.map +0 -1
- package/dist/site-extract-repository-L6BHWVDU.js +0 -30
- /package/dist/{chunk-3HBPKR5G.js.map → chunk-D7LM5QZN.js.map} +0 -0
- /package/dist/{db-YAI5AQOI.js.map → db-N6MPVMEF.js.map} +0 -0
- /package/dist/{site-extract-repository-L6BHWVDU.js.map → location-data-repository-Z4NQOU5Y.js.map} +0 -0
- /package/dist/{worker-645BZPEK.js.map → worker-EBB6CTGW.js.map} +0 -0
|
@@ -10,8 +10,10 @@ var CORE_SCHEMA_VERSION = "2026-07-24.2";
|
|
|
10
10
|
var CORE_SCHEMA_TABLES = [
|
|
11
11
|
"billing_authorizations",
|
|
12
12
|
"billing_debits",
|
|
13
|
+
"billing_debit_allocations",
|
|
13
14
|
"billing_events",
|
|
14
15
|
"browser_profile_labels",
|
|
16
|
+
"connected_account_billing",
|
|
15
17
|
"concurrency_locks",
|
|
16
18
|
"credit_lots",
|
|
17
19
|
"free_credit_refreshes",
|
|
@@ -65,6 +67,7 @@ var CORE_SCHEMA_INDEXES = [
|
|
|
65
67
|
"site_audit_jobs_user_id",
|
|
66
68
|
"site_audit_phase_log_job_id",
|
|
67
69
|
"site_extract_jobs_status",
|
|
70
|
+
"site_extract_jobs_idempotency",
|
|
68
71
|
"site_extract_jobs_user_id",
|
|
69
72
|
"site_extract_pages_job_id",
|
|
70
73
|
"workflow_artifacts_run",
|
|
@@ -79,6 +82,19 @@ var CORE_SCHEMA_COLUMNS = {
|
|
|
79
82
|
credit_lots: ["never_expires"],
|
|
80
83
|
harvest_attempts: ["debug_json"],
|
|
81
84
|
oauth_tokens: ["replaced_by", "rotated_at"],
|
|
85
|
+
site_extract_jobs: [
|
|
86
|
+
"attempted_urls",
|
|
87
|
+
"successful_urls",
|
|
88
|
+
"failed_urls",
|
|
89
|
+
"idempotency_key",
|
|
90
|
+
"request_fingerprint",
|
|
91
|
+
"dispatch_attempts",
|
|
92
|
+
"next_dispatch_at",
|
|
93
|
+
"dispatch_error",
|
|
94
|
+
"settlement_attempts",
|
|
95
|
+
"next_settlement_at",
|
|
96
|
+
"settlement_error"
|
|
97
|
+
],
|
|
82
98
|
users: [
|
|
83
99
|
"balance_mc",
|
|
84
100
|
"concurrency_stripe_sub_id",
|
|
@@ -112,6 +128,46 @@ async function hasCurrentCoreSchema(db) {
|
|
|
112
128
|
const result = await db.execute(`SELECT ${checks.join(" AND ")} AS ready`);
|
|
113
129
|
return Number(result.rows[0]?.ready ?? 0) === 1;
|
|
114
130
|
}
|
|
131
|
+
async function runIdempotentCoreDataBackfills(db) {
|
|
132
|
+
await db.execute(`
|
|
133
|
+
UPDATE site_extract_jobs SET
|
|
134
|
+
attempted_urls = done_urls,
|
|
135
|
+
successful_urls = (
|
|
136
|
+
SELECT COUNT(*) FROM site_extract_pages
|
|
137
|
+
WHERE job_id = site_extract_jobs.id
|
|
138
|
+
AND CASE
|
|
139
|
+
WHEN json_valid(page) = 0 THEN 0
|
|
140
|
+
WHEN json_extract(page, '$.extractionStatus') IS NOT NULL
|
|
141
|
+
THEN json_extract(page, '$.extractionStatus') = 'successful'
|
|
142
|
+
ELSE COALESCE(
|
|
143
|
+
json_extract(page, '$.status') >= 200
|
|
144
|
+
AND json_extract(page, '$.status') < 300
|
|
145
|
+
AND json_extract(page, '$.wordCount') > 0,
|
|
146
|
+
0
|
|
147
|
+
)
|
|
148
|
+
END
|
|
149
|
+
),
|
|
150
|
+
failed_urls = MAX(0, done_urls - (
|
|
151
|
+
SELECT COUNT(*) FROM site_extract_pages
|
|
152
|
+
WHERE job_id = site_extract_jobs.id
|
|
153
|
+
AND CASE
|
|
154
|
+
WHEN json_valid(page) = 0 THEN 0
|
|
155
|
+
WHEN json_extract(page, '$.extractionStatus') IS NOT NULL
|
|
156
|
+
THEN json_extract(page, '$.extractionStatus') = 'successful'
|
|
157
|
+
ELSE COALESCE(
|
|
158
|
+
json_extract(page, '$.status') >= 200
|
|
159
|
+
AND json_extract(page, '$.status') < 300
|
|
160
|
+
AND json_extract(page, '$.wordCount') > 0,
|
|
161
|
+
0
|
|
162
|
+
)
|
|
163
|
+
END
|
|
164
|
+
))
|
|
165
|
+
WHERE done_urls > 0
|
|
166
|
+
AND attempted_urls = 0
|
|
167
|
+
AND successful_urls = 0
|
|
168
|
+
AND failed_urls = 0
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
115
171
|
function getDb() {
|
|
116
172
|
if (!_db) _db = createClient({ url: DB_URL, authToken: DB_TOKEN });
|
|
117
173
|
return _db;
|
|
@@ -138,8 +194,12 @@ async function migrate() {
|
|
|
138
194
|
`);
|
|
139
195
|
applied = { rows: [] };
|
|
140
196
|
}
|
|
141
|
-
if (applied.rows.length > 0 && await hasCurrentCoreSchema(db))
|
|
197
|
+
if (applied.rows.length > 0 && await hasCurrentCoreSchema(db)) {
|
|
198
|
+
await runIdempotentCoreDataBackfills(db);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
142
201
|
if (await hasCurrentCoreSchema(db)) {
|
|
202
|
+
await runIdempotentCoreDataBackfills(db);
|
|
143
203
|
await db.execute({
|
|
144
204
|
sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
|
|
145
205
|
args: [CORE_SCHEMA_VERSION]
|
|
@@ -397,6 +457,30 @@ async function migrate() {
|
|
|
397
457
|
)
|
|
398
458
|
`);
|
|
399
459
|
await db.execute(`CREATE INDEX IF NOT EXISTS billing_debits_user_status ON billing_debits(user_id, status)`);
|
|
460
|
+
await db.execute(`
|
|
461
|
+
CREATE TABLE IF NOT EXISTS billing_debit_allocations (
|
|
462
|
+
debit_idempotency_key TEXT NOT NULL REFERENCES billing_debits(idempotency_key),
|
|
463
|
+
lot_id INTEGER NOT NULL REFERENCES credit_lots(id),
|
|
464
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
465
|
+
amount_mc INTEGER NOT NULL,
|
|
466
|
+
expires_at TEXT NOT NULL,
|
|
467
|
+
PRIMARY KEY (debit_idempotency_key, lot_id)
|
|
468
|
+
)
|
|
469
|
+
`);
|
|
470
|
+
await db.execute(`
|
|
471
|
+
CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
472
|
+
user_id INTEGER PRIMARY KEY REFERENCES users(id),
|
|
473
|
+
stripe_subscription_id TEXT,
|
|
474
|
+
stripe_subscription_item_id TEXT,
|
|
475
|
+
price_id TEXT,
|
|
476
|
+
quantity INTEGER NOT NULL DEFAULT 0,
|
|
477
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
478
|
+
last_error_code TEXT,
|
|
479
|
+
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
480
|
+
synced_at TEXT,
|
|
481
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
482
|
+
)
|
|
483
|
+
`);
|
|
400
484
|
await db.execute(`
|
|
401
485
|
CREATE TABLE IF NOT EXISTS site_audit_jobs (
|
|
402
486
|
id TEXT PRIMARY KEY,
|
|
@@ -432,6 +516,17 @@ async function migrate() {
|
|
|
432
516
|
options TEXT,
|
|
433
517
|
total_urls INTEGER DEFAULT 0,
|
|
434
518
|
done_urls INTEGER DEFAULT 0,
|
|
519
|
+
attempted_urls INTEGER DEFAULT 0,
|
|
520
|
+
successful_urls INTEGER DEFAULT 0,
|
|
521
|
+
failed_urls INTEGER DEFAULT 0,
|
|
522
|
+
idempotency_key TEXT,
|
|
523
|
+
request_fingerprint TEXT,
|
|
524
|
+
dispatch_attempts INTEGER DEFAULT 0,
|
|
525
|
+
next_dispatch_at TEXT,
|
|
526
|
+
dispatch_error TEXT,
|
|
527
|
+
settlement_attempts INTEGER DEFAULT 0,
|
|
528
|
+
next_settlement_at TEXT,
|
|
529
|
+
settlement_error TEXT,
|
|
435
530
|
artifacts TEXT,
|
|
436
531
|
error TEXT,
|
|
437
532
|
billed_mc INTEGER,
|
|
@@ -450,6 +545,51 @@ async function migrate() {
|
|
|
450
545
|
await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_user_id ON site_extract_jobs(user_id)`);
|
|
451
546
|
await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_status ON site_extract_jobs(status)`);
|
|
452
547
|
await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_pages_job_id ON site_extract_pages(job_id)`);
|
|
548
|
+
try {
|
|
549
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN attempted_urls INTEGER DEFAULT 0`);
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN successful_urls INTEGER DEFAULT 0`);
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN failed_urls INTEGER DEFAULT 0`);
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN idempotency_key TEXT`);
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN request_fingerprint TEXT`);
|
|
566
|
+
} catch {
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_attempts INTEGER DEFAULT 0`);
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_dispatch_at TEXT`);
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_error TEXT`);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_attempts INTEGER DEFAULT 0`);
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_settlement_at TEXT`);
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_error TEXT`);
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS site_extract_jobs_idempotency ON site_extract_jobs(user_id, idempotency_key) WHERE idempotency_key IS NOT NULL`);
|
|
453
593
|
await db.execute(`
|
|
454
594
|
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
|
455
595
|
token TEXT PRIMARY KEY,
|
|
@@ -670,6 +810,7 @@ async function migrate() {
|
|
|
670
810
|
if (!await hasCurrentCoreSchema(db)) {
|
|
671
811
|
throw new Error("core schema migration finished without all required tables, indexes, and columns");
|
|
672
812
|
}
|
|
813
|
+
await runIdempotentCoreDataBackfills(db);
|
|
673
814
|
await db.execute({
|
|
674
815
|
sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
|
|
675
816
|
args: [CORE_SCHEMA_VERSION]
|
|
@@ -1647,6 +1788,75 @@ async function setSubscriptionTier(userId, tier, concurrency, subId) {
|
|
|
1647
1788
|
args: [tier, Math.max(0, Math.round(concurrency)), subId, userId]
|
|
1648
1789
|
});
|
|
1649
1790
|
}
|
|
1791
|
+
function rowToConnectedAccountBillingState(row) {
|
|
1792
|
+
return {
|
|
1793
|
+
user_id: Number(row.user_id),
|
|
1794
|
+
stripe_subscription_id: row.stripe_subscription_id != null ? String(row.stripe_subscription_id) : null,
|
|
1795
|
+
stripe_subscription_item_id: row.stripe_subscription_item_id != null ? String(row.stripe_subscription_item_id) : null,
|
|
1796
|
+
price_id: row.price_id != null ? String(row.price_id) : null,
|
|
1797
|
+
quantity: Math.max(0, Number(row.quantity ?? 0)),
|
|
1798
|
+
status: ["pending", "synced", "blocked", "error"].includes(String(row.status)) ? String(row.status) : "error",
|
|
1799
|
+
last_error_code: row.last_error_code != null ? String(row.last_error_code) : null,
|
|
1800
|
+
checked_at: String(row.checked_at),
|
|
1801
|
+
synced_at: row.synced_at != null ? String(row.synced_at) : null,
|
|
1802
|
+
updated_at: String(row.updated_at)
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
async function getConnectedAccountBillingState(userId) {
|
|
1806
|
+
const res = await getDb().execute({
|
|
1807
|
+
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
1808
|
+
args: [userId]
|
|
1809
|
+
});
|
|
1810
|
+
const row = res.rows[0];
|
|
1811
|
+
return row ? rowToConnectedAccountBillingState(row) : null;
|
|
1812
|
+
}
|
|
1813
|
+
async function setConnectedAccountBillingState(input) {
|
|
1814
|
+
const quantity = Math.max(0, Math.round(input.quantity));
|
|
1815
|
+
await getDb().execute({
|
|
1816
|
+
sql: `INSERT INTO connected_account_billing
|
|
1817
|
+
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)
|
|
1818
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
1819
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
1820
|
+
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
1821
|
+
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
1822
|
+
price_id = excluded.price_id,
|
|
1823
|
+
quantity = excluded.quantity,
|
|
1824
|
+
status = excluded.status,
|
|
1825
|
+
last_error_code = excluded.last_error_code,
|
|
1826
|
+
checked_at = datetime('now'),
|
|
1827
|
+
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
1828
|
+
updated_at = datetime('now')`,
|
|
1829
|
+
args: [
|
|
1830
|
+
input.userId,
|
|
1831
|
+
input.stripeSubscriptionId,
|
|
1832
|
+
input.stripeSubscriptionItemId,
|
|
1833
|
+
input.priceId,
|
|
1834
|
+
quantity,
|
|
1835
|
+
input.status,
|
|
1836
|
+
input.lastErrorCode ?? null,
|
|
1837
|
+
input.synced === true ? 1 : 0,
|
|
1838
|
+
input.synced === true ? 1 : 0
|
|
1839
|
+
]
|
|
1840
|
+
});
|
|
1841
|
+
const state = await getConnectedAccountBillingState(input.userId);
|
|
1842
|
+
if (!state) throw new Error("connected account billing state was not persisted");
|
|
1843
|
+
return state;
|
|
1844
|
+
}
|
|
1845
|
+
async function listUsersDueForConnectedAccountBillingReconciliation(staleMinutes = 60, limit = 20) {
|
|
1846
|
+
const modifier = `-${Math.max(1, Math.round(staleMinutes))} minutes`;
|
|
1847
|
+
const res = await getDb().execute({
|
|
1848
|
+
sql: `SELECT u.*
|
|
1849
|
+
FROM users u
|
|
1850
|
+
LEFT JOIN connected_account_billing b ON b.user_id = u.id
|
|
1851
|
+
WHERE u.active = 1
|
|
1852
|
+
AND u.subscription_id IS NOT NULL
|
|
1853
|
+
AND (b.checked_at IS NULL OR b.checked_at <= datetime('now', ?))
|
|
1854
|
+
ORDER BY COALESCE(b.checked_at, u.created_at) ASC
|
|
1855
|
+
LIMIT ?`,
|
|
1856
|
+
args: [modifier, Math.max(1, Math.min(100, Math.round(limit)))]
|
|
1857
|
+
});
|
|
1858
|
+
return res.rows.map((row) => rowToUser(row));
|
|
1859
|
+
}
|
|
1650
1860
|
async function setMemoryPlan(userId, plan, subscriptionId) {
|
|
1651
1861
|
await getDb().execute({
|
|
1652
1862
|
sql: "UPDATE users SET memory_plan = ?, memory_subscription_id = ? WHERE id = ?",
|
|
@@ -1826,6 +2036,8 @@ async function debitMcIdempotent(userId, mc, operation, description, idempotency
|
|
|
1826
2036
|
try {
|
|
1827
2037
|
results = await db.batch([
|
|
1828
2038
|
{
|
|
2039
|
+
// An unfunded claim may be retried with a corrected estimate under the
|
|
2040
|
+
// same logical operation fingerprint. Applied claims remain immutable.
|
|
1829
2041
|
sql: "UPDATE billing_debits SET amount_mc = ? WHERE idempotency_key = ? AND user_id = ? AND operation = ? AND description = ? AND status = 'claimed'",
|
|
1830
2042
|
args: [mc, idempotencyKey, userId, operation, description]
|
|
1831
2043
|
},
|
|
@@ -1834,17 +2046,29 @@ async function debitMcIdempotent(userId, mc, operation, description, idempotency
|
|
|
1834
2046
|
args: [idempotencyKey, userId, mc, operation, description]
|
|
1835
2047
|
},
|
|
1836
2048
|
{
|
|
1837
|
-
sql:
|
|
1838
|
-
|
|
2049
|
+
sql: `UPDATE users
|
|
2050
|
+
SET balance_mc = balance_mc - ?
|
|
2051
|
+
WHERE id = ?
|
|
2052
|
+
AND balance_mc >= ?
|
|
2053
|
+
AND (SELECT COALESCE(SUM(remaining_mc), 0)
|
|
2054
|
+
FROM credit_lots
|
|
2055
|
+
WHERE user_id = ?
|
|
2056
|
+
AND remaining_mc > 0
|
|
2057
|
+
AND (never_expires = 1 OR expires_at > datetime('now'))) >= ?
|
|
2058
|
+
AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed')`,
|
|
2059
|
+
args: [mc, userId, mc, userId, mc, idempotencyKey, userId, mc, operation, description]
|
|
1839
2060
|
},
|
|
1840
2061
|
{
|
|
1841
2062
|
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",
|
|
1842
2063
|
args: [idempotencyKey, userId, mc, operation, description]
|
|
1843
2064
|
},
|
|
1844
2065
|
{
|
|
1845
|
-
sql: `
|
|
2066
|
+
sql: `INSERT OR IGNORE INTO billing_debit_allocations
|
|
2067
|
+
(debit_idempotency_key, lot_id, user_id, amount_mc, expires_at)
|
|
2068
|
+
WITH ordered AS MATERIALIZED (
|
|
1846
2069
|
SELECT id,
|
|
1847
2070
|
remaining_mc,
|
|
2071
|
+
expires_at,
|
|
1848
2072
|
COALESCE(SUM(remaining_mc) OVER (
|
|
1849
2073
|
ORDER BY never_expires ASC, expires_at ASC, granted_at ASC, id ASC
|
|
1850
2074
|
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
@@ -1852,14 +2076,26 @@ async function debitMcIdempotent(userId, mc, operation, description, idempotency
|
|
|
1852
2076
|
FROM credit_lots
|
|
1853
2077
|
WHERE user_id = ? AND remaining_mc > 0 AND (never_expires = 1 OR expires_at > datetime('now'))
|
|
1854
2078
|
), allocation AS (
|
|
1855
|
-
SELECT id, MIN(remaining_mc, MAX(0, ? - prior_mc)) AS take_mc
|
|
2079
|
+
SELECT id, expires_at, MIN(remaining_mc, MAX(0, ? - prior_mc)) AS take_mc
|
|
1856
2080
|
FROM ordered
|
|
1857
2081
|
)
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
WHERE
|
|
2082
|
+
SELECT ?, id, ?, take_mc, expires_at
|
|
2083
|
+
FROM allocation
|
|
2084
|
+
WHERE take_mc > 0
|
|
2085
|
+
AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,
|
|
2086
|
+
args: [userId, mc, idempotencyKey, userId, idempotencyKey]
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
sql: `UPDATE credit_lots
|
|
2090
|
+
SET remaining_mc = remaining_mc - COALESCE((
|
|
2091
|
+
SELECT amount_mc FROM billing_debit_allocations
|
|
2092
|
+
WHERE debit_idempotency_key = ? AND lot_id = credit_lots.id
|
|
2093
|
+
), 0)
|
|
2094
|
+
WHERE id IN (
|
|
2095
|
+
SELECT lot_id FROM billing_debit_allocations WHERE debit_idempotency_key = ?
|
|
2096
|
+
)
|
|
1861
2097
|
AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,
|
|
1862
|
-
args: [
|
|
2098
|
+
args: [idempotencyKey, idempotencyKey, idempotencyKey]
|
|
1863
2099
|
},
|
|
1864
2100
|
{
|
|
1865
2101
|
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'",
|
|
@@ -1923,6 +2159,7 @@ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountM
|
|
|
1923
2159
|
e.amount_mc AS final_amount_mc,
|
|
1924
2160
|
e.billing_class,
|
|
1925
2161
|
e.source_surface,
|
|
2162
|
+
e.settled_at,
|
|
1926
2163
|
d.amount_mc AS held_amount_mc
|
|
1927
2164
|
FROM billing_events e
|
|
1928
2165
|
JOIN billing_debits d ON d.idempotency_key = ?
|
|
@@ -1943,11 +2180,35 @@ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountM
|
|
|
1943
2180
|
if (!Number.isSafeInteger(heldAmountMc) || !Number.isSafeInteger(durableFinalAmountMc) || durableFinalAmountMc < 0 || durableFinalAmountMc > heldAmountMc) {
|
|
1944
2181
|
throw new Error("idempotent debit settlement receipt is invalid");
|
|
1945
2182
|
}
|
|
2183
|
+
const nominalRefundMc = heldAmountMc - durableFinalAmountMc;
|
|
2184
|
+
const allocationRefund = await db.execute({
|
|
2185
|
+
sql: `WITH ordered AS (
|
|
2186
|
+
SELECT a.amount_mc,
|
|
2187
|
+
a.expires_at,
|
|
2188
|
+
COALESCE(SUM(a.amount_mc) OVER (
|
|
2189
|
+
ORDER BY a.expires_at ASC, a.lot_id ASC
|
|
2190
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
2191
|
+
), 0) AS prior_mc
|
|
2192
|
+
FROM billing_debit_allocations a
|
|
2193
|
+
WHERE a.debit_idempotency_key = ?
|
|
2194
|
+
), refunds AS (
|
|
2195
|
+
SELECT expires_at,
|
|
2196
|
+
amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc
|
|
2197
|
+
FROM ordered
|
|
2198
|
+
)
|
|
2199
|
+
SELECT (SELECT COUNT(*) FROM ordered) AS allocation_count,
|
|
2200
|
+
COALESCE(SUM(CASE WHEN expires_at > ? THEN refund_mc ELSE 0 END), 0) AS credited_refund_mc
|
|
2201
|
+
FROM refunds`,
|
|
2202
|
+
args: [debitIdempotencyKey, durableFinalAmountMc, String(row.settled_at)]
|
|
2203
|
+
});
|
|
2204
|
+
const allocationCount = Number(allocationRefund.rows[0]?.allocation_count ?? 0);
|
|
2205
|
+
const creditedRefundMc = allocationCount > 0 ? Number(allocationRefund.rows[0]?.credited_refund_mc ?? 0) : 0;
|
|
1946
2206
|
const balance = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
|
|
1947
2207
|
return {
|
|
1948
2208
|
balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),
|
|
1949
2209
|
final_amount_mc: durableFinalAmountMc,
|
|
1950
|
-
refund_mc:
|
|
2210
|
+
refund_mc: creditedRefundMc,
|
|
2211
|
+
expired_refund_mc: nominalRefundMc - creditedRefundMc,
|
|
1951
2212
|
duplicate: true
|
|
1952
2213
|
};
|
|
1953
2214
|
};
|
|
@@ -1961,13 +2222,34 @@ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountM
|
|
|
1961
2222
|
args: [eventId, userId, settlementKey, billingClass, sourceSurface, finalAmountMc, metadata]
|
|
1962
2223
|
},
|
|
1963
2224
|
{
|
|
1964
|
-
sql: `
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2225
|
+
sql: `WITH ordered AS (
|
|
2226
|
+
SELECT a.lot_id,
|
|
2227
|
+
a.amount_mc,
|
|
2228
|
+
a.expires_at,
|
|
2229
|
+
COALESCE(SUM(a.amount_mc) OVER (
|
|
2230
|
+
ORDER BY a.expires_at ASC, a.lot_id ASC
|
|
2231
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
2232
|
+
), 0) AS prior_mc
|
|
2233
|
+
FROM billing_debit_allocations a
|
|
2234
|
+
WHERE a.debit_idempotency_key = ?
|
|
2235
|
+
), refunds AS (
|
|
2236
|
+
SELECT lot_id,
|
|
2237
|
+
expires_at,
|
|
2238
|
+
amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc
|
|
2239
|
+
FROM ordered
|
|
1970
2240
|
)
|
|
2241
|
+
UPDATE users
|
|
2242
|
+
SET balance_mc = balance_mc + CASE
|
|
2243
|
+
WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(
|
|
2244
|
+
(SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),
|
|
2245
|
+
0
|
|
2246
|
+
)
|
|
2247
|
+
-- Holds created before allocation provenance cannot be safely
|
|
2248
|
+
-- tied back to a source lot. Treat their unused portion as
|
|
2249
|
+
-- expired instead of minting spendable credits with a guessed
|
|
2250
|
+
-- expiry.
|
|
2251
|
+
ELSE 0
|
|
2252
|
+
END
|
|
1971
2253
|
WHERE id = ?
|
|
1972
2254
|
AND EXISTS (
|
|
1973
2255
|
SELECT 1
|
|
@@ -1984,7 +2266,7 @@ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountM
|
|
|
1984
2266
|
)`,
|
|
1985
2267
|
args: [
|
|
1986
2268
|
debitIdempotencyKey,
|
|
1987
|
-
|
|
2269
|
+
finalAmountMc,
|
|
1988
2270
|
userId,
|
|
1989
2271
|
debitIdempotencyKey,
|
|
1990
2272
|
settlementKey,
|
|
@@ -2005,34 +2287,74 @@ async function settleDebitMcIdempotent(userId, debitIdempotencyKey, finalAmountM
|
|
|
2005
2287
|
args: [settlementKey, userId, billingClass, sourceSurface]
|
|
2006
2288
|
},
|
|
2007
2289
|
{
|
|
2008
|
-
sql: `
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2290
|
+
sql: `WITH ordered AS (
|
|
2291
|
+
SELECT a.lot_id,
|
|
2292
|
+
a.amount_mc,
|
|
2293
|
+
a.expires_at,
|
|
2294
|
+
COALESCE(SUM(a.amount_mc) OVER (
|
|
2295
|
+
ORDER BY a.expires_at ASC, a.lot_id ASC
|
|
2296
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
2297
|
+
), 0) AS prior_mc
|
|
2298
|
+
FROM billing_debit_allocations a
|
|
2299
|
+
WHERE a.debit_idempotency_key = ?
|
|
2300
|
+
), refunds AS (
|
|
2301
|
+
SELECT lot_id,
|
|
2302
|
+
expires_at,
|
|
2303
|
+
amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc
|
|
2304
|
+
FROM ordered
|
|
2305
|
+
)
|
|
2306
|
+
UPDATE credit_lots
|
|
2307
|
+
SET remaining_mc = remaining_mc + COALESCE((SELECT refund_mc FROM refunds WHERE refunds.lot_id = credit_lots.id), 0)
|
|
2308
|
+
WHERE id IN (
|
|
2309
|
+
SELECT lot_id FROM refunds
|
|
2310
|
+
WHERE refund_mc > 0 AND expires_at > datetime('now')
|
|
2311
|
+
)
|
|
2312
|
+
AND EXISTS (SELECT 1 FROM billing_events WHERE idempotency_key = ? AND status = 'applying')`,
|
|
2313
|
+
args: [debitIdempotencyKey, finalAmountMc, settlementKey]
|
|
2023
2314
|
},
|
|
2024
2315
|
{
|
|
2025
|
-
sql: `
|
|
2026
|
-
|
|
2027
|
-
|
|
2316
|
+
sql: `WITH ordered AS (
|
|
2317
|
+
SELECT a.amount_mc,
|
|
2318
|
+
a.expires_at,
|
|
2319
|
+
COALESCE(SUM(a.amount_mc) OVER (
|
|
2320
|
+
ORDER BY a.expires_at ASC, a.lot_id ASC
|
|
2321
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
2322
|
+
), 0) AS prior_mc
|
|
2323
|
+
FROM billing_debit_allocations a
|
|
2324
|
+
WHERE a.debit_idempotency_key = ?
|
|
2325
|
+
), refunds AS (
|
|
2326
|
+
SELECT expires_at,
|
|
2327
|
+
amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc
|
|
2328
|
+
FROM ordered
|
|
2329
|
+
), credited AS (
|
|
2330
|
+
SELECT e.user_id,
|
|
2331
|
+
CASE
|
|
2332
|
+
WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(
|
|
2333
|
+
(SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),
|
|
2334
|
+
0
|
|
2335
|
+
)
|
|
2336
|
+
ELSE 0
|
|
2337
|
+
END AS refund_mc
|
|
2338
|
+
FROM billing_events e
|
|
2339
|
+
JOIN billing_debits d ON d.idempotency_key = ?
|
|
2340
|
+
WHERE e.idempotency_key = ?
|
|
2341
|
+
AND e.status = 'applying'
|
|
2342
|
+
)
|
|
2343
|
+
INSERT INTO ledger (user_id, amount_mc, operation, description)
|
|
2344
|
+
SELECT user_id,
|
|
2345
|
+
refund_mc,
|
|
2028
2346
|
?,
|
|
2029
2347
|
?
|
|
2030
|
-
FROM
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2348
|
+
FROM credited
|
|
2349
|
+
WHERE refund_mc > 0`,
|
|
2350
|
+
args: [
|
|
2351
|
+
debitIdempotencyKey,
|
|
2352
|
+
finalAmountMc,
|
|
2353
|
+
debitIdempotencyKey,
|
|
2354
|
+
settlementKey,
|
|
2355
|
+
refundOperation,
|
|
2356
|
+
description
|
|
2357
|
+
]
|
|
2036
2358
|
},
|
|
2037
2359
|
{
|
|
2038
2360
|
sql: `UPDATE billing_events
|
|
@@ -2311,6 +2633,9 @@ export {
|
|
|
2311
2633
|
setExtraConcurrencySlots,
|
|
2312
2634
|
setConcurrencySubId,
|
|
2313
2635
|
setSubscriptionTier,
|
|
2636
|
+
getConnectedAccountBillingState,
|
|
2637
|
+
setConnectedAccountBillingState,
|
|
2638
|
+
listUsersDueForConnectedAccountBillingReconciliation,
|
|
2314
2639
|
setMemoryPlan,
|
|
2315
2640
|
getMemoryPlan,
|
|
2316
2641
|
getMemoryProvisioned,
|
|
@@ -2338,4 +2663,4 @@ export {
|
|
|
2338
2663
|
insertInboxMessage,
|
|
2339
2664
|
listActiveUserIds
|
|
2340
2665
|
};
|
|
2341
|
-
//# sourceMappingURL=chunk-
|
|
2666
|
+
//# sourceMappingURL=chunk-IFYER7O4.js.map
|