stripe-experiment-sync 0.0.4 → 1.0.0

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.
@@ -0,0 +1,68 @@
1
+ -- Remove redundant underscore prefixes from columns in metadata tables
2
+ --
3
+ -- For tables that are already prefixed with underscore (indicating they are
4
+ -- metadata/system tables), the underscore prefix on columns is redundant.
5
+ -- This migration removes those redundant prefixes to keep naming cleaner.
6
+ --
7
+ -- Affected tables: _sync_status, _managed_webhooks
8
+
9
+ -- Create a new trigger function for metadata tables that references updated_at without underscore
10
+ CREATE OR REPLACE FUNCTION set_updated_at_metadata() RETURNS trigger
11
+ LANGUAGE plpgsql
12
+ AS $$
13
+ begin
14
+ new.updated_at = now();
15
+ return NEW;
16
+ end;
17
+ $$;
18
+
19
+ -- Update _sync_status table
20
+ -- Step 1: Drop constraints and triggers that reference the old column names
21
+ DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_sync_status";
22
+ ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS _sync_status_resource_account_key;
23
+ ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;
24
+ DROP INDEX IF EXISTS "stripe"."idx_sync_status_resource_account";
25
+
26
+ -- Step 2: Rename columns
27
+ ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_id" TO "id";
28
+ ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_last_synced_at" TO "last_synced_at";
29
+ ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_updated_at" TO "updated_at";
30
+ ALTER TABLE "stripe"."_sync_status" RENAME COLUMN "_account_id" TO "account_id";
31
+
32
+ -- Step 3: Recreate constraints and trigger with new column names
33
+ ALTER TABLE "stripe"."_sync_status"
34
+ ADD CONSTRAINT _sync_status_resource_account_key
35
+ UNIQUE (resource, "account_id");
36
+
37
+ CREATE INDEX IF NOT EXISTS idx_sync_status_resource_account
38
+ ON "stripe"."_sync_status" (resource, "account_id");
39
+
40
+ ALTER TABLE "stripe"."_sync_status"
41
+ ADD CONSTRAINT fk_sync_status_account
42
+ FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");
43
+
44
+ CREATE TRIGGER handle_updated_at
45
+ BEFORE UPDATE ON "stripe"."_sync_status"
46
+ FOR EACH ROW
47
+ EXECUTE PROCEDURE set_updated_at_metadata();
48
+
49
+ -- Update _managed_webhooks table
50
+ -- Step 1: Drop constraints and triggers that reference the old column names
51
+ DROP TRIGGER IF EXISTS handle_updated_at ON "stripe"."_managed_webhooks";
52
+ ALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;
53
+
54
+ -- Step 2: Rename columns
55
+ ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_id" TO "id";
56
+ ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_last_synced_at" TO "last_synced_at";
57
+ ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_updated_at" TO "updated_at";
58
+ ALTER TABLE "stripe"."_managed_webhooks" RENAME COLUMN "_account_id" TO "account_id";
59
+
60
+ -- Step 3: Recreate foreign key constraint and trigger with new column names
61
+ ALTER TABLE "stripe"."_managed_webhooks"
62
+ ADD CONSTRAINT fk_managed_webhooks_account
63
+ FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" ("_id");
64
+
65
+ CREATE TRIGGER handle_updated_at
66
+ BEFORE UPDATE ON "stripe"."_managed_webhooks"
67
+ FOR EACH ROW
68
+ EXECUTE PROCEDURE set_updated_at_metadata();
@@ -0,0 +1,239 @@
1
+ -- Rename _id back to id to match Stripe API field names
2
+ --
3
+ -- Migration 0048 added underscore prefixes to all "reserved" columns including id.
4
+ -- However, id is actually a field that comes directly from the Stripe API and should
5
+ -- match the API naming for agent/user comprehension.
6
+ --
7
+ -- Additionally, this migration converts id from a regular column to a GENERATED column
8
+ -- derived from _raw_data->>'id', ensuring the raw_data is the single source of truth.
9
+
10
+ -- ============================================================================
11
+ -- Step 1: Drop all foreign key constraints referencing accounts._id
12
+ -- ============================================================================
13
+
14
+ ALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS fk_active_entitlements_account;
15
+ ALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS fk_charges_account;
16
+ ALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS fk_checkout_session_line_items_account;
17
+ ALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS fk_checkout_sessions_account;
18
+ ALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS fk_credit_notes_account;
19
+ ALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS fk_customers_account;
20
+ ALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS fk_disputes_account;
21
+ ALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS fk_early_fraud_warnings_account;
22
+ ALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS fk_features_account;
23
+ ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS fk_invoices_account;
24
+ ALTER TABLE "stripe"."_managed_webhooks" DROP CONSTRAINT IF EXISTS fk_managed_webhooks_account;
25
+ ALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS fk_payment_intents_account;
26
+ ALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS fk_payment_methods_account;
27
+ ALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS fk_plans_account;
28
+ ALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS fk_prices_account;
29
+ ALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS fk_products_account;
30
+ ALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS fk_refunds_account;
31
+ ALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS fk_reviews_account;
32
+ ALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS fk_setup_intents_account;
33
+ ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS fk_subscription_items_account;
34
+ ALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS fk_subscription_schedules_account;
35
+ ALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS fk_subscriptions_account;
36
+ ALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS fk_tax_ids_account;
37
+ ALTER TABLE "stripe"."_sync_status" DROP CONSTRAINT IF EXISTS fk_sync_status_account;
38
+
39
+ -- ============================================================================
40
+ -- Step 2: Convert accounts._id to generated column accounts.id
41
+ -- ============================================================================
42
+
43
+ ALTER TABLE "stripe"."accounts" DROP CONSTRAINT IF EXISTS accounts_pkey;
44
+ ALTER TABLE "stripe"."accounts" DROP COLUMN IF EXISTS "_id";
45
+ ALTER TABLE "stripe"."accounts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
46
+ ALTER TABLE "stripe"."accounts" ADD PRIMARY KEY (id);
47
+
48
+ -- ============================================================================
49
+ -- Step 3: Convert _id to generated column id for all Stripe entity tables
50
+ -- ============================================================================
51
+
52
+ -- active_entitlements
53
+ ALTER TABLE "stripe"."active_entitlements" DROP CONSTRAINT IF EXISTS active_entitlements_pkey;
54
+ ALTER TABLE "stripe"."active_entitlements" DROP COLUMN IF EXISTS "_id";
55
+ ALTER TABLE "stripe"."active_entitlements" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
56
+ ALTER TABLE "stripe"."active_entitlements" ADD PRIMARY KEY (id);
57
+
58
+ -- charges
59
+ ALTER TABLE "stripe"."charges" DROP CONSTRAINT IF EXISTS charges_pkey;
60
+ ALTER TABLE "stripe"."charges" DROP COLUMN IF EXISTS "_id";
61
+ ALTER TABLE "stripe"."charges" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
62
+ ALTER TABLE "stripe"."charges" ADD PRIMARY KEY (id);
63
+
64
+ -- checkout_session_line_items
65
+ ALTER TABLE "stripe"."checkout_session_line_items" DROP CONSTRAINT IF EXISTS checkout_session_line_items_pkey;
66
+ ALTER TABLE "stripe"."checkout_session_line_items" DROP COLUMN IF EXISTS "_id";
67
+ ALTER TABLE "stripe"."checkout_session_line_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
68
+ ALTER TABLE "stripe"."checkout_session_line_items" ADD PRIMARY KEY (id);
69
+
70
+ -- checkout_sessions
71
+ ALTER TABLE "stripe"."checkout_sessions" DROP CONSTRAINT IF EXISTS checkout_sessions_pkey;
72
+ ALTER TABLE "stripe"."checkout_sessions" DROP COLUMN IF EXISTS "_id";
73
+ ALTER TABLE "stripe"."checkout_sessions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
74
+ ALTER TABLE "stripe"."checkout_sessions" ADD PRIMARY KEY (id);
75
+
76
+ -- credit_notes
77
+ ALTER TABLE "stripe"."credit_notes" DROP CONSTRAINT IF EXISTS credit_notes_pkey;
78
+ ALTER TABLE "stripe"."credit_notes" DROP COLUMN IF EXISTS "_id";
79
+ ALTER TABLE "stripe"."credit_notes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
80
+ ALTER TABLE "stripe"."credit_notes" ADD PRIMARY KEY (id);
81
+
82
+ -- coupons
83
+ ALTER TABLE "stripe"."coupons" DROP CONSTRAINT IF EXISTS coupons_pkey;
84
+ ALTER TABLE "stripe"."coupons" DROP COLUMN IF EXISTS "_id";
85
+ ALTER TABLE "stripe"."coupons" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
86
+ ALTER TABLE "stripe"."coupons" ADD PRIMARY KEY (id);
87
+
88
+ -- customers
89
+ ALTER TABLE "stripe"."customers" DROP CONSTRAINT IF EXISTS customers_pkey;
90
+ ALTER TABLE "stripe"."customers" DROP COLUMN IF EXISTS "_id";
91
+ ALTER TABLE "stripe"."customers" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
92
+ ALTER TABLE "stripe"."customers" ADD PRIMARY KEY (id);
93
+
94
+ -- disputes
95
+ ALTER TABLE "stripe"."disputes" DROP CONSTRAINT IF EXISTS disputes_pkey;
96
+ ALTER TABLE "stripe"."disputes" DROP COLUMN IF EXISTS "_id";
97
+ ALTER TABLE "stripe"."disputes" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
98
+ ALTER TABLE "stripe"."disputes" ADD PRIMARY KEY (id);
99
+
100
+ -- early_fraud_warnings
101
+ ALTER TABLE "stripe"."early_fraud_warnings" DROP CONSTRAINT IF EXISTS early_fraud_warnings_pkey;
102
+ ALTER TABLE "stripe"."early_fraud_warnings" DROP COLUMN IF EXISTS "_id";
103
+ ALTER TABLE "stripe"."early_fraud_warnings" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
104
+ ALTER TABLE "stripe"."early_fraud_warnings" ADD PRIMARY KEY (id);
105
+
106
+ -- events
107
+ ALTER TABLE "stripe"."events" DROP CONSTRAINT IF EXISTS events_pkey;
108
+ ALTER TABLE "stripe"."events" DROP COLUMN IF EXISTS "_id";
109
+ ALTER TABLE "stripe"."events" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
110
+ ALTER TABLE "stripe"."events" ADD PRIMARY KEY (id);
111
+
112
+ -- features
113
+ ALTER TABLE "stripe"."features" DROP CONSTRAINT IF EXISTS features_pkey;
114
+ ALTER TABLE "stripe"."features" DROP COLUMN IF EXISTS "_id";
115
+ ALTER TABLE "stripe"."features" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
116
+ ALTER TABLE "stripe"."features" ADD PRIMARY KEY (id);
117
+
118
+ -- invoices
119
+ ALTER TABLE "stripe"."invoices" DROP CONSTRAINT IF EXISTS invoices_pkey;
120
+ ALTER TABLE "stripe"."invoices" DROP COLUMN IF EXISTS "_id";
121
+ ALTER TABLE "stripe"."invoices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
122
+ ALTER TABLE "stripe"."invoices" ADD PRIMARY KEY (id);
123
+
124
+ -- payment_intents
125
+ ALTER TABLE "stripe"."payment_intents" DROP CONSTRAINT IF EXISTS payment_intents_pkey;
126
+ ALTER TABLE "stripe"."payment_intents" DROP COLUMN IF EXISTS "_id";
127
+ ALTER TABLE "stripe"."payment_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
128
+ ALTER TABLE "stripe"."payment_intents" ADD PRIMARY KEY (id);
129
+
130
+ -- payment_methods
131
+ ALTER TABLE "stripe"."payment_methods" DROP CONSTRAINT IF EXISTS payment_methods_pkey;
132
+ ALTER TABLE "stripe"."payment_methods" DROP COLUMN IF EXISTS "_id";
133
+ ALTER TABLE "stripe"."payment_methods" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
134
+ ALTER TABLE "stripe"."payment_methods" ADD PRIMARY KEY (id);
135
+
136
+ -- payouts
137
+ ALTER TABLE "stripe"."payouts" DROP CONSTRAINT IF EXISTS payouts_pkey;
138
+ ALTER TABLE "stripe"."payouts" DROP COLUMN IF EXISTS "_id";
139
+ ALTER TABLE "stripe"."payouts" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
140
+ ALTER TABLE "stripe"."payouts" ADD PRIMARY KEY (id);
141
+
142
+ -- plans
143
+ ALTER TABLE "stripe"."plans" DROP CONSTRAINT IF EXISTS plans_pkey;
144
+ ALTER TABLE "stripe"."plans" DROP COLUMN IF EXISTS "_id";
145
+ ALTER TABLE "stripe"."plans" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
146
+ ALTER TABLE "stripe"."plans" ADD PRIMARY KEY (id);
147
+
148
+ -- prices
149
+ ALTER TABLE "stripe"."prices" DROP CONSTRAINT IF EXISTS prices_pkey;
150
+ ALTER TABLE "stripe"."prices" DROP COLUMN IF EXISTS "_id";
151
+ ALTER TABLE "stripe"."prices" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
152
+ ALTER TABLE "stripe"."prices" ADD PRIMARY KEY (id);
153
+
154
+ -- products
155
+ ALTER TABLE "stripe"."products" DROP CONSTRAINT IF EXISTS products_pkey;
156
+ ALTER TABLE "stripe"."products" DROP COLUMN IF EXISTS "_id";
157
+ ALTER TABLE "stripe"."products" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
158
+ ALTER TABLE "stripe"."products" ADD PRIMARY KEY (id);
159
+
160
+ -- refunds
161
+ ALTER TABLE "stripe"."refunds" DROP CONSTRAINT IF EXISTS refunds_pkey;
162
+ ALTER TABLE "stripe"."refunds" DROP COLUMN IF EXISTS "_id";
163
+ ALTER TABLE "stripe"."refunds" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
164
+ ALTER TABLE "stripe"."refunds" ADD PRIMARY KEY (id);
165
+
166
+ -- reviews
167
+ ALTER TABLE "stripe"."reviews" DROP CONSTRAINT IF EXISTS reviews_pkey;
168
+ ALTER TABLE "stripe"."reviews" DROP COLUMN IF EXISTS "_id";
169
+ ALTER TABLE "stripe"."reviews" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
170
+ ALTER TABLE "stripe"."reviews" ADD PRIMARY KEY (id);
171
+
172
+ -- setup_intents
173
+ ALTER TABLE "stripe"."setup_intents" DROP CONSTRAINT IF EXISTS setup_intents_pkey;
174
+ ALTER TABLE "stripe"."setup_intents" DROP COLUMN IF EXISTS "_id";
175
+ ALTER TABLE "stripe"."setup_intents" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
176
+ ALTER TABLE "stripe"."setup_intents" ADD PRIMARY KEY (id);
177
+
178
+ -- subscription_items
179
+ ALTER TABLE "stripe"."subscription_items" DROP CONSTRAINT IF EXISTS subscription_items_pkey;
180
+ ALTER TABLE "stripe"."subscription_items" DROP COLUMN IF EXISTS "_id";
181
+ ALTER TABLE "stripe"."subscription_items" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
182
+ ALTER TABLE "stripe"."subscription_items" ADD PRIMARY KEY (id);
183
+
184
+ -- subscription_schedules
185
+ ALTER TABLE "stripe"."subscription_schedules" DROP CONSTRAINT IF EXISTS subscription_schedules_pkey;
186
+ ALTER TABLE "stripe"."subscription_schedules" DROP COLUMN IF EXISTS "_id";
187
+ ALTER TABLE "stripe"."subscription_schedules" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
188
+ ALTER TABLE "stripe"."subscription_schedules" ADD PRIMARY KEY (id);
189
+
190
+ -- subscriptions
191
+ ALTER TABLE "stripe"."subscriptions" DROP CONSTRAINT IF EXISTS subscriptions_pkey;
192
+ ALTER TABLE "stripe"."subscriptions" DROP COLUMN IF EXISTS "_id";
193
+ ALTER TABLE "stripe"."subscriptions" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
194
+ ALTER TABLE "stripe"."subscriptions" ADD PRIMARY KEY (id);
195
+
196
+ -- tax_ids
197
+ ALTER TABLE "stripe"."tax_ids" DROP CONSTRAINT IF EXISTS tax_ids_pkey;
198
+ ALTER TABLE "stripe"."tax_ids" DROP COLUMN IF EXISTS "_id";
199
+ ALTER TABLE "stripe"."tax_ids" ADD COLUMN "id" TEXT GENERATED ALWAYS AS ((_raw_data->>'id')::TEXT) STORED;
200
+ ALTER TABLE "stripe"."tax_ids" ADD PRIMARY KEY (id);
201
+
202
+ -- ============================================================================
203
+ -- Step 4: Handle metadata tables
204
+ -- ============================================================================
205
+
206
+ -- _managed_webhooks (internal metadata table, doesn't use _raw_data pattern)
207
+ -- Already uses "id" without underscore (migration 0049), no changes needed
208
+
209
+ -- _sync_status (internal table, uses auto-incrementing id not from Stripe)
210
+ -- Already uses "id" without underscore (migration 0049), no changes needed
211
+
212
+ -- ============================================================================
213
+ -- Step 5: Recreate all foreign key constraints
214
+ -- ============================================================================
215
+
216
+ ALTER TABLE "stripe"."active_entitlements" ADD CONSTRAINT fk_active_entitlements_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
217
+ ALTER TABLE "stripe"."charges" ADD CONSTRAINT fk_charges_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
218
+ ALTER TABLE "stripe"."checkout_session_line_items" ADD CONSTRAINT fk_checkout_session_line_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
219
+ ALTER TABLE "stripe"."checkout_sessions" ADD CONSTRAINT fk_checkout_sessions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
220
+ ALTER TABLE "stripe"."credit_notes" ADD CONSTRAINT fk_credit_notes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
221
+ ALTER TABLE "stripe"."customers" ADD CONSTRAINT fk_customers_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
222
+ ALTER TABLE "stripe"."disputes" ADD CONSTRAINT fk_disputes_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
223
+ ALTER TABLE "stripe"."early_fraud_warnings" ADD CONSTRAINT fk_early_fraud_warnings_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
224
+ ALTER TABLE "stripe"."features" ADD CONSTRAINT fk_features_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
225
+ ALTER TABLE "stripe"."invoices" ADD CONSTRAINT fk_invoices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
226
+ ALTER TABLE "stripe"."_managed_webhooks" ADD CONSTRAINT fk_managed_webhooks_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);
227
+ ALTER TABLE "stripe"."payment_intents" ADD CONSTRAINT fk_payment_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
228
+ ALTER TABLE "stripe"."payment_methods" ADD CONSTRAINT fk_payment_methods_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
229
+ ALTER TABLE "stripe"."plans" ADD CONSTRAINT fk_plans_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
230
+ ALTER TABLE "stripe"."prices" ADD CONSTRAINT fk_prices_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
231
+ ALTER TABLE "stripe"."products" ADD CONSTRAINT fk_products_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
232
+ ALTER TABLE "stripe"."refunds" ADD CONSTRAINT fk_refunds_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
233
+ ALTER TABLE "stripe"."reviews" ADD CONSTRAINT fk_reviews_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
234
+ ALTER TABLE "stripe"."setup_intents" ADD CONSTRAINT fk_setup_intents_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
235
+ ALTER TABLE "stripe"."subscription_items" ADD CONSTRAINT fk_subscription_items_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
236
+ ALTER TABLE "stripe"."subscription_schedules" ADD CONSTRAINT fk_subscription_schedules_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
237
+ ALTER TABLE "stripe"."subscriptions" ADD CONSTRAINT fk_subscriptions_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
238
+ ALTER TABLE "stripe"."tax_ids" ADD CONSTRAINT fk_tax_ids_account FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id);
239
+ ALTER TABLE "stripe"."_sync_status" ADD CONSTRAINT fk_sync_status_account FOREIGN KEY ("account_id") REFERENCES "stripe"."accounts" (id);
@@ -0,0 +1,7 @@
1
+ -- Remove UUID from managed webhooks
2
+ -- UUID-based routing is no longer used; webhooks are identified by exact URL match
3
+ -- Legacy webhooks with UUID in URL will be automatically deleted and recreated
4
+
5
+ drop index if exists "stripe"."stripe_managed_webhooks_uuid_idx";
6
+
7
+ alter table "stripe"."_managed_webhooks" drop column if exists "uuid";
@@ -0,0 +1,7 @@
1
+ -- Add unique constraint on URL per account to prevent duplicate webhooks at database level
2
+ -- This prevents race conditions where multiple instances try to create webhooks for the same URL
3
+ -- Since UUIDs have been removed from URLs, we can enforce strict uniqueness on the URL column per account
4
+ -- Note: Different accounts can have webhooks with the same URL
5
+
6
+ alter table "stripe"."_managed_webhooks"
7
+ add constraint managed_webhooks_url_account_unique unique ("url", "account_id");
@@ -0,0 +1,104 @@
1
+ -- Observable Sync System: Track sync runs and individual object syncs
2
+ -- Enables observability for long-running syncs (days, not minutes)
3
+ --
4
+ -- Two-level hierarchy:
5
+ -- _sync_run: Parent sync operation (one active per account)
6
+ -- _sync_obj_run: Individual object syncs within a run
7
+ --
8
+ -- Features:
9
+ -- - Only one active run per account (EXCLUDE constraint)
10
+ -- - Configurable object concurrency (max_concurrent)
11
+ -- - Stale detection (is_stale in dashboard view)
12
+ -- - Progress tracking per object
13
+
14
+ -- Step 1: Create _sync_run table (parent sync operation)
15
+ CREATE TABLE IF NOT EXISTS "stripe"."_sync_run" (
16
+ "_account_id" TEXT NOT NULL,
17
+ started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
18
+ status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'complete', 'error')),
19
+ max_concurrent INTEGER NOT NULL DEFAULT 3,
20
+ completed_at TIMESTAMPTZ,
21
+ error_message TEXT,
22
+ triggered_by TEXT,
23
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
24
+
25
+ PRIMARY KEY ("_account_id", started_at),
26
+
27
+ -- Only one active run per account
28
+ CONSTRAINT one_active_run_per_account
29
+ EXCLUDE ("_account_id" WITH =) WHERE (status = 'running'),
30
+
31
+ -- Foreign key to accounts table
32
+ CONSTRAINT fk_sync_run_account
33
+ FOREIGN KEY ("_account_id") REFERENCES "stripe"."accounts" (id)
34
+ );
35
+
36
+ -- Step 2: Add updated_at trigger for _sync_run
37
+ -- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)
38
+ CREATE TRIGGER handle_updated_at
39
+ BEFORE UPDATE ON "stripe"."_sync_run"
40
+ FOR EACH ROW
41
+ EXECUTE PROCEDURE set_updated_at_metadata();
42
+
43
+ -- Step 3: Create _sync_obj_run table (individual object syncs)
44
+ CREATE TABLE IF NOT EXISTS "stripe"."_sync_obj_run" (
45
+ "_account_id" TEXT NOT NULL,
46
+ run_started_at TIMESTAMPTZ NOT NULL,
47
+ object TEXT NOT NULL,
48
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'complete', 'error')),
49
+ started_at TIMESTAMPTZ,
50
+ completed_at TIMESTAMPTZ,
51
+ processed_count INTEGER DEFAULT 0,
52
+ cursor TEXT,
53
+ error_message TEXT,
54
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
55
+
56
+ PRIMARY KEY ("_account_id", run_started_at, object),
57
+
58
+ -- Foreign key to parent sync run
59
+ CONSTRAINT fk_sync_obj_run_parent
60
+ FOREIGN KEY ("_account_id", run_started_at) REFERENCES "stripe"."_sync_run" ("_account_id", started_at)
61
+ );
62
+
63
+ -- Step 4: Add updated_at trigger for _sync_obj_run
64
+ -- Use set_updated_at_metadata() since this is a metadata table with updated_at (not _updated_at)
65
+ CREATE TRIGGER handle_updated_at
66
+ BEFORE UPDATE ON "stripe"."_sync_obj_run"
67
+ FOR EACH ROW
68
+ EXECUTE PROCEDURE set_updated_at_metadata();
69
+
70
+ -- Step 5: Create indexes for efficient queries
71
+ CREATE INDEX IF NOT EXISTS idx_sync_run_account_status
72
+ ON "stripe"."_sync_run" ("_account_id", status);
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_sync_obj_run_status
75
+ ON "stripe"."_sync_obj_run" ("_account_id", run_started_at, status);
76
+
77
+ -- Step 6: Create sync_dashboard view for observability
78
+ CREATE OR REPLACE VIEW "stripe"."sync_dashboard" AS
79
+ SELECT
80
+ r."_account_id" as account_id,
81
+ r.started_at as run_started_at,
82
+ r.status as run_status,
83
+ r.completed_at as run_completed_at,
84
+ r.max_concurrent,
85
+ r.triggered_by,
86
+ o.object,
87
+ o.status as object_status,
88
+ o.started_at as object_started_at,
89
+ o.completed_at as object_completed_at,
90
+ o.processed_count,
91
+ o.error_message,
92
+ o.updated_at,
93
+ -- Duration in seconds
94
+ EXTRACT(EPOCH FROM (COALESCE(o.completed_at, now()) - o.started_at))::integer as duration_seconds,
95
+ -- Stale detection: running but no update in 5 min
96
+ CASE
97
+ WHEN o.status = 'running' AND o.updated_at < now() - interval '5 minutes'
98
+ THEN true
99
+ ELSE false
100
+ END as is_stale
101
+ FROM "stripe"."_sync_run" r
102
+ LEFT JOIN "stripe"."_sync_obj_run" o
103
+ ON o."_account_id" = r."_account_id"
104
+ AND o.run_started_at = r.started_at;
@@ -0,0 +1,5 @@
1
+ -- Drop the old _sync_status table
2
+ -- This table has been replaced by _sync_run and _sync_obj_run for better observability
3
+ -- See migration 0053_sync_observability.sql
4
+
5
+ DROP TABLE IF EXISTS "stripe"."_sync_status";
package/dist/pg.cjs ADDED
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
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
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/pg.ts
31
+ var pg_exports = {};
32
+ __export(pg_exports, {
33
+ PgAdapter: () => PgAdapter
34
+ });
35
+ module.exports = __toCommonJS(pg_exports);
36
+
37
+ // src/database/pg-adapter.ts
38
+ var import_pg = __toESM(require("pg"), 1);
39
+ var PgAdapter = class {
40
+ pool;
41
+ constructor(config) {
42
+ this.pool = new import_pg.default.Pool(config);
43
+ }
44
+ async query(sql, params) {
45
+ const result = await this.pool.query(sql, params);
46
+ return {
47
+ rows: result.rows,
48
+ rowCount: result.rowCount ?? 0
49
+ };
50
+ }
51
+ async end() {
52
+ await this.pool.end();
53
+ }
54
+ /**
55
+ * Execute a function while holding a PostgreSQL advisory lock.
56
+ * Uses a dedicated connection to ensure lock is held for the duration.
57
+ */
58
+ async withAdvisoryLock(lockId, fn) {
59
+ const client = await this.pool.connect();
60
+ try {
61
+ await client.query("SELECT pg_advisory_lock($1)", [lockId]);
62
+ return await fn();
63
+ } finally {
64
+ try {
65
+ await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
66
+ } finally {
67
+ client.release();
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Returns a pg-compatible client for use with libraries that expect pg.Client.
73
+ * Used by pg-node-migrations to run database migrations.
74
+ */
75
+ toPgClient() {
76
+ return {
77
+ query: async (sql) => {
78
+ const result = typeof sql === "string" ? await this.pool.query(sql) : await this.pool.query(sql.text, sql.values);
79
+ return { rows: result.rows, rowCount: result.rowCount ?? 0 };
80
+ }
81
+ };
82
+ }
83
+ };
84
+ // Annotate the CommonJS export names for ESM import in node:
85
+ 0 && (module.exports = {
86
+ PgAdapter
87
+ });
package/dist/pg.d.cts ADDED
@@ -0,0 +1,28 @@
1
+ import { PoolConfig } from 'pg';
2
+ import { D as DatabaseAdapter, P as PgCompatibleClient } from './adapter-BtXT5w9r.cjs';
3
+
4
+ /**
5
+ * Database adapter implementation using node-postgres (pg).
6
+ * This is the default adapter for Node.js environments.
7
+ */
8
+ declare class PgAdapter implements DatabaseAdapter {
9
+ private pool;
10
+ constructor(config: PoolConfig);
11
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
12
+ rows: T[];
13
+ rowCount: number;
14
+ }>;
15
+ end(): Promise<void>;
16
+ /**
17
+ * Execute a function while holding a PostgreSQL advisory lock.
18
+ * Uses a dedicated connection to ensure lock is held for the duration.
19
+ */
20
+ withAdvisoryLock<T>(lockId: number, fn: () => Promise<T>): Promise<T>;
21
+ /**
22
+ * Returns a pg-compatible client for use with libraries that expect pg.Client.
23
+ * Used by pg-node-migrations to run database migrations.
24
+ */
25
+ toPgClient(): PgCompatibleClient;
26
+ }
27
+
28
+ export { PgAdapter };
package/dist/pg.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { PoolConfig } from 'pg';
2
+ import { D as DatabaseAdapter, P as PgCompatibleClient } from './adapter-BtXT5w9r.js';
3
+
4
+ /**
5
+ * Database adapter implementation using node-postgres (pg).
6
+ * This is the default adapter for Node.js environments.
7
+ */
8
+ declare class PgAdapter implements DatabaseAdapter {
9
+ private pool;
10
+ constructor(config: PoolConfig);
11
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
12
+ rows: T[];
13
+ rowCount: number;
14
+ }>;
15
+ end(): Promise<void>;
16
+ /**
17
+ * Execute a function while holding a PostgreSQL advisory lock.
18
+ * Uses a dedicated connection to ensure lock is held for the duration.
19
+ */
20
+ withAdvisoryLock<T>(lockId: number, fn: () => Promise<T>): Promise<T>;
21
+ /**
22
+ * Returns a pg-compatible client for use with libraries that expect pg.Client.
23
+ * Used by pg-node-migrations to run database migrations.
24
+ */
25
+ toPgClient(): PgCompatibleClient;
26
+ }
27
+
28
+ export { PgAdapter };
package/dist/pg.js ADDED
@@ -0,0 +1,50 @@
1
+ // src/database/pg-adapter.ts
2
+ import pg from "pg";
3
+ var PgAdapter = class {
4
+ pool;
5
+ constructor(config) {
6
+ this.pool = new pg.Pool(config);
7
+ }
8
+ async query(sql, params) {
9
+ const result = await this.pool.query(sql, params);
10
+ return {
11
+ rows: result.rows,
12
+ rowCount: result.rowCount ?? 0
13
+ };
14
+ }
15
+ async end() {
16
+ await this.pool.end();
17
+ }
18
+ /**
19
+ * Execute a function while holding a PostgreSQL advisory lock.
20
+ * Uses a dedicated connection to ensure lock is held for the duration.
21
+ */
22
+ async withAdvisoryLock(lockId, fn) {
23
+ const client = await this.pool.connect();
24
+ try {
25
+ await client.query("SELECT pg_advisory_lock($1)", [lockId]);
26
+ return await fn();
27
+ } finally {
28
+ try {
29
+ await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
30
+ } finally {
31
+ client.release();
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * Returns a pg-compatible client for use with libraries that expect pg.Client.
37
+ * Used by pg-node-migrations to run database migrations.
38
+ */
39
+ toPgClient() {
40
+ return {
41
+ query: async (sql) => {
42
+ const result = typeof sql === "string" ? await this.pool.query(sql) : await this.pool.query(sql.text, sql.values);
43
+ return { rows: result.rows, rowCount: result.rowCount ?? 0 };
44
+ }
45
+ };
46
+ }
47
+ };
48
+ export {
49
+ PgAdapter
50
+ };