stripe-experiment-sync 1.0.18 → 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-M5TTM27L.js → chunk-4P6TAP7L.js} +1 -1
- package/dist/{chunk-XO57OJUE.js → chunk-CMGFQCD7.js} +1 -1
- package/dist/{chunk-HV64EIZU.js → chunk-HZ2OIPQ5.js} +2 -2
- package/dist/{chunk-K4JQHI7Y.js → chunk-XKBCLBFT.js} +129 -54
- package/dist/cli/index.cjs +129 -54
- package/dist/cli/index.js +4 -4
- package/dist/cli/lib.cjs +129 -54
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +129 -54
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- 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/README.md
CHANGED
|
@@ -202,6 +202,9 @@ Supported objects: `all`, `charge`, `checkout_sessions`, `credit_note`, `custome
|
|
|
202
202
|
|
|
203
203
|
The sync engine tracks cursors per account and resource, enabling incremental syncing that resumes after interruptions.
|
|
204
204
|
|
|
205
|
+
For paged backfills, the engine keeps a separate per-run pagination cursor (`page_cursor`) while the
|
|
206
|
+
incremental cursor continues to track the highest `created` timestamp.
|
|
207
|
+
|
|
205
208
|
> **Tip:** For large Stripe accounts (>10,000 objects), loop through date ranges day-by-day to avoid timeouts.
|
|
206
209
|
|
|
207
210
|
## Account Management
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
StripeSync,
|
|
3
3
|
createStripeWebSocketClient,
|
|
4
4
|
runMigrations
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XKBCLBFT.js";
|
|
6
6
|
import {
|
|
7
7
|
install,
|
|
8
8
|
uninstall
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4P6TAP7L.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/config.ts
|
|
12
12
|
import dotenv from "dotenv";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CMGFQCD7.js";
|
|
4
4
|
|
|
5
5
|
// src/stripeSync.ts
|
|
6
6
|
import Stripe3 from "stripe";
|
|
@@ -398,7 +398,8 @@ var PostgresClient = class {
|
|
|
398
398
|
`UPDATE "${this.config.schema}"."_sync_obj_runs" o
|
|
399
399
|
SET status = 'error',
|
|
400
400
|
error_message = 'Auto-cancelled: stale (no update in 5 min)',
|
|
401
|
-
completed_at = now()
|
|
401
|
+
completed_at = now(),
|
|
402
|
+
page_cursor = NULL
|
|
402
403
|
WHERE o."_account_id" = $1
|
|
403
404
|
AND o.status = 'running'
|
|
404
405
|
AND o.updated_at < now() - interval '5 minutes'`,
|
|
@@ -544,7 +545,7 @@ var PostgresClient = class {
|
|
|
544
545
|
*/
|
|
545
546
|
async getObjectRun(accountId, runStartedAt, object) {
|
|
546
547
|
const result = await this.query(
|
|
547
|
-
`SELECT object, status, processed_count, cursor
|
|
548
|
+
`SELECT object, status, processed_count, cursor, page_cursor
|
|
548
549
|
FROM "${this.config.schema}"."_sync_obj_runs"
|
|
549
550
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
550
551
|
[accountId, runStartedAt, object]
|
|
@@ -555,7 +556,8 @@ var PostgresClient = class {
|
|
|
555
556
|
object: row.object,
|
|
556
557
|
status: row.status,
|
|
557
558
|
processedCount: row.processed_count,
|
|
558
|
-
cursor: row.cursor
|
|
559
|
+
cursor: row.cursor,
|
|
560
|
+
pageCursor: row.page_cursor
|
|
559
561
|
};
|
|
560
562
|
}
|
|
561
563
|
/**
|
|
@@ -570,6 +572,23 @@ var PostgresClient = class {
|
|
|
570
572
|
[accountId, runStartedAt, object, count]
|
|
571
573
|
);
|
|
572
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Update the pagination page_cursor used for backfills using Stripe list calls.
|
|
577
|
+
*/
|
|
578
|
+
async updateObjectPageCursor(accountId, runStartedAt, object, pageCursor) {
|
|
579
|
+
await this.query(
|
|
580
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
581
|
+
SET page_cursor = $4, updated_at = now()
|
|
582
|
+
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
583
|
+
[accountId, runStartedAt, object, pageCursor]
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Clear the pagination page_cursor for an object sync.
|
|
588
|
+
*/
|
|
589
|
+
async clearObjectPageCursor(accountId, runStartedAt, object) {
|
|
590
|
+
await this.updateObjectPageCursor(accountId, runStartedAt, object, null);
|
|
591
|
+
}
|
|
573
592
|
/**
|
|
574
593
|
* Update the cursor for an object sync.
|
|
575
594
|
* Only updates if the new cursor is higher than the existing one (cursors should never decrease).
|
|
@@ -602,9 +621,10 @@ var PostgresClient = class {
|
|
|
602
621
|
}
|
|
603
622
|
/**
|
|
604
623
|
* Get the highest cursor from previous syncs for an object type.
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
624
|
+
* Uses only completed object runs.
|
|
625
|
+
* - During the initial backfill we page through history, but we also update the cursor as we go.
|
|
626
|
+
* If we crash mid-backfill and reuse that cursor, we can accidentally switch into incremental mode
|
|
627
|
+
* too early and only ever fetch the newest page (breaking the historical backfill).
|
|
608
628
|
*
|
|
609
629
|
* Handles two cursor formats:
|
|
610
630
|
* - Numeric: compared as bigint for correct ordering
|
|
@@ -619,11 +639,31 @@ var PostgresClient = class {
|
|
|
619
639
|
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
620
640
|
WHERE o."_account_id" = $1
|
|
621
641
|
AND o.object = $2
|
|
622
|
-
AND o.cursor IS NOT NULL
|
|
642
|
+
AND o.cursor IS NOT NULL
|
|
643
|
+
AND o.status = 'complete'`,
|
|
623
644
|
[accountId, object]
|
|
624
645
|
);
|
|
625
646
|
return result.rows[0]?.cursor ?? null;
|
|
626
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Get the highest cursor from previous syncs for an object type, excluding the current run.
|
|
650
|
+
*/
|
|
651
|
+
async getLastCursorBeforeRun(accountId, object, runStartedAt) {
|
|
652
|
+
const result = await this.query(
|
|
653
|
+
`SELECT CASE
|
|
654
|
+
WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C")
|
|
655
|
+
ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text
|
|
656
|
+
END as cursor
|
|
657
|
+
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
658
|
+
WHERE o."_account_id" = $1
|
|
659
|
+
AND o.object = $2
|
|
660
|
+
AND o.cursor IS NOT NULL
|
|
661
|
+
AND o.status = 'complete'
|
|
662
|
+
AND o.run_started_at < $3`,
|
|
663
|
+
[accountId, object, runStartedAt]
|
|
664
|
+
);
|
|
665
|
+
return result.rows[0]?.cursor ?? null;
|
|
666
|
+
}
|
|
627
667
|
/**
|
|
628
668
|
* Delete all sync runs and object runs for an account.
|
|
629
669
|
* Useful for testing or resetting sync state.
|
|
@@ -644,7 +684,7 @@ var PostgresClient = class {
|
|
|
644
684
|
async completeObjectSync(accountId, runStartedAt, object) {
|
|
645
685
|
await this.query(
|
|
646
686
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
647
|
-
SET status = 'complete', completed_at = now()
|
|
687
|
+
SET status = 'complete', completed_at = now(), page_cursor = NULL
|
|
648
688
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
649
689
|
[accountId, runStartedAt, object]
|
|
650
690
|
);
|
|
@@ -660,7 +700,7 @@ var PostgresClient = class {
|
|
|
660
700
|
async failObjectSync(accountId, runStartedAt, object, errorMessage) {
|
|
661
701
|
await this.query(
|
|
662
702
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
663
|
-
SET status = 'error', error_message = $4, completed_at = now()
|
|
703
|
+
SET status = 'error', error_message = $4, completed_at = now(), page_cursor = NULL
|
|
664
704
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
665
705
|
[accountId, runStartedAt, object, errorMessage]
|
|
666
706
|
);
|
|
@@ -1961,57 +2001,74 @@ var StripeSync = class {
|
|
|
1961
2001
|
* ```
|
|
1962
2002
|
*/
|
|
1963
2003
|
async processNext(object, params) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
processed: 0,
|
|
1979
|
-
hasMore: false,
|
|
1980
|
-
runStartedAt
|
|
1981
|
-
};
|
|
1982
|
-
}
|
|
1983
|
-
if (objRun?.status === "pending") {
|
|
1984
|
-
const started = await this.postgresClient.tryStartObjectSync(
|
|
1985
|
-
accountId,
|
|
1986
|
-
runStartedAt,
|
|
1987
|
-
resourceName
|
|
1988
|
-
);
|
|
1989
|
-
if (!started) {
|
|
2004
|
+
try {
|
|
2005
|
+
await this.getCurrentAccount();
|
|
2006
|
+
const accountId = await this.getAccountId();
|
|
2007
|
+
const resourceName = this.getResourceName(object);
|
|
2008
|
+
let runStartedAt;
|
|
2009
|
+
if (params?.runStartedAt) {
|
|
2010
|
+
runStartedAt = params.runStartedAt;
|
|
2011
|
+
} else {
|
|
2012
|
+
const { runKey } = await this.joinOrCreateSyncRun(params?.triggeredBy ?? "processNext");
|
|
2013
|
+
runStartedAt = runKey.runStartedAt;
|
|
2014
|
+
}
|
|
2015
|
+
await this.postgresClient.createObjectRuns(accountId, runStartedAt, [resourceName]);
|
|
2016
|
+
const objRun = await this.postgresClient.getObjectRun(accountId, runStartedAt, resourceName);
|
|
2017
|
+
if (objRun?.status === "complete" || objRun?.status === "error") {
|
|
1990
2018
|
return {
|
|
1991
2019
|
processed: 0,
|
|
1992
|
-
hasMore:
|
|
2020
|
+
hasMore: false,
|
|
1993
2021
|
runStartedAt
|
|
1994
2022
|
};
|
|
1995
2023
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2024
|
+
if (objRun?.status === "pending") {
|
|
2025
|
+
const started = await this.postgresClient.tryStartObjectSync(
|
|
2026
|
+
accountId,
|
|
2027
|
+
runStartedAt,
|
|
2028
|
+
resourceName
|
|
2029
|
+
);
|
|
2030
|
+
if (!started) {
|
|
2031
|
+
return {
|
|
2032
|
+
processed: 0,
|
|
2033
|
+
hasMore: true,
|
|
2034
|
+
runStartedAt
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
let cursor = null;
|
|
2039
|
+
if (!params?.created) {
|
|
2040
|
+
const lastCursor = await this.postgresClient.getLastCursorBeforeRun(
|
|
2041
|
+
accountId,
|
|
2042
|
+
resourceName,
|
|
2043
|
+
runStartedAt
|
|
2044
|
+
);
|
|
2003
2045
|
cursor = lastCursor ?? null;
|
|
2004
2046
|
}
|
|
2047
|
+
const result = await this.fetchOnePage(
|
|
2048
|
+
object,
|
|
2049
|
+
accountId,
|
|
2050
|
+
resourceName,
|
|
2051
|
+
runStartedAt,
|
|
2052
|
+
cursor,
|
|
2053
|
+
objRun?.pageCursor ?? null,
|
|
2054
|
+
params
|
|
2055
|
+
);
|
|
2056
|
+
return result;
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
throw this.appendMigrationHint(error);
|
|
2005
2059
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2060
|
+
}
|
|
2061
|
+
appendMigrationHint(error) {
|
|
2062
|
+
const hint = "Error occurred. Make sure you are up to date with DB migrations which can sometimes help with this. Details:";
|
|
2063
|
+
const withHint = (message) => message.includes(hint) ? message : `${hint}
|
|
2064
|
+
${message}`;
|
|
2065
|
+
if (error instanceof Error) {
|
|
2066
|
+
const { stack } = error;
|
|
2067
|
+
error.message = withHint(error.message);
|
|
2068
|
+
if (stack) error.stack = stack;
|
|
2069
|
+
return error;
|
|
2070
|
+
}
|
|
2071
|
+
return new Error(withHint(String(error)));
|
|
2015
2072
|
}
|
|
2016
2073
|
/**
|
|
2017
2074
|
* Get the database resource name for a SyncObject type
|
|
@@ -2043,7 +2100,7 @@ var StripeSync = class {
|
|
|
2043
2100
|
* Uses resourceRegistry for DRY list/upsert operations.
|
|
2044
2101
|
* Uses the observable sync system for tracking progress.
|
|
2045
2102
|
*/
|
|
2046
|
-
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, params) {
|
|
2103
|
+
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, pageCursor, params) {
|
|
2047
2104
|
const limit = 100;
|
|
2048
2105
|
if (object === "payment_method" || object === "tax_id") {
|
|
2049
2106
|
this.config.logger?.warn(`processNext for ${object} requires customer context`);
|
|
@@ -2071,7 +2128,16 @@ var StripeSync = class {
|
|
|
2071
2128
|
listParams.created = created;
|
|
2072
2129
|
}
|
|
2073
2130
|
}
|
|
2131
|
+
if (pageCursor) {
|
|
2132
|
+
listParams.starting_after = pageCursor;
|
|
2133
|
+
}
|
|
2074
2134
|
const response = await config.listFn(listParams);
|
|
2135
|
+
if (response.data.length === 0 && response.has_more) {
|
|
2136
|
+
const message = `Stripe returned has_more=true with empty page for ${resourceName}. Aborting to avoid infinite loop.`;
|
|
2137
|
+
this.config.logger?.warn(message);
|
|
2138
|
+
await this.postgresClient.failObjectSync(accountId, runStartedAt, resourceName, message);
|
|
2139
|
+
return { processed: 0, hasMore: false, runStartedAt };
|
|
2140
|
+
}
|
|
2075
2141
|
if (response.data.length > 0) {
|
|
2076
2142
|
this.config.logger?.info(`processNext: upserting ${response.data.length} ${resourceName}`);
|
|
2077
2143
|
await config.upsertFn(response.data, accountId, params?.backfillRelatedEntities);
|
|
@@ -2092,6 +2158,15 @@ var StripeSync = class {
|
|
|
2092
2158
|
String(maxCreated)
|
|
2093
2159
|
);
|
|
2094
2160
|
}
|
|
2161
|
+
const lastId = response.data[response.data.length - 1].id;
|
|
2162
|
+
if (response.has_more) {
|
|
2163
|
+
await this.postgresClient.updateObjectPageCursor(
|
|
2164
|
+
accountId,
|
|
2165
|
+
runStartedAt,
|
|
2166
|
+
resourceName,
|
|
2167
|
+
lastId
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2095
2170
|
}
|
|
2096
2171
|
if (!response.has_more) {
|
|
2097
2172
|
await this.postgresClient.completeObjectSync(accountId, runStartedAt, resourceName);
|
package/dist/cli/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var import_commander = require("commander");
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "stripe-experiment-sync",
|
|
36
|
-
version: "1.0.
|
|
36
|
+
version: "1.0.19",
|
|
37
37
|
private: false,
|
|
38
38
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
39
39
|
type: "module",
|
|
@@ -580,7 +580,8 @@ var PostgresClient = class {
|
|
|
580
580
|
`UPDATE "${this.config.schema}"."_sync_obj_runs" o
|
|
581
581
|
SET status = 'error',
|
|
582
582
|
error_message = 'Auto-cancelled: stale (no update in 5 min)',
|
|
583
|
-
completed_at = now()
|
|
583
|
+
completed_at = now(),
|
|
584
|
+
page_cursor = NULL
|
|
584
585
|
WHERE o."_account_id" = $1
|
|
585
586
|
AND o.status = 'running'
|
|
586
587
|
AND o.updated_at < now() - interval '5 minutes'`,
|
|
@@ -726,7 +727,7 @@ var PostgresClient = class {
|
|
|
726
727
|
*/
|
|
727
728
|
async getObjectRun(accountId, runStartedAt, object) {
|
|
728
729
|
const result = await this.query(
|
|
729
|
-
`SELECT object, status, processed_count, cursor
|
|
730
|
+
`SELECT object, status, processed_count, cursor, page_cursor
|
|
730
731
|
FROM "${this.config.schema}"."_sync_obj_runs"
|
|
731
732
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
732
733
|
[accountId, runStartedAt, object]
|
|
@@ -737,7 +738,8 @@ var PostgresClient = class {
|
|
|
737
738
|
object: row.object,
|
|
738
739
|
status: row.status,
|
|
739
740
|
processedCount: row.processed_count,
|
|
740
|
-
cursor: row.cursor
|
|
741
|
+
cursor: row.cursor,
|
|
742
|
+
pageCursor: row.page_cursor
|
|
741
743
|
};
|
|
742
744
|
}
|
|
743
745
|
/**
|
|
@@ -752,6 +754,23 @@ var PostgresClient = class {
|
|
|
752
754
|
[accountId, runStartedAt, object, count]
|
|
753
755
|
);
|
|
754
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* Update the pagination page_cursor used for backfills using Stripe list calls.
|
|
759
|
+
*/
|
|
760
|
+
async updateObjectPageCursor(accountId, runStartedAt, object, pageCursor) {
|
|
761
|
+
await this.query(
|
|
762
|
+
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
763
|
+
SET page_cursor = $4, updated_at = now()
|
|
764
|
+
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
765
|
+
[accountId, runStartedAt, object, pageCursor]
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Clear the pagination page_cursor for an object sync.
|
|
770
|
+
*/
|
|
771
|
+
async clearObjectPageCursor(accountId, runStartedAt, object) {
|
|
772
|
+
await this.updateObjectPageCursor(accountId, runStartedAt, object, null);
|
|
773
|
+
}
|
|
755
774
|
/**
|
|
756
775
|
* Update the cursor for an object sync.
|
|
757
776
|
* Only updates if the new cursor is higher than the existing one (cursors should never decrease).
|
|
@@ -784,9 +803,10 @@ var PostgresClient = class {
|
|
|
784
803
|
}
|
|
785
804
|
/**
|
|
786
805
|
* Get the highest cursor from previous syncs for an object type.
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
806
|
+
* Uses only completed object runs.
|
|
807
|
+
* - During the initial backfill we page through history, but we also update the cursor as we go.
|
|
808
|
+
* If we crash mid-backfill and reuse that cursor, we can accidentally switch into incremental mode
|
|
809
|
+
* too early and only ever fetch the newest page (breaking the historical backfill).
|
|
790
810
|
*
|
|
791
811
|
* Handles two cursor formats:
|
|
792
812
|
* - Numeric: compared as bigint for correct ordering
|
|
@@ -801,11 +821,31 @@ var PostgresClient = class {
|
|
|
801
821
|
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
802
822
|
WHERE o."_account_id" = $1
|
|
803
823
|
AND o.object = $2
|
|
804
|
-
AND o.cursor IS NOT NULL
|
|
824
|
+
AND o.cursor IS NOT NULL
|
|
825
|
+
AND o.status = 'complete'`,
|
|
805
826
|
[accountId, object]
|
|
806
827
|
);
|
|
807
828
|
return result.rows[0]?.cursor ?? null;
|
|
808
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Get the highest cursor from previous syncs for an object type, excluding the current run.
|
|
832
|
+
*/
|
|
833
|
+
async getLastCursorBeforeRun(accountId, object, runStartedAt) {
|
|
834
|
+
const result = await this.query(
|
|
835
|
+
`SELECT CASE
|
|
836
|
+
WHEN BOOL_OR(o.cursor !~ '^\\d+$') THEN MAX(o.cursor COLLATE "C")
|
|
837
|
+
ELSE MAX(CASE WHEN o.cursor ~ '^\\d+$' THEN o.cursor::bigint END)::text
|
|
838
|
+
END as cursor
|
|
839
|
+
FROM "${this.config.schema}"."_sync_obj_runs" o
|
|
840
|
+
WHERE o."_account_id" = $1
|
|
841
|
+
AND o.object = $2
|
|
842
|
+
AND o.cursor IS NOT NULL
|
|
843
|
+
AND o.status = 'complete'
|
|
844
|
+
AND o.run_started_at < $3`,
|
|
845
|
+
[accountId, object, runStartedAt]
|
|
846
|
+
);
|
|
847
|
+
return result.rows[0]?.cursor ?? null;
|
|
848
|
+
}
|
|
809
849
|
/**
|
|
810
850
|
* Delete all sync runs and object runs for an account.
|
|
811
851
|
* Useful for testing or resetting sync state.
|
|
@@ -826,7 +866,7 @@ var PostgresClient = class {
|
|
|
826
866
|
async completeObjectSync(accountId, runStartedAt, object) {
|
|
827
867
|
await this.query(
|
|
828
868
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
829
|
-
SET status = 'complete', completed_at = now()
|
|
869
|
+
SET status = 'complete', completed_at = now(), page_cursor = NULL
|
|
830
870
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
831
871
|
[accountId, runStartedAt, object]
|
|
832
872
|
);
|
|
@@ -842,7 +882,7 @@ var PostgresClient = class {
|
|
|
842
882
|
async failObjectSync(accountId, runStartedAt, object, errorMessage) {
|
|
843
883
|
await this.query(
|
|
844
884
|
`UPDATE "${this.config.schema}"."_sync_obj_runs"
|
|
845
|
-
SET status = 'error', error_message = $4, completed_at = now()
|
|
885
|
+
SET status = 'error', error_message = $4, completed_at = now(), page_cursor = NULL
|
|
846
886
|
WHERE "_account_id" = $1 AND run_started_at = $2 AND object = $3`,
|
|
847
887
|
[accountId, runStartedAt, object, errorMessage]
|
|
848
888
|
);
|
|
@@ -2143,57 +2183,74 @@ var StripeSync = class {
|
|
|
2143
2183
|
* ```
|
|
2144
2184
|
*/
|
|
2145
2185
|
async processNext(object, params) {
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
processed: 0,
|
|
2161
|
-
hasMore: false,
|
|
2162
|
-
runStartedAt
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
if (objRun?.status === "pending") {
|
|
2166
|
-
const started = await this.postgresClient.tryStartObjectSync(
|
|
2167
|
-
accountId,
|
|
2168
|
-
runStartedAt,
|
|
2169
|
-
resourceName
|
|
2170
|
-
);
|
|
2171
|
-
if (!started) {
|
|
2186
|
+
try {
|
|
2187
|
+
await this.getCurrentAccount();
|
|
2188
|
+
const accountId = await this.getAccountId();
|
|
2189
|
+
const resourceName = this.getResourceName(object);
|
|
2190
|
+
let runStartedAt;
|
|
2191
|
+
if (params?.runStartedAt) {
|
|
2192
|
+
runStartedAt = params.runStartedAt;
|
|
2193
|
+
} else {
|
|
2194
|
+
const { runKey } = await this.joinOrCreateSyncRun(params?.triggeredBy ?? "processNext");
|
|
2195
|
+
runStartedAt = runKey.runStartedAt;
|
|
2196
|
+
}
|
|
2197
|
+
await this.postgresClient.createObjectRuns(accountId, runStartedAt, [resourceName]);
|
|
2198
|
+
const objRun = await this.postgresClient.getObjectRun(accountId, runStartedAt, resourceName);
|
|
2199
|
+
if (objRun?.status === "complete" || objRun?.status === "error") {
|
|
2172
2200
|
return {
|
|
2173
2201
|
processed: 0,
|
|
2174
|
-
hasMore:
|
|
2202
|
+
hasMore: false,
|
|
2175
2203
|
runStartedAt
|
|
2176
2204
|
};
|
|
2177
2205
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2206
|
+
if (objRun?.status === "pending") {
|
|
2207
|
+
const started = await this.postgresClient.tryStartObjectSync(
|
|
2208
|
+
accountId,
|
|
2209
|
+
runStartedAt,
|
|
2210
|
+
resourceName
|
|
2211
|
+
);
|
|
2212
|
+
if (!started) {
|
|
2213
|
+
return {
|
|
2214
|
+
processed: 0,
|
|
2215
|
+
hasMore: true,
|
|
2216
|
+
runStartedAt
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
let cursor = null;
|
|
2221
|
+
if (!params?.created) {
|
|
2222
|
+
const lastCursor = await this.postgresClient.getLastCursorBeforeRun(
|
|
2223
|
+
accountId,
|
|
2224
|
+
resourceName,
|
|
2225
|
+
runStartedAt
|
|
2226
|
+
);
|
|
2185
2227
|
cursor = lastCursor ?? null;
|
|
2186
2228
|
}
|
|
2229
|
+
const result = await this.fetchOnePage(
|
|
2230
|
+
object,
|
|
2231
|
+
accountId,
|
|
2232
|
+
resourceName,
|
|
2233
|
+
runStartedAt,
|
|
2234
|
+
cursor,
|
|
2235
|
+
objRun?.pageCursor ?? null,
|
|
2236
|
+
params
|
|
2237
|
+
);
|
|
2238
|
+
return result;
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
throw this.appendMigrationHint(error);
|
|
2187
2241
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2242
|
+
}
|
|
2243
|
+
appendMigrationHint(error) {
|
|
2244
|
+
const hint = "Error occurred. Make sure you are up to date with DB migrations which can sometimes help with this. Details:";
|
|
2245
|
+
const withHint = (message) => message.includes(hint) ? message : `${hint}
|
|
2246
|
+
${message}`;
|
|
2247
|
+
if (error instanceof Error) {
|
|
2248
|
+
const { stack } = error;
|
|
2249
|
+
error.message = withHint(error.message);
|
|
2250
|
+
if (stack) error.stack = stack;
|
|
2251
|
+
return error;
|
|
2252
|
+
}
|
|
2253
|
+
return new Error(withHint(String(error)));
|
|
2197
2254
|
}
|
|
2198
2255
|
/**
|
|
2199
2256
|
* Get the database resource name for a SyncObject type
|
|
@@ -2225,7 +2282,7 @@ var StripeSync = class {
|
|
|
2225
2282
|
* Uses resourceRegistry for DRY list/upsert operations.
|
|
2226
2283
|
* Uses the observable sync system for tracking progress.
|
|
2227
2284
|
*/
|
|
2228
|
-
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, params) {
|
|
2285
|
+
async fetchOnePage(object, accountId, resourceName, runStartedAt, cursor, pageCursor, params) {
|
|
2229
2286
|
const limit = 100;
|
|
2230
2287
|
if (object === "payment_method" || object === "tax_id") {
|
|
2231
2288
|
this.config.logger?.warn(`processNext for ${object} requires customer context`);
|
|
@@ -2253,7 +2310,16 @@ var StripeSync = class {
|
|
|
2253
2310
|
listParams.created = created;
|
|
2254
2311
|
}
|
|
2255
2312
|
}
|
|
2313
|
+
if (pageCursor) {
|
|
2314
|
+
listParams.starting_after = pageCursor;
|
|
2315
|
+
}
|
|
2256
2316
|
const response = await config.listFn(listParams);
|
|
2317
|
+
if (response.data.length === 0 && response.has_more) {
|
|
2318
|
+
const message = `Stripe returned has_more=true with empty page for ${resourceName}. Aborting to avoid infinite loop.`;
|
|
2319
|
+
this.config.logger?.warn(message);
|
|
2320
|
+
await this.postgresClient.failObjectSync(accountId, runStartedAt, resourceName, message);
|
|
2321
|
+
return { processed: 0, hasMore: false, runStartedAt };
|
|
2322
|
+
}
|
|
2257
2323
|
if (response.data.length > 0) {
|
|
2258
2324
|
this.config.logger?.info(`processNext: upserting ${response.data.length} ${resourceName}`);
|
|
2259
2325
|
await config.upsertFn(response.data, accountId, params?.backfillRelatedEntities);
|
|
@@ -2274,6 +2340,15 @@ var StripeSync = class {
|
|
|
2274
2340
|
String(maxCreated)
|
|
2275
2341
|
);
|
|
2276
2342
|
}
|
|
2343
|
+
const lastId = response.data[response.data.length - 1].id;
|
|
2344
|
+
if (response.has_more) {
|
|
2345
|
+
await this.postgresClient.updateObjectPageCursor(
|
|
2346
|
+
accountId,
|
|
2347
|
+
runStartedAt,
|
|
2348
|
+
resourceName,
|
|
2349
|
+
lastId
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2277
2352
|
}
|
|
2278
2353
|
if (!response.has_more) {
|
|
2279
2354
|
await this.postgresClient.completeObjectSync(accountId, runStartedAt, resourceName);
|
package/dist/cli/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
migrateCommand,
|
|
6
6
|
syncCommand,
|
|
7
7
|
uninstallCommand
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-HZ2OIPQ5.js";
|
|
9
|
+
import "../chunk-XKBCLBFT.js";
|
|
10
|
+
import "../chunk-4P6TAP7L.js";
|
|
11
11
|
import {
|
|
12
12
|
package_default
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-CMGFQCD7.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|