stripe-experiment-sync 1.0.8 → 1.0.9-beta.1765909347
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/{chunk-J7BH3XD6.js → chunk-2CYYWBTD.js} +486 -27
- package/dist/{chunk-CKWVW2JK.js → chunk-3W3CERIG.js} +3 -1
- package/dist/{chunk-YH6KRZDQ.js → chunk-DBJCCGXP.js} +28 -10
- package/dist/{chunk-X2OQQCC2.js → chunk-SI3VFP3M.js} +16 -5
- package/dist/cli/index.cjs +534 -45
- package/dist/cli/index.js +9 -8
- package/dist/cli/lib.cjs +529 -41
- package/dist/cli/lib.d.cts +2 -0
- package/dist/cli/lib.d.ts +2 -0
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +490 -27
- package/dist/index.d.cts +107 -4
- package/dist/index.d.ts +107 -4
- package/dist/index.js +6 -2
- package/dist/migrations/0059_sigma_subscription_item_change_events_v2_beta.sql +61 -0
- package/dist/migrations/0060_sigma_exchange_rates_from_usd.sql +38 -0
- package/dist/supabase/index.cjs +18 -5
- package/dist/supabase/index.js +2 -2
- package/package.json +3 -1
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
StripeSync,
|
|
3
3
|
createStripeWebSocketClient,
|
|
4
4
|
runMigrations
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2CYYWBTD.js";
|
|
6
6
|
import {
|
|
7
7
|
install,
|
|
8
8
|
uninstall
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SI3VFP3M.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/config.ts
|
|
12
12
|
import dotenv from "dotenv";
|
|
@@ -18,6 +18,7 @@ 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);
|
|
21
22
|
const questions = [];
|
|
22
23
|
if (!config.stripeApiKey) {
|
|
23
24
|
questions.push({
|
|
@@ -29,8 +30,8 @@ async function loadConfig(options) {
|
|
|
29
30
|
if (!input || input.trim() === "") {
|
|
30
31
|
return "Stripe API key is required";
|
|
31
32
|
}
|
|
32
|
-
if (!input.startsWith("sk_")) {
|
|
33
|
-
return 'Stripe API key should start with "sk_"';
|
|
33
|
+
if (!input.startsWith("sk_") && !input.startsWith("rk_")) {
|
|
34
|
+
return 'Stripe API key should start with "sk_" or "rk_"';
|
|
34
35
|
}
|
|
35
36
|
return true;
|
|
36
37
|
}
|
|
@@ -53,11 +54,22 @@ async function loadConfig(options) {
|
|
|
53
54
|
}
|
|
54
55
|
});
|
|
55
56
|
}
|
|
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
|
+
}
|
|
56
65
|
if (questions.length > 0) {
|
|
57
|
-
console.log(chalk.yellow("\nMissing
|
|
66
|
+
console.log(chalk.yellow("\nMissing configuration. Please provide:"));
|
|
58
67
|
const answers = await inquirer.prompt(questions);
|
|
59
68
|
Object.assign(config, answers);
|
|
60
69
|
}
|
|
70
|
+
if (config.enableSigmaSync === void 0) {
|
|
71
|
+
config.enableSigmaSync = false;
|
|
72
|
+
}
|
|
61
73
|
return config;
|
|
62
74
|
}
|
|
63
75
|
|
|
@@ -117,7 +129,9 @@ var VALID_SYNC_OBJECTS = [
|
|
|
117
129
|
"credit_note",
|
|
118
130
|
"early_fraud_warning",
|
|
119
131
|
"refund",
|
|
120
|
-
"checkout_sessions"
|
|
132
|
+
"checkout_sessions",
|
|
133
|
+
"subscription_item_change_events_v2_beta",
|
|
134
|
+
"exchange_rates_from_usd"
|
|
121
135
|
];
|
|
122
136
|
async function backfillCommand(options, entityName) {
|
|
123
137
|
let stripeSync = null;
|
|
@@ -146,8 +160,8 @@ async function backfillCommand(options, entityName) {
|
|
|
146
160
|
if (!input || input.trim() === "") {
|
|
147
161
|
return "Stripe API key is required";
|
|
148
162
|
}
|
|
149
|
-
if (!input.startsWith("sk_")) {
|
|
150
|
-
return 'Stripe API key should start with "sk_"';
|
|
163
|
+
if (!input.startsWith("sk_") && !input.startsWith("rk_")) {
|
|
164
|
+
return 'Stripe API key should start with "sk_" or "rk_"';
|
|
151
165
|
}
|
|
152
166
|
return true;
|
|
153
167
|
}
|
|
@@ -204,6 +218,7 @@ async function backfillCommand(options, entityName) {
|
|
|
204
218
|
stripeSync = new StripeSync({
|
|
205
219
|
databaseUrl: config.databaseUrl,
|
|
206
220
|
stripeSecretKey: config.stripeApiKey,
|
|
221
|
+
enableSigmaSync: process.env.ENABLE_SIGMA_SYNC === "true",
|
|
207
222
|
stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
|
|
208
223
|
autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
|
|
209
224
|
backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
|
|
@@ -367,6 +382,7 @@ Mode: ${modeLabel}`));
|
|
|
367
382
|
stripeSync = new StripeSync({
|
|
368
383
|
databaseUrl: config.databaseUrl,
|
|
369
384
|
stripeSecretKey: config.stripeApiKey,
|
|
385
|
+
enableSigmaSync: config.enableSigmaSync,
|
|
370
386
|
stripeApiVersion: process.env.STRIPE_API_VERSION || "2020-08-27",
|
|
371
387
|
autoExpandLists: process.env.AUTO_EXPAND_LISTS === "true",
|
|
372
388
|
backfillRelatedEntities: process.env.BACKFILL_RELATED_ENTITIES !== "false",
|
|
@@ -514,7 +530,8 @@ async function installCommand(options) {
|
|
|
514
530
|
mask: "*",
|
|
515
531
|
validate: (input) => {
|
|
516
532
|
if (!input.trim()) return "Stripe key is required";
|
|
517
|
-
if (!input.startsWith("sk_")
|
|
533
|
+
if (!input.startsWith("sk_") && !input.startsWith("rk_"))
|
|
534
|
+
return 'Stripe key should start with "sk_" or "rk_"';
|
|
518
535
|
return true;
|
|
519
536
|
}
|
|
520
537
|
});
|
|
@@ -584,7 +601,8 @@ async function uninstallCommand(options) {
|
|
|
584
601
|
mask: "*",
|
|
585
602
|
validate: (input) => {
|
|
586
603
|
if (!input.trim()) return "Stripe key is required";
|
|
587
|
-
if (!input.startsWith("sk_")
|
|
604
|
+
if (!input.startsWith("sk_") && !input.startsWith("rk_"))
|
|
605
|
+
return 'Stripe key should start with "sk_" or "rk_"';
|
|
588
606
|
return true;
|
|
589
607
|
}
|
|
590
608
|
});
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3W3CERIG.js";
|
|
4
4
|
|
|
5
5
|
// src/supabase/supabase.ts
|
|
6
6
|
import { SupabaseManagementAPI } from "supabase-management-js";
|
|
7
7
|
|
|
8
|
-
// raw-ts:/
|
|
8
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
9
9
|
var stripe_setup_default = "import { StripeSync, runMigrations } 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 authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n let stripeSync = null\n try {\n // Get and validate database URL\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n throw new Error('SUPABASE_DB_URL environment variable is not set')\n }\n // Remove sslmode from connection string (not supported by pg in Deno)\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n\n await runMigrations({ databaseUrl: dbUrl })\n\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 2 }, // Need 2 for advisory lock + queries\n stripeSecretKey: Deno.env.get('STRIPE_SECRET_KEY'),\n })\n\n // Release any stale advisory locks from previous timeouts\n await stripeSync.postgresClient.query('SELECT pg_advisory_unlock_all()')\n\n // Construct webhook URL from SUPABASE_URL (available in all Edge Functions)\n const supabaseUrl = Deno.env.get('SUPABASE_URL')\n if (!supabaseUrl) {\n throw new Error('SUPABASE_URL environment variable is not set')\n }\n const webhookUrl = supabaseUrl + '/functions/v1/stripe-webhook'\n\n const webhook = await stripeSync.findOrCreateManagedWebhook(webhookUrl)\n\n await stripeSync.postgresClient.pool.end()\n\n return new Response(\n JSON.stringify({\n success: true,\n message: 'Setup complete',\n webhookId: webhook.id,\n }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } catch (error) {\n console.error('Setup error:', error)\n // Cleanup on error\n if (stripeSync) {\n try {\n await stripeSync.postgresClient.query('SELECT pg_advisory_unlock_all()')\n await stripeSync.postgresClient.pool.end()\n } catch (cleanupErr) {\n console.warn('Cleanup failed:', cleanupErr)\n }\n }\n return new Response(JSON.stringify({ success: false, error: error.message }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n})\n";
|
|
10
10
|
|
|
11
|
-
// raw-ts:/
|
|
11
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
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
|
-
// raw-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 })\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";
|
|
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";
|
|
16
16
|
|
|
17
17
|
// src/supabase/edge-function-code.ts
|
|
18
18
|
var setupFunctionCode = stripe_setup_default;
|
|
@@ -319,6 +319,17 @@ var SupabaseSetupClient = class {
|
|
|
319
319
|
} catch (err) {
|
|
320
320
|
console.warn("Could not delete vault secret:", err);
|
|
321
321
|
}
|
|
322
|
+
try {
|
|
323
|
+
await this.runSQL(`
|
|
324
|
+
SELECT pg_terminate_backend(pid)
|
|
325
|
+
FROM pg_stat_activity
|
|
326
|
+
WHERE datname = current_database()
|
|
327
|
+
AND pid != pg_backend_pid()
|
|
328
|
+
AND query ILIKE '%stripe.%'
|
|
329
|
+
`);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
console.warn("Could not terminate connections:", err);
|
|
332
|
+
}
|
|
322
333
|
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
323
334
|
} catch (error) {
|
|
324
335
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|