stripe-experiment-sync 1.0.6 → 1.0.8
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 +25 -4
- package/dist/{chunk-3UERGK2O.js → chunk-CKWVW2JK.js} +18 -6
- package/dist/chunk-J7BH3XD6.js +3436 -0
- package/dist/chunk-X2OQQCC2.js +409 -0
- package/dist/chunk-YH6KRZDQ.js +634 -0
- package/dist/cli/index.cjs +4592 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +59 -0
- package/dist/cli/lib.cjs +4571 -0
- package/dist/cli/lib.d.cts +69 -0
- package/dist/cli/lib.d.ts +69 -0
- package/dist/cli/lib.js +21 -0
- package/dist/index.cjs +243 -58
- package/dist/index.d.cts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.js +9 -3255
- package/dist/migrations/0057_rename_sync_tables.sql +57 -0
- package/dist/migrations/0058_improve_sync_runs_status.sql +36 -0
- package/dist/supabase/index.cjs +46 -21
- package/dist/supabase/index.d.cts +6 -1
- package/dist/supabase/index.d.ts +6 -1
- package/dist/supabase/index.js +12 -382
- package/package.json +18 -6
package/dist/index.cjs
CHANGED
|
@@ -33,24 +33,25 @@ __export(index_exports, {
|
|
|
33
33
|
PostgresClient: () => PostgresClient,
|
|
34
34
|
StripeSync: () => StripeSync,
|
|
35
35
|
VERSION: () => VERSION,
|
|
36
|
+
createStripeWebSocketClient: () => createStripeWebSocketClient,
|
|
36
37
|
hashApiKey: () => hashApiKey,
|
|
37
38
|
runMigrations: () => runMigrations
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(index_exports);
|
|
40
41
|
|
|
41
|
-
// ../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.
|
|
42
|
+
// ../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js
|
|
42
43
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
|
|
43
44
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
44
45
|
|
|
45
46
|
// package.json
|
|
46
47
|
var package_default = {
|
|
47
48
|
name: "stripe-experiment-sync",
|
|
48
|
-
version: "1.0.
|
|
49
|
+
version: "1.0.8-beta.1765856228",
|
|
49
50
|
private: false,
|
|
50
51
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
51
52
|
type: "module",
|
|
52
53
|
main: "./dist/index.cjs",
|
|
53
|
-
bin: "./dist/cli/index.
|
|
54
|
+
bin: "./dist/cli/index.js",
|
|
54
55
|
exports: {
|
|
55
56
|
".": {
|
|
56
57
|
types: "./dist/index.d.ts",
|
|
@@ -61,12 +62,17 @@ var package_default = {
|
|
|
61
62
|
types: "./dist/supabase/index.d.ts",
|
|
62
63
|
import: "./dist/supabase/index.js",
|
|
63
64
|
require: "./dist/supabase/index.cjs"
|
|
65
|
+
},
|
|
66
|
+
"./cli": {
|
|
67
|
+
types: "./dist/cli/lib.d.ts",
|
|
68
|
+
import: "./dist/cli/lib.js",
|
|
69
|
+
require: "./dist/cli/lib.cjs"
|
|
64
70
|
}
|
|
65
71
|
},
|
|
66
72
|
scripts: {
|
|
67
73
|
clean: "rimraf dist",
|
|
68
74
|
prebuild: "npm run clean",
|
|
69
|
-
build: "tsup src/index.ts src/supabase/index.ts --format esm,cjs --dts --shims && cp -r src/database/migrations dist/migrations",
|
|
75
|
+
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",
|
|
70
76
|
lint: "eslint src --ext .ts",
|
|
71
77
|
test: "vitest"
|
|
72
78
|
},
|
|
@@ -74,21 +80,28 @@ var package_default = {
|
|
|
74
80
|
"dist"
|
|
75
81
|
],
|
|
76
82
|
dependencies: {
|
|
83
|
+
"@ngrok/ngrok": "^1.4.1",
|
|
84
|
+
chalk: "^5.3.0",
|
|
85
|
+
commander: "^12.1.0",
|
|
86
|
+
dotenv: "^16.4.7",
|
|
87
|
+
express: "^4.18.2",
|
|
88
|
+
inquirer: "^12.3.0",
|
|
77
89
|
pg: "^8.16.3",
|
|
78
90
|
"pg-node-migrations": "0.0.8",
|
|
91
|
+
stripe: "^17.7.0",
|
|
79
92
|
"supabase-management-js": "^0.1.6",
|
|
80
93
|
ws: "^8.18.0",
|
|
81
94
|
yesql: "^7.0.0"
|
|
82
95
|
},
|
|
83
|
-
peerDependencies: {
|
|
84
|
-
stripe: "> 11"
|
|
85
|
-
},
|
|
86
96
|
devDependencies: {
|
|
97
|
+
"@types/express": "^4.17.21",
|
|
98
|
+
"@types/inquirer": "^9.0.7",
|
|
87
99
|
"@types/node": "^24.10.1",
|
|
88
100
|
"@types/pg": "^8.15.5",
|
|
89
101
|
"@types/ws": "^8.5.13",
|
|
90
102
|
"@types/yesql": "^4.1.4",
|
|
91
103
|
"@vitest/ui": "^4.0.9",
|
|
104
|
+
tsx: "^4.19.2",
|
|
92
105
|
vitest: "^3.2.4"
|
|
93
106
|
},
|
|
94
107
|
repository: {
|
|
@@ -143,9 +156,9 @@ var ORDERED_STRIPE_TABLES = [
|
|
|
143
156
|
"reviews",
|
|
144
157
|
"_managed_webhooks",
|
|
145
158
|
"customers",
|
|
146
|
-
"
|
|
147
|
-
// Must be deleted before
|
|
148
|
-
"
|
|
159
|
+
"_sync_obj_runs",
|
|
160
|
+
// Must be deleted before _sync_runs (foreign key)
|
|
161
|
+
"_sync_runs"
|
|
149
162
|
];
|
|
150
163
|
var TABLES_WITH_ACCOUNT_ID = /* @__PURE__ */ new Set(["_managed_webhooks"]);
|
|
151
164
|
var PostgresClient = class {
|
|
@@ -436,7 +449,7 @@ var PostgresClient = class {
|
|
|
436
449
|
// Observable Sync System Methods
|
|
437
450
|
// =============================================================================
|
|
438
451
|
// These methods support long-running syncs with full observability.
|
|
439
|
-
// Uses two tables:
|
|
452
|
+
// Uses two tables: _sync_runs (parent) and _sync_obj_runs (children)
|
|
440
453
|
// RunKey = (accountId, runStartedAt) - natural composite key
|
|
441
454
|
/**
|
|
442
455
|
* Cancel stale runs (running but no object updated in 5 minutes).
|
|
@@ -446,7 +459,7 @@ var PostgresClient = class {
|
|
|
446
459
|
*/
|
|
447
460
|
async cancelStaleRuns(accountId) {
|
|
448
461
|
await this.query(
|
|
449
|
-
`UPDATE "${this.config.schema}"."
|
|
462
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs" o
|
|
450
463
|
SET status = 'error',
|
|
451
464
|
error_message = 'Auto-cancelled: stale (no update in 5 min)',
|
|
452
465
|
completed_at = now()
|
|
@@ -456,17 +469,17 @@ var PostgresClient = class {
|
|
|
456
469
|
[accountId]
|
|
457
470
|
);
|
|
458
471
|
await this.query(
|
|
459
|
-
`UPDATE "${this.config.schema}"."
|
|
472
|
+
`UPDATE "${this.config.schema}"."_sync_runs" r
|
|
460
473
|
SET closed_at = now()
|
|
461
474
|
WHERE r."_account_id" = $1
|
|
462
475
|
AND r.closed_at IS NULL
|
|
463
476
|
AND EXISTS (
|
|
464
|
-
SELECT 1 FROM "${this.config.schema}"."
|
|
477
|
+
SELECT 1 FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
465
478
|
WHERE o."_account_id" = r."_account_id"
|
|
466
479
|
AND o.run_started_at = r.started_at
|
|
467
480
|
)
|
|
468
481
|
AND NOT EXISTS (
|
|
469
|
-
SELECT 1 FROM "${this.config.schema}"."
|
|
482
|
+
SELECT 1 FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
470
483
|
WHERE o."_account_id" = r."_account_id"
|
|
471
484
|
AND o.run_started_at = r.started_at
|
|
472
485
|
AND o.status IN ('pending', 'running')
|
|
@@ -484,7 +497,7 @@ var PostgresClient = class {
|
|
|
484
497
|
async getOrCreateSyncRun(accountId, triggeredBy) {
|
|
485
498
|
await this.cancelStaleRuns(accountId);
|
|
486
499
|
const existing = await this.query(
|
|
487
|
-
`SELECT "_account_id", started_at FROM "${this.config.schema}"."
|
|
500
|
+
`SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs"
|
|
488
501
|
WHERE "_account_id" = $1 AND closed_at IS NULL`,
|
|
489
502
|
[accountId]
|
|
490
503
|
);
|
|
@@ -494,7 +507,7 @@ var PostgresClient = class {
|
|
|
494
507
|
}
|
|
495
508
|
try {
|
|
496
509
|
const result = await this.query(
|
|
497
|
-
`INSERT INTO "${this.config.schema}"."
|
|
510
|
+
`INSERT INTO "${this.config.schema}"."_sync_runs" ("_account_id", triggered_by, started_at)
|
|
498
511
|
VALUES ($1, $2, date_trunc('milliseconds', now()))
|
|
499
512
|
RETURNING "_account_id", started_at`,
|
|
500
513
|
[accountId, triggeredBy ?? null]
|
|
@@ -513,7 +526,7 @@ var PostgresClient = class {
|
|
|
513
526
|
*/
|
|
514
527
|
async getActiveSyncRun(accountId) {
|
|
515
528
|
const result = await this.query(
|
|
516
|
-
`SELECT "_account_id", started_at FROM "${this.config.schema}"."
|
|
529
|
+
`SELECT "_account_id", started_at FROM "${this.config.schema}"."_sync_runs"
|
|
517
530
|
WHERE "_account_id" = $1 AND closed_at IS NULL`,
|
|
518
531
|
[accountId]
|
|
519
532
|
);
|
|
@@ -523,12 +536,12 @@ var PostgresClient = class {
|
|
|
523
536
|
}
|
|
524
537
|
/**
|
|
525
538
|
* Get sync run config (for concurrency control).
|
|
526
|
-
* Status is derived from
|
|
539
|
+
* Status is derived from sync_runs view.
|
|
527
540
|
*/
|
|
528
541
|
async getSyncRun(accountId, runStartedAt) {
|
|
529
542
|
const result = await this.query(
|
|
530
543
|
`SELECT "_account_id", started_at, max_concurrent, closed_at
|
|
531
|
-
FROM "${this.config.schema}"."
|
|
544
|
+
FROM "${this.config.schema}"."_sync_runs"
|
|
532
545
|
WHERE "_account_id" = $1 AND started_at = $2`,
|
|
533
546
|
[accountId, runStartedAt]
|
|
534
547
|
);
|
|
@@ -547,7 +560,7 @@ var PostgresClient = class {
|
|
|
547
560
|
*/
|
|
548
561
|
async closeSyncRun(accountId, runStartedAt) {
|
|
549
562
|
await this.query(
|
|
550
|
-
`UPDATE "${this.config.schema}"."
|
|
563
|
+
`UPDATE "${this.config.schema}"."_sync_runs"
|
|
551
564
|
SET closed_at = now()
|
|
552
565
|
WHERE "_account_id" = $1 AND started_at = $2 AND closed_at IS NULL`,
|
|
553
566
|
[accountId, runStartedAt]
|
|
@@ -561,7 +574,7 @@ var PostgresClient = class {
|
|
|
561
574
|
if (objects.length === 0) return;
|
|
562
575
|
const values = objects.map((_, i) => `($1, $2, $${i + 3})`).join(", ");
|
|
563
576
|
await this.query(
|
|
564
|
-
`INSERT INTO "${this.config.schema}"."
|
|
577
|
+
`INSERT INTO "${this.config.schema}"."_sync_obj_runs" ("_account_id", run_started_at, object)
|
|
565
578
|
VALUES ${values}
|
|
566
579
|
ON CONFLICT ("_account_id", run_started_at, object) DO NOTHING`,
|
|
567
580
|
[accountId, runStartedAt, ...objects]
|
|
@@ -580,7 +593,7 @@ var PostgresClient = class {
|
|
|
580
593
|
const runningCount = await this.countRunningObjects(accountId, runStartedAt);
|
|
581
594
|
if (runningCount >= run.maxConcurrent) return false;
|
|
582
595
|
const result = await this.query(
|
|
583
|
-
`UPDATE "${this.config.schema}"."
|
|
596
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
584
597
|
SET status = 'running', started_at = now(), updated_at = now()
|
|
585
598
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3 AND status = 'pending'
|
|
586
599
|
RETURNING *`,
|
|
@@ -594,7 +607,7 @@ var PostgresClient = class {
|
|
|
594
607
|
async getObjectRun(accountId, runStartedAt, object) {
|
|
595
608
|
const result = await this.query(
|
|
596
609
|
`SELECT object, status, processed_count, cursor
|
|
597
|
-
FROM "${this.config.schema}"."
|
|
610
|
+
FROM "${this.config.schema}"."_sync_obj_runs"
|
|
598
611
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
599
612
|
[accountId, runStartedAt, object]
|
|
600
613
|
);
|
|
@@ -613,7 +626,7 @@ var PostgresClient = class {
|
|
|
613
626
|
*/
|
|
614
627
|
async incrementObjectProgress(accountId, runStartedAt, object, count) {
|
|
615
628
|
await this.query(
|
|
616
|
-
`UPDATE "${this.config.schema}"."
|
|
629
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
617
630
|
SET processed_count = processed_count + $4, updated_at = now()
|
|
618
631
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
619
632
|
[accountId, runStartedAt, object, count]
|
|
@@ -629,7 +642,7 @@ var PostgresClient = class {
|
|
|
629
642
|
const isNumeric = cursor !== null && /^\d+$/.test(cursor);
|
|
630
643
|
if (isNumeric) {
|
|
631
644
|
await this.query(
|
|
632
|
-
`UPDATE "${this.config.schema}"."
|
|
645
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
633
646
|
SET cursor = GREATEST(COALESCE(cursor::bigint, 0), $4::bigint)::text,
|
|
634
647
|
updated_at = now()
|
|
635
648
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
@@ -637,7 +650,7 @@ var PostgresClient = class {
|
|
|
637
650
|
);
|
|
638
651
|
} else {
|
|
639
652
|
await this.query(
|
|
640
|
-
`UPDATE "${this.config.schema}"."
|
|
653
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
641
654
|
SET cursor = $4, updated_at = now()
|
|
642
655
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
643
656
|
[accountId, runStartedAt, object, cursor]
|
|
@@ -653,7 +666,7 @@ var PostgresClient = class {
|
|
|
653
666
|
async getLastCompletedCursor(accountId, object) {
|
|
654
667
|
const result = await this.query(
|
|
655
668
|
`SELECT MAX(o.cursor::bigint)::text as cursor
|
|
656
|
-
FROM "${this.config.schema}"."
|
|
669
|
+
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
657
670
|
WHERE o."_account_id" = $1
|
|
658
671
|
AND o.object = $2
|
|
659
672
|
AND o.cursor IS NOT NULL`,
|
|
@@ -667,10 +680,10 @@ var PostgresClient = class {
|
|
|
667
680
|
*/
|
|
668
681
|
async deleteSyncRuns(accountId) {
|
|
669
682
|
await this.query(
|
|
670
|
-
`DELETE FROM "${this.config.schema}"."
|
|
683
|
+
`DELETE FROM "${this.config.schema}"."_sync_obj_runs" WHERE "_account_id" = $1`,
|
|
671
684
|
[accountId]
|
|
672
685
|
);
|
|
673
|
-
await this.query(`DELETE FROM "${this.config.schema}"."
|
|
686
|
+
await this.query(`DELETE FROM "${this.config.schema}"."_sync_runs" WHERE "_account_id" = $1`, [
|
|
674
687
|
accountId
|
|
675
688
|
]);
|
|
676
689
|
}
|
|
@@ -680,7 +693,7 @@ var PostgresClient = class {
|
|
|
680
693
|
*/
|
|
681
694
|
async completeObjectSync(accountId, runStartedAt, object) {
|
|
682
695
|
await this.query(
|
|
683
|
-
`UPDATE "${this.config.schema}"."
|
|
696
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
684
697
|
SET status = 'complete', completed_at = now()
|
|
685
698
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
686
699
|
[accountId, runStartedAt, object]
|
|
@@ -696,7 +709,7 @@ var PostgresClient = class {
|
|
|
696
709
|
*/
|
|
697
710
|
async failObjectSync(accountId, runStartedAt, object, errorMessage) {
|
|
698
711
|
await this.query(
|
|
699
|
-
`UPDATE "${this.config.schema}"."
|
|
712
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
700
713
|
SET status = 'error', error_message = $4, completed_at = now()
|
|
701
714
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
702
715
|
[accountId, runStartedAt, object, errorMessage]
|
|
@@ -711,7 +724,7 @@ var PostgresClient = class {
|
|
|
711
724
|
*/
|
|
712
725
|
async hasAnyObjectErrors(accountId, runStartedAt) {
|
|
713
726
|
const result = await this.query(
|
|
714
|
-
`SELECT COUNT(*) as count FROM "${this.config.schema}"."
|
|
727
|
+
`SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs"
|
|
715
728
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'error'`,
|
|
716
729
|
[accountId, runStartedAt]
|
|
717
730
|
);
|
|
@@ -722,7 +735,7 @@ var PostgresClient = class {
|
|
|
722
735
|
*/
|
|
723
736
|
async countRunningObjects(accountId, runStartedAt) {
|
|
724
737
|
const result = await this.query(
|
|
725
|
-
`SELECT COUNT(*) as count FROM "${this.config.schema}"."
|
|
738
|
+
`SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs"
|
|
726
739
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'running'`,
|
|
727
740
|
[accountId, runStartedAt]
|
|
728
741
|
);
|
|
@@ -738,7 +751,7 @@ var PostgresClient = class {
|
|
|
738
751
|
const runningCount = await this.countRunningObjects(accountId, runStartedAt);
|
|
739
752
|
if (runningCount >= run.maxConcurrent) return null;
|
|
740
753
|
const result = await this.query(
|
|
741
|
-
`SELECT object FROM "${this.config.schema}"."
|
|
754
|
+
`SELECT object FROM "${this.config.schema}"."_sync_obj_runs"
|
|
742
755
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND status = 'pending'
|
|
743
756
|
ORDER BY object
|
|
744
757
|
LIMIT 1`,
|
|
@@ -751,7 +764,7 @@ var PostgresClient = class {
|
|
|
751
764
|
*/
|
|
752
765
|
async areAllObjectsComplete(accountId, runStartedAt) {
|
|
753
766
|
const result = await this.query(
|
|
754
|
-
`SELECT COUNT(*) as count FROM "${this.config.schema}"."
|
|
767
|
+
`SELECT COUNT(*) as count FROM "${this.config.schema}"."_sync_obj_runs"
|
|
755
768
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND status IN ('pending', 'running')`,
|
|
756
769
|
[accountId, runStartedAt]
|
|
757
770
|
);
|
|
@@ -1717,19 +1730,8 @@ var StripeSync = class {
|
|
|
1717
1730
|
if (params?.runStartedAt) {
|
|
1718
1731
|
runStartedAt = params.runStartedAt;
|
|
1719
1732
|
} else {
|
|
1720
|
-
const runKey = await this.
|
|
1721
|
-
|
|
1722
|
-
params?.triggeredBy ?? "processNext"
|
|
1723
|
-
);
|
|
1724
|
-
if (!runKey) {
|
|
1725
|
-
const activeRun = await this.postgresClient.getActiveSyncRun(accountId);
|
|
1726
|
-
if (!activeRun) {
|
|
1727
|
-
throw new Error("Failed to get or create sync run");
|
|
1728
|
-
}
|
|
1729
|
-
runStartedAt = activeRun.runStartedAt;
|
|
1730
|
-
} else {
|
|
1731
|
-
runStartedAt = runKey.runStartedAt;
|
|
1732
|
-
}
|
|
1733
|
+
const { runKey } = await this.joinOrCreateSyncRun(params?.triggeredBy ?? "processNext");
|
|
1734
|
+
runStartedAt = runKey.runStartedAt;
|
|
1733
1735
|
}
|
|
1734
1736
|
await this.postgresClient.createObjectRuns(accountId, runStartedAt, [resourceName]);
|
|
1735
1737
|
const objRun = await this.postgresClient.getObjectRun(accountId, runStartedAt, resourceName);
|
|
@@ -1892,18 +1894,39 @@ var StripeSync = class {
|
|
|
1892
1894
|
}
|
|
1893
1895
|
return { synced: totalSynced };
|
|
1894
1896
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
+
/**
|
|
1898
|
+
* Join existing sync run or create a new one.
|
|
1899
|
+
* Returns sync run key and list of supported objects to sync.
|
|
1900
|
+
*
|
|
1901
|
+
* Cooperative behavior: If a sync run already exists, joins it instead of failing.
|
|
1902
|
+
* This is used by workers and background processes that should cooperate.
|
|
1903
|
+
*
|
|
1904
|
+
* @param triggeredBy - What triggered this sync (for observability)
|
|
1905
|
+
* @returns Run key and list of objects to sync
|
|
1906
|
+
*/
|
|
1907
|
+
async joinOrCreateSyncRun(triggeredBy = "worker") {
|
|
1897
1908
|
await this.getCurrentAccount();
|
|
1898
1909
|
const accountId = await this.getAccountId();
|
|
1899
|
-
const
|
|
1900
|
-
if (!
|
|
1910
|
+
const result = await this.postgresClient.getOrCreateSyncRun(accountId, triggeredBy);
|
|
1911
|
+
if (!result) {
|
|
1901
1912
|
const activeRun = await this.postgresClient.getActiveSyncRun(accountId);
|
|
1902
1913
|
if (!activeRun) {
|
|
1903
1914
|
throw new Error("Failed to get or create sync run");
|
|
1904
1915
|
}
|
|
1905
|
-
return
|
|
1916
|
+
return {
|
|
1917
|
+
runKey: { accountId: activeRun.accountId, runStartedAt: activeRun.runStartedAt },
|
|
1918
|
+
objects: this.getSupportedSyncObjects()
|
|
1919
|
+
};
|
|
1906
1920
|
}
|
|
1921
|
+
const { accountId: runAccountId, runStartedAt } = result;
|
|
1922
|
+
return {
|
|
1923
|
+
runKey: { accountId: runAccountId, runStartedAt },
|
|
1924
|
+
objects: this.getSupportedSyncObjects()
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
async processUntilDone(params) {
|
|
1928
|
+
const { object } = params ?? { object: "all" };
|
|
1929
|
+
const { runKey } = await this.joinOrCreateSyncRun("processUntilDone");
|
|
1907
1930
|
return this.processUntilDoneWithRun(runKey.runStartedAt, object, params);
|
|
1908
1931
|
}
|
|
1909
1932
|
/**
|
|
@@ -2518,14 +2541,14 @@ var StripeSync = class {
|
|
|
2518
2541
|
throw error;
|
|
2519
2542
|
}
|
|
2520
2543
|
}
|
|
2521
|
-
async fetchAndUpsert(
|
|
2544
|
+
async fetchAndUpsert(fetch2, upsert, accountId, resourceName, runStartedAt) {
|
|
2522
2545
|
const CHECKPOINT_SIZE = 100;
|
|
2523
2546
|
let totalSynced = 0;
|
|
2524
2547
|
let currentBatch = [];
|
|
2525
2548
|
try {
|
|
2526
2549
|
this.config.logger?.info("Fetching items to sync from Stripe");
|
|
2527
2550
|
try {
|
|
2528
|
-
for await (const item of
|
|
2551
|
+
for await (const item of fetch2()) {
|
|
2529
2552
|
currentBatch.push(item);
|
|
2530
2553
|
if (currentBatch.length >= CHECKPOINT_SIZE) {
|
|
2531
2554
|
this.config.logger?.info(`Upserting batch of ${currentBatch.length} items`);
|
|
@@ -3251,11 +3274,11 @@ var StripeSync = class {
|
|
|
3251
3274
|
}
|
|
3252
3275
|
}
|
|
3253
3276
|
}
|
|
3254
|
-
async fetchMissingEntities(ids,
|
|
3277
|
+
async fetchMissingEntities(ids, fetch2) {
|
|
3255
3278
|
if (!ids.length) return [];
|
|
3256
3279
|
const entities = [];
|
|
3257
3280
|
for (const id of ids) {
|
|
3258
|
-
const entity = await
|
|
3281
|
+
const entity = await fetch2(id);
|
|
3259
3282
|
entities.push(entity);
|
|
3260
3283
|
}
|
|
3261
3284
|
return entities;
|
|
@@ -3362,6 +3385,167 @@ async function runMigrations(config) {
|
|
|
3362
3385
|
}
|
|
3363
3386
|
}
|
|
3364
3387
|
|
|
3388
|
+
// src/websocket-client.ts
|
|
3389
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
3390
|
+
var CLI_VERSION = "1.33.0";
|
|
3391
|
+
var PONG_WAIT = 10 * 1e3;
|
|
3392
|
+
var PING_PERIOD = PONG_WAIT * 2 / 10;
|
|
3393
|
+
function getClientUserAgent() {
|
|
3394
|
+
return JSON.stringify({
|
|
3395
|
+
name: "stripe-cli",
|
|
3396
|
+
version: CLI_VERSION,
|
|
3397
|
+
publisher: "stripe",
|
|
3398
|
+
os: process.platform
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
async function createCliSession(stripeApiKey) {
|
|
3402
|
+
const params = new URLSearchParams();
|
|
3403
|
+
params.append("device_name", "stripe-sync-engine");
|
|
3404
|
+
params.append("websocket_features[]", "webhooks");
|
|
3405
|
+
const response = await fetch("https://api.stripe.com/v1/stripecli/sessions", {
|
|
3406
|
+
method: "POST",
|
|
3407
|
+
headers: {
|
|
3408
|
+
Authorization: `Bearer ${stripeApiKey}`,
|
|
3409
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3410
|
+
"User-Agent": `Stripe/v1 stripe-cli/${CLI_VERSION}`,
|
|
3411
|
+
"X-Stripe-Client-User-Agent": getClientUserAgent()
|
|
3412
|
+
},
|
|
3413
|
+
body: params.toString()
|
|
3414
|
+
});
|
|
3415
|
+
if (!response.ok) {
|
|
3416
|
+
const error = await response.text();
|
|
3417
|
+
throw new Error(`Failed to create CLI session: ${error}`);
|
|
3418
|
+
}
|
|
3419
|
+
return await response.json();
|
|
3420
|
+
}
|
|
3421
|
+
async function createStripeWebSocketClient(options) {
|
|
3422
|
+
const { stripeApiKey, onEvent, onReady, onError, onClose } = options;
|
|
3423
|
+
const session = await createCliSession(stripeApiKey);
|
|
3424
|
+
let ws = null;
|
|
3425
|
+
let pingInterval = null;
|
|
3426
|
+
let connected = false;
|
|
3427
|
+
let shouldReconnect = true;
|
|
3428
|
+
function connect() {
|
|
3429
|
+
const wsUrl = `${session.websocket_url}?websocket_feature=${encodeURIComponent(session.websocket_authorized_feature)}`;
|
|
3430
|
+
ws = new import_ws.default(wsUrl, {
|
|
3431
|
+
headers: {
|
|
3432
|
+
"Accept-Encoding": "identity",
|
|
3433
|
+
"User-Agent": `Stripe/v1 stripe-cli/${CLI_VERSION}`,
|
|
3434
|
+
"X-Stripe-Client-User-Agent": getClientUserAgent(),
|
|
3435
|
+
"Websocket-Id": session.websocket_id
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
ws.on("pong", () => {
|
|
3439
|
+
});
|
|
3440
|
+
ws.on("open", () => {
|
|
3441
|
+
connected = true;
|
|
3442
|
+
pingInterval = setInterval(() => {
|
|
3443
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3444
|
+
ws.ping();
|
|
3445
|
+
}
|
|
3446
|
+
}, PING_PERIOD);
|
|
3447
|
+
if (onReady) {
|
|
3448
|
+
onReady(session.secret);
|
|
3449
|
+
}
|
|
3450
|
+
});
|
|
3451
|
+
ws.on("message", async (data) => {
|
|
3452
|
+
try {
|
|
3453
|
+
const message = JSON.parse(data.toString());
|
|
3454
|
+
const ack = {
|
|
3455
|
+
type: "event_ack",
|
|
3456
|
+
event_id: message.webhook_id,
|
|
3457
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3458
|
+
webhook_id: message.webhook_id
|
|
3459
|
+
};
|
|
3460
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3461
|
+
ws.send(JSON.stringify(ack));
|
|
3462
|
+
}
|
|
3463
|
+
let response;
|
|
3464
|
+
try {
|
|
3465
|
+
const result = await onEvent(message);
|
|
3466
|
+
response = {
|
|
3467
|
+
type: "webhook_response",
|
|
3468
|
+
webhook_id: message.webhook_id,
|
|
3469
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3470
|
+
forward_url: "stripe-sync-engine",
|
|
3471
|
+
status: result?.status ?? 200,
|
|
3472
|
+
http_headers: {},
|
|
3473
|
+
body: JSON.stringify({
|
|
3474
|
+
event_type: result?.event_type,
|
|
3475
|
+
event_id: result?.event_id,
|
|
3476
|
+
database_url: result?.databaseUrl,
|
|
3477
|
+
error: result?.error
|
|
3478
|
+
}),
|
|
3479
|
+
request_headers: message.http_headers,
|
|
3480
|
+
request_body: message.event_payload,
|
|
3481
|
+
notification_id: message.webhook_id
|
|
3482
|
+
};
|
|
3483
|
+
} catch (err) {
|
|
3484
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3485
|
+
response = {
|
|
3486
|
+
type: "webhook_response",
|
|
3487
|
+
webhook_id: message.webhook_id,
|
|
3488
|
+
webhook_conversation_id: message.webhook_conversation_id,
|
|
3489
|
+
forward_url: "stripe-sync-engine",
|
|
3490
|
+
status: 500,
|
|
3491
|
+
http_headers: {},
|
|
3492
|
+
body: JSON.stringify({ error: errorMessage }),
|
|
3493
|
+
request_headers: message.http_headers,
|
|
3494
|
+
request_body: message.event_payload,
|
|
3495
|
+
notification_id: message.webhook_id
|
|
3496
|
+
};
|
|
3497
|
+
if (onError) {
|
|
3498
|
+
onError(err instanceof Error ? err : new Error(errorMessage));
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
3502
|
+
ws.send(JSON.stringify(response));
|
|
3503
|
+
}
|
|
3504
|
+
} catch (err) {
|
|
3505
|
+
if (onError) {
|
|
3506
|
+
onError(err instanceof Error ? err : new Error(String(err)));
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
ws.on("error", (error) => {
|
|
3511
|
+
if (onError) {
|
|
3512
|
+
onError(error);
|
|
3513
|
+
}
|
|
3514
|
+
});
|
|
3515
|
+
ws.on("close", (code, reason) => {
|
|
3516
|
+
connected = false;
|
|
3517
|
+
if (pingInterval) {
|
|
3518
|
+
clearInterval(pingInterval);
|
|
3519
|
+
pingInterval = null;
|
|
3520
|
+
}
|
|
3521
|
+
if (onClose) {
|
|
3522
|
+
onClose(code, reason.toString());
|
|
3523
|
+
}
|
|
3524
|
+
if (shouldReconnect) {
|
|
3525
|
+
const delay = (session.reconnect_delay || 5) * 1e3;
|
|
3526
|
+
setTimeout(() => {
|
|
3527
|
+
connect();
|
|
3528
|
+
}, delay);
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
connect();
|
|
3533
|
+
return {
|
|
3534
|
+
close: () => {
|
|
3535
|
+
shouldReconnect = false;
|
|
3536
|
+
if (pingInterval) {
|
|
3537
|
+
clearInterval(pingInterval);
|
|
3538
|
+
pingInterval = null;
|
|
3539
|
+
}
|
|
3540
|
+
if (ws) {
|
|
3541
|
+
ws.close(1e3, "Connection Done");
|
|
3542
|
+
ws = null;
|
|
3543
|
+
}
|
|
3544
|
+
},
|
|
3545
|
+
isConnected: () => connected
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3365
3549
|
// src/index.ts
|
|
3366
3550
|
var VERSION = package_default.version;
|
|
3367
3551
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -3369,6 +3553,7 @@ var VERSION = package_default.version;
|
|
|
3369
3553
|
PostgresClient,
|
|
3370
3554
|
StripeSync,
|
|
3371
3555
|
VERSION,
|
|
3556
|
+
createStripeWebSocketClient,
|
|
3372
3557
|
hashApiKey,
|
|
3373
3558
|
runMigrations
|
|
3374
3559
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -105,7 +105,7 @@ declare class PostgresClient {
|
|
|
105
105
|
} | null>;
|
|
106
106
|
/**
|
|
107
107
|
* Get sync run config (for concurrency control).
|
|
108
|
-
* Status is derived from
|
|
108
|
+
* Status is derived from sync_runs view.
|
|
109
109
|
*/
|
|
110
110
|
getSyncRun(accountId: string, runStartedAt: Date): Promise<{
|
|
111
111
|
accountId: string;
|
|
@@ -335,6 +335,13 @@ interface ProcessNextParams extends SyncParams {
|
|
|
335
335
|
triggeredBy?: string;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Identifies a specific sync run.
|
|
340
|
+
*/
|
|
341
|
+
type RunKey = {
|
|
342
|
+
accountId: string;
|
|
343
|
+
runStartedAt: Date;
|
|
344
|
+
};
|
|
338
345
|
declare class StripeSync {
|
|
339
346
|
private config;
|
|
340
347
|
stripe: Stripe;
|
|
@@ -470,6 +477,20 @@ declare class StripeSync {
|
|
|
470
477
|
* @returns Sync result with count of synced items
|
|
471
478
|
*/
|
|
472
479
|
private processObjectUntilDone;
|
|
480
|
+
/**
|
|
481
|
+
* Join existing sync run or create a new one.
|
|
482
|
+
* Returns sync run key and list of supported objects to sync.
|
|
483
|
+
*
|
|
484
|
+
* Cooperative behavior: If a sync run already exists, joins it instead of failing.
|
|
485
|
+
* This is used by workers and background processes that should cooperate.
|
|
486
|
+
*
|
|
487
|
+
* @param triggeredBy - What triggered this sync (for observability)
|
|
488
|
+
* @returns Run key and list of objects to sync
|
|
489
|
+
*/
|
|
490
|
+
joinOrCreateSyncRun(triggeredBy?: string): Promise<{
|
|
491
|
+
runKey: RunKey;
|
|
492
|
+
objects: Exclude<SyncObject, 'all' | 'customer_with_entitlements'>[];
|
|
493
|
+
}>;
|
|
473
494
|
processUntilDone(params?: SyncParams): Promise<SyncBackfill>;
|
|
474
495
|
/**
|
|
475
496
|
* Internal implementation of processUntilDone with an existing run.
|
|
@@ -600,6 +621,37 @@ declare function runMigrations(config: MigrationConfig): Promise<void>;
|
|
|
600
621
|
*/
|
|
601
622
|
declare function hashApiKey(apiKey: string): string;
|
|
602
623
|
|
|
624
|
+
interface WebhookProcessingResult {
|
|
625
|
+
status: number;
|
|
626
|
+
databaseUrl: string;
|
|
627
|
+
event_type?: string;
|
|
628
|
+
event_id?: string;
|
|
629
|
+
error?: string;
|
|
630
|
+
}
|
|
631
|
+
interface StripeWebSocketOptions {
|
|
632
|
+
stripeApiKey: string;
|
|
633
|
+
onEvent: (event: StripeWebhookEvent) => Promise<WebhookProcessingResult | void> | WebhookProcessingResult | void;
|
|
634
|
+
onReady?: (secret: string) => void;
|
|
635
|
+
onError?: (error: Error) => void;
|
|
636
|
+
onClose?: (code: number, reason: string) => void;
|
|
637
|
+
}
|
|
638
|
+
interface StripeWebSocketClient {
|
|
639
|
+
close: () => void;
|
|
640
|
+
isConnected: () => boolean;
|
|
641
|
+
}
|
|
642
|
+
interface StripeWebhookEvent {
|
|
643
|
+
type: string;
|
|
644
|
+
webhook_id: string;
|
|
645
|
+
webhook_conversation_id: string;
|
|
646
|
+
event_payload: string;
|
|
647
|
+
http_headers: Record<string, string>;
|
|
648
|
+
endpoint: {
|
|
649
|
+
url: string;
|
|
650
|
+
status: string;
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
declare function createStripeWebSocketClient(options: StripeWebSocketOptions): Promise<StripeWebSocketClient>;
|
|
654
|
+
|
|
603
655
|
declare const VERSION: string;
|
|
604
656
|
|
|
605
|
-
export { type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncConfig, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, hashApiKey, runMigrations };
|
|
657
|
+
export { type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncConfig, type StripeWebSocketClient, type StripeWebSocketOptions, type StripeWebhookEvent, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, type WebhookProcessingResult, createStripeWebSocketClient, hashApiKey, runMigrations };
|