@saasicat/adapter-drizzle 1.0.0-rc.2 → 1.0.0-rc.21

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
@@ -8,11 +8,11 @@ var __export = (target, all) => {
8
8
  for (var name in all)
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
- var __copyProps = (to, from, except, desc4) => {
11
+ var __copyProps = (to, from, except, desc10) => {
12
12
  if (from && typeof from === "object" || typeof from === "function") {
13
13
  for (let key of __getOwnPropNames(from))
14
14
  if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc4 = __getOwnPropDesc(from, key)) || desc4.enumerable });
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc10 = __getOwnPropDesc(from, key)) || desc10.enumerable });
16
16
  }
17
17
  return to;
18
18
  };
@@ -23,19 +23,30 @@ var index_exports = {};
23
23
  __export(index_exports, {
24
24
  AsyncLocalRlsBypassAdapter: () => AsyncLocalRlsBypassAdapter,
25
25
  DRIZZLE_DB_TOKEN: () => DRIZZLE_DB_TOKEN,
26
+ DrizzleAppliedSettingsRepository: () => DrizzleAppliedSettingsRepository,
26
27
  DrizzleAuditAdapter: () => DrizzleAuditAdapter,
27
28
  DrizzleAuditQueryAdapter: () => DrizzleAuditQueryAdapter,
28
29
  DrizzleAuditStatsAdapter: () => DrizzleAuditStatsAdapter,
30
+ DrizzleBundleRepository: () => DrizzleBundleRepository,
29
31
  DrizzleMfaAdapter: () => DrizzleMfaAdapter,
32
+ DrizzlePaymentEventLog: () => DrizzlePaymentEventLog,
30
33
  DrizzlePlanCatalogImportSink: () => DrizzlePlanCatalogImportSink,
31
34
  DrizzlePlanCatalogReadSink: () => DrizzlePlanCatalogReadSink,
35
+ DrizzlePlanRepository: () => DrizzlePlanRepository,
32
36
  DrizzlePlanVersionRepository: () => DrizzlePlanVersionRepository,
37
+ DrizzlePromoCodeHoldRepository: () => DrizzlePromoCodeHoldRepository,
33
38
  DrizzlePromoCodeRedemptionRepository: () => DrizzlePromoCodeRedemptionRepository,
34
39
  DrizzlePromoCodeRepository: () => DrizzlePromoCodeRepository,
35
40
  DrizzlePromoCodeValidationLogRepository: () => DrizzlePromoCodeValidationLogRepository,
36
41
  DrizzlePromoSubscriptionLookup: () => DrizzlePromoSubscriptionLookup,
42
+ DrizzleSubscriberPaymentMethodRepository: () => DrizzleSubscriberPaymentMethodRepository,
43
+ DrizzleSubscriberRepository: () => DrizzleSubscriberRepository,
44
+ DrizzleSubscriptionBundleRepository: () => DrizzleSubscriptionBundleRepository,
45
+ DrizzleSubscriptionContractRepository: () => DrizzleSubscriptionContractRepository,
37
46
  DrizzleSubscriptionRepository: () => DrizzleSubscriptionRepository,
47
+ DrizzleSubscriptionUsageAdapter: () => DrizzleSubscriptionUsageAdapter,
38
48
  DrizzleSuperAdminBootstrapAdapter: () => DrizzleSuperAdminBootstrapAdapter,
49
+ DrizzleTenantSubscriptionWrite: () => DrizzleTenantSubscriptionWrite,
39
50
  DrizzleTransactionRunner: () => DrizzleTransactionRunner,
40
51
  PASSWORD_HASHER_TOKEN: () => PASSWORD_HASHER_TOKEN,
41
52
  ZeroPromoRevenueDeductionAggregator: () => ZeroPromoRevenueDeductionAggregator,
@@ -46,16 +57,14 @@ __export(index_exports, {
46
57
  module.exports = __toCommonJS(index_exports);
47
58
 
48
59
  // src/client.ts
60
+ var import_core = require("@saasicat/core");
49
61
  var DRIZZLE_DB_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/adapter-drizzle/DrizzleDb");
50
62
  function resolveDb(client, tx) {
51
63
  return tx ?? client;
52
64
  }
53
65
  __name(resolveDb, "resolveDb");
54
66
  function toQuotaMap(value) {
55
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
56
- return value;
57
- }
58
- return {};
67
+ return (0, import_core.readQuotaRecord)(value);
59
68
  }
60
69
  __name(toQuotaMap, "toQuotaMap");
61
70
  function toStringArray(value) {
@@ -70,9 +79,9 @@ __name(escapeLikePattern, "escapeLikePattern");
70
79
  // src/async-local-rls-bypass.adapter.ts
71
80
  var import_common = require("@nestjs/common");
72
81
  var import_node_async_hooks = require("node:async_hooks");
73
- function _ts_decorate(decorators, target, key, desc4) {
74
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
75
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
82
+ function _ts_decorate(decorators, target, key, desc10) {
83
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
84
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
76
85
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
77
86
  return c > 3 && r && Object.defineProperty(target, key, r), r;
78
87
  }
@@ -96,20 +105,35 @@ AsyncLocalRlsBypassAdapter = _ts_decorate([
96
105
  (0, import_common.Injectable)()
97
106
  ], AsyncLocalRlsBypassAdapter);
98
107
 
99
- // src/drizzle-audit.adapter.ts
108
+ // src/drizzle-applied-settings.repository.ts
100
109
  var import_node_crypto = require("node:crypto");
101
110
  var import_common2 = require("@nestjs/common");
111
+ var import_drizzle_orm = require("drizzle-orm");
102
112
 
103
113
  // src/schema.ts
104
114
  var schema_exports = {};
105
115
  __export(schema_exports, {
116
+ appliedSettings: () => appliedSettings,
106
117
  auditLogs: () => auditLogs,
118
+ bundleVersions: () => bundleVersions,
119
+ bundles: () => bundles,
120
+ contractLineItems: () => contractLineItems,
107
121
  featureCatalogEntries: () => featureCatalogEntries,
122
+ paymentEventLog: () => paymentEventLog,
108
123
  planVersions: () => planVersions,
109
124
  plans: () => plans,
125
+ promoCodeHolds: () => promoCodeHolds,
110
126
  promoCodeRedemptions: () => promoCodeRedemptions,
111
127
  promoCodeValidationLogs: () => promoCodeValidationLogs,
112
128
  promoCodes: () => promoCodes,
129
+ settingsChanges: () => settingsChanges,
130
+ subscriberCorrections: () => subscriberCorrections,
131
+ subscriberPaymentMethodSetups: () => subscriberPaymentMethodSetups,
132
+ subscriberPaymentMethods: () => subscriberPaymentMethods,
133
+ subscriberTenants: () => subscriberTenants,
134
+ subscribers: () => subscribers,
135
+ subscriptionBundles: () => subscriptionBundles,
136
+ subscriptionContracts: () => subscriptionContracts,
113
137
  subscriptions: () => subscriptions,
114
138
  superAdminMfa: () => superAdminMfa,
115
139
  superAdminUsers: () => superAdminUsers
@@ -133,6 +157,42 @@ var subscriptions = (0, import_pg_core.pgTable)("subscriptions", {
133
157
  pendingPlanVersionId: (0, import_pg_core.text)("pendingPlanVersionId"),
134
158
  isPilot: (0, import_pg_core.boolean)("isPilot").notNull().default(false),
135
159
  startedAt: ts("startedAt"),
160
+ // Entitlement resolution ends a subscription by reading these two, so the
161
+ // query map has to describe them: a column this file omits is a column the
162
+ // repository cannot return, and the platform then treats a subscription
163
+ // that ended last January as running.
164
+ canceledAt: ts("canceledAt"),
165
+ canceledEffectiveAt: ts("canceledEffectiveAt"),
166
+ // The billing window, the commitment and the day it is billed on. Omitted
167
+ // until 2026-08-27, and a column this file omits is one the repositories
168
+ // cannot return — which is why the renewal, plan-change and cancellation
169
+ // paths could not be served from this adapter at all.
170
+ trialEndsAt: ts("trialEndsAt"),
171
+ currentPeriodStart: ts("currentPeriodStart"),
172
+ currentPeriodEnd: ts("currentPeriodEnd"),
173
+ minimumTermUntil: ts("minimumTermUntil"),
174
+ billingAnchorDay: (0, import_pg_core.integer)("billingAnchorDay"),
175
+ // A pending version change and how far it has got: announced, reminded
176
+ // about, accepted, and the date it lands.
177
+ pendingPlanVersionEffectiveAt: ts("pendingPlanVersionEffectiveAt"),
178
+ pendingPlanVersionAccepted: (0, import_pg_core.boolean)("pendingPlanVersionAccepted").notNull().default(false),
179
+ pendingPlanVersionAcceptedAt: ts("pendingPlanVersionAcceptedAt"),
180
+ pendingPlanVersionAcceptedByUserId: (0, import_pg_core.text)("pendingPlanVersionAcceptedByUserId"),
181
+ pendingPlanVersionNotifiedAt: ts("pendingPlanVersionNotifiedAt"),
182
+ pendingPlanVersionReminderSentAt: ts("pendingPlanVersionReminderSentAt"),
183
+ postTrialPlan: (0, import_pg_core.text)("postTrialPlan"),
184
+ pendingBillingCycle: (0, import_pg_core.text)("pendingBillingCycle"),
185
+ // A negotiated price and the note that explains it, a pilot window, the
186
+ // offer this subscription came from, and the package as it was sold.
187
+ customMonthlyNet: (0, import_pg_core.numeric)("customMonthlyNet", {
188
+ precision: 10,
189
+ scale: 2
190
+ }),
191
+ customNote: (0, import_pg_core.text)("customNote"),
192
+ pilotEndsAt: ts("pilotEndsAt"),
193
+ pilotNote: (0, import_pg_core.text)("pilotNote"),
194
+ checkoutOfferId: (0, import_pg_core.text)("checkoutOfferId"),
195
+ packageSnapshot: (0, import_pg_core.jsonb)("packageSnapshot"),
136
196
  createdAt: ts("createdAt").notNull().defaultNow(),
137
197
  updatedAt: ts("updatedAt").notNull()
138
198
  });
@@ -157,6 +217,12 @@ var planVersions = (0, import_pg_core.pgTable)("plan_versions", {
157
217
  publishedChanges: (0, import_pg_core.jsonb)("publishedChanges"),
158
218
  changeNote: (0, import_pg_core.text)("changeNote").notNull(),
159
219
  nonRegressive: (0, import_pg_core.boolean)("nonRegressive").notNull().default(true),
220
+ // The booking window for new subscriptions, and the day the plan itself
221
+ // stops. All three nullable, so a schema that predates them stays valid and
222
+ // an adapter that does not maintain them says so rather than writing them.
223
+ validFrom: ts("validFrom"),
224
+ validUntil: ts("validUntil"),
225
+ endsAt: ts("endsAt"),
160
226
  createdByUserId: (0, import_pg_core.text)("createdByUserId"),
161
227
  publishedByUserId: (0, import_pg_core.text)("publishedByUserId"),
162
228
  createdAt: ts("createdAt").notNull().defaultNow(),
@@ -164,7 +230,6 @@ var planVersions = (0, import_pg_core.pgTable)("plan_versions", {
164
230
  });
165
231
  var plans = (0, import_pg_core.pgTable)("plans", {
166
232
  id: (0, import_pg_core.text)("id").primaryKey(),
167
- projectKey: (0, import_pg_core.text)("projectKey").notNull(),
168
233
  planKey: (0, import_pg_core.text)("planKey").notNull(),
169
234
  label: (0, import_pg_core.text)("label").notNull(),
170
235
  description: (0, import_pg_core.text)("description"),
@@ -174,9 +239,66 @@ var plans = (0, import_pg_core.pgTable)("plans", {
174
239
  updatedAt: ts("updatedAt").notNull(),
175
240
  deletedAt: ts("deletedAt")
176
241
  });
242
+ var subscriptionContracts = (0, import_pg_core.pgTable)("subscription_contracts", {
243
+ id: (0, import_pg_core.text)("id").primaryKey(),
244
+ tenantId: (0, import_pg_core.text)("tenantId").notNull(),
245
+ // Postgres enum, declared as text: parameterized values are coerced and
246
+ // reads come back as strings, which is what the records expect.
247
+ status: (0, import_pg_core.text)("status").notNull().default("active"),
248
+ effectiveFrom: ts("effectiveFrom").notNull(),
249
+ effectiveUntil: ts("effectiveUntil"),
250
+ originalOfferId: (0, import_pg_core.text)("originalOfferId"),
251
+ originalPlanVersionId: (0, import_pg_core.text)("originalPlanVersionId"),
252
+ originalBundleVersionIds: (0, import_pg_core.jsonb)("originalBundleVersionIds").notNull(),
253
+ entitlementSnapshot: (0, import_pg_core.jsonb)("entitlementSnapshot"),
254
+ priceSnapshot: (0, import_pg_core.jsonb)("priceSnapshot").notNull(),
255
+ promotionSnapshots: (0, import_pg_core.jsonb)("promotionSnapshots").notNull(),
256
+ promoCodeSnapshots: (0, import_pg_core.jsonb)("promoCodeSnapshots").notNull(),
257
+ termsSnapshot: (0, import_pg_core.jsonb)("termsSnapshot"),
258
+ // Who the contract is between, copied when it was concluded.
259
+ subscriberId: (0, import_pg_core.text)("subscriberId").notNull(),
260
+ subscriberSnapshot: (0, import_pg_core.jsonb)("subscriberSnapshot").notNull(),
261
+ issuerSnapshot: (0, import_pg_core.jsonb)("issuerSnapshot"),
262
+ partiesMigrated: (0, import_pg_core.boolean)("partiesMigrated").notNull().default(false),
263
+ createdAt: ts("createdAt").notNull().defaultNow(),
264
+ updatedAt: ts("updatedAt").notNull()
265
+ });
266
+ var contractLineItems = (0, import_pg_core.pgTable)("contract_line_items", {
267
+ id: (0, import_pg_core.text)("id").primaryKey(),
268
+ contractId: (0, import_pg_core.text)("contractId").notNull(),
269
+ kind: (0, import_pg_core.text)("kind").notNull(),
270
+ sourceKey: (0, import_pg_core.text)("sourceKey").notNull(),
271
+ sourceVersionId: (0, import_pg_core.text)("sourceVersionId"),
272
+ titleSnapshot: (0, import_pg_core.text)("titleSnapshot").notNull(),
273
+ descriptionSnapshot: (0, import_pg_core.text)("descriptionSnapshot"),
274
+ quantity: (0, import_pg_core.integer)("quantity").notNull().default(1),
275
+ unit: (0, import_pg_core.text)("unit"),
276
+ priceNet: (0, import_pg_core.numeric)("priceNet", {
277
+ precision: 10,
278
+ scale: 2
279
+ }).notNull(),
280
+ priceGross: (0, import_pg_core.numeric)("priceGross", {
281
+ precision: 10,
282
+ scale: 2
283
+ }).notNull(),
284
+ billingCycle: (0, import_pg_core.text)("billingCycle").notNull(),
285
+ currency: (0, import_pg_core.text)("currency").notNull(),
286
+ taxRate: (0, import_pg_core.numeric)("taxRate", {
287
+ precision: 5,
288
+ scale: 2
289
+ }).notNull(),
290
+ taxAmount: (0, import_pg_core.numeric)("taxAmount", {
291
+ precision: 10,
292
+ scale: 2
293
+ }).notNull(),
294
+ minimumTermUntil: ts("minimumTermUntil"),
295
+ featuresSnapshot: (0, import_pg_core.jsonb)("featuresSnapshot").notNull(),
296
+ quotaEffectsSnapshot: (0, import_pg_core.jsonb)("quotaEffectsSnapshot").notNull(),
297
+ metadata: (0, import_pg_core.jsonb)("metadata"),
298
+ createdAt: ts("createdAt").notNull().defaultNow()
299
+ });
177
300
  var featureCatalogEntries = (0, import_pg_core.pgTable)("feature_catalog_entries", {
178
301
  id: (0, import_pg_core.text)("id").primaryKey(),
179
- projectKey: (0, import_pg_core.text)("projectKey").notNull(),
180
302
  featureKey: (0, import_pg_core.text)("featureKey").notNull(),
181
303
  label: (0, import_pg_core.text)("label").notNull(),
182
304
  description: (0, import_pg_core.text)("description"),
@@ -213,6 +335,7 @@ var promoCodes = (0, import_pg_core.pgTable)("promo_codes", {
213
335
  validUntil: ts("validUntil"),
214
336
  maxRedemptions: (0, import_pg_core.integer)("maxRedemptions"),
215
337
  redemptionsCount: (0, import_pg_core.integer)("redemptionsCount").notNull().default(0),
338
+ heldCount: (0, import_pg_core.integer)("heldCount").notNull().default(0),
216
339
  appliesToPlans: (0, import_pg_core.text)("appliesToPlans").array(),
217
340
  appliesToBilling: (0, import_pg_core.text)("appliesToBilling"),
218
341
  firstTimeCustomersOnly: (0, import_pg_core.boolean)("firstTimeCustomersOnly").notNull().default(true),
@@ -248,6 +371,16 @@ var promoCodeRedemptions = (0, import_pg_core.pgTable)("promo_code_redemptions",
248
371
  redeemedAt: ts("redeemedAt").notNull().defaultNow(),
249
372
  reversedAt: ts("reversedAt")
250
373
  });
374
+ var promoCodeHolds = (0, import_pg_core.pgTable)("promo_code_holds", {
375
+ id: (0, import_pg_core.text)("id").primaryKey(),
376
+ promoCodeId: (0, import_pg_core.text)("promoCodeId").notNull(),
377
+ checkoutOfferId: (0, import_pg_core.text)("checkoutOfferId").notNull(),
378
+ expiresAt: ts("expiresAt").notNull(),
379
+ handedOverTx: (0, import_pg_core.bigint)("handedOverTx", {
380
+ mode: "bigint"
381
+ }),
382
+ createdAt: ts("createdAt").notNull().defaultNow()
383
+ });
251
384
  var promoCodeValidationLogs = (0, import_pg_core.pgTable)("promo_code_validation_logs", {
252
385
  id: (0, import_pg_core.text)("id").primaryKey(),
253
386
  promoCodeId: (0, import_pg_core.text)("promoCodeId"),
@@ -289,11 +422,166 @@ var superAdminMfa = (0, import_pg_core.pgTable)("super_admin_mfa", {
289
422
  enabledAt: ts("enabledAt"),
290
423
  updatedAt: ts("updatedAt").notNull()
291
424
  });
425
+ var bundles = (0, import_pg_core.pgTable)("bundles", {
426
+ id: (0, import_pg_core.text)("id").primaryKey(),
427
+ bundleKey: (0, import_pg_core.text)("bundleKey").notNull(),
428
+ label: (0, import_pg_core.text)("label").notNull(),
429
+ description: (0, import_pg_core.text)("description"),
430
+ icon: (0, import_pg_core.text)("icon"),
431
+ sortOrder: (0, import_pg_core.integer)("sortOrder").notNull().default(0),
432
+ i18n: (0, import_pg_core.jsonb)("i18n").notNull(),
433
+ createdAt: ts("createdAt").notNull().defaultNow(),
434
+ updatedAt: ts("updatedAt").notNull(),
435
+ deletedAt: ts("deletedAt")
436
+ });
437
+ var bundleVersions = (0, import_pg_core.pgTable)("bundle_versions", {
438
+ id: (0, import_pg_core.text)("id").primaryKey(),
439
+ bundleId: (0, import_pg_core.text)("bundleId").notNull(),
440
+ version: (0, import_pg_core.integer)("version").notNull(),
441
+ baseVersionId: (0, import_pg_core.text)("baseVersionId"),
442
+ features: (0, import_pg_core.jsonb)("features").notNull(),
443
+ quotas: (0, import_pg_core.jsonb)("quotas").notNull(),
444
+ compatibility: (0, import_pg_core.jsonb)("compatibility").notNull(),
445
+ pricingOverrides: (0, import_pg_core.jsonb)("pricingOverrides").notNull(),
446
+ // Nullable, unlike a plan version's: a bundle may carry no base price and
447
+ // resolve one per plan through `pricingOverrides` instead.
448
+ monthlyNet: (0, import_pg_core.numeric)("monthlyNet", {
449
+ precision: 10,
450
+ scale: 2
451
+ }),
452
+ yearlyNet: (0, import_pg_core.numeric)("yearlyNet", {
453
+ precision: 10,
454
+ scale: 2
455
+ }),
456
+ marketed: (0, import_pg_core.boolean)("marketed").notNull().default(true),
457
+ publishedAt: ts("publishedAt"),
458
+ supersededAt: ts("supersededAt"),
459
+ publishedChanges: (0, import_pg_core.jsonb)("publishedChanges"),
460
+ changeNote: (0, import_pg_core.text)("changeNote").notNull(),
461
+ nonRegressive: (0, import_pg_core.boolean)("nonRegressive").notNull().default(true),
462
+ validFrom: ts("validFrom"),
463
+ validUntil: ts("validUntil"),
464
+ createdByUserId: (0, import_pg_core.text)("createdByUserId"),
465
+ publishedByUserId: (0, import_pg_core.text)("publishedByUserId"),
466
+ createdAt: ts("createdAt").notNull().defaultNow(),
467
+ updatedAt: ts("updatedAt").notNull()
468
+ });
469
+ var subscriptionBundles = (0, import_pg_core.pgTable)("subscription_bundles", {
470
+ id: (0, import_pg_core.text)("id").primaryKey(),
471
+ subscriptionId: (0, import_pg_core.text)("subscriptionId").notNull(),
472
+ bundleVersionId: (0, import_pg_core.text)("bundleVersionId").notNull(),
473
+ startedAt: ts("startedAt").notNull(),
474
+ minimumTermEndsAt: ts("minimumTermEndsAt"),
475
+ // The rhythm this booking is billed in and the window it is billed for. A
476
+ // bundle's periods end on the day the plan's do; all three are null on a
477
+ // booking made before these columns existed, and on one whose plan had no
478
+ // period to align to — a trial, or a subscription awaiting sales.
479
+ billingCycle: (0, import_pg_core.text)("billingCycle"),
480
+ currentPeriodStart: ts("currentPeriodStart"),
481
+ currentPeriodEnd: ts("currentPeriodEnd"),
482
+ canceledAt: ts("canceledAt"),
483
+ canceledEffectiveAt: ts("canceledEffectiveAt"),
484
+ createdAt: ts("createdAt").notNull().defaultNow(),
485
+ updatedAt: ts("updatedAt").notNull()
486
+ });
487
+ var appliedSettings = (0, import_pg_core.pgTable)("applied_settings", {
488
+ id: (0, import_pg_core.text)("id").primaryKey().default("installation"),
489
+ fingerprint: (0, import_pg_core.text)("fingerprint").notNull(),
490
+ settings: (0, import_pg_core.jsonb)("settings").notNull(),
491
+ source: (0, import_pg_core.text)("source").notNull(),
492
+ appliedAt: ts("appliedAt").notNull()
493
+ });
494
+ var settingsChanges = (0, import_pg_core.pgTable)("settings_changes", {
495
+ id: (0, import_pg_core.text)("id").primaryKey(),
496
+ // Numbered by the database at the write that recorded the change, and the
497
+ // order the list is read in. Never set by the adapter.
498
+ seq: (0, import_pg_core.serial)("seq").notNull(),
499
+ noticedAt: ts("noticedAt").notNull(),
500
+ source: (0, import_pg_core.text)("source").notNull(),
501
+ previous: (0, import_pg_core.jsonb)("previous").notNull(),
502
+ current: (0, import_pg_core.jsonb)("current").notNull(),
503
+ acknowledgedAt: ts("acknowledgedAt"),
504
+ acknowledgedBy: (0, import_pg_core.text)("acknowledgedBy")
505
+ });
506
+ var subscribers = (0, import_pg_core.pgTable)("subscribers", {
507
+ id: (0, import_pg_core.text)("id").primaryKey(),
508
+ // Counted by the database, from where `constraints.postgres.sql` starts it.
509
+ // Never set by the adapter.
510
+ customerSequence: (0, import_pg_core.serial)("customerSequence").notNull(),
511
+ customerNumberPrefix: (0, import_pg_core.text)("customerNumberPrefix").notNull().default(""),
512
+ legalName: (0, import_pg_core.text)("legalName").notNull(),
513
+ vatId: (0, import_pg_core.text)("vatId"),
514
+ taxNumber: (0, import_pg_core.text)("taxNumber"),
515
+ addressLine1: (0, import_pg_core.text)("addressLine1"),
516
+ addressLine2: (0, import_pg_core.text)("addressLine2"),
517
+ postalCode: (0, import_pg_core.text)("postalCode"),
518
+ city: (0, import_pg_core.text)("city"),
519
+ country: (0, import_pg_core.text)("country"),
520
+ invoiceEmail: (0, import_pg_core.text)("invoiceEmail"),
521
+ migrated: (0, import_pg_core.boolean)("migrated").notNull().default(false),
522
+ createdAt: ts("createdAt").notNull().defaultNow(),
523
+ updatedAt: ts("updatedAt").notNull()
524
+ });
525
+ var subscriberTenants = (0, import_pg_core.pgTable)("subscriber_tenants", {
526
+ id: (0, import_pg_core.text)("id").primaryKey(),
527
+ subscriberId: (0, import_pg_core.text)("subscriberId").notNull(),
528
+ tenantId: (0, import_pg_core.text)("tenantId").notNull(),
529
+ linkedAt: ts("linkedAt").notNull().defaultNow(),
530
+ unlinkedAt: ts("unlinkedAt")
531
+ });
532
+ var subscriberCorrections = (0, import_pg_core.pgTable)("subscriber_corrections", {
533
+ id: (0, import_pg_core.text)("id").primaryKey(),
534
+ subscriberId: (0, import_pg_core.text)("subscriberId").notNull(),
535
+ previous: (0, import_pg_core.jsonb)("previous").notNull(),
536
+ corrected: (0, import_pg_core.jsonb)("corrected").notNull(),
537
+ reason: (0, import_pg_core.text)("reason").notNull(),
538
+ correctedBy: (0, import_pg_core.text)("correctedBy").notNull(),
539
+ correctedAt: ts("correctedAt").notNull()
540
+ });
541
+ var subscriberPaymentMethodSetups = (0, import_pg_core.pgTable)("subscriber_payment_method_setups", {
542
+ id: (0, import_pg_core.text)("id").primaryKey(),
543
+ subscriberId: (0, import_pg_core.text)("subscriberId").notNull(),
544
+ gatewayAccount: (0, import_pg_core.text)("gatewayAccount").notNull(),
545
+ sessionRef: (0, import_pg_core.text)("sessionRef").notNull(),
546
+ customerRef: (0, import_pg_core.text)("customerRef").notNull(),
547
+ startedAt: ts("startedAt").notNull().defaultNow(),
548
+ completedAt: ts("completedAt")
549
+ });
550
+ var paymentEventLog = (0, import_pg_core.pgTable)("PaymentEventLog", {
551
+ id: (0, import_pg_core.text)("id").primaryKey(),
552
+ gatewayAccount: (0, import_pg_core.text)("gatewayAccount").notNull(),
553
+ eventId: (0, import_pg_core.text)("eventId").notNull(),
554
+ provider: (0, import_pg_core.text)("provider").notNull(),
555
+ sessionId: (0, import_pg_core.text)("sessionId"),
556
+ status: (0, import_pg_core.text)("status").notNull(),
557
+ payload: (0, import_pg_core.jsonb)("payload"),
558
+ processedAt: ts("processedAt").notNull().defaultNow()
559
+ });
560
+ var subscriberPaymentMethods = (0, import_pg_core.pgTable)("subscriber_payment_methods", {
561
+ id: (0, import_pg_core.text)("id").primaryKey(),
562
+ subscriberId: (0, import_pg_core.text)("subscriberId").notNull(),
563
+ gatewayAccount: (0, import_pg_core.text)("gatewayAccount").notNull(),
564
+ provider: (0, import_pg_core.text)("provider").notNull(),
565
+ customerRef: (0, import_pg_core.text)("customerRef").notNull(),
566
+ paymentMethodRef: (0, import_pg_core.text)("paymentMethodRef").notNull(),
567
+ type: (0, import_pg_core.text)("type").notNull(),
568
+ brand: (0, import_pg_core.text)("brand"),
569
+ last4: (0, import_pg_core.text)("last4").notNull(),
570
+ expiryMonth: (0, import_pg_core.integer)("expiryMonth"),
571
+ expiryYear: (0, import_pg_core.integer)("expiryYear"),
572
+ country: (0, import_pg_core.text)("country"),
573
+ bankCode: (0, import_pg_core.text)("bankCode"),
574
+ mandateReference: (0, import_pg_core.text)("mandateReference"),
575
+ status: (0, import_pg_core.text)("status").notNull().default("ACTIVE"),
576
+ confirmedAt: ts("confirmedAt").notNull(),
577
+ replacedAt: ts("replacedAt"),
578
+ createdAt: ts("createdAt").notNull().defaultNow()
579
+ });
292
580
 
293
- // src/drizzle-audit.adapter.ts
294
- function _ts_decorate2(decorators, target, key, desc4) {
295
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
296
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
581
+ // src/drizzle-applied-settings.repository.ts
582
+ function _ts_decorate2(decorators, target, key, desc10) {
583
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
584
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
297
585
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
298
586
  return c > 3 && r && Object.defineProperty(target, key, r), r;
299
587
  }
@@ -308,6 +596,126 @@ function _ts_param(paramIndex, decorator) {
308
596
  };
309
597
  }
310
598
  __name(_ts_param, "_ts_param");
599
+ var INSTALLATION_ROW_ID = "installation";
600
+ var DrizzleAppliedSettingsRepository = class {
601
+ static {
602
+ __name(this, "DrizzleAppliedSettingsRepository");
603
+ }
604
+ db;
605
+ constructor(db) {
606
+ this.db = db;
607
+ }
608
+ async readApplied() {
609
+ const rows = await this.db.select().from(appliedSettings).where((0, import_drizzle_orm.eq)(appliedSettings.id, INSTALLATION_ROW_ID)).limit(1);
610
+ const row = rows[0];
611
+ if (!row) return null;
612
+ return {
613
+ fingerprint: row.fingerprint,
614
+ settings: toValues(row.settings),
615
+ source: row.source,
616
+ appliedAt: row.appliedAt
617
+ };
618
+ }
619
+ async writeApplied(record, expectedFingerprint) {
620
+ return replaceRecord(this.db, record, expectedFingerprint);
621
+ }
622
+ async recordChange(change, record, expectedFingerprint) {
623
+ return this.db.transaction(async (tx) => {
624
+ const db = tx;
625
+ if (!await replaceRecord(db, record, expectedFingerprint)) return null;
626
+ const row = {
627
+ id: (0, import_node_crypto.randomUUID)(),
628
+ noticedAt: change.noticedAt,
629
+ source: change.source,
630
+ previous: change.previous,
631
+ current: change.current,
632
+ acknowledgedAt: null,
633
+ acknowledgedBy: null
634
+ };
635
+ await db.insert(settingsChanges).values(row);
636
+ return toChangeRecord(row);
637
+ });
638
+ }
639
+ async listChanges(filter = {}) {
640
+ const acknowledgement = filter.acknowledged === void 0 ? void 0 : filter.acknowledged ? (0, import_drizzle_orm.isNotNull)(settingsChanges.acknowledgedAt) : (0, import_drizzle_orm.isNull)(settingsChanges.acknowledgedAt);
641
+ const query = this.db.select().from(settingsChanges).where(acknowledgement).orderBy((0, import_drizzle_orm.desc)(settingsChanges.seq));
642
+ const rows = filter.limit === void 0 ? await query : await query.limit(filter.limit);
643
+ return rows.map(toChangeRecord);
644
+ }
645
+ async acknowledgeChange(id, acknowledgedBy, acknowledgedAt) {
646
+ await this.db.update(settingsChanges).set({
647
+ acknowledgedAt,
648
+ acknowledgedBy
649
+ }).where((0, import_drizzle_orm.and)((0, import_drizzle_orm.eq)(settingsChanges.id, id), (0, import_drizzle_orm.isNull)(settingsChanges.acknowledgedAt)));
650
+ const rows = await this.db.select().from(settingsChanges).where((0, import_drizzle_orm.eq)(settingsChanges.id, id)).limit(1);
651
+ return rows[0] ? toChangeRecord(rows[0]) : null;
652
+ }
653
+ };
654
+ DrizzleAppliedSettingsRepository = _ts_decorate2([
655
+ (0, import_common2.Injectable)(),
656
+ _ts_param(0, (0, import_common2.Inject)(DRIZZLE_DB_TOKEN)),
657
+ _ts_metadata("design:type", Function),
658
+ _ts_metadata("design:paramtypes", [
659
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
660
+ ])
661
+ ], DrizzleAppliedSettingsRepository);
662
+ async function replaceRecord(db, record, expectedFingerprint) {
663
+ const values = {
664
+ fingerprint: record.fingerprint,
665
+ settings: record.settings,
666
+ source: record.source,
667
+ appliedAt: record.appliedAt
668
+ };
669
+ const written = expectedFingerprint === null ? await db.insert(appliedSettings).values({
670
+ id: INSTALLATION_ROW_ID,
671
+ ...values
672
+ }).onConflictDoNothing({
673
+ target: appliedSettings.id
674
+ }).returning({
675
+ id: appliedSettings.id
676
+ }) : await db.update(appliedSettings).set(values).where((0, import_drizzle_orm.and)((0, import_drizzle_orm.eq)(appliedSettings.id, INSTALLATION_ROW_ID), (0, import_drizzle_orm.eq)(appliedSettings.fingerprint, expectedFingerprint))).returning({
677
+ id: appliedSettings.id
678
+ });
679
+ return written.length === 1;
680
+ }
681
+ __name(replaceRecord, "replaceRecord");
682
+ function toChangeRecord(row) {
683
+ return {
684
+ id: row.id,
685
+ noticedAt: row.noticedAt,
686
+ source: row.source,
687
+ previous: toValues(row.previous),
688
+ current: toValues(row.current),
689
+ acknowledgedAt: row.acknowledgedAt,
690
+ acknowledgedBy: row.acknowledgedBy
691
+ };
692
+ }
693
+ __name(toChangeRecord, "toChangeRecord");
694
+ function toValues(value) {
695
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
696
+ }
697
+ __name(toValues, "toValues");
698
+
699
+ // src/drizzle-audit.adapter.ts
700
+ var import_node_crypto2 = require("node:crypto");
701
+ var import_common3 = require("@nestjs/common");
702
+ function _ts_decorate3(decorators, target, key, desc10) {
703
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
704
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
705
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
706
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
707
+ }
708
+ __name(_ts_decorate3, "_ts_decorate");
709
+ function _ts_metadata2(k, v) {
710
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
711
+ }
712
+ __name(_ts_metadata2, "_ts_metadata");
713
+ function _ts_param2(paramIndex, decorator) {
714
+ return function(target, key) {
715
+ decorator(target, key, paramIndex);
716
+ };
717
+ }
718
+ __name(_ts_param2, "_ts_param");
311
719
  function buildActorTag(actor) {
312
720
  return `${actor.source}:${actor.email}:${actor.context}`;
313
721
  }
@@ -322,7 +730,7 @@ var DrizzleAuditAdapter = class {
322
730
  }
323
731
  async write(input) {
324
732
  await this.db.insert(auditLogs).values({
325
- id: (0, import_node_crypto.randomUUID)(),
733
+ id: (0, import_node_crypto2.randomUUID)(),
326
734
  tenantId: null,
327
735
  userId: input.actor.userId,
328
736
  entity: input.entity,
@@ -333,35 +741,35 @@ var DrizzleAuditAdapter = class {
333
741
  });
334
742
  }
335
743
  };
336
- DrizzleAuditAdapter = _ts_decorate2([
337
- (0, import_common2.Injectable)(),
338
- _ts_param(0, (0, import_common2.Inject)(DRIZZLE_DB_TOKEN)),
339
- _ts_metadata("design:type", Function),
340
- _ts_metadata("design:paramtypes", [
744
+ DrizzleAuditAdapter = _ts_decorate3([
745
+ (0, import_common3.Injectable)(),
746
+ _ts_param2(0, (0, import_common3.Inject)(DRIZZLE_DB_TOKEN)),
747
+ _ts_metadata2("design:type", Function),
748
+ _ts_metadata2("design:paramtypes", [
341
749
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
342
750
  ])
343
751
  ], DrizzleAuditAdapter);
344
752
 
345
753
  // src/drizzle-audit-query.adapter.ts
346
- var import_common3 = require("@nestjs/common");
347
- var import_drizzle_orm = require("drizzle-orm");
348
- function _ts_decorate3(decorators, target, key, desc4) {
349
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
350
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
754
+ var import_common4 = require("@nestjs/common");
755
+ var import_drizzle_orm2 = require("drizzle-orm");
756
+ function _ts_decorate4(decorators, target, key, desc10) {
757
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
758
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
351
759
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
352
760
  return c > 3 && r && Object.defineProperty(target, key, r), r;
353
761
  }
354
- __name(_ts_decorate3, "_ts_decorate");
355
- function _ts_metadata2(k, v) {
762
+ __name(_ts_decorate4, "_ts_decorate");
763
+ function _ts_metadata3(k, v) {
356
764
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
357
765
  }
358
- __name(_ts_metadata2, "_ts_metadata");
359
- function _ts_param2(paramIndex, decorator) {
766
+ __name(_ts_metadata3, "_ts_metadata");
767
+ function _ts_param3(paramIndex, decorator) {
360
768
  return function(target, key) {
361
769
  decorator(target, key, paramIndex);
362
770
  };
363
771
  }
364
- __name(_ts_param2, "_ts_param");
772
+ __name(_ts_param3, "_ts_param");
365
773
  var DEFAULT_PAGE_SIZE = 50;
366
774
  var MAX_PAGE_SIZE = 200;
367
775
  var DrizzleAuditQueryAdapter = class {
@@ -376,33 +784,33 @@ var DrizzleAuditQueryAdapter = class {
376
784
  const page = Math.max(filter.page ?? 1, 1);
377
785
  const pageSize = Math.min(Math.max(filter.pageSize ?? DEFAULT_PAGE_SIZE, 1), MAX_PAGE_SIZE);
378
786
  const conditions = [
379
- filter.tenantId ? (0, import_drizzle_orm.eq)(auditLogs.tenantId, filter.tenantId) : void 0,
380
- filter.userId ? (0, import_drizzle_orm.eq)(auditLogs.userId, filter.userId) : void 0,
381
- filter.entity ? (0, import_drizzle_orm.eq)(auditLogs.entity, filter.entity) : void 0,
382
- filter.entityId ? (0, import_drizzle_orm.eq)(auditLogs.entityId, filter.entityId) : void 0,
383
- filter.action ? (0, import_drizzle_orm.eq)(auditLogs.action, filter.action) : void 0,
787
+ filter.tenantId ? (0, import_drizzle_orm2.eq)(auditLogs.tenantId, filter.tenantId) : void 0,
788
+ filter.userId ? (0, import_drizzle_orm2.eq)(auditLogs.userId, filter.userId) : void 0,
789
+ filter.entity ? (0, import_drizzle_orm2.eq)(auditLogs.entity, filter.entity) : void 0,
790
+ filter.entityId ? (0, import_drizzle_orm2.eq)(auditLogs.entityId, filter.entityId) : void 0,
791
+ filter.action ? (0, import_drizzle_orm2.eq)(auditLogs.action, filter.action) : void 0,
384
792
  toActorTagCondition(filter.actorTag),
385
- filter.from ? (0, import_drizzle_orm.gte)(auditLogs.createdAt, new Date(filter.from)) : void 0,
386
- filter.to ? (0, import_drizzle_orm.lte)(auditLogs.createdAt, new Date(filter.to)) : void 0
793
+ filter.from ? (0, import_drizzle_orm2.gte)(auditLogs.createdAt, new Date(filter.from)) : void 0,
794
+ filter.to ? (0, import_drizzle_orm2.lte)(auditLogs.createdAt, new Date(filter.to)) : void 0
387
795
  ];
388
- const rows = await this.db.select().from(auditLogs).where((0, import_drizzle_orm.and)(...conditions)).orderBy((0, import_drizzle_orm.desc)(auditLogs.createdAt)).limit(pageSize).offset((page - 1) * pageSize);
796
+ const rows = await this.db.select().from(auditLogs).where((0, import_drizzle_orm2.and)(...conditions)).orderBy((0, import_drizzle_orm2.desc)(auditLogs.createdAt)).limit(pageSize).offset((page - 1) * pageSize);
389
797
  return rows.map(toAuditEntry);
390
798
  }
391
799
  };
392
- DrizzleAuditQueryAdapter = _ts_decorate3([
393
- (0, import_common3.Injectable)(),
394
- _ts_param2(0, (0, import_common3.Inject)(DRIZZLE_DB_TOKEN)),
395
- _ts_metadata2("design:type", Function),
396
- _ts_metadata2("design:paramtypes", [
800
+ DrizzleAuditQueryAdapter = _ts_decorate4([
801
+ (0, import_common4.Injectable)(),
802
+ _ts_param3(0, (0, import_common4.Inject)(DRIZZLE_DB_TOKEN)),
803
+ _ts_metadata3("design:type", Function),
804
+ _ts_metadata3("design:paramtypes", [
397
805
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
398
806
  ])
399
807
  ], DrizzleAuditQueryAdapter);
400
808
  function toActorTagCondition(actorTag) {
401
809
  if (!actorTag) return void 0;
402
810
  if (actorTag.endsWith("*")) {
403
- return (0, import_drizzle_orm.like)(auditLogs.actorTag, `${escapeLikePattern(actorTag.slice(0, -1))}%`);
811
+ return (0, import_drizzle_orm2.like)(auditLogs.actorTag, `${escapeLikePattern(actorTag.slice(0, -1))}%`);
404
812
  }
405
- return (0, import_drizzle_orm.eq)(auditLogs.actorTag, actorTag);
813
+ return (0, import_drizzle_orm2.eq)(auditLogs.actorTag, actorTag);
406
814
  }
407
815
  __name(toActorTagCondition, "toActorTagCondition");
408
816
  function toAuditEntry(row) {
@@ -425,25 +833,25 @@ function toAuditEntry(row) {
425
833
  __name(toAuditEntry, "toAuditEntry");
426
834
 
427
835
  // src/drizzle-audit-stats.adapter.ts
428
- var import_common4 = require("@nestjs/common");
429
- var import_drizzle_orm2 = require("drizzle-orm");
430
- function _ts_decorate4(decorators, target, key, desc4) {
431
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
432
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
836
+ var import_common5 = require("@nestjs/common");
837
+ var import_drizzle_orm3 = require("drizzle-orm");
838
+ function _ts_decorate5(decorators, target, key, desc10) {
839
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
840
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
433
841
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
434
842
  return c > 3 && r && Object.defineProperty(target, key, r), r;
435
843
  }
436
- __name(_ts_decorate4, "_ts_decorate");
437
- function _ts_metadata3(k, v) {
844
+ __name(_ts_decorate5, "_ts_decorate");
845
+ function _ts_metadata4(k, v) {
438
846
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
439
847
  }
440
- __name(_ts_metadata3, "_ts_metadata");
441
- function _ts_param3(paramIndex, decorator) {
848
+ __name(_ts_metadata4, "_ts_metadata");
849
+ function _ts_param4(paramIndex, decorator) {
442
850
  return function(target, key) {
443
851
  decorator(target, key, paramIndex);
444
852
  };
445
853
  }
446
- __name(_ts_param3, "_ts_param");
854
+ __name(_ts_param4, "_ts_param");
447
855
  var DrizzleAuditStatsAdapter = class {
448
856
  static {
449
857
  __name(this, "DrizzleAuditStatsAdapter");
@@ -454,40 +862,40 @@ var DrizzleAuditStatsAdapter = class {
454
862
  }
455
863
  async countSince(since) {
456
864
  const rows = await this.db.select({
457
- value: (0, import_drizzle_orm2.count)()
458
- }).from(auditLogs).where((0, import_drizzle_orm2.gte)(auditLogs.createdAt, since));
865
+ value: (0, import_drizzle_orm3.count)()
866
+ }).from(auditLogs).where((0, import_drizzle_orm3.gte)(auditLogs.createdAt, since));
459
867
  return rows[0]?.value ?? 0;
460
868
  }
461
869
  };
462
- DrizzleAuditStatsAdapter = _ts_decorate4([
463
- (0, import_common4.Injectable)(),
464
- _ts_param3(0, (0, import_common4.Inject)(DRIZZLE_DB_TOKEN)),
465
- _ts_metadata3("design:type", Function),
466
- _ts_metadata3("design:paramtypes", [
870
+ DrizzleAuditStatsAdapter = _ts_decorate5([
871
+ (0, import_common5.Injectable)(),
872
+ _ts_param4(0, (0, import_common5.Inject)(DRIZZLE_DB_TOKEN)),
873
+ _ts_metadata4("design:type", Function),
874
+ _ts_metadata4("design:paramtypes", [
467
875
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
468
876
  ])
469
877
  ], DrizzleAuditStatsAdapter);
470
878
 
471
879
  // src/drizzle-mfa.adapter.ts
472
- var import_common5 = require("@nestjs/common");
473
- var import_drizzle_orm3 = require("drizzle-orm");
474
- function _ts_decorate5(decorators, target, key, desc4) {
475
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
476
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
880
+ var import_common6 = require("@nestjs/common");
881
+ var import_drizzle_orm4 = require("drizzle-orm");
882
+ function _ts_decorate6(decorators, target, key, desc10) {
883
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
884
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
477
885
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
478
886
  return c > 3 && r && Object.defineProperty(target, key, r), r;
479
887
  }
480
- __name(_ts_decorate5, "_ts_decorate");
481
- function _ts_metadata4(k, v) {
888
+ __name(_ts_decorate6, "_ts_decorate");
889
+ function _ts_metadata5(k, v) {
482
890
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
483
891
  }
484
- __name(_ts_metadata4, "_ts_metadata");
485
- function _ts_param4(paramIndex, decorator) {
892
+ __name(_ts_metadata5, "_ts_metadata");
893
+ function _ts_param5(paramIndex, decorator) {
486
894
  return function(target, key) {
487
895
  decorator(target, key, paramIndex);
488
896
  };
489
897
  }
490
- __name(_ts_param4, "_ts_param");
898
+ __name(_ts_param5, "_ts_param");
491
899
  var DrizzleMfaAdapter = class {
492
900
  static {
493
901
  __name(this, "DrizzleMfaAdapter");
@@ -497,7 +905,7 @@ var DrizzleMfaAdapter = class {
497
905
  this.db = db;
498
906
  }
499
907
  async getSecret(userId) {
500
- const rows = await this.db.select().from(superAdminMfa).where((0, import_drizzle_orm3.eq)(superAdminMfa.userId, userId)).limit(1);
908
+ const rows = await this.db.select().from(superAdminMfa).where((0, import_drizzle_orm4.eq)(superAdminMfa.userId, userId)).limit(1);
501
909
  return rows[0]?.secret ?? null;
502
910
  }
503
911
  async setSecret(userId, secret) {
@@ -517,41 +925,41 @@ var DrizzleMfaAdapter = class {
517
925
  });
518
926
  }
519
927
  async isEnabled(userId) {
520
- const rows = await this.db.select().from(superAdminMfa).where((0, import_drizzle_orm3.eq)(superAdminMfa.userId, userId)).limit(1);
928
+ const rows = await this.db.select().from(superAdminMfa).where((0, import_drizzle_orm4.eq)(superAdminMfa.userId, userId)).limit(1);
521
929
  const row = rows[0];
522
930
  return !!row?.secret && !!row?.enabledAt;
523
931
  }
524
932
  };
525
- DrizzleMfaAdapter = _ts_decorate5([
526
- (0, import_common5.Injectable)(),
527
- _ts_param4(0, (0, import_common5.Inject)(DRIZZLE_DB_TOKEN)),
528
- _ts_metadata4("design:type", Function),
529
- _ts_metadata4("design:paramtypes", [
933
+ DrizzleMfaAdapter = _ts_decorate6([
934
+ (0, import_common6.Injectable)(),
935
+ _ts_param5(0, (0, import_common6.Inject)(DRIZZLE_DB_TOKEN)),
936
+ _ts_metadata5("design:type", Function),
937
+ _ts_metadata5("design:paramtypes", [
530
938
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
531
939
  ])
532
940
  ], DrizzleMfaAdapter);
533
941
 
534
942
  // src/drizzle-plan-catalog-import-sink.adapter.ts
535
- var import_node_crypto2 = require("node:crypto");
536
- var import_common6 = require("@nestjs/common");
537
- var import_drizzle_orm4 = require("drizzle-orm");
538
- function _ts_decorate6(decorators, target, key, desc4) {
539
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
540
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
943
+ var import_node_crypto3 = require("node:crypto");
944
+ var import_common7 = require("@nestjs/common");
945
+ var import_drizzle_orm5 = require("drizzle-orm");
946
+ function _ts_decorate7(decorators, target, key, desc10) {
947
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
948
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
541
949
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
542
950
  return c > 3 && r && Object.defineProperty(target, key, r), r;
543
951
  }
544
- __name(_ts_decorate6, "_ts_decorate");
545
- function _ts_metadata5(k, v) {
952
+ __name(_ts_decorate7, "_ts_decorate");
953
+ function _ts_metadata6(k, v) {
546
954
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
547
955
  }
548
- __name(_ts_metadata5, "_ts_metadata");
549
- function _ts_param5(paramIndex, decorator) {
956
+ __name(_ts_metadata6, "_ts_metadata");
957
+ function _ts_param6(paramIndex, decorator) {
550
958
  return function(target, key) {
551
959
  decorator(target, key, paramIndex);
552
960
  };
553
961
  }
554
- __name(_ts_param5, "_ts_param");
962
+ __name(_ts_param6, "_ts_param");
555
963
  var DrizzlePlanCatalogImportSink = class {
556
964
  static {
557
965
  __name(this, "DrizzlePlanCatalogImportSink");
@@ -563,14 +971,13 @@ var DrizzlePlanCatalogImportSink = class {
563
971
  async upsertPlan(input) {
564
972
  const existing = await this.db.select({
565
973
  id: plans.id
566
- }).from(plans).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(plans.projectKey, input.projectKey), (0, import_drizzle_orm4.eq)(plans.planKey, input.planKey))).limit(1);
974
+ }).from(plans).where((0, import_drizzle_orm5.eq)(plans.planKey, input.planKey)).limit(1);
567
975
  if (existing[0]) return {
568
976
  created: false,
569
977
  skipReason: "exists"
570
978
  };
571
979
  await this.db.insert(plans).values({
572
- id: (0, import_node_crypto2.randomUUID)(),
573
- projectKey: input.projectKey,
980
+ id: (0, import_node_crypto3.randomUUID)(),
574
981
  planKey: input.planKey,
575
982
  label: input.label,
576
983
  description: input.description ?? null,
@@ -584,7 +991,7 @@ var DrizzlePlanCatalogImportSink = class {
584
991
  async upsertPlanVersion(input) {
585
992
  const existing = await this.db.select({
586
993
  id: planVersions.id
587
- }).from(planVersions).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(planVersions.planId, input.planKey), (0, import_drizzle_orm4.eq)(planVersions.version, input.version))).limit(1);
994
+ }).from(planVersions).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.eq)(planVersions.planId, input.planKey), (0, import_drizzle_orm5.eq)(planVersions.version, input.version))).limit(1);
588
995
  if (existing[0]) return {
589
996
  created: false,
590
997
  skipReason: "exists"
@@ -594,10 +1001,10 @@ var DrizzlePlanCatalogImportSink = class {
594
1001
  await this.db.update(planVersions).set({
595
1002
  supersededAt: now,
596
1003
  updatedAt: now
597
- }).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(planVersions.planId, input.planKey), (0, import_drizzle_orm4.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm4.isNull)(planVersions.supersededAt), (0, import_drizzle_orm4.lt)(planVersions.version, input.version)));
1004
+ }).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.eq)(planVersions.planId, input.planKey), (0, import_drizzle_orm5.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm5.isNull)(planVersions.supersededAt), (0, import_drizzle_orm5.lt)(planVersions.version, input.version)));
598
1005
  }
599
1006
  await this.db.insert(planVersions).values({
600
- id: (0, import_node_crypto2.randomUUID)(),
1007
+ id: (0, import_node_crypto3.randomUUID)(),
601
1008
  planId: input.planKey,
602
1009
  version: input.version,
603
1010
  features: input.features,
@@ -616,14 +1023,13 @@ var DrizzlePlanCatalogImportSink = class {
616
1023
  async upsertFeatureCatalogEntry(input) {
617
1024
  const existing = await this.db.select({
618
1025
  id: featureCatalogEntries.id
619
- }).from(featureCatalogEntries).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(featureCatalogEntries.projectKey, input.projectKey), (0, import_drizzle_orm4.eq)(featureCatalogEntries.featureKey, input.featureKey))).limit(1);
1026
+ }).from(featureCatalogEntries).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.eq)(featureCatalogEntries.featureKey, input.featureKey))).limit(1);
620
1027
  if (existing[0]) return {
621
1028
  created: false,
622
1029
  skipReason: "exists"
623
1030
  };
624
1031
  await this.db.insert(featureCatalogEntries).values({
625
- id: (0, import_node_crypto2.randomUUID)(),
626
- projectKey: input.projectKey,
1032
+ id: (0, import_node_crypto3.randomUUID)(),
627
1033
  featureKey: input.featureKey,
628
1034
  label: input.label ?? input.featureKey,
629
1035
  icon: input.icon ?? null,
@@ -637,35 +1043,40 @@ var DrizzlePlanCatalogImportSink = class {
637
1043
  };
638
1044
  }
639
1045
  };
640
- DrizzlePlanCatalogImportSink = _ts_decorate6([
641
- (0, import_common6.Injectable)(),
642
- _ts_param5(0, (0, import_common6.Inject)(DRIZZLE_DB_TOKEN)),
643
- _ts_metadata5("design:type", Function),
644
- _ts_metadata5("design:paramtypes", [
1046
+ DrizzlePlanCatalogImportSink = _ts_decorate7([
1047
+ (0, import_common7.Injectable)(),
1048
+ _ts_param6(0, (0, import_common7.Inject)(DRIZZLE_DB_TOKEN)),
1049
+ _ts_metadata6("design:type", Function),
1050
+ _ts_metadata6("design:paramtypes", [
645
1051
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
646
1052
  ])
647
1053
  ], DrizzlePlanCatalogImportSink);
648
1054
 
649
1055
  // src/drizzle-plan-catalog-read-sink.adapter.ts
650
- var import_common7 = require("@nestjs/common");
651
- var import_drizzle_orm5 = require("drizzle-orm");
652
- function _ts_decorate7(decorators, target, key, desc4) {
653
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
654
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
1056
+ var import_common8 = require("@nestjs/common");
1057
+ var import_drizzle_orm6 = require("drizzle-orm");
1058
+ var import_core2 = require("@saasicat/core");
1059
+ function _ts_decorate8(decorators, target, key, desc10) {
1060
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1061
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
655
1062
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
656
1063
  return c > 3 && r && Object.defineProperty(target, key, r), r;
657
1064
  }
658
- __name(_ts_decorate7, "_ts_decorate");
659
- function _ts_metadata6(k, v) {
1065
+ __name(_ts_decorate8, "_ts_decorate");
1066
+ function _ts_metadata7(k, v) {
660
1067
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
661
1068
  }
662
- __name(_ts_metadata6, "_ts_metadata");
663
- function _ts_param6(paramIndex, decorator) {
1069
+ __name(_ts_metadata7, "_ts_metadata");
1070
+ function _ts_param7(paramIndex, decorator) {
664
1071
  return function(target, key) {
665
1072
  decorator(target, key, paramIndex);
666
1073
  };
667
1074
  }
668
- __name(_ts_param6, "_ts_param");
1075
+ __name(_ts_param7, "_ts_param");
1076
+ var CATALOG_VERSION_FIELDS = {
1077
+ validityWindows: false,
1078
+ endsAt: false
1079
+ };
669
1080
  var DrizzlePlanCatalogReadSink = class {
670
1081
  static {
671
1082
  __name(this, "DrizzlePlanCatalogReadSink");
@@ -674,70 +1085,29 @@ var DrizzlePlanCatalogReadSink = class {
674
1085
  constructor(db) {
675
1086
  this.db = db;
676
1087
  }
677
- async loadSnapshot(projectKey) {
678
- const planRows = await this.db.select().from(plans).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.eq)(plans.projectKey, projectKey), (0, import_drizzle_orm5.isNull)(plans.deletedAt))).orderBy((0, import_drizzle_orm5.asc)(plans.sortOrder));
1088
+ async loadSnapshot() {
1089
+ const planRows = await this.db.select().from(plans).where((0, import_drizzle_orm6.isNull)(plans.deletedAt)).orderBy((0, import_drizzle_orm6.asc)(plans.sortOrder));
679
1090
  const planKeys = planRows.map((plan) => plan.planKey);
680
- const liveVersionRows = planKeys.length === 0 ? [] : await this.db.select().from(planVersions).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.inArray)(planVersions.planId, planKeys), (0, import_drizzle_orm5.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm5.isNull)(planVersions.supersededAt)));
681
- const featureRows = await this.db.select().from(featureCatalogEntries).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.eq)(featureCatalogEntries.projectKey, projectKey), (0, import_drizzle_orm5.isNull)(featureCatalogEntries.deletedAt))).orderBy((0, import_drizzle_orm5.asc)(featureCatalogEntries.sortOrder));
1091
+ const liveVersionRows = planKeys.length === 0 ? [] : await this.db.select().from(planVersions).where((0, import_drizzle_orm6.and)((0, import_drizzle_orm6.inArray)(planVersions.planId, planKeys), (0, import_drizzle_orm6.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm6.isNull)(planVersions.supersededAt)));
1092
+ const featureRows = await this.db.select().from(featureCatalogEntries).where((0, import_drizzle_orm6.isNull)(featureCatalogEntries.deletedAt)).orderBy((0, import_drizzle_orm6.asc)(featureCatalogEntries.sortOrder));
682
1093
  return {
683
- plans: planRows.map(toPlanRow),
684
- livePlanVersions: liveVersionRows.map(toPlanVersionRow),
1094
+ plans: planRows.map((row) => (0, import_core2.toPlanRow)(row)),
1095
+ livePlanVersions: liveVersionRows.map((row) => (0, import_core2.toPlanVersionRow)(row, row.planId, CATALOG_VERSION_FIELDS)),
685
1096
  featureEntries: featureRows.map(toFeatureCatalogEntryRow)
686
1097
  };
687
1098
  }
688
1099
  };
689
- DrizzlePlanCatalogReadSink = _ts_decorate7([
690
- (0, import_common7.Injectable)(),
691
- _ts_param6(0, (0, import_common7.Inject)(DRIZZLE_DB_TOKEN)),
692
- _ts_metadata6("design:type", Function),
693
- _ts_metadata6("design:paramtypes", [
1100
+ DrizzlePlanCatalogReadSink = _ts_decorate8([
1101
+ (0, import_common8.Injectable)(),
1102
+ _ts_param7(0, (0, import_common8.Inject)(DRIZZLE_DB_TOKEN)),
1103
+ _ts_metadata7("design:type", Function),
1104
+ _ts_metadata7("design:paramtypes", [
694
1105
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
695
1106
  ])
696
1107
  ], DrizzlePlanCatalogReadSink);
697
- function toPlanRow(row) {
698
- return {
699
- id: row.id,
700
- projectKey: row.projectKey,
701
- planKey: row.planKey,
702
- label: row.label,
703
- description: row.description,
704
- icon: row.icon,
705
- sortOrder: row.sortOrder,
706
- createdAt: row.createdAt.toISOString(),
707
- updatedAt: row.updatedAt.toISOString(),
708
- deletedAt: row.deletedAt ? row.deletedAt.toISOString() : null
709
- };
710
- }
711
- __name(toPlanRow, "toPlanRow");
712
- function toPlanVersionRow(row) {
713
- return {
714
- id: row.id,
715
- planId: row.planId,
716
- version: row.version,
717
- baseVersionId: row.baseVersionId,
718
- publishedAt: row.publishedAt ? row.publishedAt.toISOString() : null,
719
- supersededAt: row.supersededAt ? row.supersededAt.toISOString() : null,
720
- publishedChanges: row.publishedChanges ?? null,
721
- changeNote: row.changeNote,
722
- nonRegressive: row.nonRegressive,
723
- validFrom: null,
724
- validUntil: null,
725
- createdByUserId: row.createdByUserId,
726
- publishedByUserId: row.publishedByUserId,
727
- createdAt: row.createdAt.toISOString(),
728
- updatedAt: row.updatedAt.toISOString(),
729
- features: toStringArray(row.features),
730
- quotas: toQuotaMap(row.quotas),
731
- monthlyNet: String(row.monthlyNet),
732
- yearlyNet: String(row.yearlyNet),
733
- marketed: row.marketed
734
- };
735
- }
736
- __name(toPlanVersionRow, "toPlanVersionRow");
737
1108
  function toFeatureCatalogEntryRow(row) {
738
1109
  return {
739
1110
  id: row.id,
740
- projectKey: row.projectKey,
741
1111
  featureKey: row.featureKey,
742
1112
  label: row.label,
743
1113
  description: row.description,
@@ -763,26 +1133,84 @@ function toFeatureCatalogEntryRow(row) {
763
1133
  }
764
1134
  __name(toFeatureCatalogEntryRow, "toFeatureCatalogEntryRow");
765
1135
 
1136
+ // src/drizzle-payment-event-log.adapter.ts
1137
+ var import_node_crypto4 = require("node:crypto");
1138
+ var import_drizzle_orm7 = require("drizzle-orm");
1139
+ var import_common9 = require("@nestjs/common");
1140
+ function _ts_decorate9(decorators, target, key, desc10) {
1141
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1142
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1143
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1144
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1145
+ }
1146
+ __name(_ts_decorate9, "_ts_decorate");
1147
+ function _ts_metadata8(k, v) {
1148
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1149
+ }
1150
+ __name(_ts_metadata8, "_ts_metadata");
1151
+ function _ts_param8(paramIndex, decorator) {
1152
+ return function(target, key) {
1153
+ decorator(target, key, paramIndex);
1154
+ };
1155
+ }
1156
+ __name(_ts_param8, "_ts_param");
1157
+ var DrizzlePaymentEventLog = class {
1158
+ static {
1159
+ __name(this, "DrizzlePaymentEventLog");
1160
+ }
1161
+ db;
1162
+ constructor(db) {
1163
+ this.db = db;
1164
+ }
1165
+ async claim(claim, tx) {
1166
+ const inserted = await resolveDb(this.db, tx).insert(paymentEventLog).values({
1167
+ id: (0, import_node_crypto4.randomUUID)(),
1168
+ gatewayAccount: claim.gatewayAccount,
1169
+ eventId: claim.eventId,
1170
+ provider: claim.provider,
1171
+ sessionId: claim.sessionId,
1172
+ status: claim.kind,
1173
+ payload: claim.summary
1174
+ }).onConflictDoNothing().returning({
1175
+ id: paymentEventLog.id
1176
+ });
1177
+ return inserted.length === 1;
1178
+ }
1179
+ async releaseSession(gatewayAccount, eventId, tx) {
1180
+ await resolveDb(this.db, tx).update(paymentEventLog).set({
1181
+ sessionId: null
1182
+ }).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(paymentEventLog.gatewayAccount, gatewayAccount), (0, import_drizzle_orm7.eq)(paymentEventLog.eventId, eventId)));
1183
+ }
1184
+ };
1185
+ DrizzlePaymentEventLog = _ts_decorate9([
1186
+ (0, import_common9.Injectable)(),
1187
+ _ts_param8(0, (0, import_common9.Inject)(DRIZZLE_DB_TOKEN)),
1188
+ _ts_metadata8("design:type", Function),
1189
+ _ts_metadata8("design:paramtypes", [
1190
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1191
+ ])
1192
+ ], DrizzlePaymentEventLog);
1193
+
766
1194
  // src/drizzle-plan-version.repository.ts
767
- var import_common8 = require("@nestjs/common");
768
- var import_drizzle_orm6 = require("drizzle-orm");
769
- function _ts_decorate8(decorators, target, key, desc4) {
770
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
771
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
1195
+ var import_common10 = require("@nestjs/common");
1196
+ var import_drizzle_orm8 = require("drizzle-orm");
1197
+ function _ts_decorate10(decorators, target, key, desc10) {
1198
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1199
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
772
1200
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
773
1201
  return c > 3 && r && Object.defineProperty(target, key, r), r;
774
1202
  }
775
- __name(_ts_decorate8, "_ts_decorate");
776
- function _ts_metadata7(k, v) {
1203
+ __name(_ts_decorate10, "_ts_decorate");
1204
+ function _ts_metadata9(k, v) {
777
1205
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
778
1206
  }
779
- __name(_ts_metadata7, "_ts_metadata");
780
- function _ts_param7(paramIndex, decorator) {
1207
+ __name(_ts_metadata9, "_ts_metadata");
1208
+ function _ts_param9(paramIndex, decorator) {
781
1209
  return function(target, key) {
782
1210
  decorator(target, key, paramIndex);
783
1211
  };
784
1212
  }
785
- __name(_ts_param7, "_ts_param");
1213
+ __name(_ts_param9, "_ts_param");
786
1214
  var DrizzlePlanVersionRepository = class {
787
1215
  static {
788
1216
  __name(this, "DrizzlePlanVersionRepository");
@@ -793,7 +1221,7 @@ var DrizzlePlanVersionRepository = class {
793
1221
  }
794
1222
  async findLatestLive(planId, tx) {
795
1223
  const db = resolveDb(this.db, tx);
796
- const rows = await db.select().from(planVersions).where((0, import_drizzle_orm6.and)((0, import_drizzle_orm6.eq)(planVersions.planId, planId), (0, import_drizzle_orm6.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm6.isNull)(planVersions.supersededAt))).orderBy((0, import_drizzle_orm6.desc)(planVersions.version)).limit(1);
1224
+ const rows = await db.select().from(planVersions).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(planVersions.planId, planId), (0, import_drizzle_orm8.isNotNull)(planVersions.publishedAt), (0, import_drizzle_orm8.isNull)(planVersions.supersededAt))).orderBy((0, import_drizzle_orm8.desc)(planVersions.version)).limit(1);
797
1225
  const row = rows[0];
798
1226
  if (!row) return null;
799
1227
  return {
@@ -803,319 +1231,1992 @@ var DrizzlePlanVersionRepository = class {
803
1231
  };
804
1232
  }
805
1233
  };
806
- DrizzlePlanVersionRepository = _ts_decorate8([
807
- (0, import_common8.Injectable)(),
808
- _ts_param7(0, (0, import_common8.Inject)(DRIZZLE_DB_TOKEN)),
809
- _ts_metadata7("design:type", Function),
810
- _ts_metadata7("design:paramtypes", [
1234
+ DrizzlePlanVersionRepository = _ts_decorate10([
1235
+ (0, import_common10.Injectable)(),
1236
+ _ts_param9(0, (0, import_common10.Inject)(DRIZZLE_DB_TOKEN)),
1237
+ _ts_metadata9("design:type", Function),
1238
+ _ts_metadata9("design:paramtypes", [
811
1239
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
812
1240
  ])
813
1241
  ], DrizzlePlanVersionRepository);
814
1242
 
815
- // src/drizzle-promo-code-redemption.repository.ts
816
- var import_node_crypto3 = require("node:crypto");
817
- var import_common9 = require("@nestjs/common");
818
- var import_drizzle_orm7 = require("drizzle-orm");
819
- function _ts_decorate9(decorators, target, key, desc4) {
820
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
821
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
1243
+ // src/drizzle-promo-code-hold.repository.ts
1244
+ var import_node_crypto6 = require("node:crypto");
1245
+ var import_common12 = require("@nestjs/common");
1246
+ var import_drizzle_orm10 = require("drizzle-orm");
1247
+
1248
+ // src/drizzle-promo-code.repository.ts
1249
+ var import_node_crypto5 = require("node:crypto");
1250
+ var import_common11 = require("@nestjs/common");
1251
+ var import_drizzle_orm9 = require("drizzle-orm");
1252
+ function _ts_decorate11(decorators, target, key, desc10) {
1253
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1254
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
822
1255
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
823
1256
  return c > 3 && r && Object.defineProperty(target, key, r), r;
824
1257
  }
825
- __name(_ts_decorate9, "_ts_decorate");
826
- function _ts_metadata8(k, v) {
1258
+ __name(_ts_decorate11, "_ts_decorate");
1259
+ function _ts_metadata10(k, v) {
827
1260
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
828
1261
  }
829
- __name(_ts_metadata8, "_ts_metadata");
830
- function _ts_param8(paramIndex, decorator) {
1262
+ __name(_ts_metadata10, "_ts_metadata");
1263
+ function _ts_param10(paramIndex, decorator) {
831
1264
  return function(target, key) {
832
1265
  decorator(target, key, paramIndex);
833
1266
  };
834
1267
  }
835
- __name(_ts_param8, "_ts_param");
836
- var DrizzlePromoCodeRedemptionRepository = class {
1268
+ __name(_ts_param10, "_ts_param");
1269
+ var DrizzlePromoCodeRepository = class {
837
1270
  static {
838
- __name(this, "DrizzlePromoCodeRedemptionRepository");
1271
+ __name(this, "DrizzlePromoCodeRepository");
839
1272
  }
840
1273
  db;
841
1274
  constructor(db) {
842
1275
  this.db = db;
843
1276
  }
844
- async findBySubscription(subscriptionId, tx) {
845
- const db = resolveDb(this.db, tx);
846
- const rows = await db.select().from(promoCodeRedemptions).where((0, import_drizzle_orm7.eq)(promoCodeRedemptions.subscriptionId, subscriptionId)).limit(1);
1277
+ async findById(id) {
1278
+ const rows = await this.db.select().from(promoCodes).where((0, import_drizzle_orm9.eq)(promoCodes.id, id)).limit(1);
847
1279
  const row = rows[0];
848
1280
  return row ? toRecord(row) : null;
849
1281
  }
850
- async create(data, tx) {
1282
+ async findByCode(code, tx) {
851
1283
  const db = resolveDb(this.db, tx);
852
- const rows = await db.insert(promoCodeRedemptions).values({
853
- id: (0, import_node_crypto3.randomUUID)(),
854
- promoCodeId: data.promoCodeId,
855
- subscriptionId: data.subscriptionId,
856
- tenantId: data.tenantId,
857
- appliedValueType: data.appliedValueType,
858
- appliedValue: data.appliedValue,
859
- appliedDurationType: data.appliedDurationType,
860
- appliedDurationValue: data.appliedDurationValue,
861
- startsAt: data.startsAt,
862
- endsAt: data.endsAt
1284
+ const rows = await db.select().from(promoCodes).where((0, import_drizzle_orm9.eq)(promoCodes.code, normalizeCode(code))).limit(1);
1285
+ const row = rows[0];
1286
+ if (!row || row.deletedAt) return null;
1287
+ return toRecord(row);
1288
+ }
1289
+ async findMany(filter) {
1290
+ const conditions = [
1291
+ (0, import_drizzle_orm9.isNull)(promoCodes.deletedAt),
1292
+ filter.status ? (0, import_drizzle_orm9.eq)(promoCodes.status, filter.status) : void 0,
1293
+ filter.campaignTag ? (0, import_drizzle_orm9.eq)(promoCodes.campaignTag, filter.campaignTag) : void 0,
1294
+ filter.search ? (0, import_drizzle_orm9.like)(promoCodes.code, `%${escapeLikePattern(normalizeCode(filter.search))}%`) : void 0
1295
+ ];
1296
+ const rows = await this.db.select().from(promoCodes).where((0, import_drizzle_orm9.and)(...conditions)).orderBy(import_drizzle_orm9.sql`${promoCodes.createdAt} DESC`);
1297
+ return rows.map(toRecord);
1298
+ }
1299
+ async create(data) {
1300
+ const rows = await this.db.insert(promoCodes).values({
1301
+ id: (0, import_node_crypto5.randomUUID)(),
1302
+ code: normalizeCode(data.code),
1303
+ valueType: data.valueType,
1304
+ value: data.value.toFixed(2),
1305
+ durationType: data.durationType,
1306
+ durationValue: data.durationValue ?? null,
1307
+ validFrom: data.validFrom ?? null,
1308
+ validUntil: data.validUntil ?? null,
1309
+ maxRedemptions: data.maxRedemptions ?? null,
1310
+ appliesToPlans: data.appliesToPlans ?? [],
1311
+ appliesToBilling: data.appliesToBilling ?? null,
1312
+ firstTimeCustomersOnly: data.firstTimeCustomersOnly ?? true,
1313
+ minimumPlanAmountGross: data.minimumPlanAmountGross?.toFixed(2) ?? null,
1314
+ allowZeroInvoice: data.allowZeroInvoice ?? false,
1315
+ description: data.description ?? null,
1316
+ campaignTag: data.campaignTag ?? null,
1317
+ revenueDeductionAccount: data.revenueDeductionAccount ?? null,
1318
+ createdById: data.createdById,
1319
+ updatedAt: /* @__PURE__ */ new Date()
863
1320
  }).returning();
864
1321
  return toRecord(rows[0]);
865
1322
  }
866
- async setReversed(id, tx) {
867
- const db = resolveDb(this.db, tx);
868
- const rows = await db.update(promoCodeRedemptions).set({
869
- status: "REVERSED",
870
- reversedAt: /* @__PURE__ */ new Date()
871
- }).where((0, import_drizzle_orm7.eq)(promoCodeRedemptions.id, id)).returning();
872
- const row = rows[0];
873
- if (!row) {
874
- throw new Error(`PromoCodeRedemption ${id} not found.`);
1323
+ async update(id, data) {
1324
+ const rows = await this.db.update(promoCodes).set({
1325
+ status: data.status,
1326
+ description: data.description,
1327
+ validUntil: data.validUntil,
1328
+ maxRedemptions: data.maxRedemptions,
1329
+ updatedAt: /* @__PURE__ */ new Date()
1330
+ }).where((0, import_drizzle_orm9.eq)(promoCodes.id, id)).returning();
1331
+ const row = rows[0];
1332
+ if (!row) {
1333
+ throw new Error(`PromoCode ${id} not found.`);
1334
+ }
1335
+ return toRecord(row);
1336
+ }
1337
+ async softDelete(id) {
1338
+ await this.db.update(promoCodes).set({
1339
+ deletedAt: /* @__PURE__ */ new Date(),
1340
+ updatedAt: /* @__PURE__ */ new Date()
1341
+ }).where((0, import_drizzle_orm9.eq)(promoCodes.id, id));
1342
+ }
1343
+ async claimSlot(id, tx) {
1344
+ const db = resolveDb(this.db, tx);
1345
+ const claimed = await db.update(promoCodes).set({
1346
+ redemptionsCount: import_drizzle_orm9.sql`${promoCodes.redemptionsCount} + 1`,
1347
+ updatedAt: /* @__PURE__ */ new Date()
1348
+ }).where(hasFreeSlot(id)).returning({
1349
+ id: promoCodes.id
1350
+ });
1351
+ return claimed.length === 1;
1352
+ }
1353
+ async markExhaustedIfFull(id, tx) {
1354
+ const db = resolveDb(this.db, tx);
1355
+ await db.update(promoCodes).set({
1356
+ status: "EXHAUSTED",
1357
+ updatedAt: /* @__PURE__ */ new Date()
1358
+ }).where((0, import_drizzle_orm9.and)((0, import_drizzle_orm9.eq)(promoCodes.id, id), (0, import_drizzle_orm9.eq)(promoCodes.status, "ACTIVE"), (0, import_drizzle_orm9.isNotNull)(promoCodes.maxRedemptions), (0, import_drizzle_orm9.gte)(promoCodes.redemptionsCount, promoCodes.maxRedemptions)));
1359
+ }
1360
+ async releaseSlot(id, tx) {
1361
+ const db = resolveDb(this.db, tx);
1362
+ await db.update(promoCodes).set({
1363
+ redemptionsCount: import_drizzle_orm9.sql`GREATEST(${promoCodes.redemptionsCount} - 1, 0)`,
1364
+ status: import_drizzle_orm9.sql`CASE WHEN ${promoCodes.status} = 'EXHAUSTED' THEN 'ACTIVE' ELSE ${promoCodes.status} END`,
1365
+ updatedAt: /* @__PURE__ */ new Date()
1366
+ }).where((0, import_drizzle_orm9.eq)(promoCodes.id, id));
1367
+ }
1368
+ async expireDueCodes(now) {
1369
+ const expired = await this.db.update(promoCodes).set({
1370
+ status: "EXPIRED",
1371
+ updatedAt: /* @__PURE__ */ new Date()
1372
+ }).where((0, import_drizzle_orm9.and)((0, import_drizzle_orm9.inArray)(promoCodes.status, [
1373
+ "ACTIVE",
1374
+ "PAUSED"
1375
+ ]), (0, import_drizzle_orm9.lt)(promoCodes.validUntil, now))).returning({
1376
+ id: promoCodes.id
1377
+ });
1378
+ return expired.length;
1379
+ }
1380
+ };
1381
+ DrizzlePromoCodeRepository = _ts_decorate11([
1382
+ (0, import_common11.Injectable)(),
1383
+ _ts_param10(0, (0, import_common11.Inject)(DRIZZLE_DB_TOKEN)),
1384
+ _ts_metadata10("design:type", Function),
1385
+ _ts_metadata10("design:paramtypes", [
1386
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1387
+ ])
1388
+ ], DrizzlePromoCodeRepository);
1389
+ function hasFreeSlot(id) {
1390
+ return (0, import_drizzle_orm9.and)((0, import_drizzle_orm9.eq)(promoCodes.id, id), (0, import_drizzle_orm9.eq)(promoCodes.status, "ACTIVE"), (0, import_drizzle_orm9.isNull)(promoCodes.deletedAt), (0, import_drizzle_orm9.or)((0, import_drizzle_orm9.isNull)(promoCodes.maxRedemptions), import_drizzle_orm9.sql`${promoCodes.redemptionsCount} + ${promoCodes.heldCount} < ${promoCodes.maxRedemptions}`));
1391
+ }
1392
+ __name(hasFreeSlot, "hasFreeSlot");
1393
+ function normalizeCode(code) {
1394
+ return code.trim().toUpperCase();
1395
+ }
1396
+ __name(normalizeCode, "normalizeCode");
1397
+ function toRecord(row) {
1398
+ return {
1399
+ id: row.id,
1400
+ code: row.code,
1401
+ valueType: row.valueType,
1402
+ value: String(row.value),
1403
+ durationType: row.durationType,
1404
+ durationValue: row.durationValue,
1405
+ validFrom: row.validFrom,
1406
+ validUntil: row.validUntil,
1407
+ maxRedemptions: row.maxRedemptions,
1408
+ redemptionsCount: row.redemptionsCount,
1409
+ heldCount: row.heldCount,
1410
+ appliesToPlans: row.appliesToPlans ?? [],
1411
+ appliesToBilling: row.appliesToBilling ?? null,
1412
+ firstTimeCustomersOnly: row.firstTimeCustomersOnly,
1413
+ minimumPlanAmountGross: row.minimumPlanAmountGross === null ? null : String(row.minimumPlanAmountGross),
1414
+ allowZeroInvoice: row.allowZeroInvoice,
1415
+ status: row.status,
1416
+ description: row.description,
1417
+ campaignTag: row.campaignTag,
1418
+ revenueDeductionAccount: row.revenueDeductionAccount,
1419
+ createdById: row.createdById,
1420
+ createdAt: row.createdAt,
1421
+ updatedAt: row.updatedAt,
1422
+ deletedAt: row.deletedAt
1423
+ };
1424
+ }
1425
+ __name(toRecord, "toRecord");
1426
+
1427
+ // src/drizzle-promo-code-hold.repository.ts
1428
+ function _ts_decorate12(decorators, target, key, desc10) {
1429
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1430
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1431
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1432
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1433
+ }
1434
+ __name(_ts_decorate12, "_ts_decorate");
1435
+ function _ts_metadata11(k, v) {
1436
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1437
+ }
1438
+ __name(_ts_metadata11, "_ts_metadata");
1439
+ function _ts_param11(paramIndex, decorator) {
1440
+ return function(target, key) {
1441
+ decorator(target, key, paramIndex);
1442
+ };
1443
+ }
1444
+ __name(_ts_param11, "_ts_param");
1445
+ var HOLD_FIELDS = {
1446
+ id: promoCodeHolds.id,
1447
+ promoCodeId: promoCodeHolds.promoCodeId,
1448
+ checkoutOfferId: promoCodeHolds.checkoutOfferId,
1449
+ expiresAt: promoCodeHolds.expiresAt,
1450
+ createdAt: promoCodeHolds.createdAt
1451
+ };
1452
+ var CURRENT_TRANSACTION = import_drizzle_orm10.sql`txid_current()`;
1453
+ var atExpiry = /* @__PURE__ */ __name((expiresAt) => import_drizzle_orm10.sql.param(expiresAt, promoCodeHolds.expiresAt), "atExpiry");
1454
+ var DrizzlePromoCodeHoldRepository = class {
1455
+ static {
1456
+ __name(this, "DrizzlePromoCodeHoldRepository");
1457
+ }
1458
+ db;
1459
+ constructor(db) {
1460
+ this.db = db;
1461
+ }
1462
+ async findByCheckoutOffer(checkoutOfferId, tx) {
1463
+ const rows = await resolveDb(this.db, tx).select(HOLD_FIELDS).from(promoCodeHolds).where((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, checkoutOfferId)).limit(1);
1464
+ return rows[0] ?? null;
1465
+ }
1466
+ async take(hold) {
1467
+ return this.onTransaction(void 0, async (db) => {
1468
+ const code = await db.select({
1469
+ id: promoCodes.id
1470
+ }).from(promoCodes).where((0, import_drizzle_orm10.eq)(promoCodes.id, hold.promoCodeId)).for("update");
1471
+ if (code.length === 0) return {
1472
+ outcome: "no-slot"
1473
+ };
1474
+ const held = await db.select({
1475
+ id: promoCodeHolds.id
1476
+ }).from(promoCodeHolds).where((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, hold.checkoutOfferId)).limit(1);
1477
+ if (held.length > 0) return {
1478
+ outcome: "offer-holds-one"
1479
+ };
1480
+ const counted = await db.update(promoCodes).set({
1481
+ heldCount: import_drizzle_orm10.sql`${promoCodes.heldCount} + 1`,
1482
+ updatedAt: /* @__PURE__ */ new Date()
1483
+ }).where(hasFreeSlot(hold.promoCodeId)).returning({
1484
+ id: promoCodes.id
1485
+ });
1486
+ if (counted.length === 0) return {
1487
+ outcome: "no-slot"
1488
+ };
1489
+ const [created] = await db.insert(promoCodeHolds).values({
1490
+ id: (0, import_node_crypto6.randomUUID)(),
1491
+ ...hold
1492
+ }).returning(HOLD_FIELDS);
1493
+ return {
1494
+ outcome: "taken",
1495
+ hold: created
1496
+ };
1497
+ });
1498
+ }
1499
+ async extend(checkoutOfferId, promoCodeId, expiresAt) {
1500
+ const extended = await this.db.update(promoCodeHolds).set({
1501
+ expiresAt: import_drizzle_orm10.sql`GREATEST(${promoCodeHolds.expiresAt}, ${atExpiry(expiresAt)})`
1502
+ }).where((0, import_drizzle_orm10.and)((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, checkoutOfferId), (0, import_drizzle_orm10.eq)(promoCodeHolds.promoCodeId, promoCodeId))).returning({
1503
+ id: promoCodeHolds.id
1504
+ });
1505
+ return extended.length === 1;
1506
+ }
1507
+ async release(checkoutOfferId, tx) {
1508
+ return this.releaseWhere((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, checkoutOfferId), tx);
1509
+ }
1510
+ async releaseIfUnmoved(checkoutOfferId, expiresAt) {
1511
+ return this.releaseWhere((0, import_drizzle_orm10.and)((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, checkoutOfferId), (0, import_drizzle_orm10.eq)(promoCodeHolds.expiresAt, expiresAt)));
1512
+ }
1513
+ async handOver(checkoutOfferId, now, tx) {
1514
+ const marked = await resolveDb(this.db, tx).update(promoCodeHolds).set({
1515
+ handedOverTx: CURRENT_TRANSACTION
1516
+ }).where((0, import_drizzle_orm10.and)((0, import_drizzle_orm10.eq)(promoCodeHolds.checkoutOfferId, checkoutOfferId), (0, import_drizzle_orm10.gt)(promoCodeHolds.expiresAt, now))).returning({
1517
+ id: promoCodeHolds.id
1518
+ });
1519
+ return marked.length === 1;
1520
+ }
1521
+ async convertHandedOver(promoCodeId, tx) {
1522
+ const db = resolveDb(this.db, tx);
1523
+ const handedOver = await db.select({
1524
+ id: promoCodeHolds.id
1525
+ }).from(promoCodeHolds).where((0, import_drizzle_orm10.and)((0, import_drizzle_orm10.eq)(promoCodeHolds.promoCodeId, promoCodeId), (0, import_drizzle_orm10.eq)(promoCodeHolds.handedOverTx, CURRENT_TRANSACTION))).limit(1);
1526
+ if (handedOver.length === 0) return false;
1527
+ const gone = await db.delete(promoCodeHolds).where((0, import_drizzle_orm10.eq)(promoCodeHolds.id, handedOver[0].id)).returning({
1528
+ promoCodeId: promoCodeHolds.promoCodeId
1529
+ });
1530
+ await giveBack(db, gone, {
1531
+ redeemed: true
1532
+ });
1533
+ return gone.length === 1;
1534
+ }
1535
+ async expireDue(now, promoCodeId, tx) {
1536
+ return this.onTransaction(tx, async (db) => {
1537
+ const gone = await db.delete(promoCodeHolds).where((0, import_drizzle_orm10.and)((0, import_drizzle_orm10.lte)(promoCodeHolds.expiresAt, now), import_drizzle_orm10.sql`${promoCodeHolds.handedOverTx} IS DISTINCT FROM ${CURRENT_TRANSACTION}`, promoCodeId ? (0, import_drizzle_orm10.eq)(promoCodeHolds.promoCodeId, promoCodeId) : void 0)).returning({
1538
+ promoCodeId: promoCodeHolds.promoCodeId
1539
+ });
1540
+ await giveBack(db, gone, {
1541
+ redeemed: false
1542
+ });
1543
+ return gone.length;
1544
+ });
1545
+ }
1546
+ /** Deletes the holds `where` matches and gives their slots back. */
1547
+ releaseWhere(where, tx) {
1548
+ return this.onTransaction(tx, async (db) => {
1549
+ const gone = await db.delete(promoCodeHolds).where(where).returning({
1550
+ promoCodeId: promoCodeHolds.promoCodeId
1551
+ });
1552
+ await giveBack(db, gone, {
1553
+ redeemed: false
1554
+ });
1555
+ return gone.length === 1;
1556
+ });
1557
+ }
1558
+ /** Runs `work` on a transaction of its own, a savepoint where the caller has one. */
1559
+ onTransaction(tx, work) {
1560
+ return resolveDb(this.db, tx).transaction((transaction) => work(transaction));
1561
+ }
1562
+ };
1563
+ DrizzlePromoCodeHoldRepository = _ts_decorate12([
1564
+ (0, import_common12.Injectable)(),
1565
+ _ts_param11(0, (0, import_common12.Inject)(DRIZZLE_DB_TOKEN)),
1566
+ _ts_metadata11("design:type", Function),
1567
+ _ts_metadata11("design:paramtypes", [
1568
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1569
+ ])
1570
+ ], DrizzlePromoCodeHoldRepository);
1571
+ async function giveBack(db, ended, { redeemed }) {
1572
+ const perCode = /* @__PURE__ */ new Map();
1573
+ for (const { promoCodeId } of ended) {
1574
+ perCode.set(promoCodeId, (perCode.get(promoCodeId) ?? 0) + 1);
1575
+ }
1576
+ for (const promoCodeId of [
1577
+ ...perCode.keys()
1578
+ ].sort()) {
1579
+ const count4 = perCode.get(promoCodeId) ?? 0;
1580
+ await db.update(promoCodes).set({
1581
+ heldCount: import_drizzle_orm10.sql`GREATEST(${promoCodes.heldCount} - ${count4}, 0)`,
1582
+ ...redeemed ? {
1583
+ redemptionsCount: import_drizzle_orm10.sql`${promoCodes.redemptionsCount} + ${count4}`
1584
+ } : {},
1585
+ updatedAt: /* @__PURE__ */ new Date()
1586
+ }).where((0, import_drizzle_orm10.eq)(promoCodes.id, promoCodeId));
1587
+ }
1588
+ }
1589
+ __name(giveBack, "giveBack");
1590
+
1591
+ // src/drizzle-promo-code-redemption.repository.ts
1592
+ var import_node_crypto7 = require("node:crypto");
1593
+ var import_common13 = require("@nestjs/common");
1594
+ var import_drizzle_orm11 = require("drizzle-orm");
1595
+ function _ts_decorate13(decorators, target, key, desc10) {
1596
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1597
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1598
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1599
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1600
+ }
1601
+ __name(_ts_decorate13, "_ts_decorate");
1602
+ function _ts_metadata12(k, v) {
1603
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1604
+ }
1605
+ __name(_ts_metadata12, "_ts_metadata");
1606
+ function _ts_param12(paramIndex, decorator) {
1607
+ return function(target, key) {
1608
+ decorator(target, key, paramIndex);
1609
+ };
1610
+ }
1611
+ __name(_ts_param12, "_ts_param");
1612
+ var DrizzlePromoCodeRedemptionRepository = class {
1613
+ static {
1614
+ __name(this, "DrizzlePromoCodeRedemptionRepository");
1615
+ }
1616
+ db;
1617
+ constructor(db) {
1618
+ this.db = db;
1619
+ }
1620
+ async findBySubscription(subscriptionId, tx) {
1621
+ const db = resolveDb(this.db, tx);
1622
+ const rows = await db.select().from(promoCodeRedemptions).where((0, import_drizzle_orm11.eq)(promoCodeRedemptions.subscriptionId, subscriptionId)).limit(1);
1623
+ const row = rows[0];
1624
+ return row ? toRecord2(row) : null;
1625
+ }
1626
+ async create(data, tx) {
1627
+ const db = resolveDb(this.db, tx);
1628
+ const rows = await db.insert(promoCodeRedemptions).values({
1629
+ id: (0, import_node_crypto7.randomUUID)(),
1630
+ promoCodeId: data.promoCodeId,
1631
+ subscriptionId: data.subscriptionId,
1632
+ tenantId: data.tenantId,
1633
+ appliedValueType: data.appliedValueType,
1634
+ appliedValue: data.appliedValue,
1635
+ appliedDurationType: data.appliedDurationType,
1636
+ appliedDurationValue: data.appliedDurationValue,
1637
+ startsAt: data.startsAt,
1638
+ endsAt: data.endsAt
1639
+ }).returning();
1640
+ return toRecord2(rows[0]);
1641
+ }
1642
+ async setReversed(id, tx) {
1643
+ const db = resolveDb(this.db, tx);
1644
+ const rows = await db.update(promoCodeRedemptions).set({
1645
+ status: "REVERSED",
1646
+ reversedAt: /* @__PURE__ */ new Date()
1647
+ }).where((0, import_drizzle_orm11.eq)(promoCodeRedemptions.id, id)).returning();
1648
+ const row = rows[0];
1649
+ if (!row) {
1650
+ throw new Error(`PromoCodeRedemption ${id} not found.`);
1651
+ }
1652
+ return toRecord2(row);
1653
+ }
1654
+ async countByPromoCode(promoCodeId, status) {
1655
+ const rows = await this.db.select({
1656
+ id: promoCodeRedemptions.id
1657
+ }).from(promoCodeRedemptions).where((0, import_drizzle_orm11.and)((0, import_drizzle_orm11.eq)(promoCodeRedemptions.promoCodeId, promoCodeId), status ? (0, import_drizzle_orm11.eq)(promoCodeRedemptions.status, status) : void 0));
1658
+ return rows.length;
1659
+ }
1660
+ async listByPromoCode(promoCodeId) {
1661
+ const rows = await this.db.select().from(promoCodeRedemptions).where((0, import_drizzle_orm11.eq)(promoCodeRedemptions.promoCodeId, promoCodeId)).orderBy((0, import_drizzle_orm11.desc)(promoCodeRedemptions.redeemedAt));
1662
+ return rows.map(toRecord2);
1663
+ }
1664
+ async expireDueRedemptions(now) {
1665
+ const expired = await this.db.update(promoCodeRedemptions).set({
1666
+ status: "EXPIRED"
1667
+ }).where((0, import_drizzle_orm11.and)((0, import_drizzle_orm11.eq)(promoCodeRedemptions.status, "ACTIVE"), (0, import_drizzle_orm11.lt)(promoCodeRedemptions.endsAt, now))).returning({
1668
+ id: promoCodeRedemptions.id
1669
+ });
1670
+ return expired.length;
1671
+ }
1672
+ };
1673
+ DrizzlePromoCodeRedemptionRepository = _ts_decorate13([
1674
+ (0, import_common13.Injectable)(),
1675
+ _ts_param12(0, (0, import_common13.Inject)(DRIZZLE_DB_TOKEN)),
1676
+ _ts_metadata12("design:type", Function),
1677
+ _ts_metadata12("design:paramtypes", [
1678
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1679
+ ])
1680
+ ], DrizzlePromoCodeRedemptionRepository);
1681
+ function toRecord2(row) {
1682
+ return {
1683
+ id: row.id,
1684
+ promoCodeId: row.promoCodeId,
1685
+ subscriptionId: row.subscriptionId,
1686
+ tenantId: row.tenantId,
1687
+ appliedValueType: row.appliedValueType,
1688
+ appliedValue: String(row.appliedValue),
1689
+ appliedDurationType: row.appliedDurationType,
1690
+ appliedDurationValue: row.appliedDurationValue,
1691
+ startsAt: row.startsAt,
1692
+ endsAt: row.endsAt,
1693
+ status: row.status,
1694
+ redeemedAt: row.redeemedAt,
1695
+ reversedAt: row.reversedAt
1696
+ };
1697
+ }
1698
+ __name(toRecord2, "toRecord");
1699
+
1700
+ // src/drizzle-bundle.repository.ts
1701
+ var import_node_crypto8 = require("node:crypto");
1702
+ var import_common14 = require("@nestjs/common");
1703
+ var import_drizzle_orm12 = require("drizzle-orm");
1704
+ var import_core3 = require("@saasicat/core");
1705
+ function _ts_decorate14(decorators, target, key, desc10) {
1706
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1707
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1708
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1709
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1710
+ }
1711
+ __name(_ts_decorate14, "_ts_decorate");
1712
+ function _ts_metadata13(k, v) {
1713
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1714
+ }
1715
+ __name(_ts_metadata13, "_ts_metadata");
1716
+ function _ts_param13(paramIndex, decorator) {
1717
+ return function(target, key) {
1718
+ decorator(target, key, paramIndex);
1719
+ };
1720
+ }
1721
+ __name(_ts_param13, "_ts_param");
1722
+ var DrizzleBundleRepository = class {
1723
+ static {
1724
+ __name(this, "DrizzleBundleRepository");
1725
+ }
1726
+ db;
1727
+ validityWindows;
1728
+ constructor(db, options = {}) {
1729
+ this.db = db;
1730
+ this.validityWindows = options.validityWindows ?? false;
1731
+ if (this.validityWindows) {
1732
+ this.findActiveBundleVersion = (bundleId, asOf, tx) => this.activeVersionAt(bundleId, asOf ?? /* @__PURE__ */ new Date(), tx);
1733
+ }
1734
+ }
1735
+ findActiveBundleVersion;
1736
+ // ─── Stems ───
1737
+ async list(filter) {
1738
+ const excludeDeleted = filter.excludeDeleted ?? true;
1739
+ const rows = await this.db.select().from(bundles).where(excludeDeleted ? (0, import_drizzle_orm12.isNull)(bundles.deletedAt) : void 0).orderBy((0, import_drizzle_orm12.asc)(bundles.sortOrder), (0, import_drizzle_orm12.asc)(bundles.bundleKey));
1740
+ return rows.map(import_core3.toBundleStemRow);
1741
+ }
1742
+ async findById(bundleId) {
1743
+ const rows = await this.db.select().from(bundles).where((0, import_drizzle_orm12.eq)(bundles.id, bundleId)).limit(1);
1744
+ return rows[0] ? (0, import_core3.toBundleStemRow)(rows[0]) : null;
1745
+ }
1746
+ /**
1747
+ * Whether this project already uses this bundle key — **including retired
1748
+ * bundles**.
1749
+ *
1750
+ * Its one caller is the duplicate check in `createBundle`, and the question
1751
+ * it asks is the database's: `bundles_bundleKey_key` is an
1752
+ * unconditional unique index, so a soft-deleted bundle still occupies its
1753
+ * key. Excluding retired rows here makes the check pass and the insert then
1754
+ * fail on the constraint — a 500 where the service had
1755
+ * `BUNDLE_ALREADY_EXISTS` ready.
1756
+ *
1757
+ * Two review rounds landed on this line from opposite sides, which is what
1758
+ * finally settled it: there is no caller that wants the active-catalogue
1759
+ * reading. `list` is that lookup, and it excludes retired rows by default.
1760
+ */
1761
+ async findByKey(bundleKey) {
1762
+ const rows = await this.db.select().from(bundles).where((0, import_drizzle_orm12.eq)(bundles.bundleKey, bundleKey)).limit(1);
1763
+ return rows[0] ? (0, import_core3.toBundleStemRow)(rows[0]) : null;
1764
+ }
1765
+ async create(data) {
1766
+ const now = /* @__PURE__ */ new Date();
1767
+ const rows = await this.db.insert(bundles).values({
1768
+ id: (0, import_node_crypto8.randomUUID)(),
1769
+ ...(0, import_core3.bundleStemDefaults)(data),
1770
+ updatedAt: now
1771
+ }).returning();
1772
+ return (0, import_core3.toBundleStemRow)(rows[0]);
1773
+ }
1774
+ async update(bundleId, data) {
1775
+ const patch = {
1776
+ updatedAt: /* @__PURE__ */ new Date()
1777
+ };
1778
+ if (data.label !== void 0) patch.label = data.label;
1779
+ if (data.description !== void 0) patch.description = data.description;
1780
+ if (data.icon !== void 0) patch.icon = data.icon;
1781
+ if (data.sortOrder !== void 0) patch.sortOrder = data.sortOrder;
1782
+ if (data.i18n !== void 0) patch.i18n = data.i18n;
1783
+ const rows = await this.db.update(bundles).set(patch).where((0, import_drizzle_orm12.eq)(bundles.id, bundleId)).returning();
1784
+ if (!rows[0]) throw new Error(`Bundle '${bundleId}' not found.`);
1785
+ return (0, import_core3.toBundleStemRow)(rows[0]);
1786
+ }
1787
+ async softDelete(bundleId) {
1788
+ await this.db.update(bundles).set({
1789
+ deletedAt: /* @__PURE__ */ new Date(),
1790
+ updatedAt: /* @__PURE__ */ new Date()
1791
+ }).where((0, import_drizzle_orm12.eq)(bundles.id, bundleId));
1792
+ }
1793
+ // ─── Versions ───
1794
+ async listVersions(bundleId) {
1795
+ const rows = await this.db.select().from(bundleVersions).where((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, bundleId)).orderBy((0, import_drizzle_orm12.asc)(bundleVersions.version));
1796
+ const stem = await this.findById(bundleId);
1797
+ return rows.map((row) => this.toVersionRow(row, stem));
1798
+ }
1799
+ async findVersionById(versionId, tx) {
1800
+ const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where((0, import_drizzle_orm12.eq)(bundleVersions.id, versionId)).limit(1);
1801
+ if (!rows[0]) return null;
1802
+ return this.toVersionRow(rows[0], await this.stemOn(resolveDb(this.db, tx), rows[0].bundleId));
1803
+ }
1804
+ async findCurrentDraft(bundleId) {
1805
+ const rows = await this.db.select().from(bundleVersions).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, bundleId), (0, import_drizzle_orm12.isNull)(bundleVersions.publishedAt))).limit(1);
1806
+ if (!rows[0]) return null;
1807
+ return this.toVersionRow(rows[0], await this.findById(bundleId));
1808
+ }
1809
+ async findLatestLive(bundleId, tx) {
1810
+ const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, bundleId), import_drizzle_orm12.sql`${bundleVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm12.isNull)(bundleVersions.supersededAt))).orderBy((0, import_drizzle_orm12.desc)(bundleVersions.version)).limit(1);
1811
+ return this.withStem(rows[0], bundleId, tx);
1812
+ }
1813
+ /**
1814
+ * The version bookable at `asOf`: published, inside its validity window.
1815
+ *
1816
+ * Deliberately not filtered by `supersededAt` — a superseded predecessor is
1817
+ * still the bookable one until its window closes, which is the whole point
1818
+ * of auto-succession. `validUntil` is day-inclusive, so a version is active
1819
+ * throughout its last day.
1820
+ */
1821
+ async activeVersionAt(bundleId, asOf, tx) {
1822
+ const dayStart = new Date(asOf);
1823
+ dayStart.setUTCHours(0, 0, 0, 0);
1824
+ const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, bundleId), import_drizzle_orm12.sql`${bundleVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm12.or)((0, import_drizzle_orm12.isNull)(bundleVersions.validFrom), (0, import_drizzle_orm12.lte)(bundleVersions.validFrom, asOf)), (0, import_drizzle_orm12.or)((0, import_drizzle_orm12.isNull)(bundleVersions.validUntil), (0, import_drizzle_orm12.gte)(bundleVersions.validUntil, dayStart)))).orderBy(import_drizzle_orm12.sql`"validFrom" DESC NULLS LAST`, (0, import_drizzle_orm12.desc)(bundleVersions.version)).limit(1);
1825
+ return this.withStem(rows[0], bundleId, tx);
1826
+ }
1827
+ async createDraft(data) {
1828
+ const existingDraft = await this.findCurrentDraft(data.bundleId);
1829
+ if (existingDraft) {
1830
+ throw new Error(`Bundle '${data.bundleId}' already has a draft version (v${existingDraft.version}); only one draft per bundle is allowed.`);
1831
+ }
1832
+ const latest = await this.db.select({
1833
+ version: bundleVersions.version
1834
+ }).from(bundleVersions).where((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, data.bundleId)).orderBy((0, import_drizzle_orm12.desc)(bundleVersions.version)).limit(1);
1835
+ const now = /* @__PURE__ */ new Date();
1836
+ const rows = await this.db.insert(bundleVersions).values({
1837
+ id: (0, import_node_crypto8.randomUUID)(),
1838
+ bundleId: data.bundleId,
1839
+ version: latest[0] ? latest[0].version + 1 : 1,
1840
+ ...(0, import_core3.bundleDraftDefaults)(data),
1841
+ validFrom: this.validityWindows ? toNullableDate(data.validFrom) : null,
1842
+ validUntil: this.validityWindows ? toNullableDate(data.validUntil) : null,
1843
+ updatedAt: now
1844
+ }).returning();
1845
+ return this.toVersionRow(rows[0], await this.findById(data.bundleId));
1846
+ }
1847
+ async updateDraft(versionId, data) {
1848
+ const patch = {
1849
+ updatedAt: /* @__PURE__ */ new Date()
1850
+ };
1851
+ if (data.features !== void 0) patch.features = data.features;
1852
+ if (data.quotas !== void 0) patch.quotas = data.quotas;
1853
+ if (data.compatibility !== void 0) patch.compatibility = data.compatibility;
1854
+ if (data.pricingOverrides !== void 0) patch.pricingOverrides = data.pricingOverrides;
1855
+ if (data.monthlyNet !== void 0) patch.monthlyNet = data.monthlyNet;
1856
+ if (data.yearlyNet !== void 0) patch.yearlyNet = data.yearlyNet;
1857
+ if (data.marketed !== void 0) patch.marketed = data.marketed;
1858
+ if (data.changeNote !== void 0) patch.changeNote = data.changeNote;
1859
+ if (this.validityWindows) {
1860
+ if (data.validFrom !== void 0) patch.validFrom = toNullableDate(data.validFrom);
1861
+ if (data.validUntil !== void 0) patch.validUntil = toNullableDate(data.validUntil);
1862
+ }
1863
+ const rows = await this.db.update(bundleVersions).set(patch).where((0, import_drizzle_orm12.eq)(bundleVersions.id, versionId)).returning();
1864
+ if (!rows[0]) throw new Error(`BundleVersion '${versionId}' not found.`);
1865
+ return this.toVersionRow(rows[0], await this.findById(rows[0].bundleId));
1866
+ }
1867
+ async publishDraft(versionId, publishMeta, tx) {
1868
+ if (tx !== void 0) return this.publishWithin(resolveDb(this.db, tx), versionId, publishMeta);
1869
+ return this.db.transaction((transaction) => this.publishWithin(transaction, versionId, publishMeta));
1870
+ }
1871
+ async publishWithin(db, versionId, publishMeta) {
1872
+ const now = /* @__PURE__ */ new Date();
1873
+ const publishedRows = await db.update(bundleVersions).set({
1874
+ publishedAt: now,
1875
+ publishedByUserId: publishMeta.publishedByUserId,
1876
+ publishedChanges: publishMeta.publishedChanges,
1877
+ nonRegressive: publishMeta.nonRegressive,
1878
+ updatedAt: now,
1879
+ ...this.validityWindows ? {
1880
+ validFrom: publishMeta.validFrom,
1881
+ validUntil: publishMeta.validUntil
1882
+ } : {}
1883
+ }).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.id, versionId), (0, import_drizzle_orm12.isNull)(bundleVersions.publishedAt))).returning();
1884
+ if (!publishedRows[0]) {
1885
+ throw new Error(`BundleVersion '${versionId}' not found or already published.`);
1886
+ }
1887
+ const draft = publishedRows[0];
1888
+ await db.update(bundleVersions).set(this.validityWindows ? {
1889
+ supersededAt: now,
1890
+ validUntil: (0, import_core3.previousUtcDay)(publishMeta.validFrom)
1891
+ } : {
1892
+ supersededAt: now
1893
+ }).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.bundleId, draft.bundleId), import_drizzle_orm12.sql`${bundleVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm12.isNull)(bundleVersions.supersededAt), (0, import_drizzle_orm12.ne)(bundleVersions.id, versionId)));
1894
+ const stemRows = await db.select().from(bundles).where((0, import_drizzle_orm12.eq)(bundles.id, draft.bundleId)).limit(1);
1895
+ return this.toVersionRow(publishedRows[0], stemRows[0] ? (0, import_core3.toBundleStemRow)(stemRows[0]) : null);
1896
+ }
1897
+ async deleteDraft(versionId) {
1898
+ const deleted = await this.db.delete(bundleVersions).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(bundleVersions.id, versionId), (0, import_drizzle_orm12.isNull)(bundleVersions.publishedAt))).returning({
1899
+ id: bundleVersions.id
1900
+ });
1901
+ if (deleted.length > 0) return;
1902
+ const remaining = await this.db.select({
1903
+ publishedAt: bundleVersions.publishedAt
1904
+ }).from(bundleVersions).where((0, import_drizzle_orm12.eq)(bundleVersions.id, versionId)).limit(1);
1905
+ if (!remaining[0]) return;
1906
+ throw new Error(`BundleVersion '${versionId}' is already published and cannot be discarded (published versions are immutable \u2014 contract protection P1).`);
1907
+ }
1908
+ /**
1909
+ * One version row plus its stem, or null — the tail every version lookup
1910
+ * ends with, and the reason it is here rather than written out each time:
1911
+ * the stem has to be read on the caller's connection, and a copy that
1912
+ * forgets that is the deadlock this class already had once.
1913
+ */
1914
+ async withStem(row, bundleId, tx) {
1915
+ if (!row) return null;
1916
+ return this.toVersionRow(row, await this.stemOn(resolveDb(this.db, tx), bundleId));
1917
+ }
1918
+ /**
1919
+ * The stem, read on the connection the caller is already holding.
1920
+ *
1921
+ * `this.db` would take a second one from the pool. Inside a transaction —
1922
+ * which is where `enforceLimit()` calls this, holding a row lock — that
1923
+ * second connection can only be released when the transaction ends, and the
1924
+ * transaction cannot end until this query returns. With a small pool, or
1925
+ * enough concurrent checks to occupy it, entitlement resolution stops
1926
+ * dead. A version query that honours `tx` and a stem query beside it that
1927
+ * does not is the shape that hides it: the deadlock needs load to appear.
1928
+ */
1929
+ async stemOn(db, bundleId) {
1930
+ const rows = await db.select().from(bundles).where((0, import_drizzle_orm12.eq)(bundles.id, bundleId)).limit(1);
1931
+ return rows[0] ? (0, import_core3.toBundleStemRow)(rows[0]) : null;
1932
+ }
1933
+ toVersionRow(row, stem) {
1934
+ return {
1935
+ id: row.id,
1936
+ bundleId: row.bundleId,
1937
+ bundleKey: stem?.bundleKey ?? "",
1938
+ label: stem?.label ?? "",
1939
+ version: row.version,
1940
+ baseVersionId: row.baseVersionId,
1941
+ features: row.features ?? [],
1942
+ quotas: row.quotas ?? {},
1943
+ compatibility: row.compatibility ?? {},
1944
+ pricingOverrides: row.pricingOverrides ?? [],
1945
+ monthlyNet: row.monthlyNet,
1946
+ yearlyNet: row.yearlyNet,
1947
+ marketed: row.marketed,
1948
+ publishedAt: toIso(row.publishedAt),
1949
+ supersededAt: toIso(row.supersededAt),
1950
+ publishedChanges: Array.isArray(row.publishedChanges) ? row.publishedChanges : null,
1951
+ changeNote: row.changeNote,
1952
+ nonRegressive: row.nonRegressive,
1953
+ // Null rather than the stored value where the adapter does not
1954
+ // promise windows: returning a date it does not maintain would
1955
+ // invite a caller to trust it.
1956
+ validFrom: this.validityWindows ? toIso(row.validFrom) : null,
1957
+ validUntil: this.validityWindows ? toIso(row.validUntil) : null,
1958
+ createdByUserId: row.createdByUserId,
1959
+ publishedByUserId: row.publishedByUserId,
1960
+ createdAt: row.createdAt.toISOString(),
1961
+ updatedAt: row.updatedAt.toISOString()
1962
+ };
1963
+ }
1964
+ };
1965
+ DrizzleBundleRepository = _ts_decorate14([
1966
+ (0, import_common14.Injectable)(),
1967
+ _ts_param13(0, (0, import_common14.Inject)(DRIZZLE_DB_TOKEN)),
1968
+ _ts_param13(1, (0, import_common14.Optional)()),
1969
+ _ts_metadata13("design:type", Function),
1970
+ _ts_metadata13("design:paramtypes", [
1971
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
1972
+ typeof DrizzleBundleRepositoryOptions === "undefined" ? Object : DrizzleBundleRepositoryOptions
1973
+ ])
1974
+ ], DrizzleBundleRepository);
1975
+ function toIso(value) {
1976
+ return value ? value.toISOString() : null;
1977
+ }
1978
+ __name(toIso, "toIso");
1979
+ function toNullableDate(value) {
1980
+ return value ? new Date(value) : null;
1981
+ }
1982
+ __name(toNullableDate, "toNullableDate");
1983
+
1984
+ // src/drizzle-plan.repository.ts
1985
+ var import_node_crypto9 = require("node:crypto");
1986
+ var import_common15 = require("@nestjs/common");
1987
+ var import_drizzle_orm13 = require("drizzle-orm");
1988
+ var import_core4 = require("@saasicat/core");
1989
+ function _ts_decorate15(decorators, target, key, desc10) {
1990
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
1991
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1992
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1993
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1994
+ }
1995
+ __name(_ts_decorate15, "_ts_decorate");
1996
+ function _ts_metadata14(k, v) {
1997
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1998
+ }
1999
+ __name(_ts_metadata14, "_ts_metadata");
2000
+ function _ts_param14(paramIndex, decorator) {
2001
+ return function(target, key) {
2002
+ decorator(target, key, paramIndex);
2003
+ };
2004
+ }
2005
+ __name(_ts_param14, "_ts_param");
2006
+ var DrizzlePlanRepository = class {
2007
+ static {
2008
+ __name(this, "DrizzlePlanRepository");
2009
+ }
2010
+ db;
2011
+ validityWindows;
2012
+ constructor(db, options = {}) {
2013
+ this.db = db;
2014
+ this.validityWindows = options.validityWindows ?? false;
2015
+ if (this.validityWindows) {
2016
+ this.findActivePlanVersion = (planKey, asOf, tx) => this.activeVersionAt(planKey, asOf ?? /* @__PURE__ */ new Date(), tx);
2017
+ }
2018
+ }
2019
+ findActivePlanVersion;
2020
+ // ─── Stem operations ───
2021
+ async list(filter) {
2022
+ const excludeDeleted = filter.excludeDeleted ?? true;
2023
+ const stems = await this.db.select().from(plans).where((0, import_drizzle_orm13.and)(...excludeDeleted ? [
2024
+ (0, import_drizzle_orm13.isNull)(plans.deletedAt)
2025
+ ] : [])).orderBy((0, import_drizzle_orm13.asc)(plans.sortOrder), (0, import_drizzle_orm13.asc)(plans.planKey));
2026
+ if (!filter.onlyPublished) return stems.map(import_core4.toPlanRow);
2027
+ if (stems.length === 0) return [];
2028
+ const candidateKeys = [
2029
+ ...new Set(stems.map((stem) => stem.planKey))
2030
+ ];
2031
+ const live = await this.db.select({
2032
+ planId: planVersions.planId
2033
+ }).from(planVersions).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.inArray)(planVersions.planId, candidateKeys), import_drizzle_orm13.sql`${planVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm13.isNull)(planVersions.supersededAt)));
2034
+ const publishedKeys = new Set(live.map((row) => row.planId));
2035
+ return stems.filter((stem) => publishedKeys.has(stem.planKey)).map(import_core4.toPlanRow);
2036
+ }
2037
+ async findById(planId) {
2038
+ const rows = await this.db.select().from(plans).where((0, import_drizzle_orm13.eq)(plans.id, planId)).limit(1);
2039
+ return rows[0] ? (0, import_core4.toPlanRow)(rows[0]) : null;
2040
+ }
2041
+ /**
2042
+ * Whether this installation already uses this plan key — **including
2043
+ * retired plans**.
2044
+ *
2045
+ * `plans_planKey_key` is unconditional, so a soft delete does not free the
2046
+ * key, and the duplicate check in `createPlan` needs the database's answer
2047
+ * rather than the active catalogue's. `list` is that other lookup.
2048
+ */
2049
+ async findByKey(planKey) {
2050
+ const rows = await this.db.select().from(plans).where((0, import_drizzle_orm13.eq)(plans.planKey, planKey)).limit(1);
2051
+ return rows[0] ? (0, import_core4.toPlanRow)(rows[0]) : null;
2052
+ }
2053
+ async create(data) {
2054
+ const now = /* @__PURE__ */ new Date();
2055
+ const rows = await this.db.insert(plans).values({
2056
+ id: (0, import_node_crypto9.randomUUID)(),
2057
+ planKey: data.planKey,
2058
+ label: data.label,
2059
+ description: data.description ?? null,
2060
+ icon: data.icon ?? null,
2061
+ sortOrder: data.sortOrder ?? 0,
2062
+ createdAt: now,
2063
+ updatedAt: now
2064
+ }).returning();
2065
+ return (0, import_core4.toPlanRow)(rows[0]);
2066
+ }
2067
+ async update(planId, data) {
2068
+ const rows = await this.db.update(plans).set({
2069
+ ...data.label !== void 0 ? {
2070
+ label: data.label
2071
+ } : {},
2072
+ ...data.description !== void 0 ? {
2073
+ description: data.description
2074
+ } : {},
2075
+ ...data.icon !== void 0 ? {
2076
+ icon: data.icon
2077
+ } : {},
2078
+ ...data.sortOrder !== void 0 ? {
2079
+ sortOrder: data.sortOrder
2080
+ } : {},
2081
+ updatedAt: /* @__PURE__ */ new Date()
2082
+ }).where((0, import_drizzle_orm13.eq)(plans.id, planId)).returning();
2083
+ if (!rows[0]) throw new Error(`Plan '${planId}' not found.`);
2084
+ return (0, import_core4.toPlanRow)(rows[0]);
2085
+ }
2086
+ async softDelete(planId) {
2087
+ const now = /* @__PURE__ */ new Date();
2088
+ await this.db.update(plans).set({
2089
+ deletedAt: now,
2090
+ updatedAt: now
2091
+ }).where((0, import_drizzle_orm13.eq)(plans.id, planId));
2092
+ }
2093
+ async hardDelete(planId) {
2094
+ await this.db.delete(plans).where((0, import_drizzle_orm13.eq)(plans.id, planId));
2095
+ }
2096
+ // ─── Version lifecycle — keyed by plan key ───
2097
+ async listVersions(planKey) {
2098
+ const rows = await this.db.select().from(planVersions).where((0, import_drizzle_orm13.eq)(planVersions.planId, planKey)).orderBy((0, import_drizzle_orm13.asc)(planVersions.version));
2099
+ return rows.map((row) => this.versionRow(row));
2100
+ }
2101
+ /**
2102
+ * `tx` is beyond what the port asks for, and is what keeps a caller already
2103
+ * inside a transaction on its own connection: drawing a second one waits
2104
+ * for a connection that transaction is itself holding, which on a
2105
+ * one-connection pool never returns.
2106
+ */
2107
+ async findVersionById(versionId, tx) {
2108
+ const rows = await resolveDb(this.db, tx).select().from(planVersions).where((0, import_drizzle_orm13.eq)(planVersions.id, versionId)).limit(1);
2109
+ return rows[0] ? this.versionRow(rows[0]) : null;
2110
+ }
2111
+ async findCurrentDraft(planKey) {
2112
+ const rows = await this.db.select().from(planVersions).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.eq)(planVersions.planId, planKey), (0, import_drizzle_orm13.isNull)(planVersions.publishedAt))).orderBy((0, import_drizzle_orm13.desc)(planVersions.version)).limit(1);
2113
+ return rows[0] ? this.versionRow(rows[0]) : null;
2114
+ }
2115
+ async findLatestLivePlanVersion(planKey, tx) {
2116
+ const rows = await resolveDb(this.db, tx).select().from(planVersions).where((0, import_drizzle_orm13.and)(
2117
+ (0, import_drizzle_orm13.eq)(planVersions.planId, planKey),
2118
+ import_drizzle_orm13.sql`${planVersions.publishedAt} IS NOT NULL`,
2119
+ (0, import_drizzle_orm13.isNull)(planVersions.supersededAt),
2120
+ // A version the operator terminated is not live, whatever
2121
+ // its supersession says: `terminate` sets `endsAt` without
2122
+ // a successor to supersede it.
2123
+ (0, import_drizzle_orm13.or)((0, import_drizzle_orm13.isNull)(planVersions.endsAt), import_drizzle_orm13.sql`${planVersions.endsAt} > NOW()`)
2124
+ )).orderBy((0, import_drizzle_orm13.desc)(planVersions.version)).limit(1);
2125
+ return rows[0] ? this.versionRow(rows[0]) : null;
2126
+ }
2127
+ /**
2128
+ * The version bookable at `asOf`: published, inside its validity window,
2129
+ * not terminated.
2130
+ *
2131
+ * `validUntil` is day-inclusive — a window that closes on the 28th is still
2132
+ * open at 23:59 on the 28th — which is why the comparison is against
2133
+ * `startOfUtcDay(asOf)` and not against `asOf`. The reading half of the
2134
+ * rule `previousUtcDay` writes in `publishWithin`; both come from
2135
+ * `@saasicat/core` so the two halves cannot drift apart.
2136
+ */
2137
+ async activeVersionAt(planKey, asOf, tx) {
2138
+ const rows = await resolveDb(this.db, tx).select().from(planVersions).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.eq)(planVersions.planId, planKey), import_drizzle_orm13.sql`${planVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm13.or)((0, import_drizzle_orm13.isNull)(planVersions.validFrom), (0, import_drizzle_orm13.lte)(planVersions.validFrom, asOf)), (0, import_drizzle_orm13.or)((0, import_drizzle_orm13.isNull)(planVersions.validUntil), (0, import_drizzle_orm13.gte)(planVersions.validUntil, (0, import_core4.startOfUtcDay)(asOf))), (0, import_drizzle_orm13.or)((0, import_drizzle_orm13.isNull)(planVersions.endsAt), import_drizzle_orm13.sql`${planVersions.endsAt} > ${asOf}`))).orderBy(import_drizzle_orm13.sql`"validFrom" DESC NULLS LAST`, (0, import_drizzle_orm13.desc)(planVersions.version)).limit(1);
2139
+ return rows[0] ? this.versionRow(rows[0]) : null;
2140
+ }
2141
+ async createPlanVersionDraft(data) {
2142
+ const now = /* @__PURE__ */ new Date();
2143
+ const latest = await this.db.select({
2144
+ version: planVersions.version
2145
+ }).from(planVersions).where((0, import_drizzle_orm13.eq)(planVersions.planId, data.planId)).orderBy((0, import_drizzle_orm13.desc)(planVersions.version)).limit(1);
2146
+ const rows = await this.db.insert(planVersions).values({
2147
+ id: (0, import_node_crypto9.randomUUID)(),
2148
+ planId: data.planId,
2149
+ version: (latest[0]?.version ?? 0) + 1,
2150
+ baseVersionId: data.baseVersionId ?? null,
2151
+ features: data.features,
2152
+ quotas: data.quotas,
2153
+ monthlyNet: data.monthlyNet,
2154
+ yearlyNet: data.yearlyNet,
2155
+ marketed: data.marketed ?? true,
2156
+ changeNote: data.changeNote ?? "",
2157
+ createdByUserId: data.createdByUserId ?? null,
2158
+ createdAt: now,
2159
+ updatedAt: now,
2160
+ // publishedAt stays null — this is a draft. `bundles` is
2161
+ // app-specific and has no column here, as in adapter-prisma.
2162
+ ...this.validityWindows ? {
2163
+ validFrom: toNullableDate2(data.validFrom),
2164
+ validUntil: toNullableDate2(data.validUntil)
2165
+ } : {}
2166
+ }).returning();
2167
+ return this.versionRow(rows[0]);
2168
+ }
2169
+ async updatePlanVersionDraft(versionId, data) {
2170
+ const rows = await this.db.update(planVersions).set({
2171
+ ...data.features !== void 0 ? {
2172
+ features: data.features
2173
+ } : {},
2174
+ ...data.quotas !== void 0 ? {
2175
+ quotas: data.quotas
2176
+ } : {},
2177
+ ...data.monthlyNet !== void 0 ? {
2178
+ monthlyNet: data.monthlyNet
2179
+ } : {},
2180
+ ...data.yearlyNet !== void 0 ? {
2181
+ yearlyNet: data.yearlyNet
2182
+ } : {},
2183
+ ...data.marketed !== void 0 ? {
2184
+ marketed: data.marketed
2185
+ } : {},
2186
+ ...data.changeNote !== void 0 ? {
2187
+ changeNote: data.changeNote
2188
+ } : {},
2189
+ ...this.validityWindows && data.validFrom !== void 0 ? {
2190
+ validFrom: toNullableDate2(data.validFrom)
2191
+ } : {},
2192
+ ...this.validityWindows && data.validUntil !== void 0 ? {
2193
+ validUntil: toNullableDate2(data.validUntil)
2194
+ } : {},
2195
+ updatedAt: /* @__PURE__ */ new Date()
2196
+ }).where((0, import_drizzle_orm13.eq)(planVersions.id, versionId)).returning();
2197
+ if (!rows[0]) {
2198
+ throw new Error(`PlanVersion '${versionId}' not found.`);
2199
+ }
2200
+ return this.versionRow(rows[0]);
2201
+ }
2202
+ async publishPlanVersionDraft(versionId, publishMeta, tx) {
2203
+ if (tx !== void 0) return this.publishWithin(resolveDb(this.db, tx), versionId, publishMeta);
2204
+ return this.db.transaction((transaction) => this.publishWithin(transaction, versionId, publishMeta));
2205
+ }
2206
+ async publishWithin(db, versionId, publishMeta) {
2207
+ const now = /* @__PURE__ */ new Date();
2208
+ const publishedRows = await db.update(planVersions).set({
2209
+ publishedAt: now,
2210
+ publishedByUserId: publishMeta.publishedByUserId,
2211
+ publishedChanges: publishMeta.publishedChanges,
2212
+ nonRegressive: publishMeta.nonRegressive,
2213
+ updatedAt: now,
2214
+ ...this.validityWindows ? {
2215
+ validFrom: publishMeta.validFrom,
2216
+ validUntil: publishMeta.validUntil
2217
+ } : {}
2218
+ }).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.eq)(planVersions.id, versionId), (0, import_drizzle_orm13.isNull)(planVersions.publishedAt))).returning();
2219
+ if (!publishedRows[0]) {
2220
+ throw new Error(`PlanVersion '${versionId}' not found or already published.`);
2221
+ }
2222
+ await db.update(planVersions).set(this.validityWindows ? {
2223
+ supersededAt: now,
2224
+ validUntil: (0, import_core4.previousUtcDay)(publishMeta.validFrom),
2225
+ updatedAt: now
2226
+ } : {
2227
+ supersededAt: now,
2228
+ updatedAt: now
2229
+ }).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.eq)(planVersions.planId, publishedRows[0].planId), import_drizzle_orm13.sql`${planVersions.publishedAt} IS NOT NULL`, (0, import_drizzle_orm13.isNull)(planVersions.supersededAt), (0, import_drizzle_orm13.ne)(planVersions.id, versionId)));
2230
+ return this.versionRow(publishedRows[0]);
2231
+ }
2232
+ async deletePlanVersionDraft(versionId) {
2233
+ const deleted = await this.db.delete(planVersions).where((0, import_drizzle_orm13.and)((0, import_drizzle_orm13.eq)(planVersions.id, versionId), (0, import_drizzle_orm13.isNull)(planVersions.publishedAt))).returning({
2234
+ id: planVersions.id
2235
+ });
2236
+ if (deleted.length > 0) return;
2237
+ const existing = await this.db.select({
2238
+ publishedAt: planVersions.publishedAt
2239
+ }).from(planVersions).where((0, import_drizzle_orm13.eq)(planVersions.id, versionId)).limit(1);
2240
+ if (existing[0]) {
2241
+ throw new Error(`PlanVersion '${versionId}' is already published and cannot be discarded (published versions are immutable \u2014 contract protection P1).`);
2242
+ }
2243
+ }
2244
+ async terminate(versionId, endsAt) {
2245
+ const rows = await this.db.update(planVersions).set({
2246
+ endsAt,
2247
+ updatedAt: /* @__PURE__ */ new Date()
2248
+ }).where((0, import_drizzle_orm13.eq)(planVersions.id, versionId)).returning();
2249
+ if (!rows[0]) throw new Error(`PlanVersion '${versionId}' not found.`);
2250
+ return this.versionRow(rows[0]);
2251
+ }
2252
+ versionRow(row) {
2253
+ return (0, import_core4.toPlanVersionRow)(row, row.planId, {
2254
+ validityWindows: this.validityWindows,
2255
+ // The canonical schema always has the column, so this adapter can
2256
+ // always answer the question — unlike a Prisma consumer whose
2257
+ // schema may predate it.
2258
+ endsAt: true
2259
+ });
2260
+ }
2261
+ };
2262
+ DrizzlePlanRepository = _ts_decorate15([
2263
+ (0, import_common15.Injectable)(),
2264
+ _ts_param14(0, (0, import_common15.Inject)(DRIZZLE_DB_TOKEN)),
2265
+ _ts_param14(1, (0, import_common15.Optional)()),
2266
+ _ts_metadata14("design:type", Function),
2267
+ _ts_metadata14("design:paramtypes", [
2268
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
2269
+ typeof DrizzlePlanRepositoryOptions === "undefined" ? Object : DrizzlePlanRepositoryOptions
2270
+ ])
2271
+ ], DrizzlePlanRepository);
2272
+ function toNullableDate2(value) {
2273
+ return value ? new Date(value) : null;
2274
+ }
2275
+ __name(toNullableDate2, "toNullableDate");
2276
+
2277
+ // src/drizzle-subscription-contract.repository.ts
2278
+ var import_node_crypto10 = require("node:crypto");
2279
+ var import_common16 = require("@nestjs/common");
2280
+ var import_drizzle_orm14 = require("drizzle-orm");
2281
+ var import_core5 = require("@saasicat/core");
2282
+ function _ts_decorate16(decorators, target, key, desc10) {
2283
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
2284
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
2285
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2286
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2287
+ }
2288
+ __name(_ts_decorate16, "_ts_decorate");
2289
+ function _ts_metadata15(k, v) {
2290
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2291
+ }
2292
+ __name(_ts_metadata15, "_ts_metadata");
2293
+ function _ts_param15(paramIndex, decorator) {
2294
+ return function(target, key) {
2295
+ decorator(target, key, paramIndex);
2296
+ };
2297
+ }
2298
+ __name(_ts_param15, "_ts_param");
2299
+ var DrizzleSubscriptionContractRepository = class {
2300
+ static {
2301
+ __name(this, "DrizzleSubscriptionContractRepository");
2302
+ }
2303
+ db;
2304
+ constructor(db) {
2305
+ this.db = db;
2306
+ }
2307
+ async list(filter) {
2308
+ const rows = await this.db.select().from(subscriptionContracts).where((0, import_drizzle_orm14.and)(...filter.tenantId ? [
2309
+ (0, import_drizzle_orm14.eq)(subscriptionContracts.tenantId, filter.tenantId)
2310
+ ] : [], ...filter.status ? [
2311
+ (0, import_drizzle_orm14.eq)(subscriptionContracts.status, filter.status)
2312
+ ] : [], ...filter.asOf ? [
2313
+ inWindowAt(filter.asOf)
2314
+ ] : [])).orderBy((0, import_drizzle_orm14.desc)(subscriptionContracts.effectiveFrom), (0, import_drizzle_orm14.desc)(subscriptionContracts.createdAt));
2315
+ return this.withLineItems(this.db, rows);
2316
+ }
2317
+ async findById(contractId) {
2318
+ const rows = await this.db.select().from(subscriptionContracts).where((0, import_drizzle_orm14.eq)(subscriptionContracts.id, contractId)).limit(1);
2319
+ return (await this.withLineItems(this.db, rows))[0] ?? null;
2320
+ }
2321
+ async findActiveByTenantId(tenantId, asOf = /* @__PURE__ */ new Date(), tx) {
2322
+ const db = resolveDb(this.db, tx);
2323
+ const rows = await db.select().from(subscriptionContracts).where((0, import_drizzle_orm14.and)((0, import_drizzle_orm14.eq)(subscriptionContracts.tenantId, tenantId), (0, import_drizzle_orm14.inArray)(subscriptionContracts.status, [
2324
+ ...import_core5.ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES
2325
+ ]), inWindowAt(asOf))).orderBy((0, import_drizzle_orm14.desc)(subscriptionContracts.effectiveFrom), (0, import_drizzle_orm14.desc)(subscriptionContracts.createdAt)).limit(1);
2326
+ return (await this.withLineItems(db, rows))[0] ?? null;
2327
+ }
2328
+ async findByOriginalOfferId(offerId, tx) {
2329
+ const db = resolveDb(this.db, tx);
2330
+ const rows = await db.select().from(subscriptionContracts).where((0, import_drizzle_orm14.eq)(subscriptionContracts.originalOfferId, offerId)).orderBy((0, import_drizzle_orm14.asc)(subscriptionContracts.createdAt), (0, import_drizzle_orm14.asc)(subscriptionContracts.id)).limit(1);
2331
+ return (await this.withLineItems(db, rows))[0] ?? null;
2332
+ }
2333
+ async create(data, tx) {
2334
+ const now = /* @__PURE__ */ new Date();
2335
+ const contractId = (0, import_node_crypto10.randomUUID)();
2336
+ return resolveDb(this.db, tx).transaction(async (transaction) => {
2337
+ const db = transaction;
2338
+ const rows = await db.insert(subscriptionContracts).values({
2339
+ id: contractId,
2340
+ tenantId: data.tenantId,
2341
+ status: data.status ?? "active",
2342
+ effectiveFrom: data.effectiveFrom,
2343
+ effectiveUntil: data.effectiveUntil ?? null,
2344
+ originalOfferId: data.originalOfferId ?? null,
2345
+ originalPlanVersionId: data.originalPlanVersionId ?? null,
2346
+ originalBundleVersionIds: data.originalBundleVersionIds ?? [],
2347
+ entitlementSnapshot: data.entitlementSnapshot ?? null,
2348
+ priceSnapshot: data.priceSnapshot,
2349
+ promotionSnapshots: data.promotionSnapshots ?? [],
2350
+ promoCodeSnapshots: data.promoCodeSnapshots ?? [],
2351
+ termsSnapshot: data.termsSnapshot ?? null,
2352
+ subscriberId: data.parties.subscriberId,
2353
+ subscriberSnapshot: data.parties.subscriber,
2354
+ issuerSnapshot: data.parties.issuer,
2355
+ createdAt: now,
2356
+ updatedAt: now
2357
+ }).returning();
2358
+ const lineRows = data.lineItems.length === 0 ? [] : await db.insert(contractLineItems).values(data.lineItems.map((item) => toLineItemInsert(item, contractId, now))).returning();
2359
+ return (0, import_core5.toSubscriptionContractRecord)(rows[0], lineRows);
2360
+ });
2361
+ }
2362
+ async listRunningIssuers(limit, asOf = /* @__PURE__ */ new Date()) {
2363
+ const running = (0, import_drizzle_orm14.and)((0, import_drizzle_orm14.inArray)(subscriptionContracts.status, [
2364
+ ...import_core5.ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES
2365
+ ]), (0, import_drizzle_orm14.or)((0, import_drizzle_orm14.isNull)(subscriptionContracts.effectiveUntil), (0, import_drizzle_orm14.gt)(subscriptionContracts.effectiveUntil, asOf)));
2366
+ const [totals, rows] = await Promise.all([
2367
+ this.db.select({
2368
+ value: (0, import_drizzle_orm14.count)()
2369
+ }).from(subscriptionContracts).where(running),
2370
+ // Oldest first, and `id` last so two contracts that start in the
2371
+ // same moment are still listed in one order — a refusal that names
2372
+ // a different pair on every restart is one an operator cannot act
2373
+ // on.
2374
+ this.db.select({
2375
+ id: subscriptionContracts.id,
2376
+ tenantId: subscriptionContracts.tenantId,
2377
+ issuerSnapshot: subscriptionContracts.issuerSnapshot,
2378
+ effectiveFrom: subscriptionContracts.effectiveFrom
2379
+ }).from(subscriptionContracts).where(running).orderBy((0, import_drizzle_orm14.asc)(subscriptionContracts.effectiveFrom), (0, import_drizzle_orm14.asc)(subscriptionContracts.createdAt), (0, import_drizzle_orm14.asc)(subscriptionContracts.id)).limit(limit)
2380
+ ]);
2381
+ return {
2382
+ total: totals[0]?.value ?? 0,
2383
+ contracts: rows.map(import_core5.toRunningContractIssuer)
2384
+ };
2385
+ }
2386
+ async terminate(contractId, data) {
2387
+ const rows = await this.db.update(subscriptionContracts).set({
2388
+ effectiveUntil: data.effectiveUntil,
2389
+ // A null status leaves the column alone: the contract keeps
2390
+ // whatever it had — `active`, usually — and the new
2391
+ // `effectiveUntil` is what takes it out of the active lookup
2392
+ // once that moment arrives.
2393
+ ...data.status === null ? {} : {
2394
+ status: data.status
2395
+ },
2396
+ updatedAt: /* @__PURE__ */ new Date()
2397
+ }).where((0, import_drizzle_orm14.eq)(subscriptionContracts.id, contractId)).returning();
2398
+ if (!rows[0]) throw new Error(`SubscriptionContract '${contractId}' not found.`);
2399
+ return (await this.withLineItems(this.db, rows))[0];
2400
+ }
2401
+ /**
2402
+ * The lines for a batch of contracts in one query rather than one per
2403
+ * contract: `list` is the operator's contract history and would otherwise
2404
+ * be an N+1 over a table that only grows.
2405
+ */
2406
+ async withLineItems(db, rows) {
2407
+ if (rows.length === 0) return [];
2408
+ const lines = await db.select().from(contractLineItems).where((0, import_drizzle_orm14.inArray)(contractLineItems.contractId, rows.map((row) => row.id)));
2409
+ const byContract = /* @__PURE__ */ new Map();
2410
+ for (const line of lines) {
2411
+ const bucket = byContract.get(line.contractId);
2412
+ if (bucket) bucket.push(line);
2413
+ else byContract.set(line.contractId, [
2414
+ line
2415
+ ]);
2416
+ }
2417
+ return rows.map((row) => (0, import_core5.toSubscriptionContractRecord)(row, byContract.get(row.id) ?? []));
2418
+ }
2419
+ };
2420
+ DrizzleSubscriptionContractRepository = _ts_decorate16([
2421
+ (0, import_common16.Injectable)(),
2422
+ _ts_param15(0, (0, import_common16.Inject)(DRIZZLE_DB_TOKEN)),
2423
+ _ts_metadata15("design:type", Function),
2424
+ _ts_metadata15("design:paramtypes", [
2425
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
2426
+ ])
2427
+ ], DrizzleSubscriptionContractRepository);
2428
+ function inWindowAt(asOf) {
2429
+ return (0, import_drizzle_orm14.and)((0, import_drizzle_orm14.lte)(subscriptionContracts.effectiveFrom, asOf), (0, import_drizzle_orm14.or)((0, import_drizzle_orm14.isNull)(subscriptionContracts.effectiveUntil), (0, import_drizzle_orm14.gt)(subscriptionContracts.effectiveUntil, asOf)));
2430
+ }
2431
+ __name(inWindowAt, "inWindowAt");
2432
+ function toLineItemInsert(item, contractId, createdAt) {
2433
+ return {
2434
+ id: (0, import_node_crypto10.randomUUID)(),
2435
+ contractId,
2436
+ kind: item.kind,
2437
+ sourceKey: item.sourceKey,
2438
+ sourceVersionId: item.sourceVersionId ?? null,
2439
+ titleSnapshot: item.titleSnapshot,
2440
+ descriptionSnapshot: item.descriptionSnapshot ?? null,
2441
+ quantity: item.quantity,
2442
+ unit: item.unit ?? null,
2443
+ // Money as a decimal string all the way to the column: passing a JS
2444
+ // number here would round it on the way in, before anybody could
2445
+ // notice.
2446
+ priceNet: item.priceNet.toFixed(2),
2447
+ priceGross: item.priceGross.toFixed(2),
2448
+ billingCycle: item.billingCycle,
2449
+ currency: item.currency,
2450
+ taxRate: item.taxRate.toFixed(2),
2451
+ taxAmount: item.taxAmount.toFixed(2),
2452
+ minimumTermUntil: item.minimumTermUntil ?? null,
2453
+ featuresSnapshot: item.featuresSnapshot,
2454
+ quotaEffectsSnapshot: item.quotaEffectsSnapshot,
2455
+ metadata: item.metadata ?? null,
2456
+ createdAt
2457
+ };
2458
+ }
2459
+ __name(toLineItemInsert, "toLineItemInsert");
2460
+
2461
+ // src/drizzle-subscriber-payment-method.repository.ts
2462
+ var import_node_crypto11 = require("node:crypto");
2463
+ var import_common17 = require("@nestjs/common");
2464
+ var import_drizzle_orm15 = require("drizzle-orm");
2465
+ var import_core6 = require("@saasicat/core");
2466
+ function _ts_decorate17(decorators, target, key, desc10) {
2467
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
2468
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
2469
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2470
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2471
+ }
2472
+ __name(_ts_decorate17, "_ts_decorate");
2473
+ function _ts_metadata16(k, v) {
2474
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2475
+ }
2476
+ __name(_ts_metadata16, "_ts_metadata");
2477
+ function _ts_param16(paramIndex, decorator) {
2478
+ return function(target, key) {
2479
+ decorator(target, key, paramIndex);
2480
+ };
2481
+ }
2482
+ __name(_ts_param16, "_ts_param");
2483
+ var DrizzleSubscriberPaymentMethodRepository = class {
2484
+ static {
2485
+ __name(this, "DrizzleSubscriberPaymentMethodRepository");
2486
+ }
2487
+ db;
2488
+ constructor(db) {
2489
+ this.db = db;
2490
+ }
2491
+ async recordConfirmed(data, tx) {
2492
+ return resolveDb(this.db, tx).transaction(async (transaction) => {
2493
+ const db = transaction;
2494
+ const locked = await db.select({
2495
+ id: subscribers.id
2496
+ }).from(subscribers).where((0, import_drizzle_orm15.eq)(subscribers.id, data.subscriberId)).for("update").limit(1);
2497
+ if (locked.length === 0) {
2498
+ throw new Error(`Subscriber '${data.subscriberId}' does not exist; its payment method cannot be recorded.`);
2499
+ }
2500
+ const [recorded] = await db.select().from(subscriberPaymentMethods).where(this.byReference(data.gatewayAccount, data.paymentMethodRef)).limit(1);
2501
+ if (recorded) {
2502
+ (0, import_core6.refuseForeignPaymentMethodReference)(recorded, data.subscriberId);
2503
+ return {
2504
+ method: (0, import_core6.toSubscriberPaymentMethodRecord)(recorded),
2505
+ outcome: "already-recorded"
2506
+ };
2507
+ }
2508
+ const [active] = await db.select().from(subscriberPaymentMethods).where(this.activeOf(data.subscriberId)).limit(1);
2509
+ const replacedOnArrival = active && active.confirmedAt.getTime() > data.confirmedAt.getTime() ? active.confirmedAt : null;
2510
+ const claimed = await this.claimReference(db, {
2511
+ ...(0, import_core6.subscriberPaymentMethodColumns)(data),
2512
+ id: (0, import_node_crypto11.randomUUID)(),
2513
+ status: "REPLACED",
2514
+ replacedAt: replacedOnArrival ?? data.confirmedAt
2515
+ });
2516
+ if (replacedOnArrival) {
2517
+ return {
2518
+ method: (0, import_core6.toSubscriberPaymentMethodRecord)(claimed),
2519
+ outcome: "superseded"
2520
+ };
2521
+ }
2522
+ if (active) {
2523
+ await db.update(subscriberPaymentMethods).set({
2524
+ status: "REPLACED",
2525
+ replacedAt: data.confirmedAt
2526
+ }).where((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.id, active.id));
2527
+ }
2528
+ const [inUse] = await db.update(subscriberPaymentMethods).set({
2529
+ status: "ACTIVE",
2530
+ replacedAt: null
2531
+ }).where((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.id, claimed.id)).returning();
2532
+ return {
2533
+ method: (0, import_core6.toSubscriberPaymentMethodRecord)(inUse),
2534
+ outcome: "activated"
2535
+ };
2536
+ });
2537
+ }
2538
+ /**
2539
+ * Takes the reference for this subscriber, or refuses it to them.
2540
+ *
2541
+ * The first write of the three, and the only one that can be refused — so a
2542
+ * refusal leaves the caller's transaction as it found it, whichever way the
2543
+ * caller opened it. `onConflictDoNothing` on the reference's own columns is
2544
+ * what answers: the row the read above could not see, because the
2545
+ * transaction holding it had not committed, is waited for here rather than
2546
+ * raised as a driver error, which every driver spells differently.
2547
+ *
2548
+ * It is written `REPLACED` because the seat a subscriber's one `ACTIVE`
2549
+ * payment method occupies may still be taken; `recordConfirmed` gives it
2550
+ * the status it keeps once the seat is free. Nothing outside the
2551
+ * transaction sees the difference.
2552
+ */
2553
+ async claimReference(db, values) {
2554
+ const [claimed] = await db.insert(subscriberPaymentMethods).values(values).onConflictDoNothing({
2555
+ target: [
2556
+ subscriberPaymentMethods.gatewayAccount,
2557
+ subscriberPaymentMethods.paymentMethodRef
2558
+ ]
2559
+ }).returning();
2560
+ if (!claimed) {
2561
+ throw new import_core6.ForeignPaymentMethodReferenceError(values.gatewayAccount, values.paymentMethodRef);
2562
+ }
2563
+ return claimed;
2564
+ }
2565
+ async findActive(subscriberId, tx) {
2566
+ const [row] = await resolveDb(this.db, tx).select().from(subscriberPaymentMethods).where(this.activeOf(subscriberId)).limit(1);
2567
+ return row ? (0, import_core6.toSubscriberPaymentMethodRecord)(row) : null;
2568
+ }
2569
+ async findByReference(reference, tx) {
2570
+ const [row] = await resolveDb(this.db, tx).select().from(subscriberPaymentMethods).where((0, import_drizzle_orm15.and)((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.subscriberId, reference.subscriberId), this.byReference(reference.gatewayAccount, reference.paymentMethodRef))).limit(1);
2571
+ return row ? (0, import_core6.toSubscriberPaymentMethodRecord)(row) : null;
2572
+ }
2573
+ async accountsInUse() {
2574
+ const rows = await this.db.selectDistinct({
2575
+ gatewayAccount: subscriberPaymentMethods.gatewayAccount
2576
+ }).from(subscriberPaymentMethods).where((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.status, "ACTIVE")).orderBy((0, import_drizzle_orm15.asc)(subscriberPaymentMethods.gatewayAccount));
2577
+ return rows.map((row) => row.gatewayAccount);
2578
+ }
2579
+ async recordSetup(data, tx) {
2580
+ await resolveDb(this.db, tx).insert(subscriberPaymentMethodSetups).values({
2581
+ id: (0, import_node_crypto11.randomUUID)(),
2582
+ subscriberId: data.subscriberId,
2583
+ gatewayAccount: data.gatewayAccount,
2584
+ sessionRef: data.sessionRef,
2585
+ customerRef: data.customerRef,
2586
+ startedAt: data.startedAt
2587
+ });
2588
+ }
2589
+ async completeSetup(match, completedAt, tx) {
2590
+ const completed = await resolveDb(this.db, tx).update(subscriberPaymentMethodSetups).set({
2591
+ completedAt
2592
+ }).where((0, import_drizzle_orm15.and)((0, import_drizzle_orm15.eq)(subscriberPaymentMethodSetups.gatewayAccount, match.gatewayAccount), (0, import_drizzle_orm15.eq)(subscriberPaymentMethodSetups.sessionRef, match.sessionRef), (0, import_drizzle_orm15.eq)(subscriberPaymentMethodSetups.subscriberId, match.subscriberId), (0, import_drizzle_orm15.isNull)(subscriberPaymentMethodSetups.completedAt))).returning({
2593
+ id: subscriberPaymentMethodSetups.id
2594
+ });
2595
+ return completed.length === 1;
2596
+ }
2597
+ activeOf(subscriberId) {
2598
+ return (0, import_drizzle_orm15.and)((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.subscriberId, subscriberId), (0, import_drizzle_orm15.eq)(subscriberPaymentMethods.status, "ACTIVE"));
2599
+ }
2600
+ byReference(gatewayAccount, paymentMethodRef) {
2601
+ return (0, import_drizzle_orm15.and)((0, import_drizzle_orm15.eq)(subscriberPaymentMethods.gatewayAccount, gatewayAccount), (0, import_drizzle_orm15.eq)(subscriberPaymentMethods.paymentMethodRef, paymentMethodRef));
2602
+ }
2603
+ };
2604
+ DrizzleSubscriberPaymentMethodRepository = _ts_decorate17([
2605
+ (0, import_common17.Injectable)(),
2606
+ _ts_param16(0, (0, import_common17.Inject)(DRIZZLE_DB_TOKEN)),
2607
+ _ts_metadata16("design:type", Function),
2608
+ _ts_metadata16("design:paramtypes", [
2609
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
2610
+ ])
2611
+ ], DrizzleSubscriberPaymentMethodRepository);
2612
+
2613
+ // src/drizzle-subscriber.repository.ts
2614
+ var import_node_crypto12 = require("node:crypto");
2615
+ var import_common18 = require("@nestjs/common");
2616
+ var import_drizzle_orm16 = require("drizzle-orm");
2617
+ var import_core7 = require("@saasicat/core");
2618
+ function _ts_decorate18(decorators, target, key, desc10) {
2619
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
2620
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
2621
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2622
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2623
+ }
2624
+ __name(_ts_decorate18, "_ts_decorate");
2625
+ function _ts_metadata17(k, v) {
2626
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2627
+ }
2628
+ __name(_ts_metadata17, "_ts_metadata");
2629
+ function _ts_param17(paramIndex, decorator) {
2630
+ return function(target, key) {
2631
+ decorator(target, key, paramIndex);
2632
+ };
2633
+ }
2634
+ __name(_ts_param17, "_ts_param");
2635
+ var DrizzleSubscriberRepository = class {
2636
+ static {
2637
+ __name(this, "DrizzleSubscriberRepository");
2638
+ }
2639
+ db;
2640
+ constructor(db) {
2641
+ this.db = db;
2642
+ }
2643
+ async createForTenant(data, tx) {
2644
+ const { tenantId, ...details } = data;
2645
+ const now = /* @__PURE__ */ new Date();
2646
+ const subscriberId = (0, import_node_crypto12.randomUUID)();
2647
+ return resolveDb(this.db, tx).transaction(async (transaction) => {
2648
+ const db = transaction;
2649
+ const [created] = await db.insert(subscribers).values({
2650
+ ...details,
2651
+ id: subscriberId,
2652
+ createdAt: now,
2653
+ updatedAt: now
2654
+ }).returning();
2655
+ const linked = await db.insert(subscriberTenants).values({
2656
+ id: (0, import_node_crypto12.randomUUID)(),
2657
+ subscriberId,
2658
+ tenantId,
2659
+ linkedAt: now
2660
+ }).onConflictDoNothing().returning();
2661
+ if (linked.length === 0) {
2662
+ await db.delete(subscribers).where((0, import_drizzle_orm16.eq)(subscribers.id, subscriberId));
2663
+ return null;
2664
+ }
2665
+ return (0, import_core7.toSubscriberRecord)(created, tenantId);
2666
+ });
2667
+ }
2668
+ async findById(subscriberId, tx) {
2669
+ const db = resolveDb(this.db, tx);
2670
+ const rows = await db.select().from(subscribers).where((0, import_drizzle_orm16.eq)(subscribers.id, subscriberId)).limit(1);
2671
+ return rows[0] ? this.withLiveTenant(db, rows[0]) : null;
2672
+ }
2673
+ async findByTenantId(tenantId, tx) {
2674
+ const db = resolveDb(this.db, tx);
2675
+ const rows = await db.select({
2676
+ subscriber: subscribers
2677
+ }).from(subscriberTenants).innerJoin(subscribers, (0, import_drizzle_orm16.eq)(subscribers.id, subscriberTenants.subscriberId)).where((0, import_drizzle_orm16.and)((0, import_drizzle_orm16.eq)(subscriberTenants.tenantId, tenantId), (0, import_drizzle_orm16.isNull)(subscriberTenants.unlinkedAt))).limit(1);
2678
+ return rows[0] ? (0, import_core7.toSubscriberRecord)(rows[0].subscriber, tenantId) : null;
2679
+ }
2680
+ async updateContact(subscriberId, change, tx) {
2681
+ const db = resolveDb(this.db, tx);
2682
+ const rows = await db.update(subscribers).set({
2683
+ ...change,
2684
+ updatedAt: /* @__PURE__ */ new Date()
2685
+ }).where((0, import_drizzle_orm16.eq)(subscribers.id, subscriberId)).returning();
2686
+ return rows[0] ? this.withLiveTenant(db, rows[0]) : null;
2687
+ }
2688
+ async correctIdentity(subscriberId, data, tx) {
2689
+ return resolveDb(this.db, tx).transaction(async (transaction) => {
2690
+ const db = transaction;
2691
+ const [current] = await db.select().from(subscribers).where((0, import_drizzle_orm16.eq)(subscribers.id, subscriberId)).for("update").limit(1);
2692
+ if (!current) return null;
2693
+ const delta = (0, import_core7.identityCorrectionDelta)(current, data.corrected);
2694
+ if (Object.keys(delta.corrected).length === 0) {
2695
+ return {
2696
+ subscriber: await this.withLiveTenant(db, current),
2697
+ correction: null
2698
+ };
2699
+ }
2700
+ const [updated] = await db.update(subscribers).set({
2701
+ ...delta.corrected,
2702
+ updatedAt: /* @__PURE__ */ new Date()
2703
+ }).where((0, import_drizzle_orm16.eq)(subscribers.id, subscriberId)).returning();
2704
+ const [correction] = await db.insert(subscriberCorrections).values({
2705
+ id: (0, import_node_crypto12.randomUUID)(),
2706
+ subscriberId,
2707
+ previous: delta.previous,
2708
+ corrected: delta.corrected,
2709
+ reason: data.reason,
2710
+ correctedBy: data.correctedBy,
2711
+ correctedAt: data.correctedAt
2712
+ }).returning();
2713
+ return {
2714
+ subscriber: await this.withLiveTenant(db, updated),
2715
+ correction: (0, import_core7.toSubscriberCorrectionRecord)(correction)
2716
+ };
2717
+ });
2718
+ }
2719
+ async listCorrections(subscriberId) {
2720
+ const rows = await this.db.select().from(subscriberCorrections).where((0, import_drizzle_orm16.eq)(subscriberCorrections.subscriberId, subscriberId)).orderBy((0, import_drizzle_orm16.desc)(subscriberCorrections.correctedAt), (0, import_drizzle_orm16.desc)(subscriberCorrections.id));
2721
+ return rows.map(import_core7.toSubscriberCorrectionRecord);
2722
+ }
2723
+ async withLiveTenant(db, row) {
2724
+ const links = await db.select({
2725
+ tenantId: subscriberTenants.tenantId
2726
+ }).from(subscriberTenants).where((0, import_drizzle_orm16.and)((0, import_drizzle_orm16.eq)(subscriberTenants.subscriberId, row.id), (0, import_drizzle_orm16.isNull)(subscriberTenants.unlinkedAt))).limit(1);
2727
+ return (0, import_core7.toSubscriberRecord)(row, links[0]?.tenantId ?? null);
2728
+ }
2729
+ };
2730
+ DrizzleSubscriberRepository = _ts_decorate18([
2731
+ (0, import_common18.Injectable)(),
2732
+ _ts_param17(0, (0, import_common18.Inject)(DRIZZLE_DB_TOKEN)),
2733
+ _ts_metadata17("design:type", Function),
2734
+ _ts_metadata17("design:paramtypes", [
2735
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
2736
+ ])
2737
+ ], DrizzleSubscriberRepository);
2738
+
2739
+ // src/drizzle-subscription-usage.adapter.ts
2740
+ var import_common19 = require("@nestjs/common");
2741
+ var import_drizzle_orm17 = require("drizzle-orm");
2742
+ function _ts_decorate19(decorators, target, key, desc10) {
2743
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
2744
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
2745
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2746
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2747
+ }
2748
+ __name(_ts_decorate19, "_ts_decorate");
2749
+ function _ts_metadata18(k, v) {
2750
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2751
+ }
2752
+ __name(_ts_metadata18, "_ts_metadata");
2753
+ function _ts_param18(paramIndex, decorator) {
2754
+ return function(target, key) {
2755
+ decorator(target, key, paramIndex);
2756
+ };
2757
+ }
2758
+ __name(_ts_param18, "_ts_param");
2759
+ var DrizzleSubscriptionUsageAdapter = class {
2760
+ static {
2761
+ __name(this, "DrizzleSubscriptionUsageAdapter");
2762
+ }
2763
+ db;
2764
+ constructor(db) {
2765
+ this.db = db;
2766
+ }
2767
+ async findForTenant(tenantId) {
2768
+ const [subscription] = await this.db.select().from(subscriptions).where((0, import_drizzle_orm17.eq)(subscriptions.tenantId, tenantId)).limit(1);
2769
+ if (!subscription) return null;
2770
+ const [planVersion] = await this.db.select().from(planVersions).where((0, import_drizzle_orm17.eq)(planVersions.id, subscription.planVersionId)).limit(1);
2771
+ if (!planVersion) {
2772
+ throw new Error(`Subscription ${subscription.id} references missing PlanVersion ${subscription.planVersionId}.`);
2773
+ }
2774
+ const [pendingPlanVersion] = subscription.pendingPlanVersionId ? await this.db.select().from(planVersions).where((0, import_drizzle_orm17.eq)(planVersions.id, subscription.pendingPlanVersionId)).limit(1) : [];
2775
+ return {
2776
+ id: subscription.id,
2777
+ plan: planVersion.planId,
2778
+ billingCycle: subscription.billingCycle,
2779
+ status: subscription.status,
2780
+ isPilot: subscription.isPilot,
2781
+ pilotEndsAt: subscription.pilotEndsAt,
2782
+ trialEndsAt: subscription.trialEndsAt,
2783
+ startedAt: subscription.startedAt,
2784
+ currentPeriodStart: subscription.currentPeriodStart,
2785
+ currentPeriodEnd: subscription.currentPeriodEnd,
2786
+ minimumTermUntil: subscription.minimumTermUntil,
2787
+ canceledAt: subscription.canceledAt,
2788
+ canceledEffectiveAt: subscription.canceledEffectiveAt,
2789
+ billingAnchorDay: subscription.billingAnchorDay,
2790
+ pendingPlan: subscription.pendingPlan,
2791
+ pendingBillingCycle: subscription.pendingBillingCycle,
2792
+ pendingEffectiveAt: subscription.pendingEffectiveAt,
2793
+ planVersion: toUsagePlanVersion(planVersion),
2794
+ pendingPlanVersion: pendingPlanVersion ? {
2795
+ ...toUsagePlanVersion(pendingPlanVersion),
2796
+ // The two fields a tenant is shown before accepting: does
2797
+ // the change take anything away, and what changed.
2798
+ nonRegressive: pendingPlanVersion.nonRegressive,
2799
+ publishedChanges: pendingPlanVersion.publishedChanges
2800
+ } : null,
2801
+ pendingPlanVersionEffectiveAt: subscription.pendingPlanVersionEffectiveAt,
2802
+ pendingPlanVersionAccepted: subscription.pendingPlanVersionAccepted,
2803
+ pendingPlanVersionAcceptedAt: subscription.pendingPlanVersionAcceptedAt,
2804
+ packageSnapshot: subscription.packageSnapshot,
2805
+ checkoutOfferId: subscription.checkoutOfferId
2806
+ };
2807
+ }
2808
+ };
2809
+ DrizzleSubscriptionUsageAdapter = _ts_decorate19([
2810
+ (0, import_common19.Injectable)(),
2811
+ _ts_param18(0, (0, import_common19.Inject)(DRIZZLE_DB_TOKEN)),
2812
+ _ts_metadata18("design:type", Function),
2813
+ _ts_metadata18("design:paramtypes", [
2814
+ typeof DrizzleClient === "undefined" ? Object : DrizzleClient
2815
+ ])
2816
+ ], DrizzleSubscriptionUsageAdapter);
2817
+ function toUsagePlanVersion(row) {
2818
+ return {
2819
+ id: row.id,
2820
+ // The canonical schema stores the plan key here, which is what the
2821
+ // record's `planId` means too.
2822
+ planId: row.planId,
2823
+ version: row.version,
2824
+ publishedAt: row.publishedAt,
2825
+ supersededAt: row.supersededAt,
2826
+ changeNote: row.changeNote
2827
+ };
2828
+ }
2829
+ __name(toUsagePlanVersion, "toUsagePlanVersion");
2830
+
2831
+ // src/drizzle-tenant-subscription-write.adapter.ts
2832
+ var import_common20 = require("@nestjs/common");
2833
+ var import_drizzle_orm18 = require("drizzle-orm");
2834
+ function _ts_decorate20(decorators, target, key, desc10) {
2835
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
2836
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
2837
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2838
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2839
+ }
2840
+ __name(_ts_decorate20, "_ts_decorate");
2841
+ function _ts_metadata19(k, v) {
2842
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2843
+ }
2844
+ __name(_ts_metadata19, "_ts_metadata");
2845
+ function _ts_param19(paramIndex, decorator) {
2846
+ return function(target, key) {
2847
+ decorator(target, key, paramIndex);
2848
+ };
2849
+ }
2850
+ __name(_ts_param19, "_ts_param");
2851
+ var DrizzleTenantSubscriptionWrite = class {
2852
+ static {
2853
+ __name(this, "DrizzleTenantSubscriptionWrite");
2854
+ }
2855
+ db;
2856
+ /** Both plan-changing writes bind the version they sell; there is no opt-out here. */
2857
+ bindsPlanVersion = true;
2858
+ plans;
2859
+ constructor(db) {
2860
+ this.db = db;
2861
+ this.plans = new DrizzlePlanRepository(this.db, {
2862
+ validityWindows: true
2863
+ });
2864
+ }
2865
+ async changePlanImmediate(tenantId, input) {
2866
+ return this.db.transaction(async (tx) => {
2867
+ const db = tx;
2868
+ const current = await this.requireSubscription(db, tenantId, {
2869
+ lock: true
2870
+ });
2871
+ const planVersionId = await this.activeVersionId(input.planId, input.periodStart ?? /* @__PURE__ */ new Date(), tx);
2872
+ const pendingMovedAway = await this.pendingVersionBelongsToAnotherPlan(tx, current.pendingPlanVersionId, input.planId);
2873
+ const claimed = await this.claim(db, tenantId, input.expectedCanceledAt, {
2874
+ plan: input.planId,
2875
+ billingCycle: input.cycle,
2876
+ planVersionId,
2877
+ pendingPlan: null,
2878
+ pendingBillingCycle: null,
2879
+ pendingEffectiveAt: null,
2880
+ ...input.nextStatus ? {
2881
+ status: input.nextStatus
2882
+ } : {},
2883
+ ...periodFields(input.periodStart, input.periodEnd),
2884
+ // Null and undefined both mean "leave the trial as it is" — the
2885
+ // platform passes a carried-over end date or nothing at all.
2886
+ ...input.trialEndsAt ? {
2887
+ trialEndsAt: input.trialEndsAt
2888
+ } : {},
2889
+ ...pendingMovedAway ? clearedPendingVersion() : {}
2890
+ });
2891
+ const after = await this.requireSubscription(db, tenantId);
2892
+ return {
2893
+ plan: after.plan,
2894
+ billingCycle: after.billingCycle,
2895
+ claimed: claimed > 0
2896
+ };
2897
+ });
2898
+ }
2899
+ async schedulePlanChange(tenantId, input) {
2900
+ const claimed = await this.claim(this.db, tenantId, input.expectedCanceledAt, {
2901
+ pendingPlan: input.pendingPlan,
2902
+ pendingBillingCycle: input.pendingBillingCycle,
2903
+ pendingEffectiveAt: input.pendingEffectiveAt
2904
+ });
2905
+ return {
2906
+ claimed: claimed > 0
2907
+ };
2908
+ }
2909
+ async acceptPendingPlanVersion(tenantId, userId, now) {
2910
+ const sub = await this.requireSubscription(this.db, tenantId);
2911
+ if (!sub.pendingPlanVersionId) {
2912
+ throw new Error(`No pending PlanVersion for tenant ${tenantId}.`);
875
2913
  }
876
- return toRecord(row);
2914
+ const pendingPlanVersionId = sub.pendingPlanVersionId;
2915
+ const claimed = await this.db.update(subscriptions).set({
2916
+ pendingPlanVersionAccepted: true,
2917
+ pendingPlanVersionAcceptedAt: now,
2918
+ pendingPlanVersionAcceptedByUserId: userId,
2919
+ updatedAt: now
2920
+ }).where((0, import_drizzle_orm18.and)((0, import_drizzle_orm18.eq)(subscriptions.id, sub.id), (0, import_drizzle_orm18.eq)(subscriptions.pendingPlanVersionId, pendingPlanVersionId), (0, import_drizzle_orm18.eq)(subscriptions.pendingPlanVersionAccepted, false))).returning({
2921
+ id: subscriptions.id
2922
+ });
2923
+ const updated = await this.requireSubscription(this.db, tenantId);
2924
+ if (claimed.length === 0 && (updated.pendingPlanVersionId !== pendingPlanVersionId || !updated.pendingPlanVersionAccepted)) {
2925
+ throw new Error(`Pending PlanVersion changed while accepting it for tenant ${tenantId}.`);
2926
+ }
2927
+ return {
2928
+ accepted: true,
2929
+ acceptedAt: updated.pendingPlanVersionAcceptedAt,
2930
+ effectiveAt: updated.pendingPlanVersionEffectiveAt,
2931
+ alreadyAccepted: claimed.length === 0
2932
+ };
877
2933
  }
878
- async countByPromoCode(promoCodeId, status) {
879
- const rows = await this.db.select({
880
- id: promoCodeRedemptions.id
881
- }).from(promoCodeRedemptions).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(promoCodeRedemptions.promoCodeId, promoCodeId), status ? (0, import_drizzle_orm7.eq)(promoCodeRedemptions.status, status) : void 0));
882
- return rows.length;
2934
+ async cancelSubscription(tenantId, input) {
2935
+ const now = /* @__PURE__ */ new Date();
2936
+ const claimed = await this.db.update(subscriptions).set({
2937
+ canceledAt: input.canceledAt,
2938
+ canceledEffectiveAt: input.effectiveAt,
2939
+ // Absent means unchanged rather than null: an ordinary
2940
+ // cancellation does not touch the commitment it was measured
2941
+ // against, and writing the field on every call would erase a
2942
+ // term that is still running.
2943
+ ...input.minimumTermUntil ? {
2944
+ minimumTermUntil: input.minimumTermUntil
2945
+ } : {},
2946
+ // Written only when the cancellation is already effective.
2947
+ // Restating the status this call read would undo whatever
2948
+ // changed it in between — a trial going live between the read
2949
+ // and the write came back as `TRIAL`, entitlements and all.
2950
+ // An ordinary cancellation records dates; it has no opinion
2951
+ // about the status.
2952
+ ...input.terminateNow ? {
2953
+ status: "CANCELED"
2954
+ } : {},
2955
+ updatedAt: now
2956
+ }).where((0, import_drizzle_orm18.and)((0, import_drizzle_orm18.eq)(subscriptions.tenantId, tenantId), (0, import_drizzle_orm18.isNull)(subscriptions.canceledAt), (0, import_drizzle_orm18.isNull)(subscriptions.canceledEffectiveAt))).returning({
2957
+ id: subscriptions.id
2958
+ });
2959
+ const current = await this.requireSubscription(this.db, tenantId);
2960
+ return {
2961
+ canceledAt: current.canceledAt ?? null,
2962
+ canceledEffectiveAt: current.canceledEffectiveAt ?? null,
2963
+ status: current.status,
2964
+ alreadyCanceled: claimed.length === 0
2965
+ };
883
2966
  }
884
- async listByPromoCode(promoCodeId) {
885
- const rows = await this.db.select().from(promoCodeRedemptions).where((0, import_drizzle_orm7.eq)(promoCodeRedemptions.promoCodeId, promoCodeId)).orderBy((0, import_drizzle_orm7.desc)(promoCodeRedemptions.redeemedAt));
886
- return rows.map(toRecord);
2967
+ async applyOnboardingSelection(tenantId, input, redeemPromo) {
2968
+ return this.db.transaction(async (tx) => {
2969
+ const db = tx;
2970
+ const planVersionId = await this.activeVersionId(input.planId, input.periodStart ?? /* @__PURE__ */ new Date(), tx);
2971
+ const claimed = await this.claim(db, tenantId, input.expectedCanceledAt, {
2972
+ plan: input.planId,
2973
+ billingCycle: input.cycle,
2974
+ planVersionId,
2975
+ pendingPlan: null,
2976
+ pendingBillingCycle: null,
2977
+ pendingEffectiveAt: null,
2978
+ ...clearedPendingVersion(),
2979
+ ...input.nextStatus ? {
2980
+ status: input.nextStatus
2981
+ } : {},
2982
+ ...periodFields(input.periodStart, input.periodEnd)
2983
+ });
2984
+ const updated = await this.requireSubscription(db, tenantId);
2985
+ if (claimed === 0) {
2986
+ return {
2987
+ plan: updated.plan,
2988
+ billingCycle: updated.billingCycle,
2989
+ subscriptionId: updated.id,
2990
+ promoRedemption: null,
2991
+ claimed: false
2992
+ };
2993
+ }
2994
+ let promoRedemption = null;
2995
+ if (redeemPromo) {
2996
+ promoRedemption = await redeemPromo(tx, updated.id);
2997
+ }
2998
+ return {
2999
+ plan: updated.plan,
3000
+ billingCycle: updated.billingCycle,
3001
+ subscriptionId: updated.id,
3002
+ promoRedemption,
3003
+ claimed: true
3004
+ };
3005
+ });
887
3006
  }
888
- async expireDueRedemptions(now) {
889
- const expired = await this.db.update(promoCodeRedemptions).set({
890
- status: "EXPIRED"
891
- }).where((0, import_drizzle_orm7.and)((0, import_drizzle_orm7.eq)(promoCodeRedemptions.status, "ACTIVE"), (0, import_drizzle_orm7.lt)(promoCodeRedemptions.endsAt, now))).returning({
892
- id: promoCodeRedemptions.id
3007
+ /**
3008
+ * One `UPDATE` naming the cancellation state the caller decided against,
3009
+ * answering with how many rows it took. Reading first and updating by
3010
+ * tenant alone would leave the window this closes.
3011
+ */
3012
+ async claim(db, tenantId, expectedCanceledAt, values) {
3013
+ const claimed = await db.update(subscriptions).set({
3014
+ ...values,
3015
+ updatedAt: /* @__PURE__ */ new Date()
3016
+ }).where((0, import_drizzle_orm18.and)((0, import_drizzle_orm18.eq)(subscriptions.tenantId, tenantId), canceledAtIs(expectedCanceledAt))).returning({
3017
+ id: subscriptions.id
893
3018
  });
894
- return expired.length;
3019
+ return claimed.length;
3020
+ }
3021
+ /**
3022
+ * `lock: true` takes the row for the rest of the transaction, so a caller
3023
+ * deciding from what it read cannot have the ground move underneath it. It
3024
+ * is only meaningful inside a transaction — outside one the lock is
3025
+ * released with the statement.
3026
+ */
3027
+ async requireSubscription(db, tenantId, options = {}) {
3028
+ const query = db.select().from(subscriptions).where((0, import_drizzle_orm18.eq)(subscriptions.tenantId, tenantId));
3029
+ const rows = await (options.lock ? query.for("update") : query).limit(1);
3030
+ if (!rows[0]) throw new Error(`No subscription for tenant ${tenantId}.`);
3031
+ return rows[0];
3032
+ }
3033
+ async activeVersionId(planKey, asOf, tx) {
3034
+ const active = await this.plans.findActivePlanVersion?.(planKey, asOf, tx);
3035
+ if (!active) throw new Error(`No active PlanVersion for plan '${planKey}'.`);
3036
+ return active.id;
3037
+ }
3038
+ /**
3039
+ * On the caller's transaction, not beside it. The change already holds a
3040
+ * connection for its whole length; asking for a second one waits for the
3041
+ * connection this transaction is holding, and a one-connection pool then
3042
+ * never gets past here.
3043
+ */
3044
+ async pendingVersionBelongsToAnotherPlan(tx, pendingPlanVersionId, targetPlanKey) {
3045
+ if (!pendingPlanVersionId) return false;
3046
+ const pending = await this.plans.findVersionById(pendingPlanVersionId, tx);
3047
+ return !pending || pending.planId !== targetPlanKey;
895
3048
  }
896
3049
  };
897
- DrizzlePromoCodeRedemptionRepository = _ts_decorate9([
898
- (0, import_common9.Injectable)(),
899
- _ts_param8(0, (0, import_common9.Inject)(DRIZZLE_DB_TOKEN)),
900
- _ts_metadata8("design:type", Function),
901
- _ts_metadata8("design:paramtypes", [
3050
+ DrizzleTenantSubscriptionWrite = _ts_decorate20([
3051
+ (0, import_common20.Injectable)(),
3052
+ _ts_param19(0, (0, import_common20.Inject)(DRIZZLE_DB_TOKEN)),
3053
+ _ts_metadata19("design:type", Function),
3054
+ _ts_metadata19("design:paramtypes", [
902
3055
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
903
3056
  ])
904
- ], DrizzlePromoCodeRedemptionRepository);
905
- function toRecord(row) {
3057
+ ], DrizzleTenantSubscriptionWrite);
3058
+ function periodFields(periodStart, periodEnd) {
3059
+ if (!periodStart || !periodEnd) return {};
906
3060
  return {
907
- id: row.id,
908
- promoCodeId: row.promoCodeId,
909
- subscriptionId: row.subscriptionId,
910
- tenantId: row.tenantId,
911
- appliedValueType: row.appliedValueType,
912
- appliedValue: String(row.appliedValue),
913
- appliedDurationType: row.appliedDurationType,
914
- appliedDurationValue: row.appliedDurationValue,
915
- startsAt: row.startsAt,
916
- endsAt: row.endsAt,
917
- status: row.status,
918
- redeemedAt: row.redeemedAt,
919
- reversedAt: row.reversedAt
3061
+ currentPeriodStart: periodStart,
3062
+ currentPeriodEnd: periodEnd,
3063
+ billingAnchorDay: periodStart.getUTCDate()
920
3064
  };
921
3065
  }
922
- __name(toRecord, "toRecord");
3066
+ __name(periodFields, "periodFields");
3067
+ function clearedPendingVersion() {
3068
+ return {
3069
+ pendingPlanVersionId: null,
3070
+ pendingPlanVersionEffectiveAt: null,
3071
+ pendingPlanVersionAccepted: false,
3072
+ pendingPlanVersionAcceptedAt: null,
3073
+ pendingPlanVersionAcceptedByUserId: null,
3074
+ pendingPlanVersionNotifiedAt: null,
3075
+ pendingPlanVersionReminderSentAt: null
3076
+ };
3077
+ }
3078
+ __name(clearedPendingVersion, "clearedPendingVersion");
3079
+ function canceledAtIs(expected) {
3080
+ return expected === null ? (0, import_drizzle_orm18.isNull)(subscriptions.canceledAt) : (0, import_drizzle_orm18.eq)(subscriptions.canceledAt, expected);
3081
+ }
3082
+ __name(canceledAtIs, "canceledAtIs");
923
3083
 
924
- // src/drizzle-promo-code.repository.ts
925
- var import_node_crypto4 = require("node:crypto");
926
- var import_common10 = require("@nestjs/common");
927
- var import_drizzle_orm8 = require("drizzle-orm");
928
- function _ts_decorate10(decorators, target, key, desc4) {
929
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
930
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3084
+ // src/drizzle-subscription-bundle.repository.ts
3085
+ var import_node_crypto13 = require("node:crypto");
3086
+ var import_common21 = require("@nestjs/common");
3087
+ var import_drizzle_orm19 = require("drizzle-orm");
3088
+ function _ts_decorate21(decorators, target, key, desc10) {
3089
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3090
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
931
3091
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
932
3092
  return c > 3 && r && Object.defineProperty(target, key, r), r;
933
3093
  }
934
- __name(_ts_decorate10, "_ts_decorate");
935
- function _ts_metadata9(k, v) {
3094
+ __name(_ts_decorate21, "_ts_decorate");
3095
+ function _ts_metadata20(k, v) {
936
3096
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
937
3097
  }
938
- __name(_ts_metadata9, "_ts_metadata");
939
- function _ts_param9(paramIndex, decorator) {
3098
+ __name(_ts_metadata20, "_ts_metadata");
3099
+ function _ts_param20(paramIndex, decorator) {
940
3100
  return function(target, key) {
941
3101
  decorator(target, key, paramIndex);
942
3102
  };
943
3103
  }
944
- __name(_ts_param9, "_ts_param");
945
- var DrizzlePromoCodeRepository = class {
3104
+ __name(_ts_param20, "_ts_param");
3105
+ var DrizzleSubscriptionBundleRepository = class {
946
3106
  static {
947
- __name(this, "DrizzlePromoCodeRepository");
3107
+ __name(this, "DrizzleSubscriptionBundleRepository");
948
3108
  }
949
3109
  db;
950
3110
  constructor(db) {
951
3111
  this.db = db;
952
3112
  }
953
- async findById(id) {
954
- const rows = await this.db.select().from(promoCodes).where((0, import_drizzle_orm8.eq)(promoCodes.id, id)).limit(1);
955
- const row = rows[0];
956
- return row ? toRecord2(row) : null;
3113
+ async listBySubscription(subscriptionId) {
3114
+ const rows = await this.db.select().from(subscriptionBundles).where((0, import_drizzle_orm19.eq)(subscriptionBundles.subscriptionId, subscriptionId)).orderBy((0, import_drizzle_orm19.desc)(subscriptionBundles.startedAt));
3115
+ return rows.map(toRecord3);
957
3116
  }
958
- async findByCode(code, tx) {
959
- const db = resolveDb(this.db, tx);
960
- const rows = await db.select().from(promoCodes).where((0, import_drizzle_orm8.eq)(promoCodes.code, normalizeCode(code))).limit(1);
961
- const row = rows[0];
962
- if (!row || row.deletedAt) return null;
963
- return toRecord2(row);
3117
+ async findById(subscriptionBundleId) {
3118
+ const rows = await this.db.select().from(subscriptionBundles).where((0, import_drizzle_orm19.eq)(subscriptionBundles.id, subscriptionBundleId)).limit(1);
3119
+ return rows[0] ? toRecord3(rows[0]) : null;
964
3120
  }
965
- async findMany(filter) {
966
- const conditions = [
967
- (0, import_drizzle_orm8.isNull)(promoCodes.deletedAt),
968
- filter.status ? (0, import_drizzle_orm8.eq)(promoCodes.status, filter.status) : void 0,
969
- filter.campaignTag ? (0, import_drizzle_orm8.eq)(promoCodes.campaignTag, filter.campaignTag) : void 0,
970
- filter.search ? (0, import_drizzle_orm8.like)(promoCodes.code, `%${escapeLikePattern(normalizeCode(filter.search))}%`) : void 0
971
- ];
972
- const rows = await this.db.select().from(promoCodes).where((0, import_drizzle_orm8.and)(...conditions)).orderBy(import_drizzle_orm8.sql`${promoCodes.createdAt} DESC`);
973
- return rows.map(toRecord2);
3121
+ async listActiveBySubscription(subscriptionId, asOf = /* @__PURE__ */ new Date(), tx) {
3122
+ const rows = await resolveDb(this.db, tx).select().from(subscriptionBundles).where((0, import_drizzle_orm19.and)((0, import_drizzle_orm19.eq)(subscriptionBundles.subscriptionId, subscriptionId), stillActive(asOf)));
3123
+ return rows.map(toRecord3);
974
3124
  }
975
- async create(data) {
976
- const rows = await this.db.insert(promoCodes).values({
977
- id: (0, import_node_crypto4.randomUUID)(),
978
- code: normalizeCode(data.code),
979
- valueType: data.valueType,
980
- value: data.value.toFixed(2),
981
- durationType: data.durationType,
982
- durationValue: data.durationValue ?? null,
983
- validFrom: data.validFrom ?? null,
984
- validUntil: data.validUntil ?? null,
985
- maxRedemptions: data.maxRedemptions ?? null,
986
- appliesToPlans: data.appliesToPlans ?? [],
987
- appliesToBilling: data.appliesToBilling ?? null,
988
- firstTimeCustomersOnly: data.firstTimeCustomersOnly ?? true,
989
- minimumPlanAmountGross: data.minimumPlanAmountGross?.toFixed(2) ?? null,
990
- allowZeroInvoice: data.allowZeroInvoice ?? false,
991
- description: data.description ?? null,
992
- campaignTag: data.campaignTag ?? null,
993
- revenueDeductionAccount: data.revenueDeductionAccount ?? null,
994
- createdById: data.createdById,
995
- updatedAt: /* @__PURE__ */ new Date()
3125
+ async add(data) {
3126
+ const now = /* @__PURE__ */ new Date();
3127
+ const rows = await this.db.insert(subscriptionBundles).values({
3128
+ id: (0, import_node_crypto13.randomUUID)(),
3129
+ subscriptionId: data.subscriptionId,
3130
+ bundleVersionId: data.bundleVersionId,
3131
+ startedAt: data.startedAt ?? now,
3132
+ minimumTermEndsAt: data.minimumTermEndsAt ?? null,
3133
+ billingCycle: data.billingCycle ?? null,
3134
+ currentPeriodStart: data.currentPeriodStart ?? null,
3135
+ currentPeriodEnd: data.currentPeriodEnd ?? null,
3136
+ canceledAt: null,
3137
+ canceledEffectiveAt: null,
3138
+ updatedAt: now
996
3139
  }).returning();
997
- return toRecord2(rows[0]);
3140
+ return toRecord3(rows[0]);
998
3141
  }
999
- async update(id, data) {
1000
- const rows = await this.db.update(promoCodes).set({
1001
- status: data.status,
1002
- description: data.description,
1003
- validUntil: data.validUntil,
1004
- maxRedemptions: data.maxRedemptions,
3142
+ async cancel(subscriptionBundleId, data) {
3143
+ const rows = await this.db.update(subscriptionBundles).set({
3144
+ canceledAt: data.canceledAt,
3145
+ canceledEffectiveAt: data.canceledEffectiveAt,
1005
3146
  updatedAt: /* @__PURE__ */ new Date()
1006
- }).where((0, import_drizzle_orm8.eq)(promoCodes.id, id)).returning();
1007
- const row = rows[0];
1008
- if (!row) {
1009
- throw new Error(`PromoCode ${id} not found.`);
3147
+ }).where((0, import_drizzle_orm19.and)((0, import_drizzle_orm19.eq)(subscriptionBundles.id, subscriptionBundleId), (0, import_drizzle_orm19.isNull)(subscriptionBundles.canceledAt))).returning();
3148
+ if (!rows[0]) {
3149
+ throw new Error(`SubscriptionBundle '${subscriptionBundleId}' not found or already cancelled`);
1010
3150
  }
1011
- return toRecord2(row);
1012
- }
1013
- async softDelete(id) {
1014
- await this.db.update(promoCodes).set({
1015
- deletedAt: /* @__PURE__ */ new Date(),
1016
- updatedAt: /* @__PURE__ */ new Date()
1017
- }).where((0, import_drizzle_orm8.eq)(promoCodes.id, id));
1018
- }
1019
- async claimSlot(id, tx) {
1020
- const db = resolveDb(this.db, tx);
1021
- const claimed = await db.update(promoCodes).set({
1022
- redemptionsCount: import_drizzle_orm8.sql`${promoCodes.redemptionsCount} + 1`,
1023
- updatedAt: /* @__PURE__ */ new Date()
1024
- }).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(promoCodes.id, id), (0, import_drizzle_orm8.eq)(promoCodes.status, "ACTIVE"), (0, import_drizzle_orm8.isNull)(promoCodes.deletedAt), (0, import_drizzle_orm8.or)((0, import_drizzle_orm8.isNull)(promoCodes.maxRedemptions), (0, import_drizzle_orm8.lt)(promoCodes.redemptionsCount, promoCodes.maxRedemptions)))).returning({
1025
- id: promoCodes.id
1026
- });
1027
- return claimed.length === 1;
1028
- }
1029
- async markExhaustedIfFull(id, tx) {
1030
- const db = resolveDb(this.db, tx);
1031
- await db.update(promoCodes).set({
1032
- status: "EXHAUSTED",
1033
- updatedAt: /* @__PURE__ */ new Date()
1034
- }).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(promoCodes.id, id), (0, import_drizzle_orm8.eq)(promoCodes.status, "ACTIVE"), (0, import_drizzle_orm8.isNotNull)(promoCodes.maxRedemptions), (0, import_drizzle_orm8.gte)(promoCodes.redemptionsCount, promoCodes.maxRedemptions)));
3151
+ return toRecord3(rows[0]);
1035
3152
  }
1036
- async releaseSlot(id, tx) {
1037
- const db = resolveDb(this.db, tx);
1038
- await db.update(promoCodes).set({
1039
- redemptionsCount: import_drizzle_orm8.sql`GREATEST(${promoCodes.redemptionsCount} - 1, 0)`,
1040
- status: import_drizzle_orm8.sql`CASE WHEN ${promoCodes.status} = 'EXHAUSTED' THEN 'ACTIVE' ELSE ${promoCodes.status} END`,
3153
+ async reactivate(subscriptionBundleId) {
3154
+ const rows = await this.db.update(subscriptionBundles).set({
3155
+ canceledAt: null,
3156
+ canceledEffectiveAt: null,
1041
3157
  updatedAt: /* @__PURE__ */ new Date()
1042
- }).where((0, import_drizzle_orm8.eq)(promoCodes.id, id));
3158
+ }).where((0, import_drizzle_orm19.eq)(subscriptionBundles.id, subscriptionBundleId)).returning();
3159
+ if (!rows[0]) throw new Error(`SubscriptionBundle '${subscriptionBundleId}' not found`);
3160
+ return toRecord3(rows[0]);
1043
3161
  }
1044
- async expireDueCodes(now) {
1045
- const expired = await this.db.update(promoCodes).set({
1046
- status: "EXPIRED",
1047
- updatedAt: /* @__PURE__ */ new Date()
1048
- }).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.inArray)(promoCodes.status, [
1049
- "ACTIVE",
1050
- "PAUSED"
1051
- ]), (0, import_drizzle_orm8.lt)(promoCodes.validUntil, now))).returning({
1052
- id: promoCodes.id
1053
- });
1054
- return expired.length;
3162
+ async countActiveByBundleVersionId(bundleVersionId, asOf = /* @__PURE__ */ new Date()) {
3163
+ const rows = await this.db.select({
3164
+ id: subscriptionBundles.id
3165
+ }).from(subscriptionBundles).where((0, import_drizzle_orm19.and)((0, import_drizzle_orm19.eq)(subscriptionBundles.bundleVersionId, bundleVersionId), stillActive(asOf)));
3166
+ return rows.length;
1055
3167
  }
1056
3168
  };
1057
- DrizzlePromoCodeRepository = _ts_decorate10([
1058
- (0, import_common10.Injectable)(),
1059
- _ts_param9(0, (0, import_common10.Inject)(DRIZZLE_DB_TOKEN)),
1060
- _ts_metadata9("design:type", Function),
1061
- _ts_metadata9("design:paramtypes", [
3169
+ DrizzleSubscriptionBundleRepository = _ts_decorate21([
3170
+ (0, import_common21.Injectable)(),
3171
+ _ts_param20(0, (0, import_common21.Inject)(DRIZZLE_DB_TOKEN)),
3172
+ _ts_metadata20("design:type", Function),
3173
+ _ts_metadata20("design:paramtypes", [
1062
3174
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1063
3175
  ])
1064
- ], DrizzlePromoCodeRepository);
1065
- function normalizeCode(code) {
1066
- return code.trim().toUpperCase();
3176
+ ], DrizzleSubscriptionBundleRepository);
3177
+ function stillActive(asOf) {
3178
+ return (0, import_drizzle_orm19.or)((0, import_drizzle_orm19.isNull)(subscriptionBundles.canceledAt), (0, import_drizzle_orm19.gt)(subscriptionBundles.canceledEffectiveAt, asOf));
1067
3179
  }
1068
- __name(normalizeCode, "normalizeCode");
1069
- function toRecord2(row) {
3180
+ __name(stillActive, "stillActive");
3181
+ function toRecord3(row) {
1070
3182
  return {
1071
3183
  id: row.id,
1072
- code: row.code,
1073
- valueType: row.valueType,
1074
- value: String(row.value),
1075
- durationType: row.durationType,
1076
- durationValue: row.durationValue,
1077
- validFrom: row.validFrom,
1078
- validUntil: row.validUntil,
1079
- maxRedemptions: row.maxRedemptions,
1080
- redemptionsCount: row.redemptionsCount,
1081
- appliesToPlans: row.appliesToPlans ?? [],
1082
- appliesToBilling: row.appliesToBilling ?? null,
1083
- firstTimeCustomersOnly: row.firstTimeCustomersOnly,
1084
- minimumPlanAmountGross: row.minimumPlanAmountGross === null ? null : String(row.minimumPlanAmountGross),
1085
- allowZeroInvoice: row.allowZeroInvoice,
1086
- status: row.status,
1087
- description: row.description,
1088
- campaignTag: row.campaignTag,
1089
- revenueDeductionAccount: row.revenueDeductionAccount,
1090
- createdById: row.createdById,
3184
+ subscriptionId: row.subscriptionId,
3185
+ bundleVersionId: row.bundleVersionId,
3186
+ startedAt: row.startedAt,
3187
+ minimumTermEndsAt: row.minimumTermEndsAt,
3188
+ billingCycle: row.billingCycle,
3189
+ currentPeriodStart: row.currentPeriodStart,
3190
+ currentPeriodEnd: row.currentPeriodEnd,
3191
+ canceledAt: row.canceledAt,
3192
+ canceledEffectiveAt: row.canceledEffectiveAt,
1091
3193
  createdAt: row.createdAt,
1092
- updatedAt: row.updatedAt,
1093
- deletedAt: row.deletedAt
3194
+ updatedAt: row.updatedAt
1094
3195
  };
1095
3196
  }
1096
- __name(toRecord2, "toRecord");
3197
+ __name(toRecord3, "toRecord");
1097
3198
 
1098
3199
  // src/drizzle-promo-code-validation-log.repository.ts
1099
- var import_node_crypto5 = require("node:crypto");
1100
- var import_common11 = require("@nestjs/common");
1101
- var import_drizzle_orm9 = require("drizzle-orm");
1102
- function _ts_decorate11(decorators, target, key, desc4) {
1103
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1104
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3200
+ var import_node_crypto14 = require("node:crypto");
3201
+ var import_common22 = require("@nestjs/common");
3202
+ var import_drizzle_orm20 = require("drizzle-orm");
3203
+ function _ts_decorate22(decorators, target, key, desc10) {
3204
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3205
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1105
3206
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1106
3207
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1107
3208
  }
1108
- __name(_ts_decorate11, "_ts_decorate");
1109
- function _ts_metadata10(k, v) {
3209
+ __name(_ts_decorate22, "_ts_decorate");
3210
+ function _ts_metadata21(k, v) {
1110
3211
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1111
3212
  }
1112
- __name(_ts_metadata10, "_ts_metadata");
1113
- function _ts_param10(paramIndex, decorator) {
3213
+ __name(_ts_metadata21, "_ts_metadata");
3214
+ function _ts_param21(paramIndex, decorator) {
1114
3215
  return function(target, key) {
1115
3216
  decorator(target, key, paramIndex);
1116
3217
  };
1117
3218
  }
1118
- __name(_ts_param10, "_ts_param");
3219
+ __name(_ts_param21, "_ts_param");
1119
3220
  var DrizzlePromoCodeValidationLogRepository = class {
1120
3221
  static {
1121
3222
  __name(this, "DrizzlePromoCodeValidationLogRepository");
@@ -1126,7 +3227,7 @@ var DrizzlePromoCodeValidationLogRepository = class {
1126
3227
  }
1127
3228
  async log(args) {
1128
3229
  await this.db.insert(promoCodeValidationLogs).values({
1129
- id: (0, import_node_crypto5.randomUUID)(),
3230
+ id: (0, import_node_crypto14.randomUUID)(),
1130
3231
  promoCodeId: args.promoCodeId,
1131
3232
  codeAttempt: args.codeAttempt.trim().toUpperCase(),
1132
3233
  result: args.result,
@@ -1137,39 +3238,39 @@ var DrizzlePromoCodeValidationLogRepository = class {
1137
3238
  async countValid(promoCodeId) {
1138
3239
  const rows = await this.db.select({
1139
3240
  id: promoCodeValidationLogs.id
1140
- }).from(promoCodeValidationLogs).where((0, import_drizzle_orm9.and)((0, import_drizzle_orm9.eq)(promoCodeValidationLogs.promoCodeId, promoCodeId), (0, import_drizzle_orm9.eq)(promoCodeValidationLogs.result, "VALID")));
3241
+ }).from(promoCodeValidationLogs).where((0, import_drizzle_orm20.and)((0, import_drizzle_orm20.eq)(promoCodeValidationLogs.promoCodeId, promoCodeId), (0, import_drizzle_orm20.eq)(promoCodeValidationLogs.result, "VALID")));
1141
3242
  return rows.length;
1142
3243
  }
1143
3244
  };
1144
- DrizzlePromoCodeValidationLogRepository = _ts_decorate11([
1145
- (0, import_common11.Injectable)(),
1146
- _ts_param10(0, (0, import_common11.Inject)(DRIZZLE_DB_TOKEN)),
1147
- _ts_metadata10("design:type", Function),
1148
- _ts_metadata10("design:paramtypes", [
3245
+ DrizzlePromoCodeValidationLogRepository = _ts_decorate22([
3246
+ (0, import_common22.Injectable)(),
3247
+ _ts_param21(0, (0, import_common22.Inject)(DRIZZLE_DB_TOKEN)),
3248
+ _ts_metadata21("design:type", Function),
3249
+ _ts_metadata21("design:paramtypes", [
1149
3250
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1150
3251
  ])
1151
3252
  ], DrizzlePromoCodeValidationLogRepository);
1152
3253
 
1153
3254
  // src/drizzle-promo-subscription-lookup.adapter.ts
1154
- var import_common12 = require("@nestjs/common");
1155
- var import_drizzle_orm10 = require("drizzle-orm");
1156
- function _ts_decorate12(decorators, target, key, desc4) {
1157
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1158
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3255
+ var import_common23 = require("@nestjs/common");
3256
+ var import_drizzle_orm21 = require("drizzle-orm");
3257
+ function _ts_decorate23(decorators, target, key, desc10) {
3258
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3259
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1159
3260
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1160
3261
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1161
3262
  }
1162
- __name(_ts_decorate12, "_ts_decorate");
1163
- function _ts_metadata11(k, v) {
3263
+ __name(_ts_decorate23, "_ts_decorate");
3264
+ function _ts_metadata22(k, v) {
1164
3265
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1165
3266
  }
1166
- __name(_ts_metadata11, "_ts_metadata");
1167
- function _ts_param11(paramIndex, decorator) {
3267
+ __name(_ts_metadata22, "_ts_metadata");
3268
+ function _ts_param22(paramIndex, decorator) {
1168
3269
  return function(target, key) {
1169
3270
  decorator(target, key, paramIndex);
1170
3271
  };
1171
3272
  }
1172
- __name(_ts_param11, "_ts_param");
3273
+ __name(_ts_param22, "_ts_param");
1173
3274
  var DrizzlePromoSubscriptionLookup = class {
1174
3275
  static {
1175
3276
  __name(this, "DrizzlePromoSubscriptionLookup");
@@ -1186,7 +3287,7 @@ var DrizzlePromoSubscriptionLookup = class {
1186
3287
  plan: subscriptions.plan,
1187
3288
  billingCycle: subscriptions.billingCycle,
1188
3289
  startedAt: subscriptions.startedAt
1189
- }).from(subscriptions).where((0, import_drizzle_orm10.eq)(subscriptions.id, subscriptionId)).limit(1);
3290
+ }).from(subscriptions).where((0, import_drizzle_orm21.eq)(subscriptions.id, subscriptionId)).limit(1);
1190
3291
  const row = rows[0];
1191
3292
  if (!row) return null;
1192
3293
  return {
@@ -1198,35 +3299,35 @@ var DrizzlePromoSubscriptionLookup = class {
1198
3299
  };
1199
3300
  }
1200
3301
  };
1201
- DrizzlePromoSubscriptionLookup = _ts_decorate12([
1202
- (0, import_common12.Injectable)(),
1203
- _ts_param11(0, (0, import_common12.Inject)(DRIZZLE_DB_TOKEN)),
1204
- _ts_metadata11("design:type", Function),
1205
- _ts_metadata11("design:paramtypes", [
3302
+ DrizzlePromoSubscriptionLookup = _ts_decorate23([
3303
+ (0, import_common23.Injectable)(),
3304
+ _ts_param22(0, (0, import_common23.Inject)(DRIZZLE_DB_TOKEN)),
3305
+ _ts_metadata22("design:type", Function),
3306
+ _ts_metadata22("design:paramtypes", [
1206
3307
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1207
3308
  ])
1208
3309
  ], DrizzlePromoSubscriptionLookup);
1209
3310
 
1210
3311
  // src/drizzle-subscription.repository.ts
1211
- var import_common13 = require("@nestjs/common");
1212
- var import_drizzle_orm11 = require("drizzle-orm");
1213
- function _ts_decorate13(decorators, target, key, desc4) {
1214
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1215
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3312
+ var import_common24 = require("@nestjs/common");
3313
+ var import_drizzle_orm22 = require("drizzle-orm");
3314
+ function _ts_decorate24(decorators, target, key, desc10) {
3315
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3316
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1216
3317
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1217
3318
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1218
3319
  }
1219
- __name(_ts_decorate13, "_ts_decorate");
1220
- function _ts_metadata12(k, v) {
3320
+ __name(_ts_decorate24, "_ts_decorate");
3321
+ function _ts_metadata23(k, v) {
1221
3322
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1222
3323
  }
1223
- __name(_ts_metadata12, "_ts_metadata");
1224
- function _ts_param12(paramIndex, decorator) {
3324
+ __name(_ts_metadata23, "_ts_metadata");
3325
+ function _ts_param23(paramIndex, decorator) {
1225
3326
  return function(target, key) {
1226
3327
  decorator(target, key, paramIndex);
1227
3328
  };
1228
3329
  }
1229
- __name(_ts_param12, "_ts_param");
3330
+ __name(_ts_param23, "_ts_param");
1230
3331
  var ACTIVE_STATUSES = [
1231
3332
  "ACTIVE",
1232
3333
  "TRIAL"
@@ -1246,19 +3347,28 @@ var DrizzleSubscriptionRepository = class {
1246
3347
  const db = resolveDb(this.db, tx);
1247
3348
  await db.select({
1248
3349
  id: subscriptions.id
1249
- }).from(subscriptions).where((0, import_drizzle_orm11.eq)(subscriptions.tenantId, tenantId)).for("update");
3350
+ }).from(subscriptions).where((0, import_drizzle_orm22.eq)(subscriptions.tenantId, tenantId)).for("update");
1250
3351
  return this.loadByTenantId(db, tenantId);
1251
3352
  }
1252
3353
  async countByPlanVersionId(planVersionId) {
1253
3354
  const rows = await this.db.select({
1254
3355
  id: subscriptions.id
1255
- }).from(subscriptions).where((0, import_drizzle_orm11.or)((0, import_drizzle_orm11.eq)(subscriptions.planVersionId, planVersionId), (0, import_drizzle_orm11.eq)(subscriptions.pendingPlanVersionId, planVersionId)));
3356
+ }).from(subscriptions).where((0, import_drizzle_orm22.or)((0, import_drizzle_orm22.eq)(subscriptions.planVersionId, planVersionId), (0, import_drizzle_orm22.eq)(subscriptions.pendingPlanVersionId, planVersionId)));
1256
3357
  return rows.length;
1257
3358
  }
1258
- async countActiveByPlanKey(_projectKey) {
3359
+ async countActiveByPlanKey() {
3360
+ const now = /* @__PURE__ */ new Date();
1259
3361
  const rows = await this.db.select({
1260
3362
  plan: subscriptions.plan
1261
- }).from(subscriptions).where((0, import_drizzle_orm11.inArray)(subscriptions.status, ACTIVE_STATUSES));
3363
+ }).from(subscriptions).where((0, import_drizzle_orm22.and)((0, import_drizzle_orm22.inArray)(subscriptions.status, ACTIVE_STATUSES), (0, import_drizzle_orm22.or)(
3364
+ (0, import_drizzle_orm22.isNull)(subscriptions.canceledAt),
3365
+ (0, import_drizzle_orm22.gt)(subscriptions.canceledEffectiveAt, now),
3366
+ // The pre-split row keeps its effective date in the
3367
+ // first column; reading only the second drops that
3368
+ // customer on the day they declare rather than the day
3369
+ // they leave.
3370
+ (0, import_drizzle_orm22.and)((0, import_drizzle_orm22.isNull)(subscriptions.canceledEffectiveAt), (0, import_drizzle_orm22.gt)(subscriptions.canceledAt, now))
3371
+ )));
1262
3372
  const counts = {};
1263
3373
  for (const row of rows) {
1264
3374
  counts[row.plan] = (counts[row.plan] ?? 0) + 1;
@@ -1266,13 +3376,13 @@ var DrizzleSubscriptionRepository = class {
1266
3376
  return counts;
1267
3377
  }
1268
3378
  async loadByTenantId(db, tenantId) {
1269
- const rows = await db.select().from(subscriptions).where((0, import_drizzle_orm11.eq)(subscriptions.tenantId, tenantId)).limit(1);
3379
+ const rows = await db.select().from(subscriptions).where((0, import_drizzle_orm22.eq)(subscriptions.tenantId, tenantId)).limit(1);
1270
3380
  const row = rows[0];
1271
3381
  if (!row) return null;
1272
3382
  return this.toRecord(db, row);
1273
3383
  }
1274
3384
  async toRecord(db, row) {
1275
- const versionRows = await db.select().from(planVersions).where((0, import_drizzle_orm11.eq)(planVersions.id, row.planVersionId)).limit(1);
3385
+ const versionRows = await db.select().from(planVersions).where((0, import_drizzle_orm22.eq)(planVersions.id, row.planVersionId)).limit(1);
1276
3386
  const planVersion = versionRows[0];
1277
3387
  if (!planVersion) {
1278
3388
  throw new Error(`Subscription ${row.id} references missing PlanVersion ${row.planVersionId}.`);
@@ -1287,6 +3397,8 @@ var DrizzleSubscriptionRepository = class {
1287
3397
  pendingPlan: row.pendingPlan,
1288
3398
  pendingEffectiveAt: row.pendingEffectiveAt,
1289
3399
  customLimits: row.customLimits ?? null,
3400
+ canceledAt: row.canceledAt ?? null,
3401
+ canceledEffectiveAt: row.canceledEffectiveAt ?? null,
1290
3402
  planVersionId: row.planVersionId,
1291
3403
  planVersion: {
1292
3404
  planId: planVersion.planId,
@@ -1296,37 +3408,37 @@ var DrizzleSubscriptionRepository = class {
1296
3408
  };
1297
3409
  }
1298
3410
  };
1299
- DrizzleSubscriptionRepository = _ts_decorate13([
1300
- (0, import_common13.Injectable)(),
1301
- _ts_param12(0, (0, import_common13.Inject)(DRIZZLE_DB_TOKEN)),
1302
- _ts_metadata12("design:type", Function),
1303
- _ts_metadata12("design:paramtypes", [
3411
+ DrizzleSubscriptionRepository = _ts_decorate24([
3412
+ (0, import_common24.Injectable)(),
3413
+ _ts_param23(0, (0, import_common24.Inject)(DRIZZLE_DB_TOKEN)),
3414
+ _ts_metadata23("design:type", Function),
3415
+ _ts_metadata23("design:paramtypes", [
1304
3416
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1305
3417
  ])
1306
3418
  ], DrizzleSubscriptionRepository);
1307
3419
 
1308
3420
  // src/drizzle-super-admin-bootstrap.adapter.ts
1309
- var import_node_crypto6 = require("node:crypto");
1310
- var import_common14 = require("@nestjs/common");
1311
- var import_drizzle_orm12 = require("drizzle-orm");
1312
- var import_core = require("@saasicat/core");
1313
- function _ts_decorate14(decorators, target, key, desc4) {
1314
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1315
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3421
+ var import_node_crypto15 = require("node:crypto");
3422
+ var import_common25 = require("@nestjs/common");
3423
+ var import_drizzle_orm23 = require("drizzle-orm");
3424
+ var import_core8 = require("@saasicat/core");
3425
+ function _ts_decorate25(decorators, target, key, desc10) {
3426
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3427
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1316
3428
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1317
3429
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1318
3430
  }
1319
- __name(_ts_decorate14, "_ts_decorate");
1320
- function _ts_metadata13(k, v) {
3431
+ __name(_ts_decorate25, "_ts_decorate");
3432
+ function _ts_metadata24(k, v) {
1321
3433
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1322
3434
  }
1323
- __name(_ts_metadata13, "_ts_metadata");
1324
- function _ts_param13(paramIndex, decorator) {
3435
+ __name(_ts_metadata24, "_ts_metadata");
3436
+ function _ts_param24(paramIndex, decorator) {
1325
3437
  return function(target, key) {
1326
3438
  decorator(target, key, paramIndex);
1327
3439
  };
1328
3440
  }
1329
- __name(_ts_param13, "_ts_param");
3441
+ __name(_ts_param24, "_ts_param");
1330
3442
  var PASSWORD_HASHER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/adapter-drizzle/PasswordHasher");
1331
3443
  var DrizzleSuperAdminBootstrapAdapter = class {
1332
3444
  static {
@@ -1340,18 +3452,18 @@ var DrizzleSuperAdminBootstrapAdapter = class {
1340
3452
  }
1341
3453
  async countSuperAdmins() {
1342
3454
  const rows = await this.db.select({
1343
- value: (0, import_drizzle_orm12.count)()
1344
- }).from(superAdminUsers).where((0, import_drizzle_orm12.and)((0, import_drizzle_orm12.eq)(superAdminUsers.isActive, true), (0, import_drizzle_orm12.isNull)(superAdminUsers.deletedAt)));
3455
+ value: (0, import_drizzle_orm23.count)()
3456
+ }).from(superAdminUsers).where((0, import_drizzle_orm23.and)((0, import_drizzle_orm23.eq)(superAdminUsers.isActive, true), (0, import_drizzle_orm23.isNull)(superAdminUsers.deletedAt)));
1345
3457
  return rows[0]?.value ?? 0;
1346
3458
  }
1347
3459
  async createSuperAdmin(input) {
1348
3460
  const email = input.email.trim().toLowerCase();
1349
- const existing = await this.db.select().from(superAdminUsers).where((0, import_drizzle_orm12.eq)(superAdminUsers.email, email)).limit(1);
3461
+ const existing = await this.db.select().from(superAdminUsers).where((0, import_drizzle_orm23.eq)(superAdminUsers.email, email)).limit(1);
1350
3462
  if (existing[0]) {
1351
- throw new import_core.PlatformUserExistsError(email, existing[0].platformRole);
3463
+ throw new import_core8.PlatformUserExistsError(email, existing[0].platformRole);
1352
3464
  }
1353
3465
  const rows = await this.db.insert(superAdminUsers).values({
1354
- id: (0, import_node_crypto6.randomUUID)(),
3466
+ id: (0, import_node_crypto15.randomUUID)(),
1355
3467
  email,
1356
3468
  passwordHash: await this.passwordHasher.hash(input.password),
1357
3469
  firstName: input.firstName ?? null,
@@ -1371,36 +3483,36 @@ var DrizzleSuperAdminBootstrapAdapter = class {
1371
3483
  };
1372
3484
  }
1373
3485
  };
1374
- DrizzleSuperAdminBootstrapAdapter = _ts_decorate14([
1375
- (0, import_common14.Injectable)(),
1376
- _ts_param13(0, (0, import_common14.Inject)(DRIZZLE_DB_TOKEN)),
1377
- _ts_param13(1, (0, import_common14.Inject)(PASSWORD_HASHER_TOKEN)),
1378
- _ts_metadata13("design:type", Function),
1379
- _ts_metadata13("design:paramtypes", [
3486
+ DrizzleSuperAdminBootstrapAdapter = _ts_decorate25([
3487
+ (0, import_common25.Injectable)(),
3488
+ _ts_param24(0, (0, import_common25.Inject)(DRIZZLE_DB_TOKEN)),
3489
+ _ts_param24(1, (0, import_common25.Inject)(PASSWORD_HASHER_TOKEN)),
3490
+ _ts_metadata24("design:type", Function),
3491
+ _ts_metadata24("design:paramtypes", [
1380
3492
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
1381
3493
  typeof PasswordHasher === "undefined" ? Object : PasswordHasher
1382
3494
  ])
1383
3495
  ], DrizzleSuperAdminBootstrapAdapter);
1384
3496
 
1385
3497
  // src/drizzle-transaction-runner.adapter.ts
1386
- var import_common15 = require("@nestjs/common");
1387
- function _ts_decorate15(decorators, target, key, desc4) {
1388
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1389
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3498
+ var import_common26 = require("@nestjs/common");
3499
+ function _ts_decorate26(decorators, target, key, desc10) {
3500
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3501
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1390
3502
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1391
3503
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1392
3504
  }
1393
- __name(_ts_decorate15, "_ts_decorate");
1394
- function _ts_metadata14(k, v) {
3505
+ __name(_ts_decorate26, "_ts_decorate");
3506
+ function _ts_metadata25(k, v) {
1395
3507
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1396
3508
  }
1397
- __name(_ts_metadata14, "_ts_metadata");
1398
- function _ts_param14(paramIndex, decorator) {
3509
+ __name(_ts_metadata25, "_ts_metadata");
3510
+ function _ts_param25(paramIndex, decorator) {
1399
3511
  return function(target, key) {
1400
3512
  decorator(target, key, paramIndex);
1401
3513
  };
1402
3514
  }
1403
- __name(_ts_param14, "_ts_param");
3515
+ __name(_ts_param25, "_ts_param");
1404
3516
  var DrizzleTransactionRunner = class {
1405
3517
  static {
1406
3518
  __name(this, "DrizzleTransactionRunner");
@@ -1413,24 +3525,24 @@ var DrizzleTransactionRunner = class {
1413
3525
  return this.db.transaction((tx) => fn(tx));
1414
3526
  }
1415
3527
  };
1416
- DrizzleTransactionRunner = _ts_decorate15([
1417
- (0, import_common15.Injectable)(),
1418
- _ts_param14(0, (0, import_common15.Inject)(DRIZZLE_DB_TOKEN)),
1419
- _ts_metadata14("design:type", Function),
1420
- _ts_metadata14("design:paramtypes", [
3528
+ DrizzleTransactionRunner = _ts_decorate26([
3529
+ (0, import_common26.Injectable)(),
3530
+ _ts_param25(0, (0, import_common26.Inject)(DRIZZLE_DB_TOKEN)),
3531
+ _ts_metadata25("design:type", Function),
3532
+ _ts_metadata25("design:paramtypes", [
1421
3533
  typeof DrizzleClient === "undefined" ? Object : DrizzleClient
1422
3534
  ])
1423
3535
  ], DrizzleTransactionRunner);
1424
3536
 
1425
3537
  // src/zero-promo-revenue-aggregator.adapter.ts
1426
- var import_common16 = require("@nestjs/common");
1427
- function _ts_decorate16(decorators, target, key, desc4) {
1428
- var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1429
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc4);
3538
+ var import_common27 = require("@nestjs/common");
3539
+ function _ts_decorate27(decorators, target, key, desc10) {
3540
+ var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
3541
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
1430
3542
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1431
3543
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1432
3544
  }
1433
- __name(_ts_decorate16, "_ts_decorate");
3545
+ __name(_ts_decorate27, "_ts_decorate");
1434
3546
  var ZeroPromoRevenueDeductionAggregator = class {
1435
3547
  static {
1436
3548
  __name(this, "ZeroPromoRevenueDeductionAggregator");
@@ -1439,8 +3551,8 @@ var ZeroPromoRevenueDeductionAggregator = class {
1439
3551
  return "0.00";
1440
3552
  }
1441
3553
  };
1442
- ZeroPromoRevenueDeductionAggregator = _ts_decorate16([
1443
- (0, import_common16.Injectable)()
3554
+ ZeroPromoRevenueDeductionAggregator = _ts_decorate27([
3555
+ (0, import_common27.Injectable)()
1444
3556
  ], ZeroPromoRevenueDeductionAggregator);
1445
3557
 
1446
3558
  // src/drizzle-persistence.ts
@@ -1466,18 +3578,55 @@ function drizzlePersistence(options) {
1466
3578
  transactionRunner: provide((client) => new DrizzleTransactionRunner(client)),
1467
3579
  auditQuery: provide((client) => new DrizzleAuditQueryAdapter(client)),
1468
3580
  auditStats: provide((client) => new DrizzleAuditStatsAdapter(client)),
1469
- superAdminProvisioning: buildProvisioning(db, options.passwordHasher)
3581
+ superAdminProvisioning: buildProvisioning(db, options.passwordHasher),
3582
+ appliedSettings: provide((client) => new DrizzleAppliedSettingsRepository(client))
1470
3583
  },
1471
3584
  entitlement: {
1472
3585
  subscriptionRepository: provide((client) => new DrizzleSubscriptionRepository(client)),
1473
- planVersionRepository: provide((client) => new DrizzlePlanVersionRepository(client))
3586
+ planVersionRepository: provide((client) => new DrizzlePlanVersionRepository(client)),
3587
+ // Entitlement reads the bookings as well as the plan: a tenant's
3588
+ // effective features are the union of the plan and the bundles
3589
+ // booked on top of it, so an adapter that omits this grants less
3590
+ // than the tenant paid for.
3591
+ subscriptionBundleRepository: provide((client) => new DrizzleSubscriptionBundleRepository(client)),
3592
+ // What the tenant actually agreed to, frozen at signing. Entitlement
3593
+ // prefers it over the live catalogue where it exists, which is what
3594
+ // makes a price change stop at the contracts already signed.
3595
+ subscriptionContractRepository: provide((client) => new DrizzleSubscriptionContractRepository(client)),
3596
+ // The party each of those contracts is concluded with.
3597
+ subscriberRepository: provide((client) => new DrizzleSubscriberRepository(client)),
3598
+ // The catalogue behind those bookings: entitlement resolves a
3599
+ // booking's features by reading the pinned version.
3600
+ bundleRepository: provide((client) => new DrizzleBundleRepository(client, options.bundle))
3601
+ },
3602
+ // The editable catalogue: which plans a project sells and in which
3603
+ // versions. Present since 2026-08-27 — before that this adapter could
3604
+ // read a plan version but not author one, so a Drizzle consumer had to
3605
+ // bring their own `CatalogModule` wiring for a slice the adapter was
3606
+ // otherwise complete for.
3607
+ catalog: {
3608
+ planRepository: provide((client) => new DrizzlePlanRepository(client, options.plan)),
3609
+ bundleRepository: provide((client) => new DrizzleBundleRepository(client, options.bundle))
3610
+ },
3611
+ // The tenant's own billing page and the writes behind its buttons.
3612
+ // Both members are required, and until 2026-08-27 neither existed —
3613
+ // which left `DrizzleTenantSubscriptionWrite` unreachable through the
3614
+ // documented `drizzlePersistence({ db })` path however complete it was.
3615
+ tenantBilling: {
3616
+ subscriptionUsagePort: provide((client) => new DrizzleSubscriptionUsageAdapter(client)),
3617
+ subscriptionWritePort: provide((client) => new DrizzleTenantSubscriptionWrite(client))
1474
3618
  },
1475
3619
  promo: {
1476
3620
  promoCodeRepository: provide((client) => new DrizzlePromoCodeRepository(client)),
1477
3621
  redemptionRepository: provide((client) => new DrizzlePromoCodeRedemptionRepository(client)),
1478
3622
  validationLogRepository: provide((client) => new DrizzlePromoCodeValidationLogRepository(client)),
1479
3623
  subscriptionLookup: provide((client) => new DrizzlePromoSubscriptionLookup(client)),
1480
- revenueAggregator: new ZeroPromoRevenueDeductionAggregator()
3624
+ revenueAggregator: new ZeroPromoRevenueDeductionAggregator(),
3625
+ holdRepository: provide((client) => new DrizzlePromoCodeHoldRepository(client))
3626
+ },
3627
+ payments: {
3628
+ paymentEventLog: provide((client) => new DrizzlePaymentEventLog(client)),
3629
+ subscriberPaymentMethodRepository: provide((client) => new DrizzleSubscriberPaymentMethodRepository(client))
1481
3630
  },
1482
3631
  planCatalogReadSink: provide((client) => new DrizzlePlanCatalogReadSink(client)),
1483
3632
  planCatalogImportSink: provide((client) => new DrizzlePlanCatalogImportSink(client))
@@ -1524,19 +3673,30 @@ __name(buildProvisioning, "buildProvisioning");
1524
3673
  0 && (module.exports = {
1525
3674
  AsyncLocalRlsBypassAdapter,
1526
3675
  DRIZZLE_DB_TOKEN,
3676
+ DrizzleAppliedSettingsRepository,
1527
3677
  DrizzleAuditAdapter,
1528
3678
  DrizzleAuditQueryAdapter,
1529
3679
  DrizzleAuditStatsAdapter,
3680
+ DrizzleBundleRepository,
1530
3681
  DrizzleMfaAdapter,
3682
+ DrizzlePaymentEventLog,
1531
3683
  DrizzlePlanCatalogImportSink,
1532
3684
  DrizzlePlanCatalogReadSink,
3685
+ DrizzlePlanRepository,
1533
3686
  DrizzlePlanVersionRepository,
3687
+ DrizzlePromoCodeHoldRepository,
1534
3688
  DrizzlePromoCodeRedemptionRepository,
1535
3689
  DrizzlePromoCodeRepository,
1536
3690
  DrizzlePromoCodeValidationLogRepository,
1537
3691
  DrizzlePromoSubscriptionLookup,
3692
+ DrizzleSubscriberPaymentMethodRepository,
3693
+ DrizzleSubscriberRepository,
3694
+ DrizzleSubscriptionBundleRepository,
3695
+ DrizzleSubscriptionContractRepository,
1538
3696
  DrizzleSubscriptionRepository,
3697
+ DrizzleSubscriptionUsageAdapter,
1539
3698
  DrizzleSuperAdminBootstrapAdapter,
3699
+ DrizzleTenantSubscriptionWrite,
1540
3700
  DrizzleTransactionRunner,
1541
3701
  PASSWORD_HASHER_TOKEN,
1542
3702
  ZeroPromoRevenueDeductionAggregator,