stripe-experiment-sync 0.0.5 → 1.0.1

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/dist/index.d.cts CHANGED
@@ -1,78 +1,212 @@
1
- import { Express } from 'express';
2
- import pg, { PoolConfig, QueryResult } from 'pg';
3
- import pino from 'pino';
4
1
  import Stripe from 'stripe';
2
+ import pg, { PoolConfig, QueryResult } from 'pg';
5
3
  import { ConnectionOptions } from 'node:tls';
6
4
 
7
- interface StripeAutoSyncOptions {
8
- databaseUrl: string;
9
- stripeApiKey: string;
10
- baseUrl: () => string;
11
- webhookPath?: string;
12
- schema?: string;
13
- stripeApiVersion?: string;
14
- autoExpandLists?: boolean;
15
- backfillRelatedEntities?: boolean;
16
- keepWebhooksOnShutdown?: boolean;
17
- }
18
- interface StripeAutoSyncInfo {
19
- baseUrl: string;
20
- webhookUrl: string;
21
- webhookUuid: string;
22
- }
23
- /**
24
- * Manages Stripe webhook auto-sync infrastructure:
25
- * - Runs database migrations
26
- * - Creates managed webhook in Stripe
27
- * - Mounts webhook handler on Express app
28
- */
29
- declare class StripeAutoSync {
30
- private options;
31
- private webhookId;
32
- private webhookUuid;
33
- private stripeSync;
34
- constructor(options: StripeAutoSyncOptions);
35
- /**
36
- * Starts the Stripe Sync infrastructure and mounts webhook handler:
37
- * 1. Runs database migrations
38
- * 2. Creates StripeSync instance
39
- * 3. Creates managed webhook endpoint
40
- * 4. Mounts webhook handler on provided Express app
41
- * 5. Applies body parsing middleware (automatically skips webhook routes)
5
+ type PostgresConfig = {
6
+ schema: string;
7
+ poolConfig: PoolConfig;
8
+ };
9
+ declare class PostgresClient {
10
+ private config;
11
+ pool: pg.Pool;
12
+ constructor(config: PostgresConfig);
13
+ delete(table: string, id: string): Promise<boolean>;
14
+ query(text: string, params?: any[]): Promise<QueryResult>;
15
+ upsertMany<T extends {
16
+ [Key: string]: any;
17
+ }>(entries: T[], table: string): Promise<T[]>;
18
+ upsertManyWithTimestampProtection<T extends {
19
+ [Key: string]: any;
20
+ }>(entries: T[], table: string, accountId: string, syncTimestamp?: string): Promise<T[]>;
21
+ private cleanseArrayField;
22
+ findMissingEntries(table: string, ids: string[]): Promise<string[]>;
23
+ upsertAccount(accountData: {
24
+ id: string;
25
+ raw_data: any;
26
+ }, apiKeyHash: string): Promise<void>;
27
+ getAllAccounts(): Promise<any[]>;
28
+ /**
29
+ * Looks up an account ID by API key hash
30
+ * Uses the GIN index on api_key_hashes for fast lookups
31
+ * @param apiKeyHash - SHA-256 hash of the Stripe API key
32
+ * @returns Account ID if found, null otherwise
33
+ */
34
+ getAccountIdByApiKeyHash(apiKeyHash: string): Promise<string | null>;
35
+ /**
36
+ * Looks up full account data by API key hash
37
+ * @param apiKeyHash - SHA-256 hash of the Stripe API key
38
+ * @returns Account raw data if found, null otherwise
39
+ */
40
+ getAccountByApiKeyHash(apiKeyHash: string): Promise<any | null>;
41
+ private getAccountIdColumn;
42
+ getAccountRecordCounts(accountId: string): Promise<{
43
+ [tableName: string]: number;
44
+ }>;
45
+ deleteAccountWithCascade(accountId: string, useTransaction: boolean): Promise<{
46
+ [tableName: string]: number;
47
+ }>;
48
+ /**
49
+ * Hash a string to a 32-bit integer for use with PostgreSQL advisory locks.
50
+ * Uses a simple hash algorithm that produces consistent results.
51
+ */
52
+ private hashToInt32;
53
+ /**
54
+ * Acquire a PostgreSQL advisory lock for the given key.
55
+ * This lock is automatically released when the connection is closed or explicitly released.
56
+ * Advisory locks are session-level and will block until the lock is available.
57
+ *
58
+ * @param key - A string key to lock on (will be hashed to an integer)
59
+ */
60
+ acquireAdvisoryLock(key: string): Promise<void>;
61
+ /**
62
+ * Release a PostgreSQL advisory lock for the given key.
42
63
  *
43
- * @param app - Express app to mount webhook handler on
44
- * @returns Information about the running instance
64
+ * @param key - The same string key used to acquire the lock
45
65
  */
46
- start(app: Express): Promise<StripeAutoSyncInfo>;
66
+ releaseAdvisoryLock(key: string): Promise<void>;
47
67
  /**
48
- * Stops all services and cleans up resources:
49
- * 1. Optionally deletes Stripe webhook endpoint from Stripe and database (based on keepWebhooksOnShutdown)
68
+ * Execute a function while holding an advisory lock.
69
+ * The lock is automatically released after the function completes (success or error).
70
+ *
71
+ * IMPORTANT: This acquires a dedicated connection from the pool and holds it for the
72
+ * duration of the function execution. PostgreSQL advisory locks are session-level,
73
+ * so we must use the same connection for lock acquisition, operations, and release.
74
+ *
75
+ * @param key - A string key to lock on (will be hashed to an integer)
76
+ * @param fn - The function to execute while holding the lock
77
+ * @returns The result of the function
78
+ */
79
+ withAdvisoryLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
80
+ /**
81
+ * Cancel stale runs (running but no object updated in 5 minutes).
82
+ * Called before creating a new run to clean up crashed syncs.
83
+ * Only cancels runs that have objects AND none have recent activity.
84
+ * Runs without objects yet (just created) are not considered stale.
85
+ */
86
+ cancelStaleRuns(accountId: string): Promise<void>;
87
+ /**
88
+ * Get or create a sync run for this account.
89
+ * Returns existing run if one is active, otherwise creates new one.
90
+ * Auto-cancels stale runs before checking.
91
+ *
92
+ * @returns RunKey with isNew flag, or null if constraint violation (race condition)
93
+ */
94
+ getOrCreateSyncRun(accountId: string, triggeredBy?: string): Promise<{
95
+ accountId: string;
96
+ runStartedAt: Date;
97
+ isNew: boolean;
98
+ } | null>;
99
+ /**
100
+ * Get the active sync run for an account (if any).
50
101
  */
51
- stop(): Promise<void>;
102
+ getActiveSyncRun(accountId: string): Promise<{
103
+ accountId: string;
104
+ runStartedAt: Date;
105
+ } | null>;
52
106
  /**
53
- * Returns Express middleware for body parsing that automatically skips webhook routes.
54
- * This middleware applies JSON and URL-encoded parsers to all routes EXCEPT the webhook path,
55
- * which needs raw body for signature verification.
107
+ * Get full sync run details.
108
+ */
109
+ getSyncRun(accountId: string, runStartedAt: Date): Promise<{
110
+ accountId: string;
111
+ runStartedAt: Date;
112
+ status: string;
113
+ maxConcurrent: number;
114
+ } | null>;
115
+ /**
116
+ * Mark a sync run as complete.
117
+ */
118
+ completeSyncRun(accountId: string, runStartedAt: Date): Promise<void>;
119
+ /**
120
+ * Mark a sync run as failed.
121
+ */
122
+ failSyncRun(accountId: string, runStartedAt: Date, errorMessage: string): Promise<void>;
123
+ /**
124
+ * Create object run entries for a sync run.
125
+ * All objects start as 'pending'.
126
+ */
127
+ createObjectRuns(accountId: string, runStartedAt: Date, objects: string[]): Promise<void>;
128
+ /**
129
+ * Try to start an object sync (respects max_concurrent).
130
+ * Returns true if claimed, false if already running or at concurrency limit.
56
131
  *
57
- * @returns Express middleware function
132
+ * Note: There's a small race window where concurrent calls could result in
133
+ * max_concurrent + 1 objects running. This is acceptable behavior.
134
+ */
135
+ tryStartObjectSync(accountId: string, runStartedAt: Date, object: string): Promise<boolean>;
136
+ /**
137
+ * Get object run details.
138
+ */
139
+ getObjectRun(accountId: string, runStartedAt: Date, object: string): Promise<{
140
+ object: string;
141
+ status: string;
142
+ processedCount: number;
143
+ cursor: string | null;
144
+ } | null>;
145
+ /**
146
+ * Update progress for an object sync.
147
+ * Also touches updated_at for stale detection.
148
+ */
149
+ incrementObjectProgress(accountId: string, runStartedAt: Date, object: string, count: number): Promise<void>;
150
+ /**
151
+ * Update the cursor for an object sync.
152
+ * Only updates if the new cursor is higher than the existing one (cursors should never decrease).
153
+ * For numeric cursors (timestamps), uses GREATEST to ensure monotonic increase.
154
+ * For non-numeric cursors, just sets the value directly.
155
+ */
156
+ updateObjectCursor(accountId: string, runStartedAt: Date, object: string, cursor: string | null): Promise<void>;
157
+ /**
158
+ * Get the highest cursor from previous syncs for an object type.
159
+ * This considers completed, error, AND running runs to ensure recovery syncs
160
+ * don't re-process data that was already synced before a crash.
161
+ * A 'running' status with a cursor means the process was killed mid-sync.
162
+ */
163
+ getLastCompletedCursor(accountId: string, object: string): Promise<string | null>;
164
+ /**
165
+ * Delete all sync runs and object runs for an account.
166
+ * Useful for testing or resetting sync state.
167
+ */
168
+ deleteSyncRuns(accountId: string): Promise<void>;
169
+ /**
170
+ * Mark an object sync as complete.
171
+ */
172
+ completeObjectSync(accountId: string, runStartedAt: Date, object: string): Promise<void>;
173
+ /**
174
+ * Mark an object sync as failed.
175
+ */
176
+ failObjectSync(accountId: string, runStartedAt: Date, object: string, errorMessage: string): Promise<void>;
177
+ /**
178
+ * Count running objects in a run.
179
+ */
180
+ countRunningObjects(accountId: string, runStartedAt: Date): Promise<number>;
181
+ /**
182
+ * Get the next pending object to process.
183
+ * Returns null if no pending objects or at concurrency limit.
58
184
  */
59
- private getBodyParserMiddleware;
185
+ getNextPendingObject(accountId: string, runStartedAt: Date): Promise<string | null>;
60
186
  /**
61
- * Mounts the Stripe webhook handler on the provided Express app.
62
- * Applies raw body parser middleware for signature verification.
63
- * IMPORTANT: Must be called BEFORE app.use(express.json()) to ensure raw body parsing.
187
+ * Check if all objects in a run are complete (or error).
64
188
  */
65
- private mountWebhook;
189
+ areAllObjectsComplete(accountId: string, runStartedAt: Date): Promise<boolean>;
66
190
  }
