stripe-experiment-sync 1.0.21 → 1.0.23
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 +23 -14
- package/dist/{chunk-G26S5D5J.js → chunk-5TSB2P3P.js} +241 -28
- package/dist/{chunk-OWZ4QNLS.js → chunk-5VGSSPW3.js} +8 -3
- package/dist/chunk-H7FT5PGN.js +52657 -0
- package/dist/chunk-K22YTC4W.js +52136 -0
- package/dist/cli/index.cjs +2086 -2776
- package/dist/cli/index.js +52 -7
- package/dist/cli/lib.cjs +1986 -2784
- package/dist/cli/lib.d.cts +12 -1
- package/dist/cli/lib.d.ts +12 -1
- package/dist/cli/lib.js +6 -4
- package/dist/index.cjs +6509 -2691
- package/dist/index.d.cts +329 -314
- package/dist/index.d.ts +329 -314
- package/dist/index.js +12 -4
- package/dist/migrations/0064_add_created_gte_lte.sql +13 -0
- package/dist/migrations/0065_add_created_lte_to_pk.sql +15 -0
- package/dist/migrations/0066_rate_limits.sql +43 -0
- package/dist/migrations/0067_add_priority_to_sync_obj_runs.sql +10 -0
- package/dist/migrations/0068_sync_obj_progress_view.sql +23 -0
- package/dist/supabase/functions/sigma-data-worker/index.js +238 -0
- package/dist/supabase/functions/stripe-setup/index.js +52137 -0
- package/dist/{chunk-ECLPGCY6.js → supabase/functions/stripe-webhook/index.js} +1840 -3149
- package/dist/supabase/functions/stripe-worker/index.js +47090 -0
- package/dist/supabase/index.cjs +52287 -100
- package/dist/supabase/index.d.cts +22 -14
- package/dist/supabase/index.d.ts +22 -14
- package/dist/supabase/index.js +6 -2
- package/package.json +8 -3
- package/dist/chunk-3KTFUZTY.js +0 -477
package/dist/index.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PostgresClient,
|
|
3
3
|
StripeSync,
|
|
4
|
+
StripeSyncWorker,
|
|
4
5
|
VERSION,
|
|
5
6
|
createStripeWebSocketClient,
|
|
7
|
+
embeddedMigrations,
|
|
8
|
+
getTableName,
|
|
6
9
|
hashApiKey,
|
|
7
|
-
runMigrations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
runMigrations,
|
|
11
|
+
runMigrationsFromContent
|
|
12
|
+
} from "./chunk-K22YTC4W.js";
|
|
13
|
+
import "./chunk-5VGSSPW3.js";
|
|
10
14
|
export {
|
|
11
15
|
PostgresClient,
|
|
12
16
|
StripeSync,
|
|
17
|
+
StripeSyncWorker,
|
|
13
18
|
VERSION,
|
|
14
19
|
createStripeWebSocketClient,
|
|
20
|
+
embeddedMigrations,
|
|
21
|
+
getTableName,
|
|
15
22
|
hashApiKey,
|
|
16
|
-
runMigrations
|
|
23
|
+
runMigrations,
|
|
24
|
+
runMigrationsFromContent
|
|
17
25
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Add created_gte / created_lte columns for time-range partitioned parallel sync.
|
|
2
|
+
-- Workers use these to scope their Stripe list calls to a specific created window.
|
|
3
|
+
-- Stored as Unix epoch seconds (INTEGER) to match Stripe's created filter format.
|
|
4
|
+
-- created_gte defaults to 0 for non-chunked rows (required by PK).
|
|
5
|
+
ALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_gte INTEGER NOT NULL DEFAULT 0;
|
|
6
|
+
ALTER TABLE "stripe"."_sync_obj_runs" ADD COLUMN IF NOT EXISTS created_lte INTEGER;
|
|
7
|
+
|
|
8
|
+
-- Expand PK to include created_gte so multiple time-range chunks of the same object can coexist.
|
|
9
|
+
-- PK constraint kept original name from 0053 (_sync_obj_run_pkey) after table rename in 0057.
|
|
10
|
+
ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";
|
|
11
|
+
ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_run_pkey";
|
|
12
|
+
ALTER TABLE "stripe"."_sync_obj_runs"
|
|
13
|
+
ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Include created_lte in the PK so chunks with the same created_gte but
|
|
2
|
+
-- different created_lte can coexist. Requires a NOT NULL default first.
|
|
3
|
+
ALTER TABLE "stripe"."_sync_obj_runs"
|
|
4
|
+
ALTER COLUMN created_lte SET DEFAULT 0;
|
|
5
|
+
|
|
6
|
+
UPDATE "stripe"."_sync_obj_runs"
|
|
7
|
+
SET created_lte = 0
|
|
8
|
+
WHERE created_lte IS NULL;
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "stripe"."_sync_obj_runs"
|
|
11
|
+
ALTER COLUMN created_lte SET NOT NULL;
|
|
12
|
+
|
|
13
|
+
ALTER TABLE "stripe"."_sync_obj_runs" DROP CONSTRAINT IF EXISTS "_sync_obj_runs_pkey";
|
|
14
|
+
ALTER TABLE "stripe"."_sync_obj_runs"
|
|
15
|
+
ADD CONSTRAINT "_sync_obj_runs_pkey" PRIMARY KEY ("_account_id", run_started_at, object, created_gte, created_lte);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Rate limiting table and function for cross-process request throttling.
|
|
2
|
+
-- Used by claimNextTask to cap how many claims/sec hit the database.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS "stripe"."_rate_limits" (
|
|
5
|
+
key TEXT PRIMARY KEY,
|
|
6
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
7
|
+
window_start TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE OR REPLACE FUNCTION "stripe".check_rate_limit(
|
|
11
|
+
rate_key TEXT,
|
|
12
|
+
max_requests INTEGER,
|
|
13
|
+
window_seconds INTEGER
|
|
14
|
+
)
|
|
15
|
+
RETURNS VOID AS $$
|
|
16
|
+
DECLARE
|
|
17
|
+
now TIMESTAMPTZ := clock_timestamp();
|
|
18
|
+
window_length INTERVAL := make_interval(secs => window_seconds);
|
|
19
|
+
current_count INTEGER;
|
|
20
|
+
BEGIN
|
|
21
|
+
PERFORM pg_advisory_xact_lock(hashtext(rate_key));
|
|
22
|
+
|
|
23
|
+
INSERT INTO "stripe"."_rate_limits" (key, count, window_start)
|
|
24
|
+
VALUES (rate_key, 1, now)
|
|
25
|
+
ON CONFLICT (key) DO UPDATE
|
|
26
|
+
SET count = CASE
|
|
27
|
+
WHEN "_rate_limits".window_start + window_length <= now
|
|
28
|
+
THEN 1
|
|
29
|
+
ELSE "_rate_limits".count + 1
|
|
30
|
+
END,
|
|
31
|
+
window_start = CASE
|
|
32
|
+
WHEN "_rate_limits".window_start + window_length <= now
|
|
33
|
+
THEN now
|
|
34
|
+
ELSE "_rate_limits".window_start
|
|
35
|
+
END;
|
|
36
|
+
|
|
37
|
+
SELECT count INTO current_count FROM "stripe"."_rate_limits" WHERE key = rate_key;
|
|
38
|
+
|
|
39
|
+
IF current_count > max_requests THEN
|
|
40
|
+
RAISE EXCEPTION 'Rate limit exceeded for %', rate_key;
|
|
41
|
+
END IF;
|
|
42
|
+
END;
|
|
43
|
+
$$ LANGUAGE plpgsql;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Add priority column to _sync_obj_runs for deterministic task ordering.
|
|
2
|
+
-- Priority mirrors the `order` field from resourceRegistry so workers
|
|
3
|
+
-- always process parent resources (products, prices) before children
|
|
4
|
+
-- (subscriptions, invoices).
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "stripe"."_sync_obj_runs"
|
|
7
|
+
ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0;
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS idx_sync_obj_runs_priority
|
|
10
|
+
ON "stripe"."_sync_obj_runs" ("_account_id", run_started_at, status, priority);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Per-object sync progress view for monitoring.
|
|
2
|
+
-- Defaults to the newest run per account; callers can filter by a specific
|
|
3
|
+
-- run_started_at if needed.
|
|
4
|
+
|
|
5
|
+
DROP FUNCTION IF EXISTS "stripe"."sync_obj_progress"(TEXT, TIMESTAMPTZ);
|
|
6
|
+
|
|
7
|
+
CREATE OR REPLACE VIEW "stripe"."sync_obj_progress" AS
|
|
8
|
+
SELECT
|
|
9
|
+
r."_account_id" AS account_id,
|
|
10
|
+
r.run_started_at,
|
|
11
|
+
r.object,
|
|
12
|
+
ROUND(
|
|
13
|
+
100.0 * COUNT(*) FILTER (WHERE r.status = 'complete') / NULLIF(COUNT(*), 0),
|
|
14
|
+
1
|
|
15
|
+
) AS pct_complete,
|
|
16
|
+
COALESCE(SUM(r.processed_count), 0) AS processed
|
|
17
|
+
FROM "stripe"."_sync_obj_runs" r
|
|
18
|
+
WHERE r.run_started_at = (
|
|
19
|
+
SELECT MAX(s.started_at)
|
|
20
|
+
FROM "stripe"."_sync_runs" s
|
|
21
|
+
WHERE s."_account_id" = r."_account_id"
|
|
22
|
+
)
|
|
23
|
+
GROUP BY r."_account_id", r.run_started_at, r.object;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/supabase/edge-functions/sigma-data-worker.ts
|
|
2
|
+
import { StripeSync } from "npm:stripe-experiment-sync";
|
|
3
|
+
import postgres from "npm:postgres";
|
|
4
|
+
var BATCH_SIZE = 1;
|
|
5
|
+
var MAX_RUN_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
6
|
+
var jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
7
|
+
status,
|
|
8
|
+
headers: { "Content-Type": "application/json" }
|
|
9
|
+
});
|
|
10
|
+
Deno.serve(async (req) => {
|
|
11
|
+
const authHeader = req.headers.get("Authorization");
|
|
12
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
13
|
+
return new Response("Unauthorized", { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
const token = authHeader.substring(7);
|
|
16
|
+
const dbUrl = Deno.env.get("SUPABASE_DB_URL");
|
|
17
|
+
if (!dbUrl) {
|
|
18
|
+
return jsonResponse({ error: "SUPABASE_DB_URL not set" }, 500);
|
|
19
|
+
}
|
|
20
|
+
let sql;
|
|
21
|
+
let stripeSync;
|
|
22
|
+
try {
|
|
23
|
+
sql = postgres(dbUrl, { max: 1, prepare: false });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return jsonResponse(
|
|
26
|
+
{
|
|
27
|
+
error: "Failed to create postgres connection",
|
|
28
|
+
details: error.message,
|
|
29
|
+
stack: error.stack
|
|
30
|
+
},
|
|
31
|
+
500
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const vaultResult = await sql`
|
|
36
|
+
SELECT decrypted_secret
|
|
37
|
+
FROM vault.decrypted_secrets
|
|
38
|
+
WHERE name = 'stripe_sigma_worker_secret'
|
|
39
|
+
`;
|
|
40
|
+
if (vaultResult.length === 0) {
|
|
41
|
+
await sql.end();
|
|
42
|
+
return new Response("Sigma worker secret not configured in vault", { status: 500 });
|
|
43
|
+
}
|
|
44
|
+
const storedSecret = vaultResult[0].decrypted_secret;
|
|
45
|
+
if (token !== storedSecret) {
|
|
46
|
+
await sql.end();
|
|
47
|
+
return new Response("Forbidden: Invalid sigma worker secret", { status: 403 });
|
|
48
|
+
}
|
|
49
|
+
stripeSync = await StripeSync.create({
|
|
50
|
+
poolConfig: { connectionString: dbUrl, max: 1 },
|
|
51
|
+
stripeSecretKey: Deno.env.get("STRIPE_SECRET_KEY"),
|
|
52
|
+
enableSigma: true,
|
|
53
|
+
sigmaPageSizeOverride: 1e3
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await sql.end();
|
|
57
|
+
return jsonResponse(
|
|
58
|
+
{
|
|
59
|
+
error: "Failed to create StripeSync",
|
|
60
|
+
details: error.message,
|
|
61
|
+
stack: error.stack
|
|
62
|
+
},
|
|
63
|
+
500
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const accountId = await stripeSync.getAccountId();
|
|
68
|
+
const sigmaObjects = stripeSync.getSupportedSigmaObjects();
|
|
69
|
+
if (sigmaObjects.length === 0) {
|
|
70
|
+
return jsonResponse({ message: "No Sigma objects configured for sync" });
|
|
71
|
+
}
|
|
72
|
+
const runResult = await stripeSync.postgresClient.getOrCreateSyncRun(accountId, "sigma-worker");
|
|
73
|
+
const runStartedAt = runResult?.runStartedAt ?? (await stripeSync.postgresClient.getActiveSyncRun(accountId, "sigma-worker"))?.runStartedAt;
|
|
74
|
+
if (!runStartedAt) {
|
|
75
|
+
throw new Error("Failed to get or create sync run for sigma worker");
|
|
76
|
+
}
|
|
77
|
+
await stripeSync.postgresClient.query(
|
|
78
|
+
`UPDATE "stripe"."_sync_obj_runs"
|
|
79
|
+
SET status = 'error',
|
|
80
|
+
error_message = 'Legacy sigma worker prefix run (sigma.*); superseded by unprefixed runs',
|
|
81
|
+
completed_at = now()
|
|
82
|
+
WHERE "_account_id" = $1
|
|
83
|
+
AND run_started_at = $2
|
|
84
|
+
AND object LIKE 'sigma.%'
|
|
85
|
+
AND status IN ('pending', 'running')`,
|
|
86
|
+
[accountId, runStartedAt]
|
|
87
|
+
);
|
|
88
|
+
const runAgeMs = Date.now() - runStartedAt.getTime();
|
|
89
|
+
if (runAgeMs > MAX_RUN_AGE_MS) {
|
|
90
|
+
console.warn(
|
|
91
|
+
`Sigma worker: run too old (${Math.round(runAgeMs / 1e3 / 60)} min), closing without self-trigger`
|
|
92
|
+
);
|
|
93
|
+
await stripeSync.postgresClient.closeSyncRun(accountId, runStartedAt);
|
|
94
|
+
return jsonResponse({
|
|
95
|
+
message: "Sigma run exceeded max age, closed without processing",
|
|
96
|
+
runAgeMinutes: Math.round(runAgeMs / 1e3 / 60),
|
|
97
|
+
selfTriggered: false
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
await stripeSync.postgresClient.createObjectRuns(accountId, runStartedAt, sigmaObjects);
|
|
101
|
+
await stripeSync.postgresClient.ensureSyncRunMaxConcurrent(accountId, runStartedAt, BATCH_SIZE);
|
|
102
|
+
const runningObjects = await stripeSync.postgresClient.listObjectsByStatus(
|
|
103
|
+
accountId,
|
|
104
|
+
runStartedAt,
|
|
105
|
+
"running",
|
|
106
|
+
sigmaObjects
|
|
107
|
+
);
|
|
108
|
+
const objectsToProcess = runningObjects.slice(0, BATCH_SIZE);
|
|
109
|
+
let pendingObjects = [];
|
|
110
|
+
if (objectsToProcess.length === 0) {
|
|
111
|
+
pendingObjects = await stripeSync.postgresClient.listObjectsByStatus(
|
|
112
|
+
accountId,
|
|
113
|
+
runStartedAt,
|
|
114
|
+
"pending",
|
|
115
|
+
sigmaObjects
|
|
116
|
+
);
|
|
117
|
+
for (const objectKey of pendingObjects) {
|
|
118
|
+
if (objectsToProcess.length >= BATCH_SIZE) break;
|
|
119
|
+
const started = await stripeSync.postgresClient.tryStartObjectSync(
|
|
120
|
+
accountId,
|
|
121
|
+
runStartedAt,
|
|
122
|
+
objectKey
|
|
123
|
+
);
|
|
124
|
+
if (started) {
|
|
125
|
+
objectsToProcess.push(objectKey);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (objectsToProcess.length === 0) {
|
|
130
|
+
if (pendingObjects.length === 0) {
|
|
131
|
+
console.info("Sigma worker: all objects complete or errored - run finished");
|
|
132
|
+
return jsonResponse({ message: "Sigma sync run complete", selfTriggered: false });
|
|
133
|
+
}
|
|
134
|
+
console.info("Sigma worker: at concurrency limit, will self-trigger", {
|
|
135
|
+
pendingCount: pendingObjects.length
|
|
136
|
+
});
|
|
137
|
+
let selfTriggered2 = false;
|
|
138
|
+
try {
|
|
139
|
+
await sql`SELECT stripe.trigger_sigma_worker()`;
|
|
140
|
+
selfTriggered2 = true;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn("Failed to self-trigger sigma worker:", error.message);
|
|
143
|
+
}
|
|
144
|
+
return jsonResponse({
|
|
145
|
+
message: "At concurrency limit",
|
|
146
|
+
pendingCount: pendingObjects.length,
|
|
147
|
+
selfTriggered: selfTriggered2
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const results = [];
|
|
151
|
+
for (const object of objectsToProcess) {
|
|
152
|
+
const objectKey = object;
|
|
153
|
+
try {
|
|
154
|
+
console.info(`Sigma worker: processing ${object}`);
|
|
155
|
+
const result = await stripeSync.processNext(object, {
|
|
156
|
+
runStartedAt,
|
|
157
|
+
triggeredBy: "sigma-worker"
|
|
158
|
+
});
|
|
159
|
+
results.push({
|
|
160
|
+
object,
|
|
161
|
+
processed: result.processed,
|
|
162
|
+
hasMore: result.hasMore,
|
|
163
|
+
status: "success"
|
|
164
|
+
});
|
|
165
|
+
if (result.hasMore) {
|
|
166
|
+
console.info(
|
|
167
|
+
`Sigma worker: ${object} has more pages, processed ${result.processed} rows so far`
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
console.info(`Sigma worker: ${object} complete, processed ${result.processed} rows`);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(`Sigma worker: error processing ${object}:`, error);
|
|
174
|
+
await stripeSync.postgresClient.failObjectSync(
|
|
175
|
+
accountId,
|
|
176
|
+
runStartedAt,
|
|
177
|
+
objectKey,
|
|
178
|
+
error.message ?? "Unknown error"
|
|
179
|
+
);
|
|
180
|
+
results.push({
|
|
181
|
+
object,
|
|
182
|
+
processed: 0,
|
|
183
|
+
hasMore: false,
|
|
184
|
+
status: "error",
|
|
185
|
+
error: error.message
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const pendingAfter = await stripeSync.postgresClient.listObjectsByStatus(
|
|
190
|
+
accountId,
|
|
191
|
+
runStartedAt,
|
|
192
|
+
"pending",
|
|
193
|
+
sigmaObjects
|
|
194
|
+
);
|
|
195
|
+
const runningAfter = await stripeSync.postgresClient.listObjectsByStatus(
|
|
196
|
+
accountId,
|
|
197
|
+
runStartedAt,
|
|
198
|
+
"running",
|
|
199
|
+
sigmaObjects
|
|
200
|
+
);
|
|
201
|
+
const remainingMs = MAX_RUN_AGE_MS - (Date.now() - runStartedAt.getTime());
|
|
202
|
+
const remainingMinutes = Math.round(remainingMs / 1e3 / 60);
|
|
203
|
+
const shouldSelfTrigger = (pendingAfter.length > 0 || runningAfter.length > 0) && remainingMs > 0;
|
|
204
|
+
let selfTriggered = false;
|
|
205
|
+
if (shouldSelfTrigger) {
|
|
206
|
+
console.info("Sigma worker: more work remains, self-triggering", {
|
|
207
|
+
pending: pendingAfter.length,
|
|
208
|
+
running: runningAfter.length,
|
|
209
|
+
remainingMinutes
|
|
210
|
+
});
|
|
211
|
+
try {
|
|
212
|
+
await sql`SELECT stripe.trigger_sigma_worker()`;
|
|
213
|
+
selfTriggered = true;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.warn("Failed to self-trigger sigma worker:", error.message);
|
|
216
|
+
}
|
|
217
|
+
} else if (pendingAfter.length > 0 || runningAfter.length > 0) {
|
|
218
|
+
console.warn("Sigma worker: work remains but run timed out, closing", {
|
|
219
|
+
pending: pendingAfter.length,
|
|
220
|
+
running: runningAfter.length
|
|
221
|
+
});
|
|
222
|
+
await stripeSync.postgresClient.closeSyncRun(accountId, runStartedAt);
|
|
223
|
+
} else {
|
|
224
|
+
console.info("Sigma worker: no more work, run complete");
|
|
225
|
+
}
|
|
226
|
+
return jsonResponse({
|
|
227
|
+
results,
|
|
228
|
+
selfTriggered,
|
|
229
|
+
remaining: { pending: pendingAfter.length, running: runningAfter.length }
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error("Sigma worker error:", error);
|
|
233
|
+
return jsonResponse({ error: error.message, stack: error.stack }, 500);
|
|
234
|
+
} finally {
|
|
235
|
+
if (sql) await sql.end();
|
|
236
|
+
if (stripeSync) await stripeSync.postgresClient.pool.end();
|
|
237
|
+
}
|
|
238
|
+
});
|