stripe-experiment-sync 1.0.17 → 1.0.19
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 +3 -0
- package/dist/{chunk-I7IFXSAU.js → chunk-4P6TAP7L.js} +1 -1
- package/dist/{chunk-57SXDCMH.js → chunk-CMGFQCD7.js} +1 -1
- package/dist/{chunk-YXRCT3RK.js → chunk-HZ2OIPQ5.js} +2 -2
- package/dist/{chunk-TV67ZOCK.js → chunk-XKBCLBFT.js} +151 -62
- package/dist/cli/index.cjs +151 -62
- package/dist/cli/index.js +4 -4
- package/dist/cli/lib.cjs +151 -62
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +151 -62
- package/dist/index.d.cts +23 -5
- package/dist/index.d.ts +23 -5
- package/dist/index.js +2 -2
- package/dist/migrations/0061_add_page_cursor.sql +3 -0
- package/dist/supabase/index.cjs +1 -1
- package/dist/supabase/index.js +2 -2
- package/package.json +1 -1
package/dist/cli/lib.cjs
CHANGED
|
@@ -117,7 +117,7 @@ async function loadConfig(options) {
|
|
|
117
117
|
// package.json
|
|
118
118
|
var package_default = {
|
|
119
119
|
name: "stripe-experiment-sync",
|
|
120
|
-
version: "1.0.
|
|
120
|
+
version: "1.0.19",
|
|
121
121
|
private: false,
|
|
122
122
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
123
123
|
type: "module",
|
|
@@ -594,7 +594,8 @@ var PostgresClient = class {
|
|
|
594
594
|
`UPDATE "${this.config.schema}"."_sync_obj_runs" o
|
|
595
595
|
SET status = 'error',
|
|
596
596
|
error_message = 'Auto-cancelled: stale (no update in 5 min)',
|
|
597
|
-
completed_at = now()
|
|
597
|
+
completed_at = now(),
|
|
598
|
+
page_cursor = NULL
|
|
598
599
|
WHERE o."_account_id" = $1
|
|
599
600
|
AND o.status = 'running'
|
|
600
601
|
AND o.updated_at < now() - interval '5 minutes'`,
|
|
@@ -701,15 +702,17 @@ var PostgresClient = class {
|
|
|
701
702
|
/**
|
|
702
703
|
* Create object run entries for a sync run.
|
|
703
704
|
* All objects start as 'pending'.
|
|
705
|
+
*
|
|
706
|
+
* @param resourceNames - Database resource names (e.g. 'products', 'customers', NOT 'product', 'customer')
|
|
704
707
|
*/
|
|
705
|
-
async createObjectRuns(accountId, runStartedAt,
|
|
706
|
-
if (
|
|
707
|
-
const values =
|
|
708
|
+
async createObjectRuns(accountId, runStartedAt, resourceNames) {
|
|
709
|
+
if (resourceNames.length === 0) return;
|
|
710
|
+
const values = resourceNames.map((_, i) => `($1, $2, $${i + 3})`).join(", ");
|
|
708
711
|
await this.query(
|
|
709
712
|
`INSERT INTO "${this.config.schema}"."_sync_obj_runs" ("_account_id", run_started_at, object)
|
|
710
713
|
VALUES ${values}
|
|
711
714
|
ON CONFLICT ("_account_id", run_started_at, object) DO NOTHING`,
|
|
712
|
-
[accountId, runStartedAt, ...
|
|
715
|
+
[accountId, runStartedAt, ...resourceNames]
|
|
713
716
|
);
|
|
714
717
|
}
|
|
715
718
|
/**
|
|
@@ -738,7 +741,7 @@ var PostgresClient = class {
|
|
|
738
741
|
*/
|
|
739
742
|
async getObjectRun(accountId, runStartedAt, object) {
|
|
740
743
|
const result = await this.query(
|
|
741
|
-
`SELECT object, status, processed_count, cursor
|
|
744
|
+
`SELECT object, status, processed_count, cursor, page_cursor
|
|
742
745
|
FROM "${this.config.schema}"."_sync_obj_runs"
|
|
743
746
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
744
747
|
[accountId, runStartedAt, object]
|
|
@@ -749,7 +752,8 @@ var PostgresClient = class {
|
|
|
749
752
|
object: row.object,
|
|
750
753
|
status: row.status,
|
|
751
754
|
processedCount: row.processed_count,
|
|
752
|
-
cursor: row.cursor
|
|
755
|
+
cursor: row.cursor,
|
|
756
|
+
pageCursor: row.page_cursor
|
|
753
757
|
};
|
|
754
758
|
}
|
|
755
759
|
/**
|
|
@@ -764,6 +768,23 @@ var PostgresClient = class {
|
|
|
764
768
|
[accountId, runStartedAt, object, count]
|
|
765
769
|
);
|
|
766
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* Update the pagination page_cursor used for backfills using Stripe list calls.
|
|
773
|
+
*/
|
|
774
|
+
async updateObjectPageCursor(accountId, runStartedAt, object, pageCursor) {
|
|
775
|
+
await this.query(
|
|
776
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
777
|
+
SET page_cursor = $4, updated_at = now()
|
|
778
|
+
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
779
|
+
[accountId, runStartedAt, object, pageCursor]
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Clear the pagination page_cursor for an object sync.
|
|
784
|
+
*/
|
|
785
|
+
async clearObjectPageCursor(accountId, runStartedAt, object) {
|
|
786
|
+
await this.updateObjectPageCursor(accountId, runStartedAt, object, null);
|
|
787
|
+
}
|
|
767
788
|
/**
|
|
768
789
|
* Update the cursor for an object sync.
|
|
769
790
|
* Only updates if the new cursor is higher than the existing one (cursors should never decrease).
|
|
@@ -796,9 +817,10 @@ var PostgresClient = class {
|
|
|
796
817
|
}
|
|
797
818
|
/**
|
|
798
819
|
* Get the highest cursor from previous syncs for an object type.
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
820
|
+
* Uses only completed object runs.
|
|
821
|
+
* - During the initial backfill we page through history, but we also update the cursor as we go.
|
|
822
|
+
* If we crash mid-backfill and reuse that cursor, we can accidentally switch into incremental mode
|
|
823
|
+
* too early and only ever fetch the newest page (breaking the historical backfill).
|
|
802
824
|
*
|
|
803
825
|
* Handles two cursor formats:
|
|
804
826
|
* - Numeric: compared as bigint for correct ordering
|
|
@@ -813,11 +835,31 @@ var PostgresClient = class {
|
|
|
813
835
|
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
814
836
|
WHERE o."_account_id" = $1
|
|
815
837
|
AND o.object = $2
|
|
816
|
-
AND o.cursor IS NOT NULL
|
|
838
|
+
AND o.cursor IS NOT NULL
|
|
839
|
+
AND o.status = 'complete'`,
|
|
817
840
|
[accountId, object]
|
|
818
841
|
);
|
|
819
842
|
return result.rows[0]?.cursor ?? null;
|
|
820
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Get the highest cursor from previous syncs for an object type, excluding the current run.
|
|
846
|
+
*/
|
|
847
|
+
async getLastCursorBeforeRun(accountId, object, runStartedAt) {
|
|
848
|
+
const result = await this.query(
|
|
849
|
+
`SELECT CASE
|
|
850
|
+
WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C")
|
|
851
|
+
ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text
|
|
852
|
+
END as cursor
|
|
853
|
+
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
854
|
+
WHERE o."_account_id" = $1
|
|
855
|
+
AND o.object = $2
|
|
856
|
+
AND o.cursor IS NOT NULL
|
|
857
|
+
AND o.status = 'complete'
|
|
858
|
+
AND o.run_started_at < $3`,
|
|
859
|
+
[accountId, object, runStartedAt]
|
|
860
|
+
);
|
|
861
|
+
return result.rows[0]?.cursor ?? null;
|
|
862
|
+
}
|
|
821
863
|
/**
|
|
822
864
|
* Delete all sync runs and object runs for an account.
|
|
823
865
|
* Useful for testing or resetting sync state.
|
|
@@ -838,7 +880,7 @@ var PostgresClient = class {
|
|
|
838
880
|
async completeObjectSync(accountId, runStartedAt, object) {
|
|
839
881
|
await this.query(
|
|
840
882
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
841
|
-
SET status = 'complete', completed_at = now()
|
|
883
|
+
SET status = 'complete', completed_at = now(), page_cursor = NULL
|
|
842
884
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
843
885
|
[accountId, runStartedAt, object]
|
|
844
886
|
);
|
|
@@ -854,7 +896,7 @@ var PostgresClient = class {
|
|
|
854
896
|
async failObjectSync(accountId, runStartedAt, object, errorMessage) {
|
|
855
897
|
await this.query(
|
|
856
898
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
857
|
-
SET status = 'error', error_message = $4, completed_at = now()
|
|
899
|
+
SET status = 'error', error_message = $4, completed_at = now(), page_cursor = NULL
|
|
858
900
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
859
901
|
[accountId, runStartedAt, object, errorMessage]
|
|
860
902
|
);
|
|
@@ -2155,57 +2197,74 @@ var StripeSync = class {
|
|
|
2155
2197
|
* ```
|
|
2156
2198
|
*/
|
|
2157
2199
|
async processNext(object, params) {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
processed: 0,
|
|
2173
|
-
hasMore: false,
|
|
2174
|
-
runStartedAt
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
if (objRun?.status === "pending") {
|
|
2178
|
-
const started = await this.postgresClient.tryStartObjectSync(
|
|
2179
|
-
accountId,
|
|
2180
|
-
runStartedAt,
|
|
2181
|
-
resourceName
|
|
2182
|
-
);
|
|
2183
|
-
if (!started) {
|
|
2200
|
+
try {
|
|
2201
|
+
await this.getCurrentAccount();
|
|
2202
|
+
const accountId = await this.getAccountId();
|
|
2203
|
+
const resourceName = this.getResourceName(object);
|
|
2204
|
+
let runStartedAt;
|
|
2205
|
+
if (params?.runStartedAt) {
|
|
2206
|
+
runStartedAt = params.runStartedAt;
|
|
2207
|
+
} else {
|
|
2208
|
+
const { runKey } = await this.joinOrCreateSyncRun(params?.triggeredBy ?? "processNext");
|
|
2209
|
+
runStartedAt = runKey.runStartedAt;
|
|
2210
|
+
}
|
|
2211
|
+
await this.postgresClient.createObjectRuns(accountId, runStartedAt, [resourceName]);
|
|
2212
|
+
const objRun = await this.postgresClient.getObjectRun(accountId, runStartedAt, resourceName);
|
|
2213
|
+
if (objRun?.status === "complete" || objRun?.status === "error") {
|
|
2184
2214
|
return {
|
|
2185
2215
|
processed: 0,
|
|
2186
|
-
hasMore:
|
|
2216
|
+
hasMore: false,
|
|
2187
2217
|
runStartedAt
|
|
2188
2218
|
};
|
|
2189
2219
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2220
|
+
if (objRun?.status === "pending") {
|
|
2221
|
+
const started = await this.postgresClient.tryStartObjectSync(
|
|
2222
|
+
accountId,
|
|
2223
|
+
runStartedAt,
|
|
2224
|
+
resourceName
|
|
2225
|
+
);
|
|
2226
|
+
if (!started) {
|
|
2227
|
+
return {
|
|
2228
|
+
processed: 0,
|
|
2229
|
+
hasMore: true,
|
|
2230
|
+
runStartedAt
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
let cursor = null;
|
|
2235
|
+
if (!params?.created) {
|
|
2236
|
+
const lastCursor = await this.postgresClient.getLastCursorBeforeRun(
|
|
2237
|
+
accountId,
|
|
2238
|
+
resourceName,
|
|
2239
|
+
runStartedAt
|
|
2240
|
+
);
|
|
2197
2241
|
cursor = lastCursor ?? null;
|
|
2198
2242
|
}
|
|
2243
|
+
const result = await this.fetchOnePage(
|
|
2244
|
+
object,
|
|
2245
|
+
accountId,
|
|
2246
|
+
resourceName,
|
|
2247
|
+
runStartedAt,
|
|
2248
|
+
cursor,
|
|
2249
|
+
objRun?.pageCursor ?? null,
|
|
2250
|
+
params
|
|
2251
|
+
);
|
|
2252
|
+
return result;
|
|
2253
|
+
} catch (error) {
|
|
2254
|
+
throw this.appendMigrationHint(error);
|
|
2199
2255
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2256
|
+
}
|
|
2257
|
+
appendMigrationHint(error) {
|
|
2258
|
+
const hint = "Error occurred. Make sure you are up to date with DB migrations which can sometimes help with this. Details:";
|
|
2259
|
+
const withHint = (message) => message.includes(hint) ? message : `${hint}
|
|
2260
|
+
${message}`;
|
|
2261
|
+
if (error instanceof Error) {
|
|
2262
|
+
const { stack } = error;
|
|
2263
|
+
error.message = withHint(error.message);
|
|
2264
|
+
if (stack) error.stack = stack;
|
|
2265
|
+
return error;
|
|
2266
|
+
}
|
|
2267
|
+
return new Error(withHint(String(error)));
|
|
2209
2268
|
}
|
|
2210
2269
|
/**
|
|
2211
2270
|
* Get the database resource name for a SyncObject type
|
|
@@ -2237,7 +2296,7 @@ var StripeSync = class {
|
|
|
2237
2296
|
* Uses resourceRegistry for DRY list/upsert operations.
|
|
2238
2297
|
* Uses the observable sync system for tracking progress.
|
|
2239
2298
|
*/
|
|
2240
|
-
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, params) {
|
|
2299
|
+
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, pageCursor, params) {
|
|
2241
2300
|
const limit = 100;
|
|
2242
2301
|
if (object === "payment_method" || object === "tax_id") {
|
|
2243
2302
|
this.config.logger?.warn(`processNext for ${object} requires customer context`);
|
|
@@ -2265,7 +2324,16 @@ var StripeSync = class {
|
|
|
2265
2324
|
listParams.created = created;
|
|
2266
2325
|
}
|
|
2267
2326
|
}
|
|
2327
|
+
if (pageCursor) {
|
|
2328
|
+
listParams.starting_after = pageCursor;
|
|
2329
|
+
}
|
|
2268
2330
|
const response = await config.listFn(listParams);
|
|
2331
|
+
if (response.data.length === 0 && response.has_more) {
|
|
2332
|
+
const message = `Stripe returned has_more=true with empty page for ${resourceName}. Aborting to avoid infinite loop.`;
|
|
2333
|
+
this.config.logger?.warn(message);
|
|
2334
|
+
await this.postgresClient.failObjectSync(accountId, runStartedAt, resourceName, message);
|
|
2335
|
+
return { processed: 0, hasMore: false, runStartedAt };
|
|
2336
|
+
}
|
|
2269
2337
|
if (response.data.length > 0) {
|
|
2270
2338
|
this.config.logger?.info(`processNext: upserting ${response.data.length} ${resourceName}`);
|
|
2271
2339
|
await config.upsertFn(response.data, accountId, params?.backfillRelatedEntities);
|
|
@@ -2286,6 +2354,15 @@ var StripeSync = class {
|
|
|
2286
2354
|
String(maxCreated)
|
|
2287
2355
|
);
|
|
2288
2356
|
}
|
|
2357
|
+
const lastId = response.data[response.data.length - 1].id;
|
|
2358
|
+
if (response.has_more) {
|
|
2359
|
+
await this.postgresClient.updateObjectPageCursor(
|
|
2360
|
+
accountId,
|
|
2361
|
+
runStartedAt,
|
|
2362
|
+
resourceName,
|
|
2363
|
+
lastId
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2289
2366
|
}
|
|
2290
2367
|
if (!response.has_more) {
|
|
2291
2368
|
await this.postgresClient.completeObjectSync(accountId, runStartedAt, resourceName);
|
|
@@ -2434,31 +2511,43 @@ var StripeSync = class {
|
|
|
2434
2511
|
* This is used by workers and background processes that should cooperate.
|
|
2435
2512
|
*
|
|
2436
2513
|
* @param triggeredBy - What triggered this sync (for observability)
|
|
2514
|
+
* @param objectFilter - Optional specific object to sync (e.g. 'payment_intent'). If 'all' or undefined, syncs all objects.
|
|
2437
2515
|
* @returns Run key and list of objects to sync
|
|
2438
2516
|
*/
|
|
2439
|
-
async joinOrCreateSyncRun(triggeredBy = "worker") {
|
|
2517
|
+
async joinOrCreateSyncRun(triggeredBy = "worker", objectFilter) {
|
|
2440
2518
|
await this.getCurrentAccount();
|
|
2441
2519
|
const accountId = await this.getAccountId();
|
|
2442
2520
|
const result = await this.postgresClient.getOrCreateSyncRun(accountId, triggeredBy);
|
|
2521
|
+
const objects = objectFilter === "all" || objectFilter === void 0 ? this.getSupportedSyncObjects() : [objectFilter];
|
|
2443
2522
|
if (!result) {
|
|
2444
2523
|
const activeRun = await this.postgresClient.getActiveSyncRun(accountId);
|
|
2445
2524
|
if (!activeRun) {
|
|
2446
2525
|
throw new Error("Failed to get or create sync run");
|
|
2447
2526
|
}
|
|
2527
|
+
await this.postgresClient.createObjectRuns(
|
|
2528
|
+
activeRun.accountId,
|
|
2529
|
+
activeRun.runStartedAt,
|
|
2530
|
+
objects.map((obj) => this.getResourceName(obj))
|
|
2531
|
+
);
|
|
2448
2532
|
return {
|
|
2449
2533
|
runKey: { accountId: activeRun.accountId, runStartedAt: activeRun.runStartedAt },
|
|
2450
|
-
objects
|
|
2534
|
+
objects
|
|
2451
2535
|
};
|
|
2452
2536
|
}
|
|
2453
2537
|
const { accountId: runAccountId, runStartedAt } = result;
|
|
2538
|
+
await this.postgresClient.createObjectRuns(
|
|
2539
|
+
runAccountId,
|
|
2540
|
+
runStartedAt,
|
|
2541
|
+
objects.map((obj) => this.getResourceName(obj))
|
|
2542
|
+
);
|
|
2454
2543
|
return {
|
|
2455
2544
|
runKey: { accountId: runAccountId, runStartedAt },
|
|
2456
|
-
objects
|
|
2545
|
+
objects
|
|
2457
2546
|
};
|
|
2458
2547
|
}
|
|
2459
2548
|
async processUntilDone(params) {
|
|
2460
2549
|
const { object } = params ?? { object: "all" };
|
|
2461
|
-
const { runKey } = await this.joinOrCreateSyncRun("processUntilDone");
|
|
2550
|
+
const { runKey } = await this.joinOrCreateSyncRun("processUntilDone", object);
|
|
2462
2551
|
return this.processUntilDoneWithRun(runKey.runStartedAt, object, params);
|
|
2463
2552
|
}
|
|
2464
2553
|
/**
|
package/dist/cli/lib.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
migrateCommand,
|
|
7
7
|
syncCommand,
|
|
8
8
|
uninstallCommand
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-HZ2OIPQ5.js";
|
|
10
|
+
import "../chunk-XKBCLBFT.js";
|
|
11
|
+
import "../chunk-4P6TAP7L.js";
|
|
12
|
+
import "../chunk-CMGFQCD7.js";
|
|
13
13
|
export {
|
|
14
14
|
backfillCommand,
|
|
15
15
|
createTunnel,
|