stripe-experiment-sync 1.0.12 → 1.0.13
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-2TIRMOJR.js → chunk-2GSABFXH.js} +1 -1
- package/dist/{chunk-6KTLIYUL.js → chunk-5SS5ZEQF.js} +4 -20
- package/dist/{chunk-RAM7OG7J.js → chunk-O2N4AEQS.js} +34 -84
- package/dist/{chunk-IO2EEPFD.js → chunk-RCU5ZXAX.js} +1 -1
- package/dist/cli/index.cjs +38 -105
- package/dist/cli/index.js +6 -7
- package/dist/cli/lib.cjs +36 -102
- package/dist/cli/lib.js +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/supabase/index.cjs +34 -94
- package/dist/supabase/index.d.cts +5 -5
- package/dist/supabase/index.d.ts +5 -5
- package/dist/supabase/index.js +2 -2
- package/package.json +1 -1
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-5SS5ZEQF.js";
|
|
9
|
+
import "../chunk-2GSABFXH.js";
|
|
10
|
+
import "../chunk-O2N4AEQS.js";
|
|
11
11
|
import {
|
|
12
12
|
package_default
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-RCU5ZXAX.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -54,11 +54,10 @@ supabase.command("install").description("Install Stripe sync to Supabase Edge Fu
|
|
|
54
54
|
workerInterval: options.workerInterval
|
|
55
55
|
});
|
|
56
56
|
});
|
|
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)").
|
|
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)").action(async (options) => {
|
|
58
58
|
await uninstallCommand({
|
|
59
59
|
supabaseAccessToken: options.token,
|
|
60
|
-
supabaseProjectRef: options.project
|
|
61
|
-
stripeKey: options.stripeKey
|
|
60
|
+
supabaseProjectRef: options.project
|
|
62
61
|
});
|
|
63
62
|
});
|
|
64
63
|
program.parse();
|
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.13",
|
|
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, 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";
|
|
3648
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\n// Helper to delete edge function via Management API\nasync function deleteEdgeFunction(\n projectRef: string,\n functionSlug: string,\n accessToken: string\n): Promise<void> {\n const url = `https://api.supabase.com/v1/projects/${projectRef}/functions/${functionSlug}`\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok && response.status !== 404) {\n const text = await response.text()\n throw new Error(`Failed to delete function ${functionSlug}: ${response.status} ${text}`)\n }\n}\n\n// Helper to delete secrets via Management API\nasync function deleteSecret(\n projectRef: string,\n secretName: string,\n accessToken: string\n): Promise<void> {\n const url = `https://api.supabase.com/v1/projects/${projectRef}/secrets`\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify([secretName]),\n })\n\n if (!response.ok && response.status !== 404) {\n const text = await response.text()\n console.warn(`Failed to delete secret ${secretName}: ${response.status} ${text}`)\n }\n}\n\nDeno.serve(async (req) => {\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 DELETE requests for uninstall\n if (req.method === 'DELETE') {\n let stripeSync = null\n try {\n // Parse request body\n const body = await req.json()\n const { supabase_access_token, supabase_project_ref } = body\n\n if (!supabase_access_token || !supabase_project_ref) {\n throw new Error(\n 'supabase_access_token and supabase_project_ref are required in request body'\n )\n }\n\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 // Stripe key is required for uninstall to delete webhooks\n const stripeKey = Deno.env.get('STRIPE_SECRET_KEY')\n if (!stripeKey) {\n throw new Error('STRIPE_SECRET_KEY environment variable is required for uninstall')\n }\n\n // Step 1: Delete Stripe webhooks and clean up database\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 2 },\n stripeSecretKey: stripeKey,\n })\n\n // Delete all managed webhooks\n const webhooks = await stripeSync.listManagedWebhooks()\n for (const webhook of webhooks) {\n try {\n await stripeSync.deleteManagedWebhook(webhook.id)\n console.log(`Deleted webhook: ${webhook.id}`)\n } catch (err) {\n console.warn(`Could not delete webhook ${webhook.id}:`, err)\n }\n }\n\n // Unschedule pg_cron job\n try {\n await stripeSync.postgresClient.query(`\n DO $$\n BEGIN\n IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN\n PERFORM cron.unschedule('stripe-sync-worker');\n END IF;\n END $$;\n `)\n } catch (err) {\n console.warn('Could not unschedule pg_cron job:', err)\n }\n\n // Delete vault secret\n try {\n await stripeSync.postgresClient.query(`\n DELETE FROM vault.secrets\n WHERE name = 'stripe_sync_service_role_key'\n `)\n } catch (err) {\n console.warn('Could not delete vault secret:', err)\n }\n\n // Terminate connections holding locks on stripe schema\n try {\n await stripeSync.postgresClient.query(`\n SELECT pg_terminate_backend(pid)\n FROM pg_locks l\n JOIN pg_class c ON l.relation = c.oid\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE n.nspname = 'stripe'\n AND l.pid != pg_backend_pid()\n `)\n } catch (err) {\n console.warn('Could not terminate connections:', err)\n }\n\n // Drop schema with retry\n let dropAttempts = 0\n const maxAttempts = 3\n while (dropAttempts < maxAttempts) {\n try {\n await stripeSync.postgresClient.query('DROP SCHEMA IF EXISTS stripe CASCADE')\n break // Success, exit loop\n } catch (err) {\n dropAttempts++\n if (dropAttempts >= maxAttempts) {\n throw new Error(\n `Failed to drop schema after ${maxAttempts} attempts. ` +\n `There may be active connections or locks on the stripe schema. ` +\n `Error: ${err.message}`\n )\n }\n // Wait 1 second before retrying\n await new Promise((resolve) => setTimeout(resolve, 1000))\n }\n }\n\n await stripeSync.postgresClient.pool.end()\n\n // Step 2: Delete Supabase secrets\n try {\n await deleteSecret(supabase_project_ref, 'STRIPE_SECRET_KEY', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete STRIPE_SECRET_KEY secret:', err)\n }\n\n // Step 3: Delete Edge Functions\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-setup', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-setup function:', err)\n }\n\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-webhook', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-webhook function:', err)\n }\n\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-worker', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-worker function:', err)\n }\n\n return new Response(\n JSON.stringify({\n success: true,\n message: 'Uninstall complete',\n }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } catch (error) {\n console.error('Uninstall error:', error)\n // Cleanup on error\n if (stripeSync) {\n try {\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\n // Handle POST requests for install\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";
|
|
@@ -3659,7 +3659,6 @@ var webhookFunctionCode = stripe_webhook_default;
|
|
|
3659
3659
|
var workerFunctionCode = stripe_worker_default;
|
|
3660
3660
|
|
|
3661
3661
|
// src/supabase/supabase.ts
|
|
3662
|
-
var import_stripe3 = __toESM(require("stripe"), 1);
|
|
3663
3662
|
var STRIPE_SCHEMA_COMMENT_PREFIX = "stripe-sync";
|
|
3664
3663
|
var INSTALLATION_STARTED_SUFFIX = "installation:started";
|
|
3665
3664
|
var INSTALLATION_ERROR_SUFFIX = "installation:error";
|
|
@@ -3668,6 +3667,7 @@ var SupabaseSetupClient = class {
|
|
|
3668
3667
|
api;
|
|
3669
3668
|
projectRef;
|
|
3670
3669
|
projectBaseUrl;
|
|
3670
|
+
accessToken;
|
|
3671
3671
|
constructor(options) {
|
|
3672
3672
|
this.api = new import_supabase_management_js.SupabaseManagementAPI({
|
|
3673
3673
|
accessToken: options.accessToken,
|
|
@@ -3675,6 +3675,7 @@ var SupabaseSetupClient = class {
|
|
|
3675
3675
|
});
|
|
3676
3676
|
this.projectRef = options.projectRef;
|
|
3677
3677
|
this.projectBaseUrl = options.projectBaseUrl || process.env.SUPABASE_BASE_URL || "supabase.co";
|
|
3678
|
+
this.accessToken = options.accessToken;
|
|
3678
3679
|
}
|
|
3679
3680
|
/**
|
|
3680
3681
|
* Validate that the project exists and we have access
|
|
@@ -3694,20 +3695,20 @@ var SupabaseSetupClient = class {
|
|
|
3694
3695
|
/**
|
|
3695
3696
|
* Deploy an Edge Function
|
|
3696
3697
|
*/
|
|
3697
|
-
async deployFunction(name, code) {
|
|
3698
|
+
async deployFunction(name, code, verifyJwt = false) {
|
|
3698
3699
|
const functions = await this.api.listFunctions(this.projectRef);
|
|
3699
3700
|
const exists = functions?.some((f) => f.slug === name);
|
|
3700
3701
|
if (exists) {
|
|
3701
3702
|
await this.api.updateFunction(this.projectRef, name, {
|
|
3702
3703
|
body: code,
|
|
3703
|
-
verify_jwt:
|
|
3704
|
+
verify_jwt: verifyJwt
|
|
3704
3705
|
});
|
|
3705
3706
|
} else {
|
|
3706
3707
|
await this.api.createFunction(this.projectRef, {
|
|
3707
3708
|
slug: name,
|
|
3708
3709
|
name,
|
|
3709
3710
|
body: code,
|
|
3710
|
-
verify_jwt:
|
|
3711
|
+
verify_jwt: verifyJwt
|
|
3711
3712
|
});
|
|
3712
3713
|
}
|
|
3713
3714
|
}
|
|
@@ -3937,77 +3938,30 @@ var SupabaseSetupClient = class {
|
|
|
3937
3938
|
}
|
|
3938
3939
|
/**
|
|
3939
3940
|
* Uninstall stripe-sync from a Supabase project
|
|
3940
|
-
*
|
|
3941
|
+
* Invokes the stripe-setup edge function's DELETE endpoint which handles cleanup
|
|
3941
3942
|
*/
|
|
3942
|
-
async uninstall(
|
|
3943
|
-
const stripe = stripeSecretKey ? new import_stripe3.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
3943
|
+
async uninstall() {
|
|
3944
3944
|
try {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
}
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
await this.deleteFunction("stripe-webhook");
|
|
3962
|
-
await this.deleteFunction("stripe-worker");
|
|
3963
|
-
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
3964
|
-
try {
|
|
3965
|
-
await this.runSQL(`
|
|
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 $$;
|
|
3972
|
-
`);
|
|
3973
|
-
} catch (err) {
|
|
3974
|
-
console.warn("Could not unschedule pg_cron job:", err);
|
|
3975
|
-
}
|
|
3976
|
-
try {
|
|
3977
|
-
await this.runSQL(`
|
|
3978
|
-
DELETE FROM vault.secrets
|
|
3979
|
-
WHERE name = 'stripe_sync_service_role_key'
|
|
3980
|
-
`);
|
|
3981
|
-
} catch (err) {
|
|
3982
|
-
console.warn("Could not delete vault secret:", err);
|
|
3983
|
-
}
|
|
3984
|
-
try {
|
|
3985
|
-
await this.runSQL(`
|
|
3986
|
-
SELECT pg_terminate_backend(pid)
|
|
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()
|
|
3992
|
-
`);
|
|
3993
|
-
} catch (err) {
|
|
3994
|
-
console.warn("Could not terminate connections:", err);
|
|
3945
|
+
const serviceRoleKey = await this.getServiceRoleKey();
|
|
3946
|
+
const url = `https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-setup`;
|
|
3947
|
+
const response = await fetch(url, {
|
|
3948
|
+
method: "DELETE",
|
|
3949
|
+
headers: {
|
|
3950
|
+
Authorization: `Bearer ${serviceRoleKey}`,
|
|
3951
|
+
"Content-Type": "application/json"
|
|
3952
|
+
},
|
|
3953
|
+
body: JSON.stringify({
|
|
3954
|
+
supabase_access_token: this.accessToken,
|
|
3955
|
+
supabase_project_ref: this.projectRef
|
|
3956
|
+
})
|
|
3957
|
+
});
|
|
3958
|
+
if (!response.ok) {
|
|
3959
|
+
const text = await response.text();
|
|
3960
|
+
throw new Error(`Uninstall failed: ${response.status} ${text}`);
|
|
3995
3961
|
}
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
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
|
-
}
|
|
3962
|
+
const result = await response.json();
|
|
3963
|
+
if (result.success === false) {
|
|
3964
|
+
throw new Error(`Uninstall failed: ${result.error}`);
|
|
4011
3965
|
}
|
|
4012
3966
|
} catch (error) {
|
|
4013
3967
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4040,9 +3994,9 @@ var SupabaseSetupClient = class {
|
|
|
4040
3994
|
const versionedSetup = this.injectPackageVersion(setupFunctionCode, version);
|
|
4041
3995
|
const versionedWebhook = this.injectPackageVersion(webhookFunctionCode, version);
|
|
4042
3996
|
const versionedWorker = this.injectPackageVersion(workerFunctionCode, version);
|
|
4043
|
-
await this.deployFunction("stripe-setup", versionedSetup);
|
|
4044
|
-
await this.deployFunction("stripe-webhook", versionedWebhook);
|
|
4045
|
-
await this.deployFunction("stripe-worker", versionedWorker);
|
|
3997
|
+
await this.deployFunction("stripe-setup", versionedSetup, true);
|
|
3998
|
+
await this.deployFunction("stripe-webhook", versionedWebhook, false);
|
|
3999
|
+
await this.deployFunction("stripe-worker", versionedWorker, true);
|
|
4046
4000
|
await this.setSecrets([{ name: "STRIPE_SECRET_KEY", value: trimmedStripeKey }]);
|
|
4047
4001
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
4048
4002
|
const setupResult = await this.invokeFunction("stripe-setup", serviceRoleKey);
|
|
@@ -4078,18 +4032,14 @@ async function install(params) {
|
|
|
4078
4032
|
await client.install(stripeKey, packageVersion, workerIntervalSeconds);
|
|
4079
4033
|
}
|
|
4080
4034
|
async function uninstall(params) {
|
|
4081
|
-
const { supabaseAccessToken, supabaseProjectRef
|
|
4082
|
-
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
4083
|
-
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
4084
|
-
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
4085
|
-
}
|
|
4035
|
+
const { supabaseAccessToken, supabaseProjectRef } = params;
|
|
4086
4036
|
const client = new SupabaseSetupClient({
|
|
4087
4037
|
accessToken: supabaseAccessToken,
|
|
4088
4038
|
projectRef: supabaseProjectRef,
|
|
4089
4039
|
projectBaseUrl: params.baseProjectUrl,
|
|
4090
|
-
managementApiBaseUrl: params.
|
|
4040
|
+
managementApiBaseUrl: params.baseManagementApiBaseUrl
|
|
4091
4041
|
});
|
|
4092
|
-
await client.uninstall(
|
|
4042
|
+
await client.uninstall();
|
|
4093
4043
|
}
|
|
4094
4044
|
|
|
4095
4045
|
// src/cli/commands.ts
|
|
@@ -4551,8 +4501,7 @@ async function uninstallCommand(options) {
|
|
|
4551
4501
|
import_dotenv2.default.config();
|
|
4552
4502
|
let accessToken = options.supabaseAccessToken || process.env.SUPABASE_ACCESS_TOKEN || "";
|
|
4553
4503
|
let projectRef = options.supabaseProjectRef || process.env.SUPABASE_PROJECT_REF || "";
|
|
4554
|
-
|
|
4555
|
-
if (!accessToken || !projectRef || !stripeKey) {
|
|
4504
|
+
if (!accessToken || !projectRef) {
|
|
4556
4505
|
const inquirer2 = (await import("inquirer")).default;
|
|
4557
4506
|
const questions = [];
|
|
4558
4507
|
if (!accessToken) {
|
|
@@ -4572,25 +4521,11 @@ async function uninstallCommand(options) {
|
|
|
4572
4521
|
validate: (input) => input.trim() !== "" || "Project ref is required"
|
|
4573
4522
|
});
|
|
4574
4523
|
}
|
|
4575
|
-
if (!stripeKey) {
|
|
4576
|
-
questions.push({
|
|
4577
|
-
type: "password",
|
|
4578
|
-
name: "stripeKey",
|
|
4579
|
-
message: "Enter your Stripe secret key:",
|
|
4580
|
-
mask: "*",
|
|
4581
|
-
validate: (input) => {
|
|
4582
|
-
if (!input.trim()) return "Stripe key is required";
|
|
4583
|
-
if (!input.startsWith("sk_")) return 'Stripe key should start with "sk_"';
|
|
4584
|
-
return true;
|
|
4585
|
-
}
|
|
4586
|
-
});
|
|
4587
|
-
}
|
|
4588
4524
|
if (questions.length > 0) {
|
|
4589
4525
|
console.log(import_chalk3.default.yellow("\nMissing required configuration. Please provide:"));
|
|
4590
4526
|
const answers = await inquirer2.prompt(questions);
|
|
4591
4527
|
if (answers.accessToken) accessToken = answers.accessToken;
|
|
4592
4528
|
if (answers.projectRef) projectRef = answers.projectRef;
|
|
4593
|
-
if (answers.stripeKey) stripeKey = answers.stripeKey;
|
|
4594
4529
|
}
|
|
4595
4530
|
}
|
|
4596
4531
|
console.log(import_chalk3.default.blue("\n\u{1F5D1}\uFE0F Uninstalling Stripe Sync from Supabase...\n"));
|
|
@@ -4598,8 +4533,7 @@ async function uninstallCommand(options) {
|
|
|
4598
4533
|
console.log(import_chalk3.default.gray("Removing all resources..."));
|
|
4599
4534
|
await uninstall({
|
|
4600
4535
|
supabaseAccessToken: accessToken,
|
|
4601
|
-
supabaseProjectRef: projectRef
|
|
4602
|
-
stripeKey
|
|
4536
|
+
supabaseProjectRef: projectRef
|
|
4603
4537
|
});
|
|
4604
4538
|
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"));
|
|
4605
4539
|
console.log(import_chalk3.default.cyan.bold(" Uninstall Complete!"));
|
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-5SS5ZEQF.js";
|
|
10
|
+
import "../chunk-2GSABFXH.js";
|
|
11
|
+
import "../chunk-O2N4AEQS.js";
|
|
12
|
+
import "../chunk-RCU5ZXAX.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.13",
|
|
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
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/supabase/index.ts
|
|
@@ -48,7 +38,7 @@ module.exports = __toCommonJS(supabase_exports);
|
|
|
48
38
|
var import_supabase_management_js = require("supabase-management-js");
|
|
49
39
|
|
|
50
40
|
// 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, 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";
|
|
41
|
+
var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\n// Helper to delete edge function via Management API\nasync function deleteEdgeFunction(\n projectRef: string,\n functionSlug: string,\n accessToken: string\n): Promise<void> {\n const url = `https://api.supabase.com/v1/projects/${projectRef}/functions/${functionSlug}`\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok && response.status !== 404) {\n const text = await response.text()\n throw new Error(`Failed to delete function ${functionSlug}: ${response.status} ${text}`)\n }\n}\n\n// Helper to delete secrets via Management API\nasync function deleteSecret(\n projectRef: string,\n secretName: string,\n accessToken: string\n): Promise<void> {\n const url = `https://api.supabase.com/v1/projects/${projectRef}/secrets`\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify([secretName]),\n })\n\n if (!response.ok && response.status !== 404) {\n const text = await response.text()\n console.warn(`Failed to delete secret ${secretName}: ${response.status} ${text}`)\n }\n}\n\nDeno.serve(async (req) => {\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 DELETE requests for uninstall\n if (req.method === 'DELETE') {\n let stripeSync = null\n try {\n // Parse request body\n const body = await req.json()\n const { supabase_access_token, supabase_project_ref } = body\n\n if (!supabase_access_token || !supabase_project_ref) {\n throw new Error(\n 'supabase_access_token and supabase_project_ref are required in request body'\n )\n }\n\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 // Stripe key is required for uninstall to delete webhooks\n const stripeKey = Deno.env.get('STRIPE_SECRET_KEY')\n if (!stripeKey) {\n throw new Error('STRIPE_SECRET_KEY environment variable is required for uninstall')\n }\n\n // Step 1: Delete Stripe webhooks and clean up database\n stripeSync = new StripeSync({\n poolConfig: { connectionString: dbUrl, max: 2 },\n stripeSecretKey: stripeKey,\n })\n\n // Delete all managed webhooks\n const webhooks = await stripeSync.listManagedWebhooks()\n for (const webhook of webhooks) {\n try {\n await stripeSync.deleteManagedWebhook(webhook.id)\n console.log(`Deleted webhook: ${webhook.id}`)\n } catch (err) {\n console.warn(`Could not delete webhook ${webhook.id}:`, err)\n }\n }\n\n // Unschedule pg_cron job\n try {\n await stripeSync.postgresClient.query(`\n DO $$\n BEGIN\n IF EXISTS (SELECT 1 FROM cron.job WHERE jobname = 'stripe-sync-worker') THEN\n PERFORM cron.unschedule('stripe-sync-worker');\n END IF;\n END $$;\n `)\n } catch (err) {\n console.warn('Could not unschedule pg_cron job:', err)\n }\n\n // Delete vault secret\n try {\n await stripeSync.postgresClient.query(`\n DELETE FROM vault.secrets\n WHERE name = 'stripe_sync_service_role_key'\n `)\n } catch (err) {\n console.warn('Could not delete vault secret:', err)\n }\n\n // Terminate connections holding locks on stripe schema\n try {\n await stripeSync.postgresClient.query(`\n SELECT pg_terminate_backend(pid)\n FROM pg_locks l\n JOIN pg_class c ON l.relation = c.oid\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE n.nspname = 'stripe'\n AND l.pid != pg_backend_pid()\n `)\n } catch (err) {\n console.warn('Could not terminate connections:', err)\n }\n\n // Drop schema with retry\n let dropAttempts = 0\n const maxAttempts = 3\n while (dropAttempts < maxAttempts) {\n try {\n await stripeSync.postgresClient.query('DROP SCHEMA IF EXISTS stripe CASCADE')\n break // Success, exit loop\n } catch (err) {\n dropAttempts++\n if (dropAttempts >= maxAttempts) {\n throw new Error(\n `Failed to drop schema after ${maxAttempts} attempts. ` +\n `There may be active connections or locks on the stripe schema. ` +\n `Error: ${err.message}`\n )\n }\n // Wait 1 second before retrying\n await new Promise((resolve) => setTimeout(resolve, 1000))\n }\n }\n\n await stripeSync.postgresClient.pool.end()\n\n // Step 2: Delete Supabase secrets\n try {\n await deleteSecret(supabase_project_ref, 'STRIPE_SECRET_KEY', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete STRIPE_SECRET_KEY secret:', err)\n }\n\n // Step 3: Delete Edge Functions\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-setup', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-setup function:', err)\n }\n\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-webhook', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-webhook function:', err)\n }\n\n try {\n await deleteEdgeFunction(supabase_project_ref, 'stripe-worker', supabase_access_token)\n } catch (err) {\n console.warn('Could not delete stripe-worker function:', err)\n }\n\n return new Response(\n JSON.stringify({\n success: true,\n message: 'Uninstall complete',\n }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n }\n )\n } catch (error) {\n console.error('Uninstall error:', error)\n // Cleanup on error\n if (stripeSync) {\n try {\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\n // Handle POST requests for install\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
42
|
|
|
53
43
|
// raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
|
|
54
44
|
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 +54,7 @@ var workerFunctionCode = stripe_worker_default;
|
|
|
64
54
|
// package.json
|
|
65
55
|
var package_default = {
|
|
66
56
|
name: "stripe-experiment-sync",
|
|
67
|
-
version: "1.0.
|
|
57
|
+
version: "1.0.13",
|
|
68
58
|
private: false,
|
|
69
59
|
description: "Stripe Sync Engine to sync Stripe data to Postgres",
|
|
70
60
|
type: "module",
|
|
@@ -144,7 +134,6 @@ var package_default = {
|
|
|
144
134
|
};
|
|
145
135
|
|
|
146
136
|
// src/supabase/supabase.ts
|
|
147
|
-
var import_stripe = __toESM(require("stripe"), 1);
|
|
148
137
|
var STRIPE_SCHEMA_COMMENT_PREFIX = "stripe-sync";
|
|
149
138
|
var INSTALLATION_STARTED_SUFFIX = "installation:started";
|
|
150
139
|
var INSTALLATION_ERROR_SUFFIX = "installation:error";
|
|
@@ -153,6 +142,7 @@ var SupabaseSetupClient = class {
|
|
|
153
142
|
api;
|
|
154
143
|
projectRef;
|
|
155
144
|
projectBaseUrl;
|
|
145
|
+
accessToken;
|
|
156
146
|
constructor(options) {
|
|
157
147
|
this.api = new import_supabase_management_js.SupabaseManagementAPI({
|
|
158
148
|
accessToken: options.accessToken,
|
|
@@ -160,6 +150,7 @@ var SupabaseSetupClient = class {
|
|
|
160
150
|
});
|
|
161
151
|
this.projectRef = options.projectRef;
|
|
162
152
|
this.projectBaseUrl = options.projectBaseUrl || process.env.SUPABASE_BASE_URL || "supabase.co";
|
|
153
|
+
this.accessToken = options.accessToken;
|
|
163
154
|
}
|
|
164
155
|
/**
|
|
165
156
|
* Validate that the project exists and we have access
|
|
@@ -179,20 +170,20 @@ var SupabaseSetupClient = class {
|
|
|
179
170
|
/**
|
|
180
171
|
* Deploy an Edge Function
|
|
181
172
|
*/
|
|
182
|
-
async deployFunction(name, code) {
|
|
173
|
+
async deployFunction(name, code, verifyJwt = false) {
|
|
183
174
|
const functions = await this.api.listFunctions(this.projectRef);
|
|
184
175
|
const exists = functions?.some((f) => f.slug === name);
|
|
185
176
|
if (exists) {
|
|
186
177
|
await this.api.updateFunction(this.projectRef, name, {
|
|
187
178
|
body: code,
|
|
188
|
-
verify_jwt:
|
|
179
|
+
verify_jwt: verifyJwt
|
|
189
180
|
});
|
|
190
181
|
} else {
|
|
191
182
|
await this.api.createFunction(this.projectRef, {
|
|
192
183
|
slug: name,
|
|
193
184
|
name,
|
|
194
185
|
body: code,
|
|
195
|
-
verify_jwt:
|
|
186
|
+
verify_jwt: verifyJwt
|
|
196
187
|
});
|
|
197
188
|
}
|
|
198
189
|
}
|
|
@@ -422,77 +413,30 @@ var SupabaseSetupClient = class {
|
|
|
422
413
|
}
|
|
423
414
|
/**
|
|
424
415
|
* Uninstall stripe-sync from a Supabase project
|
|
425
|
-
*
|
|
416
|
+
* Invokes the stripe-setup edge function's DELETE endpoint which handles cleanup
|
|
426
417
|
*/
|
|
427
|
-
async uninstall(
|
|
428
|
-
const stripe = stripeSecretKey ? new import_stripe.default(stripeSecretKey, { apiVersion: "2025-02-24.acacia" }) : null;
|
|
418
|
+
async uninstall() {
|
|
429
419
|
try {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
await this.deleteFunction("stripe-webhook");
|
|
447
|
-
await this.deleteFunction("stripe-worker");
|
|
448
|
-
await this.deleteSecret("STRIPE_SECRET_KEY");
|
|
449
|
-
try {
|
|
450
|
-
await this.runSQL(`
|
|
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 $$;
|
|
457
|
-
`);
|
|
458
|
-
} catch (err) {
|
|
459
|
-
console.warn("Could not unschedule pg_cron job:", err);
|
|
460
|
-
}
|
|
461
|
-
try {
|
|
462
|
-
await this.runSQL(`
|
|
463
|
-
DELETE FROM vault.secrets
|
|
464
|
-
WHERE name = 'stripe_sync_service_role_key'
|
|
465
|
-
`);
|
|
466
|
-
} catch (err) {
|
|
467
|
-
console.warn("Could not delete vault secret:", err);
|
|
468
|
-
}
|
|
469
|
-
try {
|
|
470
|
-
await this.runSQL(`
|
|
471
|
-
SELECT pg_terminate_backend(pid)
|
|
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()
|
|
477
|
-
`);
|
|
478
|
-
} catch (err) {
|
|
479
|
-
console.warn("Could not terminate connections:", err);
|
|
420
|
+
const serviceRoleKey = await this.getServiceRoleKey();
|
|
421
|
+
const url = `https://${this.projectRef}.${this.projectBaseUrl}/functions/v1/stripe-setup`;
|
|
422
|
+
const response = await fetch(url, {
|
|
423
|
+
method: "DELETE",
|
|
424
|
+
headers: {
|
|
425
|
+
Authorization: `Bearer ${serviceRoleKey}`,
|
|
426
|
+
"Content-Type": "application/json"
|
|
427
|
+
},
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
supabase_access_token: this.accessToken,
|
|
430
|
+
supabase_project_ref: this.projectRef
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
if (!response.ok) {
|
|
434
|
+
const text = await response.text();
|
|
435
|
+
throw new Error(`Uninstall failed: ${response.status} ${text}`);
|
|
480
436
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
}
|
|
437
|
+
const result = await response.json();
|
|
438
|
+
if (result.success === false) {
|
|
439
|
+
throw new Error(`Uninstall failed: ${result.error}`);
|
|
496
440
|
}
|
|
497
441
|
} catch (error) {
|
|
498
442
|
throw new Error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -525,9 +469,9 @@ var SupabaseSetupClient = class {
|
|
|
525
469
|
const versionedSetup = this.injectPackageVersion(setupFunctionCode, version);
|
|
526
470
|
const versionedWebhook = this.injectPackageVersion(webhookFunctionCode, version);
|
|
527
471
|
const versionedWorker = this.injectPackageVersion(workerFunctionCode, version);
|
|
528
|
-
await this.deployFunction("stripe-setup", versionedSetup);
|
|
529
|
-
await this.deployFunction("stripe-webhook", versionedWebhook);
|
|
530
|
-
await this.deployFunction("stripe-worker", versionedWorker);
|
|
472
|
+
await this.deployFunction("stripe-setup", versionedSetup, true);
|
|
473
|
+
await this.deployFunction("stripe-webhook", versionedWebhook, false);
|
|
474
|
+
await this.deployFunction("stripe-worker", versionedWorker, true);
|
|
531
475
|
await this.setSecrets([{ name: "STRIPE_SECRET_KEY", value: trimmedStripeKey }]);
|
|
532
476
|
const serviceRoleKey = await this.getServiceRoleKey();
|
|
533
477
|
const setupResult = await this.invokeFunction("stripe-setup", serviceRoleKey);
|
|
@@ -563,18 +507,14 @@ async function install(params) {
|
|
|
563
507
|
await client.install(stripeKey, packageVersion, workerIntervalSeconds);
|
|
564
508
|
}
|
|
565
509
|
async function uninstall(params) {
|
|
566
|
-
const { supabaseAccessToken, supabaseProjectRef
|
|
567
|
-
const trimmedStripeKey = stripeKey && stripeKey.trim();
|
|
568
|
-
if (trimmedStripeKey && !trimmedStripeKey.startsWith("sk_") && !trimmedStripeKey.startsWith("rk_")) {
|
|
569
|
-
throw new Error('Stripe key should start with "sk_" or "rk_"');
|
|
570
|
-
}
|
|
510
|
+
const { supabaseAccessToken, supabaseProjectRef } = params;
|
|
571
511
|
const client = new SupabaseSetupClient({
|
|
572
512
|
accessToken: supabaseAccessToken,
|
|
573
513
|
projectRef: supabaseProjectRef,
|
|
574
514
|
projectBaseUrl: params.baseProjectUrl,
|
|
575
|
-
managementApiBaseUrl: params.
|
|
515
|
+
managementApiBaseUrl: params.baseManagementApiBaseUrl
|
|
576
516
|
});
|
|
577
|
-
await client.uninstall(
|
|
517
|
+
await client.uninstall();
|
|
578
518
|
}
|
|
579
519
|
// Annotate the CommonJS export names for ESM import in node:
|
|
580
520
|
0 && (module.exports = {
|