stripe-experiment-sync 1.0.9-beta.1765909347 → 1.0.10

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./chunk-3W3CERIG.js";
3
+ } from "./chunk-VEEV6P4R.js";
4
4
 
5
5
  // src/supabase/supabase.ts
6
6
  import { SupabaseManagementAPI } from "supabase-management-js";
@@ -12,7 +12,7 @@ var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:strip
12
12
  var stripe_webhook_default = "import { StripeSync } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\n }\n\n const sig = req.headers.get('stripe-signature')\n if (!sig) {\n return new Response('Missing stripe-signature header', { status: 400 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n const stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n })\n\n try {\n const rawBody = new Uint8Array(await req.arrayBuffer())\n await stripeSync.processWebhook(rawBody, sig)\n return new Response(JSON.stringify({ received: true }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n console.error('Webhook processing error:', error)\n const isSignatureError =\n error.message?.includes('signature') || error.type === 'StripeSignatureVerificationError'\n const status = isSignatureError ? 400 : 500\n return new Response(JSON.stringify({ error: error.message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n } finally {\n await stripeSync.postgresClient.pool.end()\n }\n})\n";
13
13
 
14
14
  // raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts
15
- var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron every 10 seconds. Uses pgmq for durable work queue.\n *\n * Flow:\n * 1. Read batch of messages from pgmq (qty=10, vt=60s)\n * 2. If queue empty: enqueue all objects (continuous sync)\n * 3. Process messages in parallel (Promise.all):\n * - processNext(object)\n * - Delete message on success\n * - Re-enqueue if hasMore\n * 4. Return results summary\n *\n * Concurrency:\n * - Multiple workers can run concurrently via overlapping pg_cron triggers.\n * - Each worker processes its batch of messages in parallel (Promise.all).\n * - pgmq visibility timeout prevents duplicate message reads across workers.\n * - processNext() is idempotent (uses internal cursor tracking), so duplicate\n * processing on timeout/crash is safe.\n */\n\nimport { StripeSync } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nconst QUEUE_NAME = 'stripe_sync_work'\nconst VISIBILITY_TIMEOUT = 60 // seconds\nconst BATCH_SIZE = 10\n\nDeno.serve(async (req) => {\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n let sql\n let stripeSync\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n } catch (error) {\n return new Response(\n JSON.stringify({\n error: 'Failed to create postgres connection',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n enableSigmaSync: (Deno.env.get('ENABLE_SIGMA_SYNC') ?? 'false') === 'true',\n })\n } catch (error) {\n await sql.end()\n return new Response(\n JSON.stringify({\n error: 'Failed to create StripeSync',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n // Read batch of messages from queue\n const messages = await sql`\n SELECT * FROM pgmq.read(${QUEUE_NAME}::text, ${VISIBILITY_TIMEOUT}::int, ${BATCH_SIZE}::int)\n `\n\n // If queue empty, enqueue all objects for continuous sync\n if (messages.length === 0) {\n // Create sync run to make enqueued work visible (status='pending')\n const { objects } = await stripeSync.joinOrCreateSyncRun('worker')\n const msgs = objects.map((object) => JSON.stringify({ object }))\n\n await sql`\n SELECT pgmq.send_batch(\n ${QUEUE_NAME}::text,\n ${sql.array(msgs)}::jsonb[]\n )\n `\n\n return new Response(JSON.stringify({ enqueued: objects.length, objects }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Process messages in parallel\n const results = await Promise.all(\n messages.map(async (msg) => {\n const { object } = msg.message as { object: string }\n\n try {\n const result = await stripeSync.processNext(object)\n\n // Delete message on success (cast to bigint to disambiguate overloaded function)\n await sql`SELECT pgmq.delete(${QUEUE_NAME}::text, ${msg.msg_id}::bigint)`\n\n // Re-enqueue if more pages\n if (result.hasMore) {\n await sql`SELECT pgmq.send(${QUEUE_NAME}::text, ${sql.json({ object })}::jsonb)`\n }\n\n return { object, ...result }\n } catch (error) {\n // Log error but continue to next message\n // Message will become visible again after visibility timeout\n console.error(`Error processing ${object}:`, error)\n return {\n object,\n processed: 0,\n hasMore: false,\n error: error.message,\n stack: error.stack,\n }\n }\n })\n )\n\n return new Response(JSON.stringify({ results }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n console.error('Worker error:', error)\n return new Response(JSON.stringify({ error: error.message, stack: error.stack }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n } finally {\n if (sql) await sql.end()\n if (stripeSync) await stripeSync.postgresClient.pool.end()\n }\n})\n";
15
+ var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron at a configurable interval (default: 60 seconds). Uses pgmq for durable work queue.\n *\n * Flow:\n * 1. Read batch of messages from pgmq (qty=10, vt=60s)\n * 2. If queue empty: enqueue all objects (continuous sync)\n * 3. Process messages in parallel (Promise.all):\n * - processNext(object)\n * - Delete message on success\n * - Re-enqueue if hasMore\n * 4. Return results summary\n *\n * Concurrency:\n * - Multiple workers can run concurrently via overlapping pg_cron triggers.\n * - Each worker processes its batch of messages in parallel (Promise.all).\n * - pgmq visibility timeout prevents duplicate message reads across workers.\n * - processNext() is idempotent (uses internal cursor tracking), so duplicate\n * processing on timeout/crash is safe.\n */\n\nimport { StripeSync } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nconst QUEUE_NAME = 'stripe_sync_work'\nconst VISIBILITY_TIMEOUT = 60 // seconds\nconst BATCH_SIZE = 10\n\nDeno.serve(async (req) => {\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), { status: 500 })\n }\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n let sql\n let stripeSync\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n } catch (error) {\n return new Response(\n JSON.stringify({\n error: 'Failed to create postgres connection',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 1 },\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY')!,\n })\n } catch (error) {\n await sql.end()\n return new Response(\n JSON.stringify({\n error: 'Failed to create StripeSync',\n details: error.message,\n stack: error.stack,\n }),\n { status: 500, headers: { 'Content-Type': 'application/json' } }\n )\n }\n\n try {\n // Read batch of messages from queue\n const messages = await sql`\n SELECT * FROM pgmq.read(${QUEUE_NAME}::text, ${VISIBILITY_TIMEOUT}::int, ${BATCH_SIZE}::int)\n `\n\n // If queue empty, enqueue all objects for continuous sync\n if (messages.length === 0) {\n // Create sync run to make enqueued work visible (status='pending')\n const { objects } = await stripeSync.joinOrCreateSyncRun('worker')\n const msgs = objects.map((object) => JSON.stringify({ object }))\n\n await sql`\n SELECT pgmq.send_batch(\n ${QUEUE_NAME}::text,\n ${sql.array(msgs)}::jsonb[]\n )\n `\n\n return new Response(JSON.stringify({ enqueued: objects.length, objects }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Process messages in parallel\n const results = await Promise.all(\n messages.map(async (msg) => {\n const { object } = msg.message as { object: string }\n\n try {\n const result = await stripeSync.processNext(object)\n\n // Delete message on success (cast to bigint to disambiguate overloaded function)\n await sql`SELECT pgmq.delete(${QUEUE_NAME}::text, ${msg.msg_id}::bigint)`\n\n // Re-enqueue if more pages\n if (result.hasMore) {\n await sql`SELECT pgmq.send(${QUEUE_NAME}::text, ${sql.json({ object })}::jsonb)`\n }\n\n return { object, ...result }\n } catch (error) {\n // Log error but continue to next message\n // Message will become visible again after visibility timeout\n console.error(`Error processing ${object}:`, error)\n return {\n object,\n processed: 0,\n hasMore: false,\n error: error.message,\n stack: error.stack,\n }\n }\n })\n )\n\n return new Response(JSON.stringify({ results }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n console.error('Worker error:', error)\n return new Response(JSON.stringify({ error: error.message, stack: error.stack }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n } finally {\n if (sql) await sql.end()\n if (stripeSync) await stripeSync.postgresClient.pool.end()\n }\n})\n";
16
16
 