67
191
 
192
+ /**
193
+ * Simple logger interface compatible with both pino and console
194
+ */
195
+ interface Logger {
196
+ info(message?: unknown, ...optionalParams: unknown[]): void;
197
+ warn(message?: unknown, ...optionalParams: unknown[]): void;
198
+ error(message?: unknown, ...optionalParams: unknown[]): void;
199
+ }
68
200
  type RevalidateEntity = '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' | 'entitlements';
69
201
  type StripeSyncConfig = {
70
202
  /** @deprecated Use `poolConfig` with a connection string instead. */
71
203
  databaseUrl?: string;
72
- /** Database schema name. */
73
- schema?: string;
74
204
  /** Stripe secret key used to authenticate requests to the Stripe API. Defaults to empty string */
75
205
  stripeSecretKey: string;
206
+ /** Stripe account ID. If not provided, will be retrieved from Stripe API. Used as fallback option. */
207
+ stripeAccountId?: string;
208
+ /** Stripe webhook signing secret for validating webhook signatures. Required if not using managed webhooks. */
209
+ stripeWebhookSecret?: string;
76
210
  /** Stripe API version for the webhooks, defaults to 2020-08-27 */
77
211
  stripeApiVersion?: string;
78
212
  /**
@@ -94,7 +228,28 @@ type StripeSyncConfig = {
94
228
  /** @deprecated Use `poolConfig` instead. */
95
229
  maxPostgresConnections?: number;
96
230
  poolConfig: PoolConfig;
97
- logger?: pino.Logger;
231
+ logger?: Logger;
232
+ /**
233
+ * Maximum number of retry attempts for 429 rate limit errors.
234
+ * Default: 5
235
+ */
236
+ maxRetries?: number;
237
+ /**
238
+ * Initial delay in milliseconds before first retry attempt.
239
+ * Delay increases exponentially: 1s, 2s, 4s, 8s, 16s, etc.
240
+ * Default: 1000 (1 second)
241
+ */
242
+ initialRetryDelayMs?: number;
243
+ /**
244
+ * Maximum delay in milliseconds between retry attempts.
245
+ * Default: 60000 (60 seconds)
246
+ */
247
+ maxRetryDelayMs?: number;
248
+ /**
249
+ * Random jitter in milliseconds added to retry delays to prevent thundering herd.
250
+ * Default: 500
251
+ */
252
+ retryJitterMs?: number;
98
253
  };
