stripe-experiment-sync 1.0.15 → 1.0.16

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/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.15",
49
+ version: "1.0.16",
50
50
  private: false,
51
51
  description: "Stripe Sync Engine to sync Stripe data to Postgres",
52
52
  type: "module",
@@ -108,11 +108,11 @@ var package_default = {
108
108
  },
109
109
  repository: {
110
110
  type: "git",
111
- url: "https://github.com/tx-stripe/stripe-sync-engine.git"
111
+ url: "https://github.com/stripe-experiments/sync-engine.git"
112
112
  },
113
- homepage: "https://github.com/tx-stripe/stripe-sync-engine#readme",
113
+ homepage: "https://github.com/stripe-experiments/sync-engine#readme",
114
114
  bugs: {
115
- url: "https://github.com/tx-stripe/stripe-sync-engine/issues"
115
+ url: "https://github.com/stripe-experiments/sync-engine/issues"
116
116
  },
117
117
  keywords: [
118
118
  "stripe",
@@ -124,7 +124,7 @@ var package_default = {
124
124
  "database",
125
125
  "typescript"
126
126
  ],
127
- author: "Supabase <https://supabase.com/>"
127
+ author: "Stripe <https://stripe.com/>"
128
128
  };
129
129
 
130
130
  // src/stripeSync.ts
