@noodleseed/one 0.52.0 → 0.53.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.
Files changed (36) hide show
  1. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  2. package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.d.ts +8 -0
  3. package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.d.ts.map +1 -1
  4. package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.js +49 -0
  5. package/node_modules/@noodle-borg/service/dist/billing/commercial-entitlements.js.map +1 -1
  6. package/node_modules/@noodle-borg/service/dist/billing/in-memory.d.ts.map +1 -1
  7. package/node_modules/@noodle-borg/service/dist/billing/in-memory.js +3 -2
  8. package/node_modules/@noodle-borg/service/dist/billing/in-memory.js.map +1 -1
  9. package/node_modules/@noodle-borg/service/dist/billing/model.d.ts +16 -3
  10. package/node_modules/@noodle-borg/service/dist/billing/model.d.ts.map +1 -1
  11. package/node_modules/@noodle-borg/service/dist/billing/model.js +1 -0
  12. package/node_modules/@noodle-borg/service/dist/billing/model.js.map +1 -1
  13. package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.d.ts +25 -0
  14. package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.d.ts.map +1 -0
  15. package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.js +87 -0
  16. package/node_modules/@noodle-borg/service/dist/billing/paid-plan-catalog.js.map +1 -0
  17. package/node_modules/@noodle-borg/service/dist/billing/postgres-records.d.ts +4 -0
  18. package/node_modules/@noodle-borg/service/dist/billing/postgres-records.d.ts.map +1 -1
  19. package/node_modules/@noodle-borg/service/dist/billing/postgres-records.js +38 -9
  20. package/node_modules/@noodle-borg/service/dist/billing/postgres-records.js.map +1 -1
  21. package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.d.ts.map +1 -1
  22. package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.js +28 -21
  23. package/node_modules/@noodle-borg/service/dist/billing/postgres-schema.js.map +1 -1
  24. package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.d.ts +4 -0
  25. package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.d.ts.map +1 -0
  26. package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.js +319 -0
  27. package/node_modules/@noodle-borg/service/dist/billing/postgres-subscription-schema.js.map +1 -0
  28. package/node_modules/@noodle-borg/service/dist/billing/subscription-model.d.ts +40 -0
  29. package/node_modules/@noodle-borg/service/dist/billing/subscription-model.d.ts.map +1 -0
  30. package/node_modules/@noodle-borg/service/dist/billing/subscription-model.js +380 -0
  31. package/node_modules/@noodle-borg/service/dist/billing/subscription-model.js.map +1 -0
  32. package/node_modules/@noodle-borg/service/dist/billing-public.d.ts +4 -1
  33. package/node_modules/@noodle-borg/service/dist/billing-public.d.ts.map +1 -1
  34. package/node_modules/@noodle-borg/service/dist/billing-public.js +3 -0
  35. package/node_modules/@noodle-borg/service/dist/billing-public.js.map +1 -1
  36. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
- import { FREE_PLAN_VERSION } from './model.js';
1
+ import { BILLING_PUBLIC_PLAN_VERSIONS } from './paid-plan-catalog.js';
2
2
  import { ensureBillingEnforcementActivationSchema } from './postgres-enforcement-activation-schema.js';
3
3
  import { ensureBillingEnforcementProfileSchema } from './postgres-enforcement-profile-schema.js';
4
4
  import { ensureAuthoritativeMeterSchema } from './postgres-meter-schema.js';
5
5
  import { ensureBillingMeterWriterHealthSchema } from './postgres-meter-writer-health-schema.js';
6
6
  import { planVersionRowToRecord } from './postgres-records.js';
7
+ import { ensureBillingSubscriptionSchema } from './postgres-subscription-schema.js';
7
8
  /** Additive BA-1 schema. No existing organization is linked or otherwise migrated here. */