99
254
  type SyncObject = 'all' | 'customer' | 'customer_with_entitlements' | 'invoice' | 'price' | 'product' | 'subscription' | 'subscription_schedules' | 'setup_intent' | 'payment_method' | 'dispute' | 'charge' | 'payment_intent' | 'plan' | 'tax_id' | 'credit_note' | 'early_fraud_warning' | 'refund' | 'checkout_sessions';
100
255
  interface Sync {
@@ -119,7 +274,7 @@ interface SyncBackfill {
119
274
  refunds?: Sync;
120
275
  checkoutSessions?: Sync;
121
276
  }
122
- interface SyncBackfillParams {
277
+ interface SyncParams {
123
278
  created?: {
124
279
  /**
125
280
  * Minimum value to filter by (exclusive)
@@ -150,83 +305,287 @@ interface SyncFeaturesParams {
150
305
  object: 'features';
151
306
  pagination?: Pick<Stripe.PaginationParams, 'starting_after' | 'ending_before'>;
152
307
  }
153
-
154
- interface EntitySchema {
155
- readonly properties: string[];
308
+ /**
309
+ * Result of processing a single page of items via processNext()
310
+ */
311
+ interface ProcessNextResult {
312
+ /** Number of items processed in this page */
313
+ processed: number;
314
+ /** Whether there are more items to process */
315
+ hasMore: boolean;
316
+ /** The sync run this processing belongs to */
317
+ runStartedAt: Date;
318
+ }
319
+ /**
320
+ * Parameters for processNext() including optional run context
321
+ */
322
+ interface ProcessNextParams extends SyncParams {
323
+ /** Join an existing sync run instead of creating a new one */
324
+ runStartedAt?: Date;
325
+ /** Who/what triggered this sync (for observability) */
326
+ triggeredBy?: string;
156
327
  }
157
328
 
158
- type PostgresConfig = {
159
- schema: string;
160
- poolConfig: PoolConfig;
161
- };
162
- declare class PostgresClient {
329
+ declare class StripeSync {
163
330
  private config;
164
- pool: pg.Pool;
165
- constructor(config: PostgresConfig);
166
- delete(table: string, id: string): Promise<boolean>;
167
- query(text: string, params?: string[]): Promise<QueryResult>;
168
- upsertMany<T extends {
169
- [Key: string]: any;
170
- }>(entries: T[], table: string, tableSchema: EntitySchema): Promise<T[]>;
171
- upsertManyWithTimestampProtection<T extends {
172
- [Key: string]: any;
173
- }>(entries: T[], table: string, tableSchema: EntitySchema, syncTimestamp?: string): Promise<T[]>;
174
- findMissingEntries(table: string, ids: string[]): Promise<string[]>;
331
+ stripe: Stripe;
332
+ postgresClient: PostgresClient;
333
+ constructor(config: StripeSyncConfig);
334
+ /**
335
+ * Get the Stripe account ID. Delegates to getCurrentAccount() for the actual lookup.
336
+ */
337
+ getAccountId(objectAccountId?: string): Promise<string>;
338
+ /**
339
+ * Upsert Stripe account information to the database
340
+ * @param account - Stripe account object
341
+ * @param apiKeyHash - SHA-256 hash of API key to store for fast lookups
342
+ */
343
+ private upsertAccount;
175
344
  /**
176
- * Returns an (yesql formatted) upsert function based on the key/vals of an object.
177
- * eg,
178
- * insert into customers ("id", "name")
179
- * values (:id, :name)
180
- * on conflict (id)
181
- * do update set (
182
- * "id" = :id,
183
- * "name" = :name
184
- * )
345
+ * Get the current account being synced. Uses database lookup by API key hash,
346
+ * with fallback to Stripe API if not found (first-time setup or new API key).
347
+ * @param objectAccountId - Optional account ID from event data (Connect scenarios)
185
348
  */
186
- private constructUpsertSql;
349
+ getCurrentAccount(objectAccountId?: string): Promise<Stripe.Account | null>;
187
350
  /**
188
- * Returns an (yesql formatted) upsert function with timestamp protection.
351
+ * Get all accounts that have been synced to the database
352
+ */
353
+ getAllSyncedAccounts(): Promise<Stripe.Account[]>;
354
+ /**
355
+ * DANGEROUS: Delete an account and all associated data from the database
356
+ * This operation cannot be undone!
189
357
  *
190
- * The WHERE clause in ON CONFLICT DO UPDATE only applies to the conflicting row
191
- * (the row being updated), not to all rows in the table. PostgreSQL ensures that
192
- * the condition is evaluated only for the specific row that conflicts with the INSERT.
358
+ * @param accountId - The Stripe account ID to delete
359
+ * @param options - Options for deletion behavior
360
+ * @param options.dryRun - If true, only count records without deleting (default: false)
361
+ * @param options.useTransaction - If true, use transaction for atomic deletion (default: true)
362
+ * @returns Deletion summary with counts and warnings
363
+ */
364
+ dangerouslyDeleteSyncedAccountData(accountId: string, options?: {
365
+ dryRun?: boolean;
366
+ useTransaction?: boolean;
367
+ }): Promise<{
368
+ deletedAccountId: string;
369
+ deletedRecordCounts: {
370
+ [tableName: string]: number;
371
+ };
372
+ warnings: string[];
373
+ }>;
374
+ processWebhook(payload: Buffer | string, signature: string | undefined): Promise<void>;
375
+ private readonly eventHandlers;
376
+ private readonly resourceRegistry;
377
+ processEvent(event: Stripe.Event): Promise<void>;
378
+ /**
379
+ * Returns an array of all webhook event types that this sync engine can handle.
380
+ * Useful for configuring webhook endpoints with specific event subscriptions.
381
+ */
382
+ getSupportedEventTypes(): Stripe.WebhookEndpointCreateParams.EnabledEvent[];
383
+ /**
384
+ * Returns an array of all object types that can be synced via processNext/processUntilDone.
385
+ * Ordered for backfill: parents before children (products before prices, customers before subscriptions).
386
+ * Order is determined by the `order` field in resourceRegistry.
387
+ */
388
+ getSupportedSyncObjects(): Exclude<SyncObject, 'all' | 'customer_with_entitlements'>[];
389
+ private handleChargeEvent;
390
+ private handleCustomerDeletedEvent;
391
+ private handleCustomerEvent;
392
+ private handleCheckoutSessionEvent;
393
+ private handleSubscriptionEvent;
394
+ private handleTaxIdEvent;
395
+ private handleTaxIdDeletedEvent;
396
+ private handleInvoiceEvent;
397
+ private handleProductEvent;
398
+ private handleProductDeletedEvent;
399
+ private handlePriceEvent;
400
+ private handlePriceDeletedEvent;
401
+ private handlePlanEvent;
402
+ private handlePlanDeletedEvent;
403
+ private handleSetupIntentEvent;
404
+ private handleSubscriptionScheduleEvent;
405
+ private handlePaymentMethodEvent;
406
+ private handleDisputeEvent;
407
+ private handlePaymentIntentEvent;
408
+ private handleCreditNoteEvent;
409
+ private handleEarlyFraudWarningEvent;
410
+ private handleRefundEvent;
411
+ private handleReviewEvent;
412
+ private handleEntitlementSummaryEvent;
413
+ private getSyncTimestamp;
414
+ private shouldRefetchEntity;
415
+ private fetchOrUseWebhookData;
416
+ syncSingleEntity(stripeId: string): Promise<Stripe.Product[] | Stripe.Price[] | (Stripe.Customer | Stripe.DeletedCustomer)[] | Stripe.Subscription[] | Stripe.Invoice[] | Stripe.Charge[] | Stripe.SetupIntent[] | Stripe.PaymentMethod[] | Stripe.PaymentIntent[] | Stripe.TaxId[] | Stripe.CreditNote[] | Stripe.Dispute[] | Stripe.Radar.EarlyFraudWarning[] | Stripe.Refund[] | Stripe.Checkout.Session[] | Stripe.Review[] | Stripe.Entitlements.Feature[] | undefined>;
417
+ /**
418
+ * Process one page of items for the specified object type.
419
+ * Returns the number of items processed and whether there are more pages.
193
420
  *
421
+ * This method is designed for queue-based consumption where each page
422
+ * is processed as a separate job. Uses the observable sync system for tracking.
194
423
  *
195
- * eg:
196
- * INSERT INTO "stripe"."charges" (
197
- * "id", "amount", "created", "last_synced_at"
198
- * )
199
- * VALUES (
200
- * :id, :amount, :created, :last_synced_at
201
- * )
202
- * ON CONFLICT (id) DO UPDATE SET
203
- * "amount" = EXCLUDED."amount",
204
- * "created" = EXCLUDED."created",
205
- * last_synced_at = :last_synced_at
206
- * WHERE "charges"."last_synced_at" IS NULL
207
- * OR "charges"."last_synced_at" < :last_synced_at;
208
- */
209
- private constructUpsertWithTimestampProtectionSql;
210
- /**
211
- * For array object field like invoice.custom_fields
212
- * ex: [{"name":"Project name","value":"Test Project"}]
424
+ * @param object - The Stripe object type to sync (e.g., 'customer', 'product')
425
+ * @param params - Optional parameters for filtering and run context
426
+ * @returns ProcessNextResult with processed count, hasMore flag, and runStartedAt
213
427
  *
214
- * we need to stringify it first cos passing array object directly will end up with
215
- * {
216
- * invalid input syntax for type json
217
- * detail: 'Expected ":", but found "}".',
218
- * where: 'JSON data, line 1: ...\\":\\"Project name\\",\\"value\\":\\"Test Project\\"}"}',
428
+ * @example
429
+ * ```typescript
430
+ * // Queue worker
431
+ * const { hasMore, runStartedAt } = await stripeSync.processNext('customer')
432
+ * if (hasMore) {
433
+ * await queue.send({ object: 'customer', runStartedAt })
219
434
  * }
435
+ * ```
220
436
  */
221
- private cleanseArrayField;
437
+ processNext(object: Exclude<SyncObject, 'all' | 'customer_with_entitlements'>, params?: ProcessNextParams): Promise<ProcessNextResult>;
438
+ /**
439
+ * Get the database resource name for a SyncObject type
440
+ */
441
+ private getResourceName;
442
+ /**
443
+ * Fetch one page of items from Stripe and upsert to database.
444
+ * Uses resourceRegistry for DRY list/upsert operations.
445
+ * Uses the observable sync system for tracking progress.
446
+ */
447
+ private fetchOnePage;
448
+ /**
449
+ * Process all pages for all (or specified) object types until complete.
450
+ *
451
+ * @param params - Optional parameters for filtering and specifying object types
452
+ * @returns SyncBackfill with counts for each synced resource type
453
+ */
454
+ /**
455
+ * Process all pages for a single object type until complete.
456
+ * Loops processNext() internally until hasMore is false.
457
+ *
458
+ * @param object - The object type to sync
459
+ * @param runStartedAt - The sync run to use (for sharing across objects)
460
+ * @param params - Optional sync parameters
461
+ * @returns Sync result with count of synced items
462
+ */
463
+ private processObjectUntilDone;
464
+ processUntilDone(params?: SyncParams): Promise<SyncBackfill>;
465
+ /**
466
+ * Internal implementation of processUntilDone with an existing run.
467
+ */
468
+ private processUntilDoneWithRun;
469
+ /**
470
+ * Sync payment methods with an existing run (special case - iterates customers)
471
+ */
472
+ private syncPaymentMethodsWithRun;
473
+ syncProducts(syncParams?: SyncParams): Promise<Sync>;
474
+ syncPrices(syncParams?: SyncParams): Promise<Sync>;
475
+ syncPlans(syncParams?: SyncParams): Promise<Sync>;
476
+ syncCustomers(syncParams?: SyncParams): Promise<Sync>;
477
+ syncSubscriptions(syncParams?: SyncParams): Promise<Sync>;
478
+ syncSubscriptionSchedules(syncParams?: SyncParams): Promise<Sync>;
479
+ syncInvoices(syncParams?: SyncParams): Promise<Sync>;
480
+ syncCharges(syncParams?: SyncParams): Promise<Sync>;
481
+ syncSetupIntents(syncParams?: SyncParams): Promise<Sync>;
482
+ syncPaymentIntents(syncParams?: SyncParams): Promise<Sync>;
483
+ syncTaxIds(syncParams?: SyncParams): Promise<Sync>;
484
+ syncPaymentMethods(syncParams?: SyncParams): Promise<Sync>;
485
+ syncDisputes(syncParams?: SyncParams): Promise<Sync>;
486
+ syncEarlyFraudWarnings(syncParams?: SyncParams): Promise<Sync>;
487
+ syncRefunds(syncParams?: SyncParams): Promise<Sync>;
488
+ syncCreditNotes(syncParams?: SyncParams): Promise<Sync>;
489
+ syncFeatures(syncParams?: SyncFeaturesParams): Promise<Sync>;
490
+ syncEntitlements(customerId: string, syncParams?: SyncEntitlementsParams): Promise<Sync>;
491
+ syncCheckoutSessions(syncParams?: SyncParams): Promise<Sync>;
492
+ /**
493
+ * Helper to wrap a sync operation in the observable sync system.
494
+ * Creates/gets a sync run, sets up the object run, gets cursor, and handles completion.
495
+ *
496
+ * @param resourceName - The resource being synced (e.g., 'products', 'customers')
497
+ * @param triggeredBy - What triggered this sync (for observability)
498
+ * @param fn - The sync function to execute, receives cursor and runStartedAt
499
+ * @returns The result of the sync function
500
+ */
501
+ private withSyncRun;
502
+ private fetchAndUpsert;
503
+ private upsertCharges;
504
+ private backfillCharges;
505
+ private backfillPaymentIntents;
506
+ private upsertCreditNotes;
507
+ upsertCheckoutSessions(checkoutSessions: Stripe.Checkout.Session[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Checkout.Session[]>;
508
+ upsertEarlyFraudWarning(earlyFraudWarnings: Stripe.Radar.EarlyFraudWarning[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Radar.EarlyFraudWarning[]>;
509
+ upsertRefunds(refunds: Stripe.Refund[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Refund[]>;
510
+ upsertReviews(reviews: Stripe.Review[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Review[]>;
511
+ upsertCustomers(customers: (Stripe.Customer | Stripe.DeletedCustomer)[], accountId: string, syncTimestamp?: string): Promise<(Stripe.Customer | Stripe.DeletedCustomer)[]>;
512
+ backfillCustomers(customerIds: string[], accountId: string): Promise<void>;
513
+ upsertDisputes(disputes: Stripe.Dispute[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Dispute[]>;
514
+ upsertInvoices(invoices: Stripe.Invoice[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Invoice[]>;
515
+ backfillInvoices: (invoiceIds: string[], accountId: string) => Promise<void>;
516
+ backfillPrices: (priceIds: string[], accountId: string) => Promise<void>;
517
+ upsertPlans(plans: Stripe.Plan[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Plan[]>;
518
+ deletePlan(id: string): Promise<boolean>;
519
+ upsertPrices(prices: Stripe.Price[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Price[]>;
520
+ deletePrice(id: string): Promise<boolean>;
521
+ upsertProducts(products: Stripe.Product[], accountId: string, syncTimestamp?: string): Promise<Stripe.Product[]>;
522
+ deleteProduct(id: string): Promise<boolean>;
523
+ backfillProducts(productIds: string[], accountId: string): Promise<void>;
524
+ upsertPaymentIntents(paymentIntents: Stripe.PaymentIntent[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.PaymentIntent[]>;
525
+ upsertPaymentMethods(paymentMethods: Stripe.PaymentMethod[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.PaymentMethod[]>;
526
+ upsertSetupIntents(setupIntents: Stripe.SetupIntent[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.SetupIntent[]>;
527
+ upsertTaxIds(taxIds: Stripe.TaxId[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.TaxId[]>;
528
+ deleteTaxId(id: string): Promise<boolean>;
529
+ upsertSubscriptionItems(subscriptionItems: Stripe.SubscriptionItem[], accountId: string, syncTimestamp?: string): Promise<void>;
530
+ fillCheckoutSessionsLineItems(checkoutSessionIds: string[], accountId: string, syncTimestamp?: string): Promise<void>;
531
+ upsertCheckoutSessionLineItems(lineItems: Stripe.LineItem[], checkoutSessionId: string, accountId: string, syncTimestamp?: string): Promise<void>;
532
+ markDeletedSubscriptionItems(subscriptionId: string, currentSubItemIds: string[]): Promise<{
533
+ rowCount: number;
534
+ }>;
535
+ upsertSubscriptionSchedules(subscriptionSchedules: Stripe.SubscriptionSchedule[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.SubscriptionSchedule[]>;
536
+ upsertSubscriptions(subscriptions: Stripe.Subscription[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<Stripe.Subscription[]>;
537
+ deleteRemovedActiveEntitlements(customerId: string, currentActiveEntitlementIds: string[]): Promise<{
538
+ rowCount: number;
539
+ }>;
540
+ upsertFeatures(features: Stripe.Entitlements.Feature[], accountId: string, syncTimestamp?: string): Promise<Stripe.Entitlements.Feature[]>;
541
+ backfillFeatures(featureIds: string[], accountId: string): Promise<void>;
542
+ upsertActiveEntitlements(customerId: string, activeEntitlements: Stripe.Entitlements.ActiveEntitlement[], accountId: string, backfillRelatedEntities?: boolean, syncTimestamp?: string): Promise<{
543
+ id: string;
544
+ object: "entitlements.active_entitlement";
545
+ feature: string;
546
+ customer: string;
547
+ livemode: boolean;
548
+ lookup_key: string;
549
+ }[]>;
550
+ findOrCreateManagedWebhook(url: string, params?: Omit<Stripe.WebhookEndpointCreateParams, 'url'>): Promise<Stripe.WebhookEndpoint>;
551
+ getManagedWebhook(id: string): Promise<Stripe.WebhookEndpoint | null>;
552
+ /**
553
+ * Get a managed webhook by URL and account ID.
554
+ * Used for race condition recovery: when createManagedWebhook hits a unique constraint
555
+ * violation (another instance created the webhook), we need to fetch the existing webhook
556
+ * by URL since we only know the URL, not the ID of the webhook that won the race.
557
+ */
558
+ getManagedWebhookByUrl(url: string): Promise<Stripe.WebhookEndpoint | null>;
559
+ listManagedWebhooks(): Promise<Array<Stripe.WebhookEndpoint>>;
560
+ updateManagedWebhook(id: string, params: Stripe.WebhookEndpointUpdateParams): Promise<Stripe.WebhookEndpoint>;
561
+ deleteManagedWebhook(id: string): Promise<boolean>;
562
+ upsertManagedWebhooks(webhooks: Array<Stripe.WebhookEndpoint>, accountId: string, syncTimestamp?: string): Promise<Array<Stripe.WebhookEndpoint>>;
563
+ backfillSubscriptions(subscriptionIds: string[], accountId: string): Promise<void>;
564
+ backfillSubscriptionSchedules: (subscriptionIds: string[], accountId: string) => Promise<void>;
565
+ /**
566
+ * Stripe only sends the first 10 entries by default, the option will actively fetch all entries.
567
+ */
568
+ private expandEntity;
569
+ private fetchMissingEntities;
222
570
  }
223
571
 
224
572
  type MigrationConfig = {
225
- schema: string;
226
573
  databaseUrl: string;
227
574
  ssl?: ConnectionOptions;
228
- logger?: pino.Logger;
575
+ logger?: Logger;
229
576
  };
230
577
  declare function runMigrations(config: MigrationConfig): Promise<void>;
231
578
 
232
- export { PostgresClient, type RevalidateEntity, StripeAutoSync, type StripeAutoSyncInfo, type StripeAutoSyncOptions, type StripeSyncConfig, type Sync, type SyncBackfill, type SyncBackfillParams, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, runMigrations };
579
+ /**
580
+ * Hashes a Stripe API key using SHA-256
581
+ * Used to store API key hashes in the database for fast account lookups
582
+ * without storing the actual API key or making Stripe API calls
583
+ *
584
+ * @param apiKey - The Stripe API key (e.g., sk_test_... or sk_live_...)
585
+ * @returns SHA-256 hash of the API key as a hex string
586
+ */
587
+ declare function hashApiKey(apiKey: string): string;
588
+
589
+ declare const VERSION: string;
590
+
591
+ 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 };