@@ -1072,7 +1072,13 @@ async function fetchStripeText(url, apiKey, options) {
1072
1072
  async function runSigmaQueryAndDownloadCsv(params) {
1073
1073
  const pollTimeoutMs = params.pollTimeoutMs ?? 5 * 60 * 1e3;
1074
1074
  const pollIntervalMs = params.pollIntervalMs ?? 2e3;
1075
- const stripe = new import_stripe2.default(params.apiKey);
1075
+ const stripe = new import_stripe2.default(params.apiKey, {
1076
+ appInfo: {
1077
+ name: "Stripe Sync Engine",
1078
+ version: package_default.version,
1079
+ url: package_default.homepage
1080
+ }
1081
+ });
1076
1082
  const created = await stripe.rawRequest("POST", "/v1/sigma/query_runs", {
1077
1083
  sql: params.sql
1078
1084
  });
@@ -1301,7 +1307,9 @@ var StripeSync = class {
1301
1307
  // @ts-ignore
1302
1308
  apiVersion: config.stripeApiVersion,
1303
1309
  appInfo: {
1304
- name: "Stripe Postgres Sync"
1310
+ name: "Stripe Sync Engine",
1311
+ version: package_default.version,
1312
+ url: package_default.homepage
1305
1313
  }
1306
1314
  });
1307
1315
  this.stripe = createRetryableStripeClient(baseStripe, {}, config.logger);
@@ -2778,7 +2786,7 @@ var StripeSync = class {
2778
2786
  const customerIds = await this.postgresClient.query(prepared.text, prepared.values).then(({ rows }) => rows.map((it) => it.id));
2779
2787
  this.config.logger?.info(`Getting payment methods for ${customerIds.length} customers`);
2780
2788
  let synced = 0;
2781
- const chunkSize = this.config.maxConcurrentCustomers ?? 10;
2789
+ const chunkSize = this.config.maxConcurrentCustomers ?? 3;
2782
2790
  for (const customerIdChunk of chunkArray(customerIds, chunkSize)) {
2783
2791
  await Promise.all(
2784
2792
  customerIdChunk.map(async (customerId) => {
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  createStripeWebSocketClient,
6
6
  hashApiKey,
7
7
  runMigrations
8
- } from "./chunk-GOBVFXU7.js";
9
- import "./chunk-3KVILTN4.js";
8
+ } from "./chunk-NI6UMBIL.js";
9
+ import "./chunk-VOYYTAJF.js";
10
10
  export {
11
11
  PostgresClient,
12
12
  StripeSync,
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(supabase_exports);
38
38
  var import_supabase_management_js = require("supabase-management-js");
39
39
 
40
40
  // raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts
41
- var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\n// Helper to validate accessToken against Management API\nasync function validateAccessToken(projectRef: string, accessToken: string): Promise<boolean> {\n // Try to fetch project details using the access token\n // This validates that the token is valid for the management API\n const url = `https://api.supabase.com/v1/projects/${projectRef}`\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n })\n\n // If we can successfully get the project, the token is valid\n return response.ok\n}\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 // Extract project ref from SUPABASE_URL (format: https://{projectRef}.{base})\n const supabaseUrl = Deno.env.get('SUPABASE_URL')\n if (!supabaseUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const projectRef = new URL(supabaseUrl).hostname.split('.')[0]\n\n // Validate access token for all requests\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const accessToken = authHeader.substring(7) // Remove 'Bearer '\n const isValid = await validateAccessToken(projectRef, accessToken)\n if (!isValid) {\n return new Response('Forbidden: Invalid access token for this project', { status: 403 })\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 DELETE requests for uninstall\n if (req.method === 'DELETE') {\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 // 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_worker_secret'\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(projectRef, 'STRIPE_SECRET_KEY', accessToken)\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(projectRef, 'stripe-setup', accessToken)\n } catch (err) {\n console.warn('Could not delete stripe-setup function:', err)\n }\n\n try {\n await deleteEdgeFunction(projectRef, 'stripe-webhook', accessToken)\n } catch (err) {\n console.warn('Could not delete stripe-webhook function:', err)\n }\n\n try {\n await deleteEdgeFunction(projectRef, 'stripe-worker', accessToken)\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";
41
+ var stripe_setup_default = "import { StripeSync, runMigrations, VERSION } from 'npm:stripe-experiment-sync'\nimport postgres from 'npm:postgres'\n\n// Get management API base URL from environment variable (for testing against localhost/staging)\n// Caller should provide full URL with protocol (e.g., http://localhost:54323 or https://api.supabase.com)\nconst MGMT_API_BASE_RAW = Deno.env.get('SUPABASE_MANAGEMENT_URL') || 'https://api.supabase.com'\nconst MGMT_API_BASE = MGMT_API_BASE_RAW.match(/^https?:\\/\\//)\n ? MGMT_API_BASE_RAW\n : `https://${MGMT_API_BASE_RAW}`\n\n// Helper to validate accessToken against Management API\nasync function validateAccessToken(projectRef: string, accessToken: string): Promise<boolean> {\n // Try to fetch project details using the access token\n // This validates that the token is valid for the management API\n const url = `${MGMT_API_BASE}/v1/projects/${projectRef}`\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n })\n\n // If we can successfully get the project, the token is valid\n return response.ok\n}\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 = `${MGMT_API_BASE}/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 = `${MGMT_API_BASE}/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 // Extract project ref from SUPABASE_URL (format: https://{projectRef}.{base})\n const supabaseUrl = Deno.env.get('SUPABASE_URL')\n if (!supabaseUrl) {\n return new Response(JSON.stringify({ error: 'SUPABASE_URL not set' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const projectRef = new URL(supabaseUrl).hostname.split('.')[0]\n\n // Validate access token for all requests\n const authHeader = req.headers.get('Authorization')\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Unauthorized', { status: 401 })\n }\n\n const accessToken = authHeader.substring(7) // Remove 'Bearer '\n const isValid = await validateAccessToken(projectRef, accessToken)\n if (!isValid) {\n return new Response('Forbidden: Invalid access token for this project', { status: 403 })\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 DELETE requests for uninstall\n if (req.method === 'DELETE') {\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 // 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_worker_secret'\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(projectRef, 'STRIPE_SECRET_KEY', accessToken)\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(projectRef, 'stripe-setup', accessToken)\n } catch (err) {\n console.warn('Could not delete stripe-setup function:', err)\n }\n\n try {\n await deleteEdgeFunction(projectRef, 'stripe-webhook', accessToken)\n } catch (err) {\n console.warn('Could not delete stripe-webhook function:', err)\n }\n\n try {\n await deleteEdgeFunction(projectRef, 'stripe-worker', accessToken)\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";
42
42
 
43
43
  // raw-ts:/home/runner/work/sync-engine/sync-engine/packages/sync-engine/src/supabase/edge-functions/stripe-webhook.ts
44
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";
@@ -54,7 +54,7 @@ var workerFunctionCode = stripe_worker_default;
54
54
  // package.json
55
55
  var package_default = {
56
56
  name: "stripe-experiment-sync",
57
- version: "1.0.15",
57
+ version: "1.0.16",
58
58
  private: false,
59
59
  description: "Stripe Sync Engine to sync Stripe data to Postgres",
60
60
  type: "module",
@@ -116,11 +116,11 @@ var package_default = {
116
116
  },
117
117
  repository: {
118
118
  type: "git",
119
- url: "https://github.com/tx-stripe/stripe-sync-engine.git"
119
+ url: "https://github.com/stripe-experiments/sync-engine.git"
120
120
  },
121
- homepage: "https://github.com/tx-stripe/stripe-sync-engine#readme",
121
+ homepage: "https://github.com/stripe-experiments/sync-engine#readme",
122
122
  bugs: {
123
- url: "https://github.com/tx-stripe/stripe-sync-engine/issues"
123
+ url: "https://github.com/stripe-experiments/sync-engine/issues"
124
124
  },
125
125
  keywords: [
126
126
  "stripe",
@@ -132,7 +132,7 @@ var package_default = {
132
132
  "database",
133
133
  "typescript"
134
134
  ],
135
- author: "Supabase <https://supabase.com/>"
135
+ author: "Stripe <https://stripe.com/>"
136
136
  };
137
137
 
138
138
  // src/supabase/supabase.ts
@@ -144,14 +144,16 @@ var SupabaseSetupClient = class {
144
144
  api;
145
145
  projectRef;
146
146
  projectBaseUrl;
147
+ supabaseManagementUrl;
147
148
  accessToken;
148
149
  constructor(options) {
149
150
  this.api = new import_supabase_management_js.SupabaseManagementAPI({
150
151
  accessToken: options.accessToken,
151
- baseUrl: options.managementApiBaseUrl
152
+ baseUrl: options.supabaseManagementUrl
152
153
  });
153
154
  this.projectRef = options.projectRef;
154
155
  this.projectBaseUrl = options.projectBaseUrl || process.env.SUPABASE_BASE_URL || "supabase.co";
156
+ this.supabaseManagementUrl = options.supabaseManagementUrl;
155
157
  this.accessToken = options.accessToken;
156
158
  }
157
159
  /**
@@ -458,7 +460,11 @@ var SupabaseSetupClient = class {
458
460
  await this.deployFunction("stripe-setup", versionedSetup, false);
459
461
  await this.deployFunction("stripe-webhook", versionedWebhook, false);
460
462
  await this.deployFunction("stripe-worker", versionedWorker, false);
461
- await this.setSecrets([{ name: "STRIPE_SECRET_KEY", value: trimmedStripeKey }]);
463
+ const secrets = [{ name: "STRIPE_SECRET_KEY", value: trimmedStripeKey }];
464
+ if (this.supabaseManagementUrl) {
465
+ secrets.push({ name: "SUPABASE_MANAGEMENT_URL", value: this.supabaseManagementUrl });
466
+ }
467
+ await this.setSecrets(secrets);
462
468
  const setupResult = await this.invokeFunction("stripe-setup", this.accessToken);
463
469
  if (!setupResult.success) {
464
470
  throw new Error(`Setup failed: ${setupResult.error}`);
@@ -487,7 +493,7 @@ async function install(params) {
487
493
  accessToken: supabaseAccessToken,
488
494
  projectRef: supabaseProjectRef,
489
495
  projectBaseUrl: params.baseProjectUrl,
490
- managementApiBaseUrl: params.baseManagementApiUrl
496
+ supabaseManagementUrl: params.supabaseManagementUrl
491
497
  });
492
498
  await client.install(stripeKey, packageVersion, workerIntervalSeconds);
493
499
  }
@@ -497,7 +503,7 @@ async function uninstall(params) {
497
503
  accessToken: supabaseAccessToken,
498
504
  projectRef: supabaseProjectRef,
499
505
  projectBaseUrl: params.baseProjectUrl,
500
- managementApiBaseUrl: params.baseManagementApiBaseUrl
506
+ supabaseManagementUrl: params.supabaseManagementUrl
501
507
  });
502
508
  await client.uninstall();
503
509
  }
@@ -6,7 +6,7 @@ interface DeployClientOptions {
6
6
  accessToken: string;
7
7
  projectRef: string;
8
8
  projectBaseUrl?: string;
9
- managementApiBaseUrl?: string;
9
+ supabaseManagementUrl?: string;
10
10
  }
11
11
  interface ProjectInfo {
12
12
  id: string;
@@ -17,6 +17,7 @@ declare class SupabaseSetupClient {
17
17
  private api;
18
18
  private projectRef;
19
19
  private projectBaseUrl;
20
+ private supabaseManagementUrl?;
20
21
  private accessToken;
21
22
  constructor(options: DeployClientOptions);
22
23
  /**
@@ -104,13 +105,13 @@ declare function install(params: {
104
105
  packageVersion?: string;
105
106
  workerIntervalSeconds?: number;
106
107
  baseProjectUrl?: string;
107
- baseManagementApiUrl?: string;
108
+ supabaseManagementUrl?: string;
108
109
  }): Promise<void>;
109
110
  declare function uninstall(params: {
110
111
  supabaseAccessToken: string;
111
112
  supabaseProjectRef: string;
112
113
  baseProjectUrl?: string;
113
- baseManagementApiBaseUrl?: string;
114
+ supabaseManagementUrl?: string;
114
115
  }): Promise<void>;
115
116
 
116
117
  declare const setupFunctionCode: string;
@@ -6,7 +6,7 @@ interface DeployClientOptions {
6
6
  accessToken: string;
7
7
  projectRef: string;
8
8
  projectBaseUrl?: string;
9
- managementApiBaseUrl?: string;
9
+ supabaseManagementUrl?: string;
10
10
  }
11
11
  interface ProjectInfo {
12
12
  id: string;
@@ -17,6 +17,7 @@ declare class SupabaseSetupClient {
17
17
  private api;
18
18
  private projectRef;
19
19
  private projectBaseUrl;
20
+ private supabaseManagementUrl?;
20
21
  private accessToken;
21
22
  constructor(options: DeployClientOptions);
22
23
  /**
@@ -104,13 +105,13 @@ declare function install(params: {
104
105
  packageVersion?: string;
105
106
  workerIntervalSeconds?: number;
106
107
  baseProjectUrl?: string;
107
- baseManagementApiUrl?: string;
108
+ supabaseManagementUrl?: string;
108
109
  }): Promise<void>;
109
110
  declare function uninstall(params: {
110
111
  supabaseAccessToken: string;
111
112
  supabaseProjectRef: string;
112
113
  baseProjectUrl?: string;
113
- baseManagementApiBaseUrl?: string;
114
+ supabaseManagementUrl?: string;
114
115
  }): Promise<void>;
115
116
 
116
117
  declare const setupFunctionCode: string;
@@ -9,8 +9,8 @@ import {
9
9
  uninstall,
10
10
  webhookFunctionCode,
11
11
  workerFunctionCode
12
- } from "../chunk-XIFB5ALW.js";
13
- import "../chunk-3KVILTN4.js";
12
+ } from "../chunk-2Q3SNSKG.js";
13
+ import "../chunk-VOYYTAJF.js";
14
14
  export {
15
15
  INSTALLATION_ERROR_SUFFIX,
16
16
  INSTALLATION_INSTALLED_SUFFIX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stripe-experiment-sync",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "private": false,
5
5
  "description": "Stripe Sync Engine to sync Stripe data to Postgres",
6
6
  "type": "module",
@@ -62,11 +62,11 @@
62
62
  },
63
63
  "repository": {
64
64
  "type": "git",
65
- "url": "https://github.com/tx-stripe/stripe-sync-engine.git"
65
+ "url": "https://github.com/stripe-experiments/sync-engine.git"
66
66
  },
67
- "homepage": "https://github.com/tx-stripe/stripe-sync-engine#readme",
67
+ "homepage": "https://github.com/stripe-experiments/sync-engine#readme",
68
68
  "bugs": {
69
- "url": "https://github.com/tx-stripe/stripe-sync-engine/issues"
69
+ "url": "https://github.com/stripe-experiments/sync-engine/issues"
70
70
  },
71
71
  "keywords": [
72
72
  "stripe",
@@ -78,5 +78,5 @@
78
78
  "database",
79
79
  "typescript"
80
80
  ],
81
- "author": "Supabase <https://supabase.com/>"
81
+ "author": "Stripe <https://stripe.com/>"
82
82
  }