stripe-experiment-sync 1.0.9 → 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.
- package/dist/{chunk-PQ2T7XTY.js → chunk-62FKHVHJ.js} +1 -1
- package/dist/{chunk-OLVA37VZ.js → chunk-AHNO3EMD.js} +39 -12
- package/dist/{chunk-PWJLHHPY.js → chunk-FZQ4B7VZ.js} +4 -3
- package/dist/{chunk-RR5BGG4F.js → chunk-VEEV6P4R.js} +1 -1
- package/dist/cli/index.cjs +47 -14
- package/dist/cli/index.js +10 -5
- package/dist/cli/lib.cjs +41 -13
- package/dist/cli/lib.d.cts +1 -0
- package/dist/cli/lib.d.ts +1 -0
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/supabase/index.cjs +39 -12
- package/dist/supabase/index.d.cts +4 -2
- package/dist/supabase/index.d.ts +4 -2
- package/dist/supabase/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VEEV6P4R.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
|
|
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 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
|
|
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
|
-
'
|
|
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 {
|
|
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-
|
|
5
|
+
} from "./chunk-62FKHVHJ.js";
|
|
6
6
|
import {
|
|
7
7
|
install,
|
|
8
8
|
uninstall
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AHNO3EMD.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/config.ts
|
|
12
12
|
import dotenv from "dotenv";
|
|
@@ -533,7 +533,8 @@ async function installCommand(options) {
|
|
|
533
533
|
supabaseAccessToken: accessToken,
|
|
534
534
|
supabaseProjectRef: projectRef,
|
|
535
535
|
stripeKey,
|
|
536
|
-
packageVersion: options.packageVersion
|
|
536
|
+
packageVersion: options.packageVersion,
|
|
537
|
+
workerIntervalSeconds: options.workerInterval
|
|
537
538
|
});
|
|
538
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"));
|
|
539
540
|
console.log(chalk3.cyan.bold(" Installation Complete!"));
|
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.10",
|
|
37
37
|
private: false,
|
|
38
38
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
39
39
|
type: "module",
|
|
@@ -3630,14 +3630,14 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
3630
3630
|
// src/supabase/supabase.ts
|
|
3631
3631
|
var import_supabase_management_js = require("supabase-management-js");
|
|
3632
3632
|
|
|
3633
|
-
// raw-ts:/
|
|
3633
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
3634
3634
|
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";
|
|
3635
3635
|
|
|
3636
|
-
// raw-ts:/
|
|
3636
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
3637
3637
|
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";
|
|
3638
3638
|
|
|
3639
|
-
// raw-ts:/
|
|
3640
|
-
var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron
|
|
3639
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts
|
|
3640
|
+
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";
|
|
3641
3641
|
|
|
3642
3642
|
// src/supabase/edge-function-code.ts
|
|
3643
3643
|
var setupFunctionCode = stripe_setup_default;
|
|
@@ -3711,8 +3711,29 @@ var SupabaseSetupClient = class {
|
|
|
3711
3711
|
}
|
|
3712
3712
|
/**
|
|
3713
3713
|
* Setup pg_cron job to invoke worker function
|
|
3714
|
+
* @param intervalSeconds - How often to run the worker (default: 60 seconds)
|
|
3714
3715
|
*/
|
|
3715
|
-
async setupPgCronJob() {
|
|
3716
|
+
async setupPgCronJob(intervalSeconds = 60) {
|
|
3717
|
+
if (!Number.isInteger(intervalSeconds) || intervalSeconds < 1) {
|
|
3718
|
+
throw new Error(`Invalid interval: ${intervalSeconds}. Must be a positive integer.`);
|
|
3719
|
+
}
|
|
3720
|
+
let schedule;
|
|
3721
|
+
if (intervalSeconds < 60) {
|
|
3722
|
+
schedule = `${intervalSeconds} seconds`;
|
|
3723
|
+
} else if (intervalSeconds % 60 === 0) {
|
|
3724
|
+
const minutes = intervalSeconds / 60;
|
|
3725
|
+
if (minutes < 60) {
|
|
3726
|
+
schedule = `*/${minutes} * * * *`;
|
|
3727
|
+
} else {
|
|
3728
|
+
throw new Error(
|
|
3729
|
+
`Invalid interval: ${intervalSeconds}. Intervals >= 3600 seconds (1 hour) are not supported. Use a value between 1-3599 seconds.`
|
|
3730
|
+
);
|
|
3731
|
+
}
|
|
3732
|
+
} else {
|
|
3733
|
+
throw new Error(
|
|
3734
|
+
`Invalid interval: ${intervalSeconds}. Must be either 1-59 seconds or a multiple of 60 (e.g., 60, 120, 180).`
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3716
3737
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
3717
3738
|
const escapedServiceRoleKey = serviceRoleKey.replace(/'/g, "''");
|
|
3718
3739
|
const sql3 = `
|
|
@@ -3740,11 +3761,11 @@ var SupabaseSetupClient = class {
|
|
|
3740
3761
|
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-scheduler'
|
|
3741
3762
|
);
|
|
3742
3763
|
|
|
3743
|
-
-- Create job to invoke worker
|
|
3764
|
+
-- Create job to invoke worker at configured interval
|
|
3744
3765
|
-- Worker reads from pgmq, enqueues objects if empty, and processes sync work
|
|
3745
3766
|
SELECT cron.schedule(
|
|
3746
3767
|
'stripe-sync-worker',
|
|
3747
|
-
'
|
|
3768
|
+
'${schedule}',
|
|
3748
3769
|
$$
|
|
3749
3770
|
SELECT net.http_post(
|
|
3750
3771
|
url := 'https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-worker',
|
|
@@ -3972,7 +3993,7 @@ var SupabaseSetupClient = class {
|
|
|
3972
3993
|
`from 'npm:stripe-experiment-sync@${version}'`
|
|
3973
3994
|
);
|
|
3974
3995
|
}
|
|
3975
|
-
async install(stripeKey, packageVersion) {
|
|
3996
|
+
async install(stripeKey, packageVersion, workerIntervalSeconds) {
|
|
3976
3997
|
const trimmedStripeKey = stripeKey.trim();
|
|
3977
3998
|
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
3978
3999
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
@@ -3996,7 +4017,7 @@ var SupabaseSetupClient = class {
|
|
|
3996
4017
|
if (!setupResult.success) {
|
|
3997
4018
|
throw new Error(`Setup failed: ${setupResult.error}`);
|
|
3998
4019
|
}
|
|
3999
|
-
await this.setupPgCronJob();
|
|
4020
|
+
await this.setupPgCronJob(workerIntervalSeconds);
|
|
4000
4021
|
await this.updateInstallationComment(
|
|
4001
4022
|
`${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_INSTALLED_SUFFIX}`
|
|
4002
4023
|
);
|
|
@@ -4009,14 +4030,20 @@ var SupabaseSetupClient = class {
|
|
|
4009
4030
|
}
|
|
4010
4031
|
};
|
|
4011
4032
|
async function install(params) {
|
|
4012
|
-
const {
|
|
4033
|
+
const {
|
|
4034
|
+
supabaseAccessToken,
|
|
4035
|
+
supabaseProjectRef,
|
|
4036
|
+
stripeKey,
|
|
4037
|
+
packageVersion,
|
|
4038
|
+
workerIntervalSeconds
|
|
4039
|
+
} = params;
|
|
4013
4040
|
const client = new SupabaseSetupClient({
|
|
4014
4041
|
accessToken: supabaseAccessToken,
|
|
4015
4042
|
projectRef: supabaseProjectRef,
|
|
4016
4043
|
projectBaseUrl: params.baseProjectUrl,
|
|
4017
4044
|
managementApiBaseUrl: params.baseManagementApiUrl
|
|
4018
4045
|
});
|
|
4019
|
-
await client.install(stripeKey, packageVersion);
|
|
4046
|
+
await client.install(stripeKey, packageVersion, workerIntervalSeconds);
|
|
4020
4047
|
}
|
|
4021
4048
|
async function uninstall(params) {
|
|
4022
4049
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
@@ -4467,7 +4494,8 @@ async function installCommand(options) {
|
|
|
4467
4494
|
supabaseAccessToken: accessToken,
|
|
4468
4495
|
supabaseProjectRef: projectRef,
|
|
4469
4496
|
stripeKey,
|
|
4470
|
-
packageVersion: options.packageVersion
|
|
4497
|
+
packageVersion: options.packageVersion,
|
|
4498
|
+
workerIntervalSeconds: options.workerInterval
|
|
4471
4499
|
});
|
|
4472
4500
|
console.log(import_chalk3.default.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"));
|
|
4473
4501
|
console.log(import_chalk3.default.cyan.bold(" Installation Complete!"));
|
|
@@ -4585,12 +4613,17 @@ var supabase = program.command("supabase").description("Supabase Edge Functions
|
|
|
4585
4613
|
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").option(
|
|
4586
4614
|
"--package-version <version>",
|
|
4587
4615
|
"Package version to install (e.g., 1.0.8-beta.1, defaults to latest)"
|
|
4616
|
+
).option(
|
|
4617
|
+
"--worker-interval <seconds>",
|
|
4618
|
+
"Worker interval in seconds (defaults to 60)",
|
|
4619
|
+
(val) => parseInt(val, 10)
|
|
4588
4620
|
).action(async (options) => {
|
|
4589
4621
|
await installCommand({
|
|
4590
4622
|
supabaseAccessToken: options.token,
|
|
4591
4623
|
supabaseProjectRef: options.project,
|
|
4592
4624
|
stripeKey: options.stripeKey,
|
|
4593
|
-
packageVersion: options.packageVersion
|
|
4625
|
+
packageVersion: options.packageVersion,
|
|
4626
|
+
workerInterval: options.workerInterval
|
|
4594
4627
|
});
|
|
4595
4628
|
});
|
|
4596
4629
|
supabase.command("uninstall").description("Uninstall Stripe sync from Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").action(async (options) => {
|
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-FZQ4B7VZ.js";
|
|
9
|
+
import "../chunk-62FKHVHJ.js";
|
|
10
|
+
import "../chunk-AHNO3EMD.js";
|
|
11
11
|
import {
|
|
12
12
|
package_default
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-VEEV6P4R.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -41,12 +41,17 @@ var supabase = program.command("supabase").description("Supabase Edge Functions
|
|
|
41
41
|
supabase.command("install").description("Install Stripe sync to Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").option(
|
|
42
42
|
"--package-version <version>",
|
|
43
43
|
"Package version to install (e.g., 1.0.8-beta.1, defaults to latest)"
|
|
44
|
+
).option(
|
|
45
|
+
"--worker-interval <seconds>",
|
|
46
|
+
"Worker interval in seconds (defaults to 60)",
|
|
47
|
+
(val) => parseInt(val, 10)
|
|
44
48
|
).action(async (options) => {
|
|
45
49
|
await installCommand({
|
|
46
50
|
supabaseAccessToken: options.token,
|
|
47
51
|
supabaseProjectRef: options.project,
|
|
48
52
|
stripeKey: options.stripeKey,
|
|
49
|
-
packageVersion: options.packageVersion
|
|
53
|
+
packageVersion: options.packageVersion,
|
|
54
|
+
workerInterval: options.workerInterval
|
|
50
55
|
});
|
|
51
56
|
});
|
|
52
57
|
supabase.command("uninstall").description("Uninstall Stripe sync from Supabase Edge Functions").option("--token <token>", "Supabase access token (or SUPABASE_ACCESS_TOKEN env)").option("--project <ref>", "Supabase project ref (or SUPABASE_PROJECT_REF env)").option("--stripe-key <key>", "Stripe API key (or STRIPE_API_KEY env)").action(async (options) => {
|
package/dist/cli/lib.cjs
CHANGED
|
@@ -105,7 +105,7 @@ async function loadConfig(options) {
|
|
|
105
105
|
// package.json
|
|
106
106
|
var package_default = {
|
|
107
107
|
name: "stripe-experiment-sync",
|
|
108
|
-
version: "1.0.
|
|
108
|
+
version: "1.0.10",
|
|
109
109
|
private: false,
|
|
110
110
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
111
111
|
type: "module",
|
|
@@ -3644,14 +3644,14 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
3644
3644
|
// src/supabase/supabase.ts
|
|
3645
3645
|
var import_supabase_management_js = require("supabase-management-js");
|
|
3646
3646
|
|
|
3647
|
-
// raw-ts:/
|
|
3647
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
3648
3648
|
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";
|
|
3649
3649
|
|
|
3650
|
-
// raw-ts:/
|
|
3650
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
3651
3651
|
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";
|
|
3652
3652
|
|
|
3653
|
-
// raw-ts:/
|
|
3654
|
-
var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron
|
|
3653
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts
|
|
3654
|
+
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";
|
|
3655
3655
|
|
|
3656
3656
|
// src/supabase/edge-function-code.ts
|
|
3657
3657
|
var setupFunctionCode = stripe_setup_default;
|
|
@@ -3725,8 +3725,29 @@ var SupabaseSetupClient = class {
|
|
|
3725
3725
|
}
|
|
3726
3726
|
/**
|
|
3727
3727
|
* Setup pg_cron job to invoke worker function
|
|
3728
|
+
* @param intervalSeconds - How often to run the worker (default: 60 seconds)
|
|
3728
3729
|
*/
|
|
3729
|
-
async setupPgCronJob() {
|
|
3730
|
+
async setupPgCronJob(intervalSeconds = 60) {
|
|
3731
|
+
if (!Number.isInteger(intervalSeconds) || intervalSeconds < 1) {
|
|
3732
|
+
throw new Error(`Invalid interval: ${intervalSeconds}. Must be a positive integer.`);
|
|
3733
|
+
}
|
|
3734
|
+
let schedule;
|
|
3735
|
+
if (intervalSeconds < 60) {
|
|
3736
|
+
schedule = `${intervalSeconds} seconds`;
|
|
3737
|
+
} else if (intervalSeconds % 60 === 0) {
|
|
3738
|
+
const minutes = intervalSeconds / 60;
|
|
3739
|
+
if (minutes < 60) {
|
|
3740
|
+
schedule = `*/${minutes} * * * *`;
|
|
3741
|
+
} else {
|
|
3742
|
+
throw new Error(
|
|
3743
|
+
`Invalid interval: ${intervalSeconds}. Intervals >= 3600 seconds (1 hour) are not supported. Use a value between 1-3599 seconds.`
|
|
3744
|
+
);
|
|
3745
|
+
}
|
|
3746
|
+
} else {
|
|
3747
|
+
throw new Error(
|
|
3748
|
+
`Invalid interval: ${intervalSeconds}. Must be either 1-59 seconds or a multiple of 60 (e.g., 60, 120, 180).`
|
|
3749
|
+
);
|
|
3750
|
+
}
|
|
3730
3751
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
3731
3752
|
const escapedServiceRoleKey = serviceRoleKey.replace(/'/g, "''");
|
|
3732
3753
|
const sql3 = `
|
|
@@ -3754,11 +3775,11 @@ var SupabaseSetupClient = class {
|
|
|
3754
3775
|
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-scheduler'
|
|
3755
3776
|
);
|
|
3756
3777
|
|
|
3757
|
-
-- Create job to invoke worker
|
|
3778
|
+
-- Create job to invoke worker at configured interval
|
|
3758
3779
|
-- Worker reads from pgmq, enqueues objects if empty, and processes sync work
|
|
3759
3780
|
SELECT cron.schedule(
|
|
3760
3781
|
'stripe-sync-worker',
|
|
3761
|
-
'
|
|
3782
|
+
'${schedule}',
|
|
3762
3783
|
$$
|
|
3763
3784
|
SELECT net.http_post(
|
|
3764
3785
|
url := 'https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-worker',
|
|
@@ -3986,7 +4007,7 @@ var SupabaseSetupClient = class {
|
|
|
3986
4007
|
`from 'npm:stripe-experiment-sync@${version}'`
|
|
3987
4008
|
);
|
|
3988
4009
|
}
|
|
3989
|
-
async install(stripeKey, packageVersion) {
|
|
4010
|
+
async install(stripeKey, packageVersion, workerIntervalSeconds) {
|
|
3990
4011
|
const trimmedStripeKey = stripeKey.trim();
|
|
3991
4012
|
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
3992
4013
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
@@ -4010,7 +4031,7 @@ var SupabaseSetupClient = class {
|
|
|
4010
4031
|
if (!setupResult.success) {
|
|
4011
4032
|
throw new Error(`Setup failed: ${setupResult.error}`);
|
|
4012
4033
|
}
|
|
4013
|
-
await this.setupPgCronJob();
|
|
4034
|
+
await this.setupPgCronJob(workerIntervalSeconds);
|
|
4014
4035
|
await this.updateInstallationComment(
|
|
4015
4036
|
`${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_INSTALLED_SUFFIX}`
|
|
4016
4037
|
);
|
|
@@ -4023,14 +4044,20 @@ var SupabaseSetupClient = class {
|
|
|
4023
4044
|
}
|
|
4024
4045
|
};
|
|
4025
4046
|
async function install(params) {
|
|
4026
|
-
const {
|
|
4047
|
+
const {
|
|
4048
|
+
supabaseAccessToken,
|
|
4049
|
+
supabaseProjectRef,
|
|
4050
|
+
stripeKey,
|
|
4051
|
+
packageVersion,
|
|
4052
|
+
workerIntervalSeconds
|
|
4053
|
+
} = params;
|
|
4027
4054
|
const client = new SupabaseSetupClient({
|
|
4028
4055
|
accessToken: supabaseAccessToken,
|
|
4029
4056
|
projectRef: supabaseProjectRef,
|
|
4030
4057
|
projectBaseUrl: params.baseProjectUrl,
|
|
4031
4058
|
managementApiBaseUrl: params.baseManagementApiUrl
|
|
4032
4059
|
});
|
|
4033
|
-
await client.install(stripeKey, packageVersion);
|
|
4060
|
+
await client.install(stripeKey, packageVersion, workerIntervalSeconds);
|
|
4034
4061
|
}
|
|
4035
4062
|
async function uninstall(params) {
|
|
4036
4063
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
@@ -4481,7 +4508,8 @@ async function installCommand(options) {
|
|
|
4481
4508
|
supabaseAccessToken: accessToken,
|
|
4482
4509
|
supabaseProjectRef: projectRef,
|
|
4483
4510
|
stripeKey,
|
|
4484
|
-
packageVersion: options.packageVersion
|
|
4511
|
+
packageVersion: options.packageVersion,
|
|
4512
|
+
workerIntervalSeconds: options.workerInterval
|
|
4485
4513
|
});
|
|
4486
4514
|
console.log(import_chalk3.default.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"));
|
|
4487
4515
|
console.log(import_chalk3.default.cyan.bold(" Installation Complete!"));
|
package/dist/cli/lib.d.cts
CHANGED
package/dist/cli/lib.d.ts
CHANGED
package/dist/cli/lib.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
migrateCommand,
|
|
7
7
|
syncCommand,
|
|
8
8
|
uninstallCommand
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-FZQ4B7VZ.js";
|
|
10
|
+
import "../chunk-62FKHVHJ.js";
|
|
11
|
+
import "../chunk-AHNO3EMD.js";
|
|
12
|
+
import "../chunk-VEEV6P4R.js";
|
|
13
13
|
export {
|
|
14
14
|
backfillCommand,
|
|
15
15
|
createTunnel,
|
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
46
46
|
// package.json
|
|
47
47
|
var package_default = {
|
|
48
48
|
name: "stripe-experiment-sync",
|
|
49
|
-
version: "1.0.
|
|
49
|
+
version: "1.0.10",
|
|
50
50
|
private: false,
|
|
51
51
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
52
52
|
type: "module",
|
package/dist/index.js
CHANGED
package/dist/supabase/index.cjs
CHANGED
|
@@ -47,14 +47,14 @@ module.exports = __toCommonJS(supabase_exports);
|
|
|
47
47
|
// src/supabase/supabase.ts
|
|
48
48
|
var import_supabase_management_js = require("supabase-management-js");
|
|
49
49
|
|
|
50
|
-
// raw-ts:/
|
|
50
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
51
51
|
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";
|
|
52
52
|
|
|
53
|
-
// raw-ts:/
|
|
53
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
54
54
|
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";
|
|
55
55
|
|
|
56
|
-
// raw-ts:/
|
|
57
|
-
var stripe_worker_default = "/**\n * Stripe Sync Worker\n *\n * Triggered by pg_cron
|
|
56
|
+
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-worker.ts
|
|
57
|
+
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";
|
|
58
58
|
|
|
59
59
|
// src/supabase/edge-function-code.ts
|
|
60
60
|
var setupFunctionCode = stripe_setup_default;
|
|
@@ -64,7 +64,7 @@ var workerFunctionCode = stripe_worker_default;
|
|
|
64
64
|
// package.json
|
|
65
65
|
var package_default = {
|
|
66
66
|
name: "stripe-experiment-sync",
|
|
67
|
-
version: "1.0.
|
|
67
|
+
version: "1.0.10",
|
|
68
68
|
private: false,
|
|
69
69
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
70
70
|
type: "module",
|
|
@@ -210,8 +210,29 @@ var SupabaseSetupClient = class {
|
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
212
|
* Setup pg_cron job to invoke worker function
|
|
213
|
+
* @param intervalSeconds - How often to run the worker (default: 60 seconds)
|
|
213
214
|
*/
|
|
214
|
-
async setupPgCronJob() {
|
|
215
|
+
async setupPgCronJob(intervalSeconds = 60) {
|
|
216
|
+
if (!Number.isInteger(intervalSeconds) || intervalSeconds < 1) {
|
|
217
|
+
throw new Error(`Invalid interval: ${intervalSeconds}. Must be a positive integer.`);
|
|
218
|
+
}
|
|
219
|
+
let schedule;
|
|
220
|
+
if (intervalSeconds < 60) {
|
|
221
|
+
schedule = `${intervalSeconds} seconds`;
|
|
222
|
+
} else if (intervalSeconds % 60 === 0) {
|
|
223
|
+
const minutes = intervalSeconds / 60;
|
|
224
|
+
if (minutes < 60) {
|
|
225
|
+
schedule = `*/${minutes} * * * *`;
|
|
226
|
+
} else {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Invalid interval: ${intervalSeconds}. Intervals >= 3600 seconds (1 hour) are not supported. Use a value between 1-3599 seconds.`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Invalid interval: ${intervalSeconds}. Must be either 1-59 seconds or a multiple of 60 (e.g., 60, 120, 180).`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
215
236
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
216
237
|
const escapedServiceRoleKey = serviceRoleKey.replace(/'/g, "''");
|
|
217
238
|
const sql = `
|
|
@@ -239,11 +260,11 @@ var SupabaseSetupClient = class {
|
|
|
239
260
|
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-scheduler'
|
|
240
261
|
);
|
|
241
262
|
|
|
242
|
-
-- Create job to invoke worker
|
|
263
|
+
-- Create job to invoke worker at configured interval
|
|
243
264
|
-- Worker reads from pgmq, enqueues objects if empty, and processes sync work
|
|
244
265
|
SELECT cron.schedule(
|
|
245
266
|
'stripe-sync-worker',
|
|
246
|
-
'
|
|
267
|
+
'${schedule}',
|
|
247
268
|
$$
|
|
248
269
|
SELECT net.http_post(
|
|
249
270
|
url := 'https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-worker',
|
|
@@ -471,7 +492,7 @@ var SupabaseSetupClient = class {
|
|
|
471
492
|
`from 'npm:stripe-experiment-sync@${version}'`
|
|
472
493
|
);
|
|
473
494
|
}
|
|
474
|
-
async install(stripeKey, packageVersion) {
|
|
495
|
+
async install(stripeKey, packageVersion, workerIntervalSeconds) {
|
|
475
496
|
const trimmedStripeKey = stripeKey.trim();
|
|
476
497
|
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
477
498
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
@@ -495,7 +516,7 @@ var SupabaseSetupClient = class {
|
|
|
495
516
|
if (!setupResult.success) {
|
|
496
517
|
throw new Error(`Setup failed: ${setupResult.error}`);
|
|
497
518
|
}
|
|
498
|
-
await this.setupPgCronJob();
|
|
519
|
+
await this.setupPgCronJob(workerIntervalSeconds);
|
|
499
520
|
await this.updateInstallationComment(
|
|
500
521
|
`${STRIPE_SCHEMA_COMMENT_PREFIX} v${package_default.version} ${INSTALLATION_INSTALLED_SUFFIX}`
|
|
501
522
|
);
|
|
@@ -508,14 +529,20 @@ var SupabaseSetupClient = class {
|
|
|
508
529
|
}
|
|
509
530
|
};
|
|
510
531
|
async function install(params) {
|
|
511
|
-
const {
|
|
532
|
+
const {
|
|
533
|
+
supabaseAccessToken,
|
|
534
|
+
supabaseProjectRef,
|
|
535
|
+
stripeKey,
|
|
536
|
+
packageVersion,
|
|
537
|
+
workerIntervalSeconds
|
|
538
|
+
} = params;
|
|
512
539
|
const client = new SupabaseSetupClient({
|
|
513
540
|
accessToken: supabaseAccessToken,
|
|
514
541
|
projectRef: supabaseProjectRef,
|
|
515
542
|
projectBaseUrl: params.baseProjectUrl,
|
|
516
543
|
managementApiBaseUrl: params.baseManagementApiUrl
|
|
517
544
|
});
|
|
518
|
-
await client.install(stripeKey, packageVersion);
|
|
545
|
+
await client.install(stripeKey, packageVersion, workerIntervalSeconds);
|
|
519
546
|
}
|
|
520
547
|
async function uninstall(params) {
|
|
521
548
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
@@ -39,8 +39,9 @@ declare class SupabaseSetupClient {
|
|
|
39
39
|
runSQL(sql: string): Promise<unknown>;
|
|
40
40
|
/**
|
|
41
41
|
* Setup pg_cron job to invoke worker function
|
|
42
|
+
* @param intervalSeconds - How often to run the worker (default: 60 seconds)
|
|
42
43
|
*/
|
|
43
|
-
setupPgCronJob(): Promise<void>;
|
|
44
|
+
setupPgCronJob(intervalSeconds?: number): Promise<void>;
|
|
44
45
|
/**
|
|
45
46
|
* Get the webhook URL for this project
|
|
46
47
|
*/
|
|
@@ -97,13 +98,14 @@ declare class SupabaseSetupClient {
|
|
|
97
98
|
* Inject package version into Edge Function code
|
|
98
99
|
*/
|
|
99
100
|
private injectPackageVersion;
|
|
100
|
-
install(stripeKey: string, packageVersion?: string): Promise<void>;
|
|
101
|
+
install(stripeKey: string, packageVersion?: string, workerIntervalSeconds?: number): Promise<void>;
|
|
101
102
|
}
|
|
102
103
|
declare function install(params: {
|
|
103
104
|
supabaseAccessToken: string;
|
|
104
105
|
supabaseProjectRef: string;
|
|
105
106
|
stripeKey: string;
|
|
106
107
|
packageVersion?: string;
|
|
108
|
+
workerIntervalSeconds?: number;
|
|
107
109
|
baseProjectUrl?: string;
|
|
108
110
|
baseManagementApiUrl?: string;
|
|
109
111
|
}): Promise<void>;
|
package/dist/supabase/index.d.ts
CHANGED
|
@@ -39,8 +39,9 @@ declare class SupabaseSetupClient {
|
|
|
39
39
|
runSQL(sql: string): Promise<unknown>;
|
|
40
40
|
/**
|
|
41
41
|
* Setup pg_cron job to invoke worker function
|
|
42
|
+
* @param intervalSeconds - How often to run the worker (default: 60 seconds)
|
|
42
43
|
*/
|
|
43
|
-
setupPgCronJob(): Promise<void>;
|
|
44
|
+
setupPgCronJob(intervalSeconds?: number): Promise<void>;
|
|
44
45
|
/**
|
|
45
46
|
* Get the webhook URL for this project
|
|
46
47
|
*/
|
|
@@ -97,13 +98,14 @@ declare class SupabaseSetupClient {
|
|
|
97
98
|
* Inject package version into Edge Function code
|
|
98
99
|
*/
|
|
99
100
|
private injectPackageVersion;
|
|
100
|
-
install(stripeKey: string, packageVersion?: string): Promise<void>;
|
|
101
|
+
install(stripeKey: string, packageVersion?: string, workerIntervalSeconds?: number): Promise<void>;
|
|
101
102
|
}
|
|
102
103
|
declare function install(params: {
|
|
103
104
|
supabaseAccessToken: string;
|
|
104
105
|
supabaseProjectRef: string;
|
|
105
106
|
stripeKey: string;
|
|
106
107
|
packageVersion?: string;
|
|
108
|
+
workerIntervalSeconds?: number;
|
|
107
109
|
baseProjectUrl?: string;
|
|
108
110
|
baseManagementApiUrl?: string;
|
|
109
111
|
}): Promise<void>;
|
package/dist/supabase/index.js
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
uninstall,
|
|
10
10
|
webhookFunctionCode,
|
|
11
11
|
workerFunctionCode
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import "../chunk-
|
|
12
|
+
} from "../chunk-AHNO3EMD.js";
|
|
13
|
+
import "../chunk-VEEV6P4R.js";
|
|
14
14
|
export {
|
|
15
15
|
INSTALLATION_ERROR_SUFFIX,
|
|
16
16
|
INSTALLATION_INSTALLED_SUFFIX,
|