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