17
17
  // src/supabase/edge-function-code.ts
18
18
  var setupFunctionCode = stripe_setup_default;
@@ -86,8 +86,29 @@ var SupabaseSetupClient = class {
86
86
  }
87
87
  /**
88
88
  * Setup pg_cron job to invoke worker function
89
+ * @param intervalSeconds - How often to run the worker (default: 60 seconds)
89
90
  */
90
- async setupPgCronJob() {
91
+ async setupPgCronJob(intervalSeconds = 60) {
92
+ if (!Number.isInteger(intervalSeconds) || intervalSeconds < 1) {
93
+ throw new Error(`Invalid interval: ${intervalSeconds}. Must be a positive integer.`);
94
+ }
95
+ let schedule;
96
+ if (intervalSeconds < 60) {
97
+ schedule = `${intervalSeconds} seconds`;
98
+ } else if (intervalSeconds % 60 === 0) {
99
+ const minutes = intervalSeconds / 60;
100
+ if (minutes < 60) {
101
+ schedule = `*/${minutes} * * * *`;
102
+ } else {
103
+ throw new Error(
104
+ `Invalid interval: ${intervalSeconds}. Intervals >= 3600 seconds (1 hour) are not supported. Use a value between 1-3599 seconds.`
105
+ );
106
+ }
107
+ } else {
108
+ throw new Error(
109
+ `Invalid interval: ${intervalSeconds}. Must be either 1-59 seconds or a multiple of 60 (e.g., 60, 120, 180).`
110
+ );
111
+ }
91
112
  const serviceRoleKey = await this.getServiceRoleKey();
92
113
  const escapedServiceRoleKey = serviceRoleKey.replace(/'/g, "''");
93
114
  const sql = `
@@ -115,11 +136,11 @@ var SupabaseSetupClient = class {
115
136
  SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-scheduler'
116
137
  );
117
138
 
118
- -- Create job to invoke worker every 10 seconds
139
+ -- Create job to invoke worker at configured interval
119
140
  -- Worker reads from pgmq, enqueues objects if empty, and processes sync work
120
141
  SELECT cron.schedule(
121
142
  'stripe-sync-worker',
122
- '10 seconds',
143
+ '${schedule}',
123
144
  $$
124
145
  SELECT net.http_post(
125
146
  url := 'https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-worker',
@@ -347,7 +368,7 @@ var SupabaseSetupClient = class {
347
368
  `from 'npm:stripe-experiment-sync@${version}'`
348
369
  );
349
370
  }
350
- async install(stripeKey, packageVersion) {
371
+ async install(stripeKey, packageVersion, workerIntervalSeconds) {
351
372
  const trimmedStripeKey = stripeKey.trim();
352
373
  if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
353
374
  throw new Error('Stripe key should start with "sk_" or "rk_"');
@@ -371,7 +392,7 @@ var SupabaseSetupClient = class {
371
392
  if (!setupResult.success) {
372
393
  throw new Error(`Setup failed: ${setupResult.error}`);
373
394
  }
374
- await this.setupPgCronJob();
395
+ await this.setupPgCronJob(workerIntervalSeconds);
375
396
  await this.updateInstallationComment(
376
397
  `${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_INSTALLED_SUFFIX}`
377
398
  );
@@ -384,14 +405,20 @@ var SupabaseSetupClient = class {
384
405
  }
385
406
  };
386
407
  async function install(params) {
387
- const { supabaseAccessToken, supabaseProjectRef, stripeKey, packageVersion } = params;
408
+ const {
409
+ supabaseAccessToken,
410
+ supabaseProjectRef,
411
+ stripeKey,
412
+ packageVersion,
413
+ workerIntervalSeconds
414
+ } = params;
388
415
  const client = new SupabaseSetupClient({
389
416
  accessToken: supabaseAccessToken,
390
417
  projectRef: supabaseProjectRef,
391
418
  projectBaseUrl: params.baseProjectUrl,
392
419
  managementApiBaseUrl: params.baseManagementApiUrl
393
420
  });
394
- await client.install(stripeKey, packageVersion);
421
+ await client.install(stripeKey, packageVersion, workerIntervalSeconds);
395
422
  }
396
423
  async function uninstall(params) {
397
424
  const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
@@ -2,11 +2,11 @@ import {
2
2
  StripeSync,
3
3
  createStripeWebSocketClient,
4
4
  runMigrations
5
- } from "./chunk-2CYYWBTD.js";
5
+ } from "./chunk-62FKHVHJ.js";
6
6
  import {
7
7
  install,
8
8
  uninstall
9
- } from "./chunk-SI3VFP3M.js";
9
+ } from "./chunk-AHNO3EMD.js";
10
10
 
11
11
  // src/cli/config.ts
12
12
  import dotenv from "dotenv";
@@ -18,7 +18,6 @@ async function loadConfig(options) {
18
18
  config.stripeApiKey = options.stripeKey || process.env.STRIPE_API_KEY || "";
19
19
  config.ngrokAuthToken = options.ngrokToken || process.env.NGROK_AUTH_TOKEN || "";
20
20
  config.databaseUrl = options.databaseUrl || process.env.DATABASE_URL || "";
21
- config.enableSigmaSync = options.enableSigmaSync ?? (process.env.ENABLE_SIGMA_SYNC !== void 0 ? process.env.ENABLE_SIGMA_SYNC === "true" : void 0);
22
21
  const questions = [];
23
22
  if (!config.stripeApiKey) {
24
23
  questions.push({
@@ -30,8 +29,8 @@ async function loadConfig(options) {
30
29
  if (!input || input.trim() === "") {
31
30
  return "Stripe API key is required";
32
31
  }
33
- if (!input.startsWith("sk_") && !input.startsWith("rk_")) {
34
- return 'Stripe API key should start with "sk_" or "rk_"';
32
+ if (!input.startsWith("sk_")) {
33
+ return 'Stripe API key should start with "sk_"';
35
34
  }
36
35
  return true;
37
36
  }
@@ -54,22 +53,11 @@ async function loadConfig(options) {
54
53
  }
55
54
  });
56
55
  }
57
- if (config.enableSigmaSync === void 0) {
58
- questions.push({
59
- type: "confirm",
60
- name: "enableSigmaSync",
61
- message: "Enable Sigma sync? (Requires Sigma access in Stripe API key)",
62
- default: false
63
- });
64
- }
65
56
  if (questions.length > 0) {
66
- console.log(chalk.yellow("\nMissing configuration. Please provide:"));
57
+ console.log(chalk.yellow("\nMissing required configuration. Please provide:"));
67
58
  const answers = await inquirer.prompt(questions);
68
59
  Object.assign(config, answers);
69
60
  }
70
- if (config.enableSigmaSync === void 0) {
71
- config.enableSigmaSync = false;
72
- }
73
61
  return config;
74
62
  }
75
63
 
@@ -129,9 +117,7 @@ var VALID_SYNC_OBJECTS = [
129
117
  "credit_note",
130
118
  "early_fraud_warning",
131
119
  "refund",
132
- "checkout_sessions",
133
- "subscription_item_change_events_v2_beta",
134
- "exchange_rates_from_usd"
120
+ "checkout_sessions"
135
121
  ];
136
122
  async function backfillCommand(options, entityName) {
137
123
  let stripeSync = null;
@@ -160,8 +146,8 @@ async function backfillCommand(options, entityName) {
160
146
  if (!input || input.trim() === "") {
161
147
  return "Stripe API key is required";
162
148
  }
163
- if (!input.startsWith("sk_") && !input.startsWith("rk_")) {
164
- return 'Stripe API key should start with "sk_" or "rk_"';
149
+ if (!input.startsWith("sk_")) {
150
+ return 'Stripe API key should start with "sk_"';
165
151
  }
166
152
  return true;
167
153
  }
@@ -218,7 +204,6 @@ async function backfillCommand(options, entityName) {
218
204
  stripeSync = new StripeSync({
219
205
  databaseUrl: config.databaseUrl,
220
206
  stripeSecretKey: config.stripeApiKey,
221
- enableSigmaSync: process.env.ENABLE_SIGMA_SYNC === "true",
222
207
  stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
223
208
  autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
224
209
  backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
@@ -382,7 +367,6 @@ Mode: ${modeLabel}`));
382
367
  stripeSync = new StripeSync({
383
368
  databaseUrl: config.databaseUrl,
384
369
  stripeSecretKey: config.stripeApiKey,
385
- enableSigmaSync: config.enableSigmaSync,
386
370
  stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
387
371
  autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
388
372
  backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
@@ -530,8 +514,7 @@ async function installCommand(options) {
530
514
  mask: "*",
531
515
  validate: (input) => {
532
516
  if (!input.trim()) return "Stripe key is required";
533
- if (!input.startsWith("sk_") && !input.startsWith("rk_"))
534
- return 'Stripe key should start with "sk_" or "rk_"';
517
+ if (!input.startsWith("sk_")) return 'Stripe key should start with "sk_"';
535
518
  return true;
536
519
  }
537
520
  });
@@ -550,7 +533,8 @@ async function installCommand(options) {
550
533
  supabaseAccessToken: accessToken,
551
534
  supabaseProjectRef: projectRef,
552
535
  stripeKey,
553
- packageVersion: options.packageVersion
536
+ packageVersion: options.packageVersion,
537
+ workerIntervalSeconds: options.workerInterval
554
538
  });
555
539
  console.log(chalk3.cyan("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
556
540
  console.log(chalk3.cyan.bold(" Installation Complete!"));
@@ -601,8 +585,7 @@ async function uninstallCommand(options) {
601
585
  mask: "*",
602
586
  validate: (input) => {
603
587
  if (!input.trim()) return "Stripe key is required";
604
- if (!input.startsWith("sk_") && !input.startsWith("rk_"))
605
- return 'Stripe key should start with "sk_" or "rk_"';
588
+ if (!input.startsWith("sk_")) return 'Stripe key should start with "sk_"';
606
589
  return true;
607
590
  }
608
591
  });
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "stripe-experiment-sync",
4
- version: "1.0.9-beta.1765909347",
4
+ version: "1.0.10",
5
5
  private: false,
6
6
  description: "Stripe Sync Engine to sync Stripe data to Postgres",
7
7
  type: "module",
@@ -41,7 +41,6 @@ var package_default = {
41
41
  dotenv: "^16.4.7",
42
42
  express: "^4.18.2",
43
43
  inquirer: "^12.3.0",
44
- papaparse: "5.4.1",
45
44
  pg: "^8.16.3",
46
45
  "pg-node-migrations": "0.0.8",
47
46
  stripe: "^17.7.0",
@@ -53,7 +52,6 @@ var package_default = {
53
52
  "@types/express": "^4.17.21",
54
53
  "@types/inquirer": "^9.0.7",
55
54
  "@types/node": "^24.10.1",
56
- "@types/papaparse": "5.3.16",
57
55
  "@types/pg": "^8.15.5",
58
56
  "@types/ws": "^8.5.13",
59
57
  "@types/yesql": "^4.1.4",