stripe-experiment-sync 1.0.10 → 1.0.12
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-62FKHVHJ.js → chunk-2TIRMOJR.js} +1 -1
- package/dist/{chunk-FZQ4B7VZ.js → chunk-6KTLIYUL.js} +2 -2
- package/dist/{chunk-VEEV6P4R.js → chunk-IO2EEPFD.js} +1 -1
- package/dist/{chunk-AHNO3EMD.js → chunk-RAM7OG7J.js} +36 -16
- package/dist/cli/index.cjs +36 -16
- package/dist/cli/index.js +4 -4
- package/dist/cli/lib.cjs +36 -16
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +2 -2
- package/dist/supabase/index.cjs +36 -16
- 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
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
StripeSync,
|
|
3
3
|
createStripeWebSocketClient,
|
|
4
4
|
runMigrations
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2TIRMOJR.js";
|
|
6
6
|
import {
|
|
7
7
|
install,
|
|
8
8
|
uninstall
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RAM7OG7J.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/config.ts
|
|
12
12
|
import dotenv from "dotenv";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IO2EEPFD.js";
|
|
4
4
|
|
|
5
5
|
// src/supabase/supabase.ts
|
|
6
6
|
import { SupabaseManagementAPI } from "supabase-management-js";
|
|
7
7
|
|
|
8
8
|
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
9
|
-
var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method
|
|
9
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nDeno.serve(async (req) => {\n // Require authentication for both GET and POST\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n // Handle GET requests for status\n if (req.method === 'GET') {\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n let sql\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n\n // Query installation status from schema comment\n const commentResult = await sql`\n SELECT obj_description(oid, 'pg_namespace') as comment\n FROM pg_namespace\n WHERE nspname = 'stripe'\n `\n\n const comment = commentResult[0]?.comment || null\n let installationStatus = 'not_installed'\n\n if (comment && comment.includes('stripe-sync')) {\n // Parse installation status from comment\n if (comment.includes('installation:started')) {\n installationStatus = 'installing'\n } else if (comment.includes('installation:error')) {\n installationStatus = 'error'\n } else if (comment.includes('installed')) {\n installationStatus = 'installed'\n }\n }\n\n // Query sync runs (only if schema exists)\n let syncStatus = []\n if (comment) {\n try {\n syncStatus = await sql`\n SELECT DISTINCT ON (account_id)\n account_id, started_at, closed_at, status, error_message,\n total_processed, total_objects, complete_count, error_count,\n running_count, pending_count, triggered_by, max_concurrent\n FROM stripe.sync_runs\n ORDER BY account_id, started_at DESC\n `\n } catch (err) {\n // Ignore errors if sync_runs view doesn't exist yet\n console.warn('sync_runs query failed (may not exist yet):', err)\n }\n }\n\n return new Response(\n JSON.stringify({\n package_version: VERSION,\n installation_status: installationStatus,\n sync_status: syncStatus,\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n }\n )\n } catch (error) {\n console.error('Status query error:', error)\n return new Response(\n JSON.stringify({\n error: error.message,\n package_version: VERSION,\n installation_status: 'not_installed',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } finally {\n if (sql) await sql.end()\n }\n }\n\n // Handle POST requests for setup (existing logic)\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\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
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";
|
|
@@ -301,7 +301,7 @@ var SupabaseSetupClient = class {
|
|
|
301
301
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
302
302
|
*/
|
|
303
303
|
async uninstall(stripeSecretKey) {
|
|
304
|
-
const stripe = new Stripe(stripeSecretKey, { apiVersion: "2025-02-24.acacia" });
|
|
304
|
+
const stripe = stripeSecretKey ? new Stripe(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
305
305
|
try {
|
|
306
306
|
try {
|
|
307
307
|
const webhookResult = await this.runSQL(`
|
|
@@ -310,7 +310,7 @@ var SupabaseSetupClient = class {
|
|
|
310
310
|
const webhookIds = webhookResult[0]?.rows?.map((r) => r.id) || [];
|
|
311
311
|
for (const webhookId of webhookIds) {
|
|
312
312
|
try {
|
|
313
|
-
await stripe
|
|
313
|
+
await stripe?.webhookEndpoints.del(webhookId);
|
|
314
314
|
} catch (err) {
|
|
315
315
|
console.warn(`Could not delete Stripe webhook ${webhookId}:`, err);
|
|
316
316
|
}
|
|
@@ -324,10 +324,12 @@ var SupabaseSetupClient = class {
|
|
|
324
324
|
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
325
325
|
try {
|
|
326
326
|
await this.runSQL(`
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker'
|
|
330
|
-
|
|
327
|
+
DO $$
|
|
328
|
+
BEGIN
|
|
329
|
+
IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN
|
|
330
|
+
PERFORM cron.unschedule('stripe-sync-worker');
|
|
331
|
+
END IF;
|
|
332
|
+
END $$;
|
|
331
333
|
`);
|
|
332
334
|
} catch (err) {
|
|
333
335
|
console.warn("Could not unschedule pg_cron job:", err);
|
|
@@ -343,15 +345,31 @@ var SupabaseSetupClient = class {
|
|
|
343
345
|
try {
|
|
344
346
|
await this.runSQL(`
|
|
345
347
|
SELECT pg_terminate_backend(pid)
|
|
346
|
-
FROM
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
FROM pg_locks l
|
|
349
|
+
JOIN pg_class c ON l.relation = c.oid
|
|
350
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
351
|
+
WHERE n.nspname = 'stripe'
|
|
352
|
+
AND l.pid != pg_backend_pid()
|
|
350
353
|
`);
|
|
351
354
|
} catch (err) {
|
|
352
355
|
console.warn("Could not terminate connections:", err);
|
|
353
356
|
}
|
|
354
|
-
|
|
357
|
+
let dropAttempts = 0;
|
|
358
|
+
const maxAttempts = 3;
|
|
359
|
+
while (dropAttempts < maxAttempts) {
|
|
360
|
+
try {
|
|
361
|
+
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
362
|
+
break;
|
|
363
|
+
} catch (err) {
|
|
364
|
+
dropAttempts++;
|
|
365
|
+
if (dropAttempts >= maxAttempts) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Failed to drop schema after ${maxAttempts} attempts. There may be active connections or locks on the stripe schema. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
355
373
|
} catch (error) {
|
|
356
374
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
357
375
|
}
|
|
@@ -422,13 +440,15 @@ async function install(params) {
|
|
|
422
440
|
}
|
|
423
441
|
async function uninstall(params) {
|
|
424
442
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
425
|
-
const trimmedStripeKey = stripeKey.trim();
|
|
426
|
-
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
443
|
+
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
444
|
+
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
427
445
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
428
446
|
}
|
|
429
447
|
const client = new SupabaseSetupClient({
|
|
430
448
|
accessToken: supabaseAccessToken,
|
|
431
|
-
projectRef: supabaseProjectRef
|
|
449
|
+
projectRef: supabaseProjectRef,
|
|
450
|
+
projectBaseUrl: params.baseProjectUrl,
|
|
451
|
+
managementApiBaseUrl: params.baseManagementApiUrl
|
|
432
452
|
});
|
|
433
453
|
await client.uninstall(trimmedStripeKey);
|
|
434
454
|
}
|
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.12",
|
|
37
37
|
private: false,
|
|
38
38
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
39
39
|
type: "module",
|
|
@@ -3631,7 +3631,7 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
3631
3631
|
var import_supabase_management_js = require("supabase-management-js");
|
|
3632
3632
|
|
|
3633
3633
|
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
3634
|
-
var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method
|
|
3634
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nDeno.serve(async (req) => {\n // Require authentication for both GET and POST\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n // Handle GET requests for status\n if (req.method === 'GET') {\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n let sql\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n\n // Query installation status from schema comment\n const commentResult = await sql`\n SELECT obj_description(oid, 'pg_namespace') as comment\n FROM pg_namespace\n WHERE nspname = 'stripe'\n `\n\n const comment = commentResult[0]?.comment || null\n let installationStatus = 'not_installed'\n\n if (comment && comment.includes('stripe-sync')) {\n // Parse installation status from comment\n if (comment.includes('installation:started')) {\n installationStatus = 'installing'\n } else if (comment.includes('installation:error')) {\n installationStatus = 'error'\n } else if (comment.includes('installed')) {\n installationStatus = 'installed'\n }\n }\n\n // Query sync runs (only if schema exists)\n let syncStatus = []\n if (comment) {\n try {\n syncStatus = await sql`\n SELECT DISTINCT ON (account_id)\n account_id, started_at, closed_at, status, error_message,\n total_processed, total_objects, complete_count, error_count,\n running_count, pending_count, triggered_by, max_concurrent\n FROM stripe.sync_runs\n ORDER BY account_id, started_at DESC\n `\n } catch (err) {\n // Ignore errors if sync_runs view doesn't exist yet\n console.warn('sync_runs query failed (may not exist yet):', err)\n }\n }\n\n return new Response(\n JSON.stringify({\n package_version: VERSION,\n installation_status: installationStatus,\n sync_status: syncStatus,\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n }\n )\n } catch (error) {\n console.error('Status query error:', error)\n return new Response(\n JSON.stringify({\n error: error.message,\n package_version: VERSION,\n installation_status: 'not_installed',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } finally {\n if (sql) await sql.end()\n }\n }\n\n // Handle POST requests for setup (existing logic)\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\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
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";
|
|
@@ -3926,7 +3926,7 @@ var SupabaseSetupClient = class {
|
|
|
3926
3926
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
3927
3927
|
*/
|
|
3928
3928
|
async uninstall(stripeSecretKey) {
|
|
3929
|
-
const stripe = new import_stripe3.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" });
|
|
3929
|
+
const stripe = stripeSecretKey ? new import_stripe3.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
3930
3930
|
try {
|
|
3931
3931
|
try {
|
|
3932
3932
|
const webhookResult = await this.runSQL(`
|
|
@@ -3935,7 +3935,7 @@ var SupabaseSetupClient = class {
|
|
|
3935
3935
|
const webhookIds = webhookResult[0]?.rows?.map((r) => r.id) || [];
|
|
3936
3936
|
for (const webhookId of webhookIds) {
|
|
3937
3937
|
try {
|
|
3938
|
-
await stripe
|
|
3938
|
+
await stripe?.webhookEndpoints.del(webhookId);
|
|
3939
3939
|
} catch (err) {
|
|
3940
3940
|
console.warn(`Could not delete Stripe webhook ${webhookId}:`, err);
|
|
3941
3941
|
}
|
|
@@ -3949,10 +3949,12 @@ var SupabaseSetupClient = class {
|
|
|
3949
3949
|
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
3950
3950
|
try {
|
|
3951
3951
|
await this.runSQL(`
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker'
|
|
3955
|
-
|
|
3952
|
+
DO $$
|
|
3953
|
+
BEGIN
|
|
3954
|
+
IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN
|
|
3955
|
+
PERFORM cron.unschedule('stripe-sync-worker');
|
|
3956
|
+
END IF;
|
|
3957
|
+
END $$;
|
|
3956
3958
|
`);
|
|
3957
3959
|
} catch (err) {
|
|
3958
3960
|
console.warn("Could not unschedule pg_cron job:", err);
|
|
@@ -3968,15 +3970,31 @@ var SupabaseSetupClient = class {
|
|
|
3968
3970
|
try {
|
|
3969
3971
|
await this.runSQL(`
|
|
3970
3972
|
SELECT pg_terminate_backend(pid)
|
|
3971
|
-
FROM
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3973
|
+
FROM pg_locks l
|
|
3974
|
+
JOIN pg_class c ON l.relation = c.oid
|
|
3975
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
3976
|
+
WHERE n.nspname = 'stripe'
|
|
3977
|
+
AND l.pid != pg_backend_pid()
|
|
3975
3978
|
`);
|
|
3976
3979
|
} catch (err) {
|
|
3977
3980
|
console.warn("Could not terminate connections:", err);
|
|
3978
3981
|
}
|
|
3979
|
-
|
|
3982
|
+
let dropAttempts = 0;
|
|
3983
|
+
const maxAttempts = 3;
|
|
3984
|
+
while (dropAttempts < maxAttempts) {
|
|
3985
|
+
try {
|
|
3986
|
+
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
3987
|
+
break;
|
|
3988
|
+
} catch (err) {
|
|
3989
|
+
dropAttempts++;
|
|
3990
|
+
if (dropAttempts >= maxAttempts) {
|
|
3991
|
+
throw new Error(
|
|
3992
|
+
`Failed to drop schema after ${maxAttempts} attempts. There may be active connections or locks on the stripe schema. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3980
3998
|
} catch (error) {
|
|
3981
3999
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3982
4000
|
}
|
|
@@ -4047,13 +4065,15 @@ async function install(params) {
|
|
|
4047
4065
|
}
|
|
4048
4066
|
async function uninstall(params) {
|
|
4049
4067
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
4050
|
-
const trimmedStripeKey = stripeKey.trim();
|
|
4051
|
-
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
4068
|
+
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
4069
|
+
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
4052
4070
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
4053
4071
|
}
|
|
4054
4072
|
const client = new SupabaseSetupClient({
|
|
4055
4073
|
accessToken: supabaseAccessToken,
|
|
4056
|
-
projectRef: supabaseProjectRef
|
|
4074
|
+
projectRef: supabaseProjectRef,
|
|
4075
|
+
projectBaseUrl: params.baseProjectUrl,
|
|
4076
|
+
managementApiBaseUrl: params.baseManagementApiUrl
|
|
4057
4077
|
});
|
|
4058
4078
|
await client.uninstall(trimmedStripeKey);
|
|
4059
4079
|
}
|
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-6KTLIYUL.js";
|
|
9
|
+
import "../chunk-2TIRMOJR.js";
|
|
10
|
+
import "../chunk-RAM7OG7J.js";
|
|
11
11
|
import {
|
|
12
12
|
package_default
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-IO2EEPFD.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
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.12",
|
|
109
109
|
private: false,
|
|
110
110
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
111
111
|
type: "module",
|
|
@@ -3645,7 +3645,7 @@ Creating ngrok tunnel for port ${port}...`));
|
|
|
3645
3645
|
var import_supabase_management_js = require("supabase-management-js");
|
|
3646
3646
|
|
|
3647
3647
|
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
3648
|
-
var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method
|
|
3648
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nDeno.serve(async (req) => {\n // Require authentication for both GET and POST\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n // Handle GET requests for status\n if (req.method === 'GET') {\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n let sql\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n\n // Query installation status from schema comment\n const commentResult = await sql`\n SELECT obj_description(oid, 'pg_namespace') as comment\n FROM pg_namespace\n WHERE nspname = 'stripe'\n `\n\n const comment = commentResult[0]?.comment || null\n let installationStatus = 'not_installed'\n\n if (comment && comment.includes('stripe-sync')) {\n // Parse installation status from comment\n if (comment.includes('installation:started')) {\n installationStatus = 'installing'\n } else if (comment.includes('installation:error')) {\n installationStatus = 'error'\n } else if (comment.includes('installed')) {\n installationStatus = 'installed'\n }\n }\n\n // Query sync runs (only if schema exists)\n let syncStatus = []\n if (comment) {\n try {\n syncStatus = await sql`\n SELECT DISTINCT ON (account_id)\n account_id, started_at, closed_at, status, error_message,\n total_processed, total_objects, complete_count, error_count,\n running_count, pending_count, triggered_by, max_concurrent\n FROM stripe.sync_runs\n ORDER BY account_id, started_at DESC\n `\n } catch (err) {\n // Ignore errors if sync_runs view doesn't exist yet\n console.warn('sync_runs query failed (may not exist yet):', err)\n }\n }\n\n return new Response(\n JSON.stringify({\n package_version: VERSION,\n installation_status: installationStatus,\n sync_status: syncStatus,\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n }\n )\n } catch (error) {\n console.error('Status query error:', error)\n return new Response(\n JSON.stringify({\n error: error.message,\n package_version: VERSION,\n installation_status: 'not_installed',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } finally {\n if (sql) await sql.end()\n }\n }\n\n // Handle POST requests for setup (existing logic)\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\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
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";
|
|
@@ -3940,7 +3940,7 @@ var SupabaseSetupClient = class {
|
|
|
3940
3940
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
3941
3941
|
*/
|
|
3942
3942
|
async uninstall(stripeSecretKey) {
|
|
3943
|
-
const stripe = new import_stripe3.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" });
|
|
3943
|
+
const stripe = stripeSecretKey ? new import_stripe3.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
3944
3944
|
try {
|
|
3945
3945
|
try {
|
|
3946
3946
|
const webhookResult = await this.runSQL(`
|
|
@@ -3949,7 +3949,7 @@ var SupabaseSetupClient = class {
|
|
|
3949
3949
|
const webhookIds = webhookResult[0]?.rows?.map((r) => r.id) || [];
|
|
3950
3950
|
for (const webhookId of webhookIds) {
|
|
3951
3951
|
try {
|
|
3952
|
-
await stripe
|
|
3952
|
+
await stripe?.webhookEndpoints.del(webhookId);
|
|
3953
3953
|
} catch (err) {
|
|
3954
3954
|
console.warn(`Could not delete Stripe webhook ${webhookId}:`, err);
|
|
3955
3955
|
}
|
|
@@ -3963,10 +3963,12 @@ var SupabaseSetupClient = class {
|
|
|
3963
3963
|
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
3964
3964
|
try {
|
|
3965
3965
|
await this.runSQL(`
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker'
|
|
3969
|
-
|
|
3966
|
+
DO $$
|
|
3967
|
+
BEGIN
|
|
3968
|
+
IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN
|
|
3969
|
+
PERFORM cron.unschedule('stripe-sync-worker');
|
|
3970
|
+
END IF;
|
|
3971
|
+
END $$;
|
|
3970
3972
|
`);
|
|
3971
3973
|
} catch (err) {
|
|
3972
3974
|
console.warn("Could not unschedule pg_cron job:", err);
|
|
@@ -3982,15 +3984,31 @@ var SupabaseSetupClient = class {
|
|
|
3982
3984
|
try {
|
|
3983
3985
|
await this.runSQL(`
|
|
3984
3986
|
SELECT pg_terminate_backend(pid)
|
|
3985
|
-
FROM
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3987
|
+
FROM pg_locks l
|
|
3988
|
+
JOIN pg_class c ON l.relation = c.oid
|
|
3989
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
3990
|
+
WHERE n.nspname = 'stripe'
|
|
3991
|
+
AND l.pid != pg_backend_pid()
|
|
3989
3992
|
`);
|
|
3990
3993
|
} catch (err) {
|
|
3991
3994
|
console.warn("Could not terminate connections:", err);
|
|
3992
3995
|
}
|
|
3993
|
-
|
|
3996
|
+
let dropAttempts = 0;
|
|
3997
|
+
const maxAttempts = 3;
|
|
3998
|
+
while (dropAttempts < maxAttempts) {
|
|
3999
|
+
try {
|
|
4000
|
+
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
4001
|
+
break;
|
|
4002
|
+
} catch (err) {
|
|
4003
|
+
dropAttempts++;
|
|
4004
|
+
if (dropAttempts >= maxAttempts) {
|
|
4005
|
+
throw new Error(
|
|
4006
|
+
`Failed to drop schema after ${maxAttempts} attempts. There may be active connections or locks on the stripe schema. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
4007
|
+
);
|
|
4008
|
+
}
|
|
4009
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
3994
4012
|
} catch (error) {
|
|
3995
4013
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3996
4014
|
}
|
|
@@ -4061,13 +4079,15 @@ async function install(params) {
|
|
|
4061
4079
|
}
|
|
4062
4080
|
async function uninstall(params) {
|
|
4063
4081
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
4064
|
-
const trimmedStripeKey = stripeKey.trim();
|
|
4065
|
-
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
4082
|
+
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
4083
|
+
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
4066
4084
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
4067
4085
|
}
|
|
4068
4086
|
const client = new SupabaseSetupClient({
|
|
4069
4087
|
accessToken: supabaseAccessToken,
|
|
4070
|
-
projectRef: supabaseProjectRef
|
|
4088
|
+
projectRef: supabaseProjectRef,
|
|
4089
|
+
projectBaseUrl: params.baseProjectUrl,
|
|
4090
|
+
managementApiBaseUrl: params.baseManagementApiUrl
|
|
4071
4091
|
});
|
|
4072
4092
|
await client.uninstall(trimmedStripeKey);
|
|
4073
4093
|
}
|
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-6KTLIYUL.js";
|
|
10
|
+
import "../chunk-2TIRMOJR.js";
|
|
11
|
+
import "../chunk-RAM7OG7J.js";
|
|
12
|
+
import "../chunk-IO2EEPFD.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.12",
|
|
50
50
|
private: false,
|
|
51
51
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
52
52
|
type: "module",
|
package/dist/index.d.cts
CHANGED
|
@@ -334,6 +334,36 @@ interface ProcessNextParams extends SyncParams {
|
|
|
334
334
|
/** Who/what triggered this sync (for observability) */
|
|
335
335
|
triggeredBy?: string;
|
|
336
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Installation status of the stripe-sync package
|
|
339
|
+
*/
|
|
340
|
+
type InstallationStatus = 'not_installed' | 'installing' | 'installed' | 'error';
|
|
341
|
+
/**
|
|
342
|
+
* Sync status for a single account (from sync_runs view)
|
|
343
|
+
*/
|
|
344
|
+
interface StripeSyncAccountState {
|
|
345
|
+
account_id: string;
|
|
346
|
+
started_at: string;
|
|
347
|
+
closed_at: string | null;
|
|
348
|
+
status: 'pending' | 'running' | 'complete' | 'error';
|
|
349
|
+
error_message: string | null;
|
|
350
|
+
total_processed: number;
|
|
351
|
+
total_objects: number;
|
|
352
|
+
complete_count: number;
|
|
353
|
+
error_count: number;
|
|
354
|
+
running_count: number;
|
|
355
|
+
pending_count: number;
|
|
356
|
+
triggered_by: string;
|
|
357
|
+
max_concurrent: number;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Response schema for the sync status endpoint
|
|
361
|
+
*/
|
|
362
|
+
interface StripeSyncState {
|
|
363
|
+
package_version: string;
|
|
364
|
+
installation_status: InstallationStatus;
|
|
365
|
+
sync_status: StripeSyncAccountState[];
|
|
366
|
+
}
|
|
337
367
|
|
|
338
368
|
/**
|
|
339
369
|
* Identifies a specific sync run.
|
|
@@ -654,4 +684,4 @@ declare function createStripeWebSocketClient(options: StripeWebSocketOptions): P
|
|
|
654
684
|
|
|
655
685
|
declare const VERSION: string;
|
|
656
686
|
|
|
657
|
-
export { type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncConfig, type StripeWebSocketClient, type StripeWebSocketOptions, type StripeWebhookEvent, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, type WebhookProcessingResult, createStripeWebSocketClient, hashApiKey, runMigrations };
|
|
687
|
+
export { type InstallationStatus, type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncAccountState, type StripeSyncConfig, type StripeSyncState, type StripeWebSocketClient, type StripeWebSocketOptions, type StripeWebhookEvent, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, type WebhookProcessingResult, createStripeWebSocketClient, hashApiKey, runMigrations };
|
package/dist/index.d.ts
CHANGED
|
@@ -334,6 +334,36 @@ interface ProcessNextParams extends SyncParams {
|
|
|
334
334
|
/** Who/what triggered this sync (for observability) */
|
|
335
335
|
triggeredBy?: string;
|
|
336
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Installation status of the stripe-sync package
|
|
339
|
+
*/
|
|
340
|
+
type InstallationStatus = 'not_installed' | 'installing' | 'installed' | 'error';
|
|
341
|
+
/**
|
|
342
|
+
* Sync status for a single account (from sync_runs view)
|
|
343
|
+
*/
|
|
344
|
+
interface StripeSyncAccountState {
|
|
345
|
+
account_id: string;
|
|
346
|
+
started_at: string;
|
|
347
|
+
closed_at: string | null;
|
|
348
|
+
status: 'pending' | 'running' | 'complete' | 'error';
|
|
349
|
+
error_message: string | null;
|
|
350
|
+
total_processed: number;
|
|
351
|
+
total_objects: number;
|
|
352
|
+
complete_count: number;
|
|
353
|
+
error_count: number;
|
|
354
|
+
running_count: number;
|
|
355
|
+
pending_count: number;
|
|
356
|
+
triggered_by: string;
|
|
357
|
+
max_concurrent: number;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Response schema for the sync status endpoint
|
|
361
|
+
*/
|
|
362
|
+
interface StripeSyncState {
|
|
363
|
+
package_version: string;
|
|
364
|
+
installation_status: InstallationStatus;
|
|
365
|
+
sync_status: StripeSyncAccountState[];
|
|
366
|
+
}
|
|
337
367
|
|
|
338
368
|
/**
|
|
339
369
|
* Identifies a specific sync run.
|
|
@@ -654,4 +684,4 @@ declare function createStripeWebSocketClient(options: StripeWebSocketOptions): P
|
|
|
654
684
|
|
|
655
685
|
declare const VERSION: string;
|
|
656
686
|
|
|
657
|
-
export { type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncConfig, type StripeWebSocketClient, type StripeWebSocketOptions, type StripeWebhookEvent, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, type WebhookProcessingResult, createStripeWebSocketClient, hashApiKey, runMigrations };
|
|
687
|
+
export { type InstallationStatus, type Logger, PostgresClient, type ProcessNextParams, type ProcessNextResult, type RevalidateEntity, StripeSync, type StripeSyncAccountState, type StripeSyncConfig, type StripeSyncState, type StripeWebSocketClient, type StripeWebSocketOptions, type StripeWebhookEvent, type Sync, type SyncBackfill, type SyncEntitlementsParams, type SyncFeaturesParams, type SyncObject, type SyncParams, VERSION, type WebhookProcessingResult, createStripeWebSocketClient, hashApiKey, runMigrations };
|
package/dist/index.js
CHANGED
package/dist/supabase/index.cjs
CHANGED
|
@@ -48,7 +48,7 @@ module.exports = __toCommonJS(supabase_exports);
|
|
|
48
48
|
var import_supabase_management_js = require("supabase-management-js");
|
|
49
49
|
|
|
50
50
|
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
|
|
51
|
-
var stripe_setup_default = "import { StripeSync, runMigrations } from 'npm:stripe-experiment-sync'\n\nDeno.serve(async (req) => {\n if (req.method
|
|
51
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\nDeno.serve(async (req) => {\n // Require authentication for both GET and POST\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n // Handle GET requests for status\n if (req.method === 'GET') {\n const rawDbUrl = Deno.env.get('SUPABASE_DB_URL')\n if (!rawDbUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const dbUrl = rawDbUrl.replace(/[?&]sslmode=[^&]*/g, '').replace(/[?&]$/, '')\n let sql\n\n try {\n sql = postgres(dbUrl, { max: 1, prepare: false })\n\n // Query installation status from schema comment\n const commentResult = await sql`\n SELECT obj_description(oid, 'pg_namespace') as comment\n FROM pg_namespace\n WHERE nspname = 'stripe'\n `\n\n const comment = commentResult[0]?.comment || null\n let installationStatus = 'not_installed'\n\n if (comment && comment.includes('stripe-sync')) {\n // Parse installation status from comment\n if (comment.includes('installation:started')) {\n installationStatus = 'installing'\n } else if (comment.includes('installation:error')) {\n installationStatus = 'error'\n } else if (comment.includes('installed')) {\n installationStatus = 'installed'\n }\n }\n\n // Query sync runs (only if schema exists)\n let syncStatus = []\n if (comment) {\n try {\n syncStatus = await sql`\n SELECT DISTINCT ON (account_id)\n account_id, started_at, closed_at, status, error_message,\n total_processed, total_objects, complete_count, error_count,\n running_count, pending_count, triggered_by, max_concurrent\n FROM stripe.sync_runs\n ORDER BY account_id, started_at DESC\n `\n } catch (err) {\n // Ignore errors if sync_runs view doesn't exist yet\n console.warn('sync_runs query failed (may not exist yet):', err)\n }\n }\n\n return new Response(\n JSON.stringify({\n package_version: VERSION,\n installation_status: installationStatus,\n sync_status: syncStatus,\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n },\n }\n )\n } catch (error) {\n console.error('Status query error:', error)\n return new Response(\n JSON.stringify({\n error: error.message,\n package_version: VERSION,\n installation_status: 'not_installed',\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } finally {\n if (sql) await sql.end()\n }\n }\n\n // Handle POST requests for setup (existing logic)\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\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
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";
|
|
@@ -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.12",
|
|
68
68
|
private: false,
|
|
69
69
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
70
70
|
type: "module",
|
|
@@ -425,7 +425,7 @@ var SupabaseSetupClient = class {
|
|
|
425
425
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
426
426
|
*/
|
|
427
427
|
async uninstall(stripeSecretKey) {
|
|
428
|
-
const stripe = new import_stripe.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" });
|
|
428
|
+
const stripe = stripeSecretKey ? new import_stripe.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
429
429
|
try {
|
|
430
430
|
try {
|
|
431
431
|
const webhookResult = await this.runSQL(`
|
|
@@ -434,7 +434,7 @@ var SupabaseSetupClient = class {
|
|
|
434
434
|
const webhookIds = webhookResult[0]?.rows?.map((r) => r.id) || [];
|
|
435
435
|
for (const webhookId of webhookIds) {
|
|
436
436
|
try {
|
|
437
|
-
await stripe
|
|
437
|
+
await stripe?.webhookEndpoints.del(webhookId);
|
|
438
438
|
} catch (err) {
|
|
439
439
|
console.warn(`Could not delete Stripe webhook ${webhookId}:`, err);
|
|
440
440
|
}
|
|
@@ -448,10 +448,12 @@ var SupabaseSetupClient = class {
|
|
|
448
448
|
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
449
449
|
try {
|
|
450
450
|
await this.runSQL(`
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker'
|
|
454
|
-
|
|
451
|
+
DO $$
|
|
452
|
+
BEGIN
|
|
453
|
+
IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN
|
|
454
|
+
PERFORM cron.unschedule('stripe-sync-worker');
|
|
455
|
+
END IF;
|
|
456
|
+
END $$;
|
|
455
457
|
`);
|
|
456
458
|
} catch (err) {
|
|
457
459
|
console.warn("Could not unschedule pg_cron job:", err);
|
|
@@ -467,15 +469,31 @@ var SupabaseSetupClient = class {
|
|
|
467
469
|
try {
|
|
468
470
|
await this.runSQL(`
|
|
469
471
|
SELECT pg_terminate_backend(pid)
|
|
470
|
-
FROM
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
FROM pg_locks l
|
|
473
|
+
JOIN pg_class c ON l.relation = c.oid
|
|
474
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
475
|
+
WHERE n.nspname = 'stripe'
|
|
476
|
+
AND l.pid != pg_backend_pid()
|
|
474
477
|
`);
|
|
475
478
|
} catch (err) {
|
|
476
479
|
console.warn("Could not terminate connections:", err);
|
|
477
480
|
}
|
|
478
|
-
|
|
481
|
+
let dropAttempts = 0;
|
|
482
|
+
const maxAttempts = 3;
|
|
483
|
+
while (dropAttempts < maxAttempts) {
|
|
484
|
+
try {
|
|
485
|
+
await this.runSQL(`DROP SCHEMA IF EXISTS stripe CASCADE`);
|
|
486
|
+
break;
|
|
487
|
+
} catch (err) {
|
|
488
|
+
dropAttempts++;
|
|
489
|
+
if (dropAttempts >= maxAttempts) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`Failed to drop schema after ${maxAttempts} attempts. There may be active connections or locks on the stripe schema. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
479
497
|
} catch (error) {
|
|
480
498
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
481
499
|
}
|
|
@@ -546,13 +564,15 @@ async function install(params) {
|
|
|
546
564
|
}
|
|
547
565
|
async function uninstall(params) {
|
|
548
566
|
const { supabaseAccessToken, supabaseProjectRef, stripeKey } = params;
|
|
549
|
-
const trimmedStripeKey = stripeKey.trim();
|
|
550
|
-
if (!trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
567
|
+
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
568
|
+
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
551
569
|
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
552
570
|
}
|
|
553
571
|
const client = new SupabaseSetupClient({
|
|
554
572
|
accessToken: supabaseAccessToken,
|
|
555
|
-
projectRef: supabaseProjectRef
|
|
573
|
+
projectRef: supabaseProjectRef,
|
|
574
|
+
projectBaseUrl: params.baseProjectUrl,
|
|
575
|
+
managementApiBaseUrl: params.baseManagementApiUrl
|
|
556
576
|
});
|
|
557
577
|
await client.uninstall(trimmedStripeKey);
|
|
558
578
|
}
|
|
@@ -93,7 +93,7 @@ declare class SupabaseSetupClient {
|
|
|
93
93
|
* Uninstall stripe-sync from a Supabase project
|
|
94
94
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
95
95
|
*/
|
|
96
|
-
uninstall(stripeSecretKey
|
|
96
|
+
uninstall(stripeSecretKey?: string): Promise<void>;
|
|
97
97
|
/**
|
|
98
98
|
* Inject package version into Edge Function code
|
|
99
99
|
*/
|
|
@@ -112,7 +112,9 @@ declare function install(params: {
|
|
|
112
112
|
declare function uninstall(params: {
|
|
113
113
|
supabaseAccessToken: string;
|
|
114
114
|
supabaseProjectRef: string;
|
|
115
|
-
stripeKey
|
|
115
|
+
stripeKey?: string;
|
|
116
|
+
baseProjectUrl?: string;
|
|
117
|
+
baseManagementApiUrl?: string;
|
|
116
118
|
}): Promise<void>;
|
|
117
119
|
|
|
118
120
|
declare const setupFunctionCode: string;
|
package/dist/supabase/index.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ declare class SupabaseSetupClient {
|
|
|
93
93
|
* Uninstall stripe-sync from a Supabase project
|
|
94
94
|
* Removes all Edge Functions, secrets, database resources, and Stripe webhooks
|
|
95
95
|
*/
|
|
96
|
-
uninstall(stripeSecretKey
|
|
96
|
+
uninstall(stripeSecretKey?: string): Promise<void>;
|
|
97
97
|
/**
|
|
98
98
|
* Inject package version into Edge Function code
|
|
99
99
|
*/
|
|
@@ -112,7 +112,9 @@ declare function install(params: {
|
|
|
112
112
|
declare function uninstall(params: {
|
|
113
113
|
supabaseAccessToken: string;
|
|
114
114
|
supabaseProjectRef: string;
|
|
115
|
-
stripeKey
|
|
115
|
+
stripeKey?: string;
|
|
116
|
+
baseProjectUrl?: string;
|
|
117
|
+
baseManagementApiUrl?: string;
|
|
116
118
|
}): Promise<void>;
|
|
117
119
|
|
|
118
120
|
declare const setupFunctionCode: string;
|
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-RAM7OG7J.js";
|
|
13
|
+
import "../chunk-IO2EEPFD.js";
|
|
14
14
|
export {
|
|
15
15
|
INSTALLATION_ERROR_SUFFIX,
|
|
16
16
|
INSTALLATION_INSTALLED_SUFFIX,
|