8
9
  export async function ensureBillingSchema(pool) {
9
10
  await pool.query(`
@@ -81,12 +82,16 @@ export async function ensureBillingSchema(pool) {
81
82
  billing_account_id text NOT NULL REFERENCES billing_accounts(id) ON DELETE RESTRICT,
82
83
  version integer NOT NULL CHECK (version > 0),
83
84
  plan_version_id text NOT NULL REFERENCES billing_plan_versions(id) ON DELETE RESTRICT,
84
- source_kind text NOT NULL CHECK (source_kind IN ('system-free')),
85
- generation_reason text NOT NULL CHECK (generation_reason IN ('default-account-created')),
86
- verification_status text NOT NULL CHECK (verification_status IN ('verified')),
85
+ schema_version integer NOT NULL DEFAULT 1,
86
+ source_kind text NOT NULL,
87
+ generation_reason text NOT NULL,
88
+ verification_status text NOT NULL,
87
89
  effective_from timestamptz NOT NULL,
90
+ effective_until timestamptz,
88
91
  monthly_usage_anchor timestamptz NOT NULL,
89
92
  entitlements jsonb NOT NULL,
93
+ source_subscription_id text,
94
+ source_subscription_version integer,
90
95
  content_checksum text NOT NULL,
91
96
  verified_at timestamptz NOT NULL,
92
97
  created_at timestamptz NOT NULL,
@@ -102,6 +107,7 @@ export async function ensureBillingSchema(pool) {
102
107
  REFERENCES billing_entitlement_snapshots(billing_account_id, id) ON DELETE RESTRICT
103
108
  )
104
109
  `);
110
+ await ensureBillingSubscriptionSchema(pool);
105
111
  await pool.query(`
106
112
  CREATE TABLE IF NOT EXISTS org_billing_home_accounts (
107
113
  org_slug text PRIMARY KEY REFERENCES orgs(slug) ON DELETE RESTRICT,
@@ -271,23 +277,24 @@ export async function ensureBillingSchema(pool) {
271
277
  PRIMARY KEY (migration_id, org_slug)
272
278
  )
273
279
  `);
274
- await pool.query(`INSERT INTO billing_plan_versions
275
- (id, plan_code, version, effective_from, entitlements, content_checksum, created_at)
276
- VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
277
- ON CONFLICT (id) DO NOTHING`, [
278
- FREE_PLAN_VERSION.id,
279
- FREE_PLAN_VERSION.planCode,
280
- FREE_PLAN_VERSION.version,
281
- FREE_PLAN_VERSION.effectiveFrom,
282
- JSON.stringify(FREE_PLAN_VERSION.entitlements),
283
- FREE_PLAN_VERSION.contentChecksum,
284
- FREE_PLAN_VERSION.createdAt,
285
- ]);
286
- const { rows } = await pool.query('SELECT * FROM billing_plan_versions WHERE id = $1', [FREE_PLAN_VERSION.id]);
287
- const persistedFree = rows[0] ? planVersionRowToRecord(rows[0]) : undefined;
288
- if (persistedFree === undefined ||
289
- persistedFree.contentChecksum !== FREE_PLAN_VERSION.contentChecksum) {
290
- throw new Error('persisted Free plan version does not match the immutable platform definition');
280
+ for (const plan of BILLING_PUBLIC_PLAN_VERSIONS) {
281
+ await pool.query(`INSERT INTO billing_plan_versions
282
+ (id, plan_code, version, effective_from, entitlements, content_checksum, created_at)
283
+ VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
284
+ ON CONFLICT (id) DO NOTHING`, [
285
+ plan.id,
286
+ plan.planCode,
287
+ plan.version,
288
+ plan.effectiveFrom,
289
+ JSON.stringify(plan.entitlements),
290
+ plan.contentChecksum,
291
+ plan.createdAt,
292
+ ]);
293
+ const { rows } = await pool.query('SELECT * FROM billing_plan_versions WHERE id = $1', [plan.id]);
294
+ const persisted = rows[0] ? planVersionRowToRecord(rows[0]) : undefined;
295
+ if (persisted === undefined || persisted.contentChecksum !== plan.contentChecksum) {
296
+ throw new Error(`persisted ${plan.planCode} plan version does not match the immutable platform definition`);
297
+ }
291
298
  }
292
299
  }
293
300
  //# sourceMappingURL=postgres-schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"postgres-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Pool } from 'pg';
2
+ /** Additive, dormant paid-subscription history. This schema never advances an active snapshot. */
3
+ export declare function ensureBillingSubscriptionSchema(pool: Pool): Promise<void>;
4
+ //# sourceMappingURL=postgres-subscription-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-subscription-schema.d.ts","sourceRoot":"","sources":["../../src/billing/postgres-subscription-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAE3C,kGAAkG;AAClG,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB/E"}
@@ -0,0 +1,319 @@
1
+ /** Additive, dormant paid-subscription history. This schema never advances an active snapshot. */
2
+ export async function ensureBillingSubscriptionSchema(pool) {
3
+ const client = await pool.connect();
4
+ try {
5
+ await client.query('BEGIN');
6
+ try {
7
+ // Service replicas may all start against the same database. Serialize this migration so the
8
+ // catalog checks and trigger installation below cannot race one another.
9
+ await client.query(`SELECT pg_advisory_xact_lock(hashtext('noodle.billing.subscription-schema.v1'))`);
10
+ await ensureBillingSubscriptionSchemaTx(client);
11
+ await client.query('COMMIT');
12
+ }
13
+ catch (error) {
14
+ await client.query('ROLLBACK');
15
+ throw error;
16
+ }
17
+ }
18
+ finally {
19
+ client.release();
20
+ }
21
+ }
22
+ async function ensureBillingSubscriptionSchemaTx(client) {
23
+ await client.query(`
24
+ CREATE TABLE IF NOT EXISTS billing_base_subscriptions (
25
+ id text PRIMARY KEY,
26
+ billing_account_id text NOT NULL REFERENCES billing_accounts(id) ON DELETE RESTRICT,
27
+ created_at timestamptz NOT NULL,
28
+ UNIQUE (billing_account_id, id)
29
+ )
30
+ `);
31
+ await client.query(`
32
+ CREATE TABLE IF NOT EXISTS billing_base_subscription_revisions (
33
+ schema_version integer NOT NULL CHECK (schema_version = 1),
34
+ subscription_id text NOT NULL,
35
+ billing_account_id text NOT NULL,
36
+ version integer NOT NULL CHECK (version > 0),
37
+ plan_version_id text NOT NULL
38
+ CHECK (plan_version_id IN ('pro@1', 'scale@1'))
39
+ REFERENCES billing_plan_versions(id) ON DELETE RESTRICT,
40
+ state text NOT NULL CHECK (state IN ('active', 'payment_grace', 'ended')),
41
+ billing_interval text NOT NULL CHECK (billing_interval IN ('month', 'year')),
42
+ currency text NOT NULL CHECK (currency = 'usd'),
43
+ base_amount_minor bigint NOT NULL
44
+ CHECK (base_amount_minor >= 0 AND base_amount_minor <= 9007199254740991),
45
+ current_period_start timestamptz NOT NULL,
46
+ current_period_end timestamptz NOT NULL CHECK (current_period_end > current_period_start),
47
+ effective_from timestamptz NOT NULL,
48
+ effective_until timestamptz NOT NULL CHECK (effective_until > effective_from),
49
+ renews boolean NOT NULL,
50
+ content_checksum text NOT NULL,
51
+ verified_at timestamptz NOT NULL,
52
+ created_at timestamptz NOT NULL,
53
+ PRIMARY KEY (subscription_id, version),
54
+ UNIQUE (billing_account_id, subscription_id, version),
55
+ UNIQUE (billing_account_id, subscription_id, version, plan_version_id),
56
+ CONSTRAINT billing_base_subscription_revisions_snapshot_source_key
57
+ UNIQUE (
58
+ billing_account_id,
59
+ subscription_id,
60
+ version,
61
+ plan_version_id,
62
+ effective_from,
63
+ effective_until
64
+ ),
65
+ FOREIGN KEY (billing_account_id, subscription_id)
66
+ REFERENCES billing_base_subscriptions(billing_account_id, id) ON DELETE RESTRICT,
67
+ CHECK (verified_at >= effective_from),
68
+ CHECK (created_at >= verified_at),
69
+ CHECK (state <> 'active' OR effective_until = current_period_end),
70
+ CONSTRAINT billing_base_subscription_revisions_payment_grace_check CHECK (
71
+ state <> 'payment_grace' OR
72
+ (
73
+ effective_until = current_period_end + INTERVAL '168 hours' AND
74
+ verified_at >= current_period_end
75
+ )
76
+ ),
77
+ CHECK (state <> 'ended' OR (NOT renews AND verified_at >= effective_until))
78
+ )
79
+ `);
80
+ await client.query(`
81
+ CREATE TABLE IF NOT EXISTS billing_account_current_base_subscriptions (
82
+ billing_account_id text PRIMARY KEY REFERENCES billing_accounts(id) ON DELETE RESTRICT,
83
+ subscription_id text NOT NULL,
84
+ revision_version integer NOT NULL CHECK (revision_version > 0),
85
+ FOREIGN KEY (billing_account_id, subscription_id, revision_version)
86
+ REFERENCES billing_base_subscription_revisions
87
+ (billing_account_id, subscription_id, version)
88
+ ON DELETE RESTRICT
89
+ )
90
+ `);
91
+ await client.query(`
92
+ ALTER TABLE billing_entitlement_snapshots
93
+ ADD COLUMN IF NOT EXISTS schema_version integer NOT NULL DEFAULT 1,
94
+ ADD COLUMN IF NOT EXISTS effective_until timestamptz,
95
+ ADD COLUMN IF NOT EXISTS source_subscription_id text,
96
+ ADD COLUMN IF NOT EXISTS source_subscription_version integer
97
+ `);
98
+ await client.query(`
99
+ DO $billing_snapshot_anchor_consistency$
100
+ BEGIN
101
+ IF EXISTS (
102
+ SELECT 1
103
+ FROM billing_entitlement_snapshots
104
+ GROUP BY billing_account_id
105
+ HAVING MIN(monthly_usage_anchor) <> MAX(monthly_usage_anchor)
106
+ ) THEN
107
+ RAISE EXCEPTION 'persisted billing account has inconsistent monthly usage anchors';
108
+ END IF;
109
+ END
110
+ $billing_snapshot_anchor_consistency$
111
+ `);
112
+ await client.query(`
113
+ CREATE OR REPLACE FUNCTION billing_enforce_entitlement_anchor_stability()
114
+ RETURNS trigger
115
+ LANGUAGE plpgsql
116
+ AS $billing_snapshot_anchor_guard$
117
+ BEGIN
118
+ IF TG_OP = 'UPDATE' THEN
119
+ IF NEW.billing_account_id IS DISTINCT FROM OLD.billing_account_id THEN
120
+ RAISE EXCEPTION 'billing entitlement snapshot account is immutable'
121
+ USING ERRCODE = '23514';
122
+ END IF;
123
+ IF NEW.monthly_usage_anchor IS DISTINCT FROM OLD.monthly_usage_anchor THEN
124
+ RAISE EXCEPTION 'billing account monthly usage anchor is immutable'
125
+ USING ERRCODE = '23514';
126
+ END IF;
127
+ RETURN NEW;
128
+ END IF;
129
+
130
+ PERFORM 1 FROM billing_accounts
131
+ WHERE id = NEW.billing_account_id
132
+ FOR UPDATE;
133
+ IF EXISTS (
134
+ SELECT 1 FROM billing_entitlement_snapshots
135
+ WHERE billing_account_id = NEW.billing_account_id
136
+ AND monthly_usage_anchor IS DISTINCT FROM NEW.monthly_usage_anchor
137
+ ) THEN
138
+ RAISE EXCEPTION 'billing account monthly usage anchor is immutable'
139
+ USING ERRCODE = '23514';
140
+ END IF;
141
+ RETURN NEW;
142
+ END
143
+ $billing_snapshot_anchor_guard$
144
+ `);
145
+ await client.query(`
146
+ DO $billing_snapshot_anchor_trigger$
147
+ BEGIN
148
+ IF NOT EXISTS (
149
+ SELECT 1 FROM pg_trigger
150
+ WHERE tgname = 'billing_entitlement_snapshots_anchor_stability'
151
+ AND tgrelid = 'billing_entitlement_snapshots'::regclass
152
+ AND NOT tgisinternal
153
+ ) THEN
154
+ BEGIN
155
+ CREATE TRIGGER billing_entitlement_snapshots_anchor_stability
156
+ BEFORE INSERT OR UPDATE ON billing_entitlement_snapshots
157
+ FOR EACH ROW EXECUTE FUNCTION billing_enforce_entitlement_anchor_stability();
158
+ EXCEPTION WHEN duplicate_object THEN
159
+ NULL;
160
+ END;
161
+ END IF;
162
+ END
163
+ $billing_snapshot_anchor_trigger$
164
+ `);
165
+ await client.query(`
166
+ DO $billing_paid_snapshot_constraints$
167
+ BEGIN
168
+ IF NOT EXISTS (
169
+ SELECT 1 FROM pg_constraint
170
+ WHERE conname = 'billing_base_subscription_revisions_snapshot_source_key'
171
+ AND conrelid = 'billing_base_subscription_revisions'::regclass
172
+ ) THEN
173
+ BEGIN
174
+ ALTER TABLE billing_base_subscription_revisions
175
+ ADD CONSTRAINT billing_base_subscription_revisions_snapshot_source_key
176
+ UNIQUE (
177
+ billing_account_id,
178
+ subscription_id,
179
+ version,
180
+ plan_version_id,
181
+ effective_from,
182
+ effective_until
183
+ );
184
+ EXCEPTION WHEN duplicate_object THEN
185
+ NULL;
186
+ END;
187
+ END IF;
188
+ IF NOT EXISTS (
189
+ SELECT 1 FROM pg_constraint
190
+ WHERE conname = 'billing_entitlement_snapshots_provenance_v2_check'
191
+ AND conrelid = 'billing_entitlement_snapshots'::regclass
192
+ ) THEN
193
+ BEGIN
194
+ ALTER TABLE billing_entitlement_snapshots
195
+ ADD CONSTRAINT billing_entitlement_snapshots_provenance_v2_check
196
+ CHECK (
197
+ verification_status = 'verified' AND (
198
+ (
199
+ schema_version = 1 AND
200
+ source_kind = 'system-free' AND
201
+ generation_reason = 'default-account-created' AND
202
+ effective_until IS NULL AND
203
+ source_subscription_id IS NULL AND
204
+ source_subscription_version IS NULL
205
+ ) OR (
206
+ schema_version = 2 AND
207
+ source_kind = 'subscription' AND
208
+ generation_reason = 'subscription-reconciled' AND
209
+ effective_until > effective_from AND
210
+ source_subscription_id IS NOT NULL AND
211
+ source_subscription_version > 0
212
+ )
213
+ )
214
+ );
215
+ EXCEPTION WHEN duplicate_object THEN
216
+ NULL;
217
+ END;
218
+ END IF;
219
+ END
220
+ $billing_paid_snapshot_constraints$
221
+ `);
222
+ // Replace the earlier four-column draft FK inside the same transaction. Including both effective
223
+ // bounds makes the snapshot an exact projection of the referenced immutable revision.
224
+ await client.query(`
225
+ DO $billing_paid_snapshot_revision_fk$
226
+ BEGIN
227
+ IF EXISTS (
228
+ SELECT 1 FROM pg_constraint
229
+ WHERE conname = 'billing_entitlement_snapshots_subscription_revision_fk'
230
+ AND conrelid = 'billing_entitlement_snapshots'::regclass
231
+ AND array_length(conkey, 1) <> 6
232
+ ) THEN
233
+ ALTER TABLE billing_entitlement_snapshots
234
+ DROP CONSTRAINT billing_entitlement_snapshots_subscription_revision_fk;
235
+ END IF;
236
+ IF NOT EXISTS (
237
+ SELECT 1 FROM pg_constraint
238
+ WHERE conname = 'billing_entitlement_snapshots_subscription_revision_fk'
239
+ AND conrelid = 'billing_entitlement_snapshots'::regclass
240
+ ) THEN
241
+ BEGIN
242
+ ALTER TABLE billing_entitlement_snapshots
243
+ ADD CONSTRAINT billing_entitlement_snapshots_subscription_revision_fk
244
+ FOREIGN KEY (
245
+ billing_account_id,
246
+ source_subscription_id,
247
+ source_subscription_version,
248
+ plan_version_id,
249
+ effective_from,
250
+ effective_until
251
+ ) REFERENCES billing_base_subscription_revisions (
252
+ billing_account_id,
253
+ subscription_id,
254
+ version,
255
+ plan_version_id,
256
+ effective_from,
257
+ effective_until
258
+ ) ON DELETE RESTRICT;
259
+ EXCEPTION WHEN duplicate_object THEN
260
+ NULL;
261
+ END;
262
+ END IF;
263
+ END
264
+ $billing_paid_snapshot_revision_fk$
265
+ `);
266
+ // Install the replacement provenance guard before retiring the legacy Free-only checks. PostgreSQL
267
+ // exposes all of this transactional DDL atomically, so readers never observe an unguarded table.
268
+ await client.query(`
269
+ ALTER TABLE billing_entitlement_snapshots
270
+ DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_source_kind_check,
271
+ DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_generation_reason_check,
272
+ DROP CONSTRAINT IF EXISTS billing_entitlement_snapshots_verification_status_check
273
+ `);
274
+ await client.query(`
275
+ CREATE OR REPLACE FUNCTION billing_reject_subscription_history_mutation()
276
+ RETURNS trigger
277
+ LANGUAGE plpgsql
278
+ AS $billing_subscription_history_guard$
279
+ BEGIN
280
+ RAISE EXCEPTION 'billing subscription history is append-only' USING ERRCODE = '55000';
281
+ END
282
+ $billing_subscription_history_guard$
283
+ `);
284
+ await client.query(`
285
+ DO $billing_subscription_history_triggers$
286
+ BEGIN
287
+ IF NOT EXISTS (
288
+ SELECT 1 FROM pg_trigger
289
+ WHERE tgname = 'billing_base_subscriptions_append_only'
290
+ AND tgrelid = 'billing_base_subscriptions'::regclass
291
+ AND NOT tgisinternal
292
+ ) THEN
293
+ BEGIN
294
+ CREATE TRIGGER billing_base_subscriptions_append_only
295
+ BEFORE UPDATE OR DELETE ON billing_base_subscriptions
296
+ FOR EACH ROW EXECUTE FUNCTION billing_reject_subscription_history_mutation();
297
+ EXCEPTION WHEN duplicate_object THEN
298
+ NULL;
299
+ END;
300
+ END IF;
301
+ IF NOT EXISTS (
302
+ SELECT 1 FROM pg_trigger
303
+ WHERE tgname = 'billing_base_subscription_revisions_append_only'
304
+ AND tgrelid = 'billing_base_subscription_revisions'::regclass
305
+ AND NOT tgisinternal
306
+ ) THEN
307
+ BEGIN
308
+ CREATE TRIGGER billing_base_subscription_revisions_append_only
309
+ BEFORE UPDATE OR DELETE ON billing_base_subscription_revisions
310
+ FOR EACH ROW EXECUTE FUNCTION billing_reject_subscription_history_mutation();
311
+ EXCEPTION WHEN duplicate_object THEN
312
+ NULL;
313
+ END;
314
+ END IF;
315
+ END
316
+ $billing_subscription_history_triggers$
317
+ `);
318
+ }
319
+ //# sourceMappingURL=postgres-subscription-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-subscription-schema.js","sourceRoot":"","sources":["../../src/billing/postgres-subscription-schema.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,IAAU;IAC9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC;YACH,4FAA4F;YAC5F,yEAAyE;YACzE,MAAM,MAAM,CAAC,KAAK,CAChB,iFAAiF,CAClF,CAAC;YACF,MAAM,iCAAiC,CAAC,MAAM,CAAC,CAAC;YAChD,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,iCAAiC,CAAC,MAAkB;IACjE,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;GAOlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;GAUlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;GAMlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;GAalB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgClB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;GAmBlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDlB,CAAC,CAAC;IAEH,iGAAiG;IACjG,sFAAsF;IACtF,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyClB,CAAC,CAAC;IAEH,mGAAmG;IACnG,iGAAiG;IACjG,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;GAKlB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;GASlB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiClB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type EntitlementSnapshotRecord, type PaidEntitlementSnapshotRecord } from './model.js';
2
+ export type BillingBaseSubscriptionState = 'active' | 'payment_grace' | 'ended';
3
+ export type BillingInterval = 'month' | 'year';
4
+ export interface BillingBaseSubscriptionRecord {
5
+ readonly id: string;
6
+ readonly billingAccountId: string;
7
+ readonly createdAt: string;
8
+ }
9
+ export interface BillingBaseSubscriptionRevisionRecord {
10
+ readonly schemaVersion: 1;
11
+ readonly subscriptionId: string;
12
+ readonly billingAccountId: string;
13
+ readonly version: number;
14
+ readonly planVersionId: string;
15
+ readonly state: BillingBaseSubscriptionState;
16
+ readonly billingInterval: BillingInterval;
17
+ readonly currency: 'usd';
18
+ readonly baseAmountMinor: number;
19
+ readonly currentPeriodStart: string;
20
+ readonly currentPeriodEnd: string;
21
+ readonly effectiveFrom: string;
22
+ readonly effectiveUntil: string;
23
+ readonly renews: boolean;
24
+ readonly contentChecksum: string;
25
+ readonly verifiedAt: string;
26
+ readonly createdAt: string;
27
+ }
28
+ export type BillingBaseSubscriptionRevisionInput = Omit<BillingBaseSubscriptionRevisionRecord, 'schemaVersion' | 'contentChecksum'>;
29
+ export declare function createBillingBaseSubscriptionRevision(input: BillingBaseSubscriptionRevisionInput): BillingBaseSubscriptionRevisionRecord;
30
+ export declare function parseBillingBaseSubscriptionRevision(value: unknown): BillingBaseSubscriptionRevisionRecord;
31
+ export interface CreatePaidEntitlementSnapshotInput {
32
+ readonly id: string;
33
+ readonly version: number;
34
+ readonly sourceSubscription: BillingBaseSubscriptionRevisionRecord;
35
+ readonly previousSnapshot: EntitlementSnapshotRecord;
36
+ }
37
+ export declare function createPaidEntitlementSnapshot(input: CreatePaidEntitlementSnapshotInput): PaidEntitlementSnapshotRecord;
38
+ export declare function parsePaidEntitlementSnapshot(value: unknown): PaidEntitlementSnapshotRecord;
39
+ export declare function validateBillingSubscriptionId(value: string): string;
40
+ //# sourceMappingURL=subscription-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-model.d.ts","sourceRoot":"","sources":["../../src/billing/subscription-model.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAEnC,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,4BAA4B,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC;AAChF,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAI/C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,4BAA4B,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,oCAAoC,GAAG,IAAI,CACrD,qCAAqC,EACrC,eAAe,GAAG,iBAAiB,CACpC,CAAC;AAOF,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,oCAAoC,GAC1C,qCAAqC,CAMvC;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,qCAAqC,CAyBvC;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,qCAAqC,CAAC;IACnE,QAAQ,CAAC,gBAAgB,EAAE,yBAAyB,CAAC;CACtD;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,kCAAkC,GACxC,6BAA6B,CAsC/B;AAmDD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,6BAA6B,CAmC1F;AAiLD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE"}