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/README.md
CHANGED
|
@@ -39,7 +39,7 @@ const sync = new StripeSync({
|
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
// Create a managed webhook - no additional processing needed!
|
|
42
|
-
const webhook = await sync.findOrCreateManagedWebhook('https://example.com/stripe-webhooks')
|
|
42
|
+
const webhook = await sync.webhook.findOrCreateManagedWebhook('https://example.com/stripe-webhooks')
|
|
43
43
|
|
|
44
44
|
// Cleanup when done (closes PostgreSQL connection pool)
|
|
45
45
|
await sync.close()
|
|
@@ -54,7 +54,7 @@ The Stripe Sync Engine automatically manages webhook endpoints and their process
|
|
|
54
54
|
```typescript
|
|
55
55
|
// Create or reuse an existing webhook endpoint
|
|
56
56
|
// This webhook will automatically sync all Stripe events to your database
|
|
57
|
-
const webhook = await sync.findOrCreateManagedWebhook('https://example.com/stripe-webhooks')
|
|
57
|
+
const webhook = await sync.webhook.findOrCreateManagedWebhook('https://example.com/stripe-webhooks')
|
|
58
58
|
|
|
59
59
|
// Create a webhook for specific events
|
|
60
60
|
const webhook = await sync.createManagedWebhook('https://example.com/stripe-webhooks', {
|
|
@@ -71,7 +71,7 @@ console.log(webhook.secret) // whsec_xxx
|
|
|
71
71
|
|
|
72
72
|
```typescript
|
|
73
73
|
// List all managed webhooks
|
|
74
|
-
const webhooks = await sync.listManagedWebhooks()
|
|
74
|
+
const webhooks = await sync.webhook.listManagedWebhooks()
|
|
75
75
|
|
|
76
76
|
// Get a specific webhook
|
|
77
77
|
const webhook = await sync.getManagedWebhook('we_xxx')
|
|
@@ -104,7 +104,7 @@ app.post('/stripe-webhooks', async (req, res) => {
|
|
|
104
104
|
const payload = req.body
|
|
105
105
|
|
|
106
106
|
try {
|
|
107
|
-
await sync.processWebhook(payload, signature)
|
|
107
|
+
await sync.webhook.processWebhook(payload, signature)
|
|
108
108
|
res.status(200).send({ received: true })
|
|
109
109
|
} catch (error) {
|
|
110
110
|
res.status(400).send({ error: error.message })
|
|
@@ -132,9 +132,6 @@ await sync.close()
|
|
|
132
132
|
| `autoExpandLists` | boolean | Fetch all list items from Stripe (not just the default 10) |
|
|
133
133
|
| `backfillRelatedEntities` | boolean | Ensure related entities exist for foreign key integrity |
|
|
134
134
|
| `revalidateObjectsViaStripeApi` | Array | Always fetch latest data from Stripe instead of trusting webhook payload. Possible values: charge, credit_note, customer, dispute, invoice, payment_intent, payment_method, plan, price, product, refund, review, radar.early_fraud_warning, setup_intent, subscription, subscription_schedule, tax_id |
|
|
135
|
-
| `maxRetries` | number | Maximum retry attempts for 429 rate limits. Default: 5 |
|
|
136
|
-
| `initialRetryDelayMs` | number | Initial retry delay in milliseconds. Default: 1000 |
|
|
137
|
-
| `maxRetryDelayMs` | number | Maximum retry delay in milliseconds. Default: 60000 |
|
|
138
135
|
| `logger` | Logger | Logger instance (pino-compatible) |
|
|
139
136
|
|
|
140
137
|
## Database Schema
|
|
@@ -185,17 +182,14 @@ await sync.syncSingleEntity('prod_xyz')
|
|
|
185
182
|
### Backfill Historical Data
|
|
186
183
|
|
|
187
184
|
```ts
|
|
188
|
-
// Sync all products
|
|
189
|
-
await sync.
|
|
190
|
-
object: 'product',
|
|
191
|
-
created: { gte: 1643872333 }, // Unix timestamp
|
|
192
|
-
})
|
|
185
|
+
// Sync all products
|
|
186
|
+
await sync.fullSync(['product'])
|
|
193
187
|
|
|
194
188
|
// Sync all customers
|
|
195
|
-
await sync.
|
|
189
|
+
await sync.fullSync(['customer'])
|
|
196
190
|
|
|
197
191
|
// Sync everything
|
|
198
|
-
await sync.
|
|
192
|
+
await sync.fullSync()
|
|
199
193
|
```
|
|
200
194
|
|
|
201
195
|
Supported objects: `all`, `charge`, `checkout_sessions`, `credit_note`, `customer`, `customer_with_entitlements`, `dispute`, `early_fraud_warning`, `invoice`, `payment_intent`, `payment_method`, `plan`, `price`, `product`, `refund`, `setup_intent`, `subscription`, `subscription_schedules`, `tax_id`.
|
|
@@ -277,6 +271,21 @@ The install command will:
|
|
|
277
271
|
3. Create a managed Stripe webhook pointing to your Supabase project
|
|
278
272
|
4. Set up a pg_cron job for automatic background syncing
|
|
279
273
|
|
|
274
|
+
### Required Permissions
|
|
275
|
+
|
|
276
|
+
The Supabase access token must have the following Management API permissions:
|
|
277
|
+
|
|
278
|
+
| Permission | Used For |
|
|
279
|
+
| ------------------------------ | ------------------------------------------------------------ |
|
|
280
|
+
| `projects_read` | Validate project access and existence |
|
|
281
|
+
| `edge_functions_write` | Deploy Edge Functions (setup, webhook, worker, sigma-worker) |
|
|
282
|
+
| `edge_functions_secrets_write` | Set Stripe API key and configuration secrets |
|
|
283
|
+
| `database_write` | Execute database migrations and schema setup |
|
|
284
|
+
| `database_read` | Check schema existence and verify installation |
|
|
285
|
+
| `api_gateway_keys_read` | Retrieve project's anon API key |
|
|
286
|
+
|
|
287
|
+
**Note:** When generating a personal access token or using OAuth2, ensure these permissions are granted. For OAuth2 tokens, these correspond to the fine-grained token permissions on the Management API.
|
|
288
|
+
|
|
280
289
|
## CLI Commands
|
|
281
290
|
|
|
282
291
|
```bash
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
StripeSync,
|
|
4
4
|
createStripeWebSocketClient,
|
|
5
5
|
runMigrations
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-K22YTC4W.js";
|
|
7
7
|
import {
|
|
8
8
|
install,
|
|
9
9
|
uninstall
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-H7FT5PGN.js";
|
|
11
11
|
|
|
12
12
|
// src/cli/config.ts
|
|
13
13
|
import dotenv from "dotenv";
|
|
@@ -111,6 +111,73 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
111
111
|
import chalk3 from "chalk";
|
|
112
112
|
import express from "express";
|
|
113
113
|
import dotenv2 from "dotenv";
|
|
114
|
+
async function monitorCommand(options) {
|
|
115
|
+
try {
|
|
116
|
+
dotenv2.config();
|
|
117
|
+
let databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
|
|
118
|
+
if (!databaseUrl) {
|
|
119
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
120
|
+
const answers = await inquirer2.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: "password",
|
|
123
|
+
name: "databaseUrl",
|
|
124
|
+
message: "Enter your Postgres DATABASE_URL:",
|
|
125
|
+
mask: "*",
|
|
126
|
+
validate: (input) => {
|
|
127
|
+
if (!input || input.trim() === "") return "DATABASE_URL is required";
|
|
128
|
+
if (!input.startsWith("postgres://") && !input.startsWith("postgresql://"))
|
|
129
|
+
return 'DATABASE_URL should start with "postgres://" or "postgresql://"';
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]);
|
|
134
|
+
databaseUrl = answers.databaseUrl;
|
|
135
|
+
}
|
|
136
|
+
const poolConfig = {
|
|
137
|
+
max: 1,
|
|
138
|
+
connectionString: databaseUrl,
|
|
139
|
+
keepAlive: true
|
|
140
|
+
};
|
|
141
|
+
const stripeSync = await StripeSync.create({
|
|
142
|
+
databaseUrl,
|
|
143
|
+
stripeSecretKey: options.stripeKey || process.env.STRIPE_API_KEY || process.env.STRIPE_SECRET_KEY || "sk_placeholder",
|
|
144
|
+
poolConfig
|
|
145
|
+
});
|
|
146
|
+
console.log(chalk3.blue("Monitoring table row counts (Ctrl-C to stop)...\n"));
|
|
147
|
+
const activeRun = await stripeSync.postgresClient.getActiveSyncRun(stripeSync.accountId);
|
|
148
|
+
if (!activeRun) {
|
|
149
|
+
const lastCompleted = await stripeSync.postgresClient.getCompletedRun(
|
|
150
|
+
stripeSync.accountId,
|
|
151
|
+
Infinity
|
|
152
|
+
);
|
|
153
|
+
if (lastCompleted) {
|
|
154
|
+
console.log(
|
|
155
|
+
chalk3.green(
|
|
156
|
+
`No active sync run. Last completed at ${lastCompleted.runStartedAt.toISOString()}`
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
console.log(chalk3.yellow("No active or completed sync runs found."));
|
|
161
|
+
}
|
|
162
|
+
await stripeSync.close();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const interval = stripeSync.startTableMonitor(2e3, activeRun);
|
|
166
|
+
const cleanup = () => {
|
|
167
|
+
clearInterval(interval);
|
|
168
|
+
stripeSync.close().finally(() => process.exit(0));
|
|
169
|
+
};
|
|
170
|
+
process.on("SIGINT", cleanup);
|
|
171
|
+
process.on("SIGTERM", cleanup);
|
|
172
|
+
await new Promise(() => {
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof Error) {
|
|
176
|
+
console.error(chalk3.red(error.message));
|
|
177
|
+
}
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
114
181
|
var VALID_SYNC_OBJECTS = [
|
|
115
182
|
"all",
|
|
116
183
|
"customer",
|
|
@@ -219,7 +286,7 @@ async function backfillCommand(options, entityName) {
|
|
|
219
286
|
connectionString: config.databaseUrl,
|
|
220
287
|
keepAlive: true
|
|
221
288
|
};
|
|
222
|
-
stripeSync =
|
|
289
|
+
stripeSync = await StripeSync.create({
|
|
223
290
|
databaseUrl: config.databaseUrl,
|
|
224
291
|
stripeSecretKey: config.stripeApiKey,
|
|
225
292
|
enableSigma,
|
|
@@ -229,12 +296,7 @@ async function backfillCommand(options, entityName) {
|
|
|
229
296
|
poolConfig
|
|
230
297
|
});
|
|
231
298
|
if (entityName === "all") {
|
|
232
|
-
const backfill = await stripeSync.
|
|
233
|
-
object: "all",
|
|
234
|
-
triggeredBy: "cli-backfill",
|
|
235
|
-
maxParallel: 10,
|
|
236
|
-
skipInaccessibleSigmaTables: true
|
|
237
|
-
});
|
|
299
|
+
const backfill = await stripeSync.fullSync();
|
|
238
300
|
const objectCount = Object.keys(backfill.totals).length;
|
|
239
301
|
console.log(
|
|
240
302
|
chalk3.green(
|
|
@@ -255,14 +317,19 @@ async function backfillCommand(options, entityName) {
|
|
|
255
317
|
}
|
|
256
318
|
}
|
|
257
319
|
} else {
|
|
258
|
-
const result = await stripeSync.
|
|
259
|
-
|
|
260
|
-
|
|
320
|
+
const result = await stripeSync.fullSync(
|
|
321
|
+
[entityName],
|
|
322
|
+
true,
|
|
323
|
+
20,
|
|
324
|
+
10,
|
|
325
|
+
true,
|
|
261
326
|
0
|
|
262
327
|
);
|
|
263
328
|
const tableType = isSigmaTable ? "(sigma)" : "";
|
|
264
329
|
console.log(
|
|
265
|
-
chalk3.green(
|
|
330
|
+
chalk3.green(
|
|
331
|
+
`\u2713 Full sync complete: ${result.totalSynced} ${entityName} ${tableType} rows synced`
|
|
332
|
+
)
|
|
266
333
|
);
|
|
267
334
|
}
|
|
268
335
|
await stripeSync.close();
|
|
@@ -337,7 +404,7 @@ async function migrateCommand(options) {
|
|
|
337
404
|
}
|
|
338
405
|
}
|
|
339
406
|
async function syncCommand(options) {
|
|
340
|
-
let stripeSync
|
|
407
|
+
let stripeSync;
|
|
341
408
|
let tunnel = null;
|
|
342
409
|
let server = null;
|
|
343
410
|
let webhookId = null;
|
|
@@ -357,7 +424,7 @@ Cleaning up... (signal: ${signal || "manual"})`));
|
|
|
357
424
|
const keepWebhooksOnShutdown = process.env.KEEP_WEBHOOKS_ON_SHUTDOWN === "true";
|
|
358
425
|
if (webhookId && stripeSync && !keepWebhooksOnShutdown) {
|
|
359
426
|
try {
|
|
360
|
-
await stripeSync.deleteManagedWebhook(webhookId);
|
|
427
|
+
await stripeSync.webhook.deleteManagedWebhook(webhookId);
|
|
361
428
|
console.log(chalk3.green("\u2713 Webhook cleanup complete"));
|
|
362
429
|
} catch {
|
|
363
430
|
console.log(chalk3.yellow("\u26A0 Could not delete webhook"));
|
|
@@ -420,7 +487,7 @@ Mode: ${modeLabel}`));
|
|
|
420
487
|
connectionString: config.databaseUrl,
|
|
421
488
|
keepAlive: true
|
|
422
489
|
};
|
|
423
|
-
stripeSync =
|
|
490
|
+
stripeSync = await StripeSync.create({
|
|
424
491
|
databaseUrl: config.databaseUrl,
|
|
425
492
|
stripeSecretKey: config.stripeApiKey,
|
|
426
493
|
enableSigma: config.enableSigma,
|
|
@@ -439,7 +506,7 @@ Mode: ${modeLabel}`));
|
|
|
439
506
|
const payload = JSON.parse(event.event_payload);
|
|
440
507
|
console.log(chalk3.cyan(`\u2190 ${payload.type}`) + chalk3.gray(` (${payload.id})`));
|
|
441
508
|
if (stripeSync) {
|
|
442
|
-
await stripeSync.processEvent(payload);
|
|
509
|
+
await stripeSync.webhook.processEvent(payload);
|
|
443
510
|
return {
|
|
444
511
|
status: 200,
|
|
445
512
|
event_type: payload.type,
|
|
@@ -473,7 +540,9 @@ Mode: ${modeLabel}`));
|
|
|
473
540
|
tunnel = await createTunnel(port, config.ngrokAuthToken);
|
|
474
541
|
const webhookPath = process.env.WEBHOOK_PATH || "/stripe-webhooks";
|
|
475
542
|
console.log(chalk3.blue("\nCreating Stripe webhook endpoint..."));
|
|
476
|
-
const webhook = await stripeSync.findOrCreateManagedWebhook(
|
|
543
|
+
const webhook = await stripeSync.webhook.findOrCreateManagedWebhook(
|
|
544
|
+
`${tunnel.url}${webhookPath}`
|
|
545
|
+
);
|
|
477
546
|
webhookId = webhook.id;
|
|
478
547
|
const eventCount = webhook.enabled_events?.length || 0;
|
|
479
548
|
console.log(chalk3.green(`\u2713 Webhook created: ${webhook.id}`));
|
|
@@ -494,7 +563,7 @@ Mode: ${modeLabel}`));
|
|
|
494
563
|
return res.status(400).send({ error: "Missing raw body for signature verification" });
|
|
495
564
|
}
|
|
496
565
|
try {
|
|
497
|
-
await stripeSync.processWebhook(rawBody, sig);
|
|
566
|
+
await stripeSync.webhook.processWebhook(rawBody, sig);
|
|
498
567
|
return res.status(200).send({ received: true });
|
|
499
568
|
} catch (error) {
|
|
500
569
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -518,11 +587,7 @@ Starting server on port ${port}...`));
|
|
|
518
587
|
throw new Error("StripeSync not initialized.");
|
|
519
588
|
}
|
|
520
589
|
console.log(chalk3.blue("\nStarting historical backfill (parallel sweep)..."));
|
|
521
|
-
const backfill = await stripeSync.
|
|
522
|
-
triggeredBy: "cli-historical-backfill",
|
|
523
|
-
maxParallel: 10,
|
|
524
|
-
skipInaccessibleSigmaTables: true
|
|
525
|
-
});
|
|
590
|
+
const backfill = await stripeSync.fullSync();
|
|
526
591
|
const objectCount = Object.keys(backfill.totals).length;
|
|
527
592
|
console.log(
|
|
528
593
|
chalk3.green(
|
|
@@ -543,9 +608,6 @@ Starting server on port ${port}...`));
|
|
|
543
608
|
)
|
|
544
609
|
);
|
|
545
610
|
}
|
|
546
|
-
console.log(chalk3.blue("\nStarting incremental backfill..."));
|
|
547
|
-
await stripeSync.processUntilDone();
|
|
548
|
-
console.log(chalk3.green("\u2713 Incremental backfill complete"));
|
|
549
611
|
} else {
|
|
550
612
|
console.log(chalk3.yellow("\n\u23ED\uFE0F Skipping initial sync (SKIP_BACKFILL=true)"));
|
|
551
613
|
}
|
|
@@ -562,6 +624,145 @@ Starting server on port ${port}...`));
|
|
|
562
624
|
process.exit(1);
|
|
563
625
|
}
|
|
564
626
|
}
|
|
627
|
+
async function fullSyncCommand(options) {
|
|
628
|
+
let stripeSync = null;
|
|
629
|
+
try {
|
|
630
|
+
dotenv2.config();
|
|
631
|
+
const enableSigma = options.enableSigma ?? process.env.ENABLE_SIGMA === "true";
|
|
632
|
+
const intervalSeconds = options.interval ?? 86400;
|
|
633
|
+
let stripeApiKey = options.stripeKey || process.env.STRIPE_API_KEY || process.env.STRIPE_SECRET_KEY || "";
|
|
634
|
+
let databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
|
|
635
|
+
if (!stripeApiKey || !databaseUrl) {
|
|
636
|
+
const inquirer2 = (await import("inquirer")).default;
|
|
637
|
+
const questions = [];
|
|
638
|
+
if (!stripeApiKey) {
|
|
639
|
+
questions.push({
|
|
640
|
+
type: "password",
|
|
641
|
+
name: "stripeApiKey",
|
|
642
|
+
message: "Enter your Stripe API key:",
|
|
643
|
+
mask: "*",
|
|
644
|
+
validate: (input) => {
|
|
645
|
+
if (!input || input.trim() === "") {
|
|
646
|
+
return "Stripe API key is required";
|
|
647
|
+
}
|
|
648
|
+
if (!input.startsWith("sk_") && !input.startsWith("rk_")) {
|
|
649
|
+
return 'Stripe API key should start with "sk_" or "rk_"';
|
|
650
|
+
}
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (!databaseUrl) {
|
|
656
|
+
questions.push({
|
|
657
|
+
type: "password",
|
|
658
|
+
name: "databaseUrl",
|
|
659
|
+
message: "Enter your Postgres DATABASE_URL:",
|
|
660
|
+
mask: "*",
|
|
661
|
+
validate: (input) => {
|
|
662
|
+
if (!input || input.trim() === "") {
|
|
663
|
+
return "DATABASE_URL is required";
|
|
664
|
+
}
|
|
665
|
+
if (!input.startsWith("postgres://") && !input.startsWith("postgresql://")) {
|
|
666
|
+
return 'DATABASE_URL should start with "postgres://" or "postgresql://"';
|
|
667
|
+
}
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
if (questions.length > 0) {
|
|
673
|
+
console.log(chalk3.yellow("\nMissing required configuration. Please provide:"));
|
|
674
|
+
const answers = await inquirer2.prompt(questions);
|
|
675
|
+
if (answers.stripeApiKey) stripeApiKey = answers.stripeApiKey;
|
|
676
|
+
if (answers.databaseUrl) databaseUrl = answers.databaseUrl;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const config = {
|
|
680
|
+
stripeApiKey,
|
|
681
|
+
databaseUrl
|
|
682
|
+
};
|
|
683
|
+
console.log(chalk3.blue("\nPerforming full resync of all Stripe data..."));
|
|
684
|
+
console.log(chalk3.gray(`Database: ${config.databaseUrl.replace(/:[^:@]+@/, ":****@")}`));
|
|
685
|
+
console.log(chalk3.gray(`Reconciliation interval: ${intervalSeconds}s`));
|
|
686
|
+
try {
|
|
687
|
+
await runMigrations({
|
|
688
|
+
databaseUrl: config.databaseUrl,
|
|
689
|
+
enableSigma
|
|
690
|
+
});
|
|
691
|
+
} catch (migrationError) {
|
|
692
|
+
console.error(chalk3.red("Failed to run migrations:"));
|
|
693
|
+
console.error(
|
|
694
|
+
migrationError instanceof Error ? migrationError.message : String(migrationError)
|
|
695
|
+
);
|
|
696
|
+
throw migrationError;
|
|
697
|
+
}
|
|
698
|
+
const poolConfig = {
|
|
699
|
+
max: 10,
|
|
700
|
+
connectionString: config.databaseUrl,
|
|
701
|
+
keepAlive: true
|
|
702
|
+
};
|
|
703
|
+
stripeSync = await StripeSync.create({
|
|
704
|
+
databaseUrl: config.databaseUrl,
|
|
705
|
+
stripeSecretKey: config.stripeApiKey,
|
|
706
|
+
enableSigma,
|
|
707
|
+
stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
|
|
708
|
+
autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
|
|
709
|
+
backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
|
|
710
|
+
poolConfig
|
|
711
|
+
});
|
|
712
|
+
const completedRun = await stripeSync.postgresClient.getCompletedRun(
|
|
713
|
+
stripeSync.accountId,
|
|
714
|
+
intervalSeconds
|
|
715
|
+
);
|
|
716
|
+
if (completedRun) {
|
|
717
|
+
console.log(
|
|
718
|
+
chalk3.green(
|
|
719
|
+
`\u2713 Skipping resync \u2014 a successful run completed at ${completedRun.runStartedAt.toISOString()} (within ${intervalSeconds}s window)`
|
|
720
|
+
)
|
|
721
|
+
);
|
|
722
|
+
await stripeSync.close();
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const startTime = Date.now();
|
|
726
|
+
const result = await stripeSync.fullSync(
|
|
727
|
+
void 0,
|
|
728
|
+
void 0,
|
|
729
|
+
options.workerCount,
|
|
730
|
+
options.rateLimit
|
|
731
|
+
);
|
|
732
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
733
|
+
const objectCount = Object.keys(result.totals).length;
|
|
734
|
+
console.log(
|
|
735
|
+
chalk3.green(
|
|
736
|
+
`\u2713 Full resync complete: ${result.totalSynced} rows synced across ${objectCount} objects in ${elapsed}s`
|
|
737
|
+
)
|
|
738
|
+
);
|
|
739
|
+
if (result.skipped.length > 0) {
|
|
740
|
+
console.log(
|
|
741
|
+
chalk3.yellow(
|
|
742
|
+
`Skipped ${result.skipped.length} Sigma tables without access: ${result.skipped.join(", ")}`
|
|
743
|
+
)
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
if (result.errors.length > 0) {
|
|
747
|
+
console.log(chalk3.red(`Full resync finished with ${result.errors.length} errors:`));
|
|
748
|
+
for (const err of result.errors) {
|
|
749
|
+
console.log(chalk3.red(` - ${err.object}: ${err.message}`));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
await stripeSync.close();
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (error instanceof Error) {
|
|
755
|
+
console.error(chalk3.red(error.message));
|
|
756
|
+
}
|
|
757
|
+
if (stripeSync) {
|
|
758
|
+
try {
|
|
759
|
+
await stripeSync.close();
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
565
766
|
async function installCommand(options) {
|
|
566
767
|
try {
|
|
567
768
|
dotenv2.config();
|
|
@@ -611,6 +812,14 @@ async function installCommand(options) {
|
|
|
611
812
|
}
|
|
612
813
|
}
|
|
613
814
|
console.log(chalk3.blue("\n\u{1F680} Installing Stripe Sync to Supabase Edge Functions...\n"));
|
|
815
|
+
const tokenSource = options.supabaseAccessToken ? "CLI" : process.env.SUPABASE_ACCESS_TOKEN ? "env" : "prompt";
|
|
816
|
+
const projectSource = options.supabaseProjectRef ? "CLI" : process.env.SUPABASE_PROJECT_REF ? "env" : "prompt";
|
|
817
|
+
console.log(
|
|
818
|
+
chalk3.gray(
|
|
819
|
+
`Access token source: ${tokenSource} (${accessToken.slice(0, 8)}...${accessToken.slice(-4)})`
|
|
820
|
+
)
|
|
821
|
+
);
|
|
822
|
+
console.log(chalk3.gray(`Project ref source: ${projectSource} (${projectRef})`));
|
|
614
823
|
const supabaseManagementUrl = options.supabaseManagementUrl || process.env.SUPABASE_MANAGEMENT_URL;
|
|
615
824
|
console.log(chalk3.gray("Validating project access..."));
|
|
616
825
|
await install({
|
|
@@ -619,8 +828,10 @@ async function installCommand(options) {
|
|
|
619
828
|
stripeKey,
|
|
620
829
|
packageVersion: options.packageVersion,
|
|
621
830
|
workerIntervalSeconds: options.workerInterval,
|
|
831
|
+
syncIntervalSeconds: options.syncInterval,
|
|
622
832
|
supabaseManagementUrl,
|
|
623
|
-
enableSigma: options.enableSigma
|
|
833
|
+
enableSigma: options.enableSigma,
|
|
834
|
+
rateLimit: options.rateLimit
|
|
624
835
|
});
|
|
625
836
|
console.log(chalk3.cyan("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
626
837
|
console.log(chalk3.cyan.bold(" Installation Complete!"));
|
|
@@ -699,9 +910,11 @@ async function uninstallCommand(options) {
|
|
|
699
910
|
export {
|
|
700
911
|
loadConfig,
|
|
701
912
|
createTunnel,
|
|
913
|
+
monitorCommand,
|
|
702
914
|
backfillCommand,
|
|
703
915
|
migrateCommand,
|
|
704
916
|
syncCommand,
|
|
917
|
+
fullSyncCommand,
|
|
705
918
|
installCommand,
|
|
706
919
|
uninstallCommand
|
|
707
920
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "stripe-experiment-sync",
|
|
4
|
-
version: "1.0.
|
|
4
|
+
version: "1.0.23",
|
|
5
5
|
private: false,
|
|
6
6
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
7
7
|
type: "module",
|
|
@@ -26,12 +26,15 @@ var package_default = {
|
|
|
26
26
|
},
|
|
27
27
|
scripts: {
|
|
28
28
|
clean: "rimraf dist",
|
|
29
|
-
prebuild: "npm run clean",
|
|
29
|
+
prebuild: "npm run clean && npm run build:functions",
|
|
30
|
+
"build:functions": "tsx scripts/build-functions.ts",
|
|
30
31
|
build: "tsup src/index.ts src/supabase/index.ts src/cli/index.ts src/cli/lib.ts --format esm,cjs --dts --shims && cp -r src/database/migrations dist/migrations",
|
|
31
32
|
lint: "eslint src --ext .ts",
|
|
32
33
|
test: "vitest",
|
|
33
34
|
"test:integration": "TEST_POSTGRES_DB_URL=${TEST_POSTGRES_DB_URL:-postgresql://postgres:postgres@localhost:55432/postgres} vitest run src/stripeSync*integration.test.ts",
|
|
34
35
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
36
|
+
start: "pnpm run build && node dist/cli/index.js backfill",
|
|
37
|
+
"full-sync": "pnpm run build && node dist/cli/index.js full-sync",
|
|
35
38
|
"generate:sigma-schema": "tsx src/sigma/schema/fetch-schema.ts"
|
|
36
39
|
},
|
|
37
40
|
files: [
|
|
@@ -42,13 +45,14 @@ var package_default = {
|
|
|
42
45
|
chalk: "^5.3.0",
|
|
43
46
|
commander: "^12.1.0",
|
|
44
47
|
dotenv: "^16.4.7",
|
|
48
|
+
esbuild: "^0.27.3",
|
|
45
49
|
express: "^4.18.2",
|
|
46
50
|
inquirer: "^12.3.0",
|
|
47
51
|
papaparse: "5.4.1",
|
|
48
52
|
pg: "^8.16.3",
|
|
49
53
|
"pg-node-migrations": "0.0.8",
|
|
50
54
|
stripe: "^17.7.0",
|
|
51
|
-
"supabase-management-js": "^0.
|
|
55
|
+
"supabase-management-js": "^2.0.2",
|
|
52
56
|
ws: "^8.18.0",
|
|
53
57
|
yesql: "^7.0.0"
|
|
54
58
|
},
|
|
@@ -61,6 +65,7 @@ var package_default = {
|
|
|
61
65
|
"@types/ws": "^8.5.13",
|
|
62
66
|
"@types/yesql": "^4.1.4",
|
|
63
67
|
"@vitest/ui": "^4.0.9",
|
|
68
|
+
esbuild: "^0.27.2",
|
|
64
69
|
tsx: "^4.19.2",
|
|
65
70
|
vitest: "^3.2.4"
|
|
66
71
|
},
|