@saasicat/adapter-prisma 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +142 -0
- package/dist/index.cjs +1485 -0
- package/dist/index.d.cts +859 -0
- package/dist/index.d.ts +859 -0
- package/dist/index.js +1441 -0
- package/package.json +60 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
import { PersistenceInjectionToken, PasswordHasher, SaasicatPersistenceAdapter, TransactionRunner, TransactionContext, MfaPort, AuditPort, AdminActor, AuditQueryPort, AuditQuery, AuditEntry, AuditStatsPort, RlsBypassPort, SubscriptionRepository, SubscriptionRecord, PlanVersionRepository, PlanVersionRecord, PromoCodeRepository, PromoCodeRecord, PromoCodeFilter, CreatePromoCodeData, UpdatePromoCodeData, PromoCodeRedemptionRepository, PromoCodeRedemptionRecord, PromoCodeRedemptionStatus, PromoCodeRedemptionListItem, PromoCodeValidationLogRepository, PromoSubscriptionLookup, BillingCycle, PromoRevenueDeductionAggregator, SuperAdminProvisioningPort, CreateSuperAdminCliInput, PlatformUserDto, PlanCatalogReadSink, PlanCatalogReadSnapshot, PlanCatalogImportSink, UpsertPlanInput, UpsertResult, UpsertPlanVersionInput, UpsertFeatureCatalogEntryInput } from '@saasicat/types';
|
|
2
|
+
|
|
3
|
+
declare const PRISMA_CLIENT_TOKEN: unique symbol;
|
|
4
|
+
/** Prisma `Decimal` values arrive as objects with `toString()`; tests may use plain values. */
|
|
5
|
+
type DecimalLike = {
|
|
6
|
+
toString(): string;
|
|
7
|
+
} | string | number;
|
|
8
|
+
interface SubscriptionRowLike {
|
|
9
|
+
id: string;
|
|
10
|
+
tenantId: string;
|
|
11
|
+
plan: string;
|
|
12
|
+
billingCycle: string;
|
|
13
|
+
status: string;
|
|
14
|
+
isPilot: boolean;
|
|
15
|
+
trialEntitlementPlan: string | null;
|
|
16
|
+
pendingPlan: string | null;
|
|
17
|
+
pendingEffectiveAt: Date | null;
|
|
18
|
+
customLimits: unknown;
|
|
19
|
+
planVersionId: string | null;
|
|
20
|
+
pendingPlanVersionId: string | null;
|
|
21
|
+
startedAt: Date | null;
|
|
22
|
+
}
|
|
23
|
+
interface PlanVersionRowLike {
|
|
24
|
+
id: string;
|
|
25
|
+
planId: string;
|
|
26
|
+
version: number;
|
|
27
|
+
baseVersionId: string | null;
|
|
28
|
+
features: unknown;
|
|
29
|
+
quotas: unknown;
|
|
30
|
+
monthlyNet: DecimalLike;
|
|
31
|
+
yearlyNet: DecimalLike;
|
|
32
|
+
marketed: boolean;
|
|
33
|
+
publishedAt: Date | null;
|
|
34
|
+
supersededAt: Date | null;
|
|
35
|
+
publishedChanges: unknown;
|
|
36
|
+
changeNote: string;
|
|
37
|
+
nonRegressive: boolean;
|
|
38
|
+
createdByUserId: string | null;
|
|
39
|
+
publishedByUserId: string | null;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
}
|
|
43
|
+
interface PlanRowLike {
|
|
44
|
+
id: string;
|
|
45
|
+
projectKey: string;
|
|
46
|
+
planKey: string;
|
|
47
|
+
label: string;
|
|
48
|
+
description: string | null;
|
|
49
|
+
icon: string | null;
|
|
50
|
+
sortOrder: number;
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
updatedAt: Date;
|
|
53
|
+
deletedAt: Date | null;
|
|
54
|
+
}
|
|
55
|
+
interface FeatureCatalogEntryRowLike {
|
|
56
|
+
id: string;
|
|
57
|
+
projectKey: string;
|
|
58
|
+
featureKey: string;
|
|
59
|
+
label: string;
|
|
60
|
+
description: string | null;
|
|
61
|
+
marketingLabel: string | null;
|
|
62
|
+
marketingDescription: string | null;
|
|
63
|
+
icon: string | null;
|
|
64
|
+
tier: string | null;
|
|
65
|
+
discoveryStatus: string;
|
|
66
|
+
requires: string[];
|
|
67
|
+
replaces: string[];
|
|
68
|
+
successorKey: string | null;
|
|
69
|
+
approvedAt: Date | null;
|
|
70
|
+
approvedBy: string | null;
|
|
71
|
+
approvedSignature: string | null;
|
|
72
|
+
plannedOnly: boolean;
|
|
73
|
+
core: boolean;
|
|
74
|
+
i18n: unknown;
|
|
75
|
+
sortOrder: number;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
updatedAt: Date;
|
|
78
|
+
deletedAt: Date | null;
|
|
79
|
+
}
|
|
80
|
+
interface PromoCodeRowLike {
|
|
81
|
+
id: string;
|
|
82
|
+
code: string;
|
|
83
|
+
valueType: string;
|
|
84
|
+
value: DecimalLike;
|
|
85
|
+
durationType: string;
|
|
86
|
+
durationValue: number | null;
|
|
87
|
+
validFrom: Date | null;
|
|
88
|
+
validUntil: Date | null;
|
|
89
|
+
maxRedemptions: number | null;
|
|
90
|
+
redemptionsCount: number;
|
|
91
|
+
appliesToPlans: string[];
|
|
92
|
+
appliesToBilling: string | null;
|
|
93
|
+
firstTimeCustomersOnly: boolean;
|
|
94
|
+
minimumPlanAmountGross: DecimalLike | null;
|
|
95
|
+
allowZeroInvoice: boolean;
|
|
96
|
+
status: string;
|
|
97
|
+
description: string | null;
|
|
98
|
+
campaignTag: string | null;
|
|
99
|
+
revenueDeductionAccount: string | null;
|
|
100
|
+
createdById: string;
|
|
101
|
+
createdAt: Date;
|
|
102
|
+
updatedAt: Date;
|
|
103
|
+
deletedAt: Date | null;
|
|
104
|
+
}
|
|
105
|
+
interface PromoCodeRedemptionRowLike {
|
|
106
|
+
id: string;
|
|
107
|
+
promoCodeId: string;
|
|
108
|
+
subscriptionId: string;
|
|
109
|
+
tenantId: string;
|
|
110
|
+
appliedValueType: string;
|
|
111
|
+
appliedValue: DecimalLike;
|
|
112
|
+
appliedDurationType: string;
|
|
113
|
+
appliedDurationValue: number | null;
|
|
114
|
+
startsAt: Date;
|
|
115
|
+
endsAt: Date | null;
|
|
116
|
+
status: string;
|
|
117
|
+
redeemedAt: Date;
|
|
118
|
+
reversedAt: Date | null;
|
|
119
|
+
}
|
|
120
|
+
interface AuditLogRowLike {
|
|
121
|
+
id: string;
|
|
122
|
+
tenantId: string | null;
|
|
123
|
+
userId: string | null;
|
|
124
|
+
entity: string;
|
|
125
|
+
entityId: string;
|
|
126
|
+
action: string;
|
|
127
|
+
changes: unknown;
|
|
128
|
+
actorTag: string | null;
|
|
129
|
+
ipAddress: string | null;
|
|
130
|
+
userAgent: string | null;
|
|
131
|
+
createdAt: Date;
|
|
132
|
+
}
|
|
133
|
+
interface SuperAdminUserRowLike {
|
|
134
|
+
id: string;
|
|
135
|
+
email: string;
|
|
136
|
+
passwordHash: string;
|
|
137
|
+
firstName: string | null;
|
|
138
|
+
lastName: string | null;
|
|
139
|
+
platformRole: string;
|
|
140
|
+
isActive: boolean;
|
|
141
|
+
lastLoginAt: Date | null;
|
|
142
|
+
deletedAt: Date | null;
|
|
143
|
+
createdAt: Date;
|
|
144
|
+
updatedAt: Date;
|
|
145
|
+
}
|
|
146
|
+
type SortDirection = 'asc' | 'desc';
|
|
147
|
+
interface SubscriptionDelegateLike {
|
|
148
|
+
findUnique(args: {
|
|
149
|
+
where: {
|
|
150
|
+
id?: string;
|
|
151
|
+
tenantId?: string;
|
|
152
|
+
};
|
|
153
|
+
}): Promise<SubscriptionRowLike | null>;
|
|
154
|
+
findMany(args?: {
|
|
155
|
+
where?: {
|
|
156
|
+
status?: {
|
|
157
|
+
in: string[];
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
}): Promise<SubscriptionRowLike[]>;
|
|
161
|
+
count(args?: {
|
|
162
|
+
where?: {
|
|
163
|
+
OR?: Array<{
|
|
164
|
+
planVersionId?: string;
|
|
165
|
+
pendingPlanVersionId?: string;
|
|
166
|
+
}>;
|
|
167
|
+
};
|
|
168
|
+
}): Promise<number>;
|
|
169
|
+
}
|
|
170
|
+
interface PlanVersionDelegateLike {
|
|
171
|
+
findUnique(args: {
|
|
172
|
+
where: {
|
|
173
|
+
id: string;
|
|
174
|
+
};
|
|
175
|
+
}): Promise<PlanVersionRowLike | null>;
|
|
176
|
+
findFirst(args: {
|
|
177
|
+
where: {
|
|
178
|
+
planId?: string;
|
|
179
|
+
version?: number;
|
|
180
|
+
publishedAt?: {
|
|
181
|
+
not: null;
|
|
182
|
+
} | null;
|
|
183
|
+
supersededAt?: null;
|
|
184
|
+
};
|
|
185
|
+
orderBy?: {
|
|
186
|
+
version?: SortDirection;
|
|
187
|
+
};
|
|
188
|
+
}): Promise<PlanVersionRowLike | null>;
|
|
189
|
+
findMany(args?: {
|
|
190
|
+
where?: {
|
|
191
|
+
planId?: {
|
|
192
|
+
in: string[];
|
|
193
|
+
};
|
|
194
|
+
publishedAt?: {
|
|
195
|
+
not: null;
|
|
196
|
+
} | null;
|
|
197
|
+
supersededAt?: null;
|
|
198
|
+
};
|
|
199
|
+
}): Promise<PlanVersionRowLike[]>;
|
|
200
|
+
create(args: {
|
|
201
|
+
data: {
|
|
202
|
+
planId: string;
|
|
203
|
+
version: number;
|
|
204
|
+
features: unknown;
|
|
205
|
+
quotas: unknown;
|
|
206
|
+
monthlyNet: string;
|
|
207
|
+
yearlyNet: string;
|
|
208
|
+
marketed: boolean;
|
|
209
|
+
publishedAt: Date | null;
|
|
210
|
+
changeNote: string;
|
|
211
|
+
};
|
|
212
|
+
}): Promise<PlanVersionRowLike>;
|
|
213
|
+
updateMany(args: {
|
|
214
|
+
where: {
|
|
215
|
+
planId?: string;
|
|
216
|
+
publishedAt?: {
|
|
217
|
+
not: null;
|
|
218
|
+
} | null;
|
|
219
|
+
supersededAt?: null;
|
|
220
|
+
version?: {
|
|
221
|
+
lt: number;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
data: {
|
|
225
|
+
supersededAt?: Date;
|
|
226
|
+
};
|
|
227
|
+
}): Promise<{
|
|
228
|
+
count: number;
|
|
229
|
+
}>;
|
|
230
|
+
}
|
|
231
|
+
interface PlanDelegateLike {
|
|
232
|
+
findFirst(args: {
|
|
233
|
+
where: {
|
|
234
|
+
projectKey?: string;
|
|
235
|
+
planKey?: string;
|
|
236
|
+
deletedAt?: null;
|
|
237
|
+
};
|
|
238
|
+
}): Promise<PlanRowLike | null>;
|
|
239
|
+
findMany(args?: {
|
|
240
|
+
where?: {
|
|
241
|
+
projectKey?: string;
|
|
242
|
+
deletedAt?: null;
|
|
243
|
+
};
|
|
244
|
+
orderBy?: {
|
|
245
|
+
sortOrder?: SortDirection;
|
|
246
|
+
};
|
|
247
|
+
}): Promise<PlanRowLike[]>;
|
|
248
|
+
create(args: {
|
|
249
|
+
data: {
|
|
250
|
+
projectKey: string;
|
|
251
|
+
planKey: string;
|
|
252
|
+
label: string;
|
|
253
|
+
description?: string | null;
|
|
254
|
+
sortOrder?: number;
|
|
255
|
+
};
|
|
256
|
+
}): Promise<PlanRowLike>;
|
|
257
|
+
}
|
|
258
|
+
interface FeatureCatalogEntryDelegateLike {
|
|
259
|
+
findFirst(args: {
|
|
260
|
+
where: {
|
|
261
|
+
projectKey?: string;
|
|
262
|
+
featureKey?: string;
|
|
263
|
+
deletedAt?: null;
|
|
264
|
+
};
|
|
265
|
+
}): Promise<FeatureCatalogEntryRowLike | null>;
|
|
266
|
+
findMany(args?: {
|
|
267
|
+
where?: {
|
|
268
|
+
projectKey?: string;
|
|
269
|
+
deletedAt?: null;
|
|
270
|
+
};
|
|
271
|
+
orderBy?: {
|
|
272
|
+
sortOrder?: SortDirection;
|
|
273
|
+
};
|
|
274
|
+
}): Promise<FeatureCatalogEntryRowLike[]>;
|
|
275
|
+
create(args: {
|
|
276
|
+
data: {
|
|
277
|
+
projectKey: string;
|
|
278
|
+
featureKey: string;
|
|
279
|
+
label: string;
|
|
280
|
+
icon?: string | null;
|
|
281
|
+
tier?: string | null;
|
|
282
|
+
plannedOnly?: boolean;
|
|
283
|
+
core?: boolean;
|
|
284
|
+
};
|
|
285
|
+
}): Promise<FeatureCatalogEntryRowLike>;
|
|
286
|
+
}
|
|
287
|
+
interface PromoCodeDelegateLike {
|
|
288
|
+
findUnique(args: {
|
|
289
|
+
where: {
|
|
290
|
+
id?: string;
|
|
291
|
+
code?: string;
|
|
292
|
+
};
|
|
293
|
+
}): Promise<PromoCodeRowLike | null>;
|
|
294
|
+
findMany(args?: {
|
|
295
|
+
where?: {
|
|
296
|
+
deletedAt?: null;
|
|
297
|
+
status?: string;
|
|
298
|
+
campaignTag?: string;
|
|
299
|
+
code?: {
|
|
300
|
+
contains: string;
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
orderBy?: {
|
|
304
|
+
createdAt?: SortDirection;
|
|
305
|
+
};
|
|
306
|
+
}): Promise<PromoCodeRowLike[]>;
|
|
307
|
+
create(args: {
|
|
308
|
+
data: {
|
|
309
|
+
code: string;
|
|
310
|
+
valueType: string;
|
|
311
|
+
value: string;
|
|
312
|
+
durationType: string;
|
|
313
|
+
durationValue: number | null;
|
|
314
|
+
validFrom: Date | null;
|
|
315
|
+
validUntil: Date | null;
|
|
316
|
+
maxRedemptions: number | null;
|
|
317
|
+
appliesToPlans: string[];
|
|
318
|
+
appliesToBilling: string | null;
|
|
319
|
+
firstTimeCustomersOnly: boolean;
|
|
320
|
+
minimumPlanAmountGross: string | null;
|
|
321
|
+
allowZeroInvoice: boolean;
|
|
322
|
+
description: string | null;
|
|
323
|
+
campaignTag: string | null;
|
|
324
|
+
revenueDeductionAccount: string | null;
|
|
325
|
+
createdById: string;
|
|
326
|
+
};
|
|
327
|
+
}): Promise<PromoCodeRowLike>;
|
|
328
|
+
update(args: {
|
|
329
|
+
where: {
|
|
330
|
+
id: string;
|
|
331
|
+
};
|
|
332
|
+
data: {
|
|
333
|
+
status?: string;
|
|
334
|
+
description?: string | null;
|
|
335
|
+
validUntil?: Date | null;
|
|
336
|
+
maxRedemptions?: number | null;
|
|
337
|
+
deletedAt?: Date;
|
|
338
|
+
};
|
|
339
|
+
}): Promise<PromoCodeRowLike>;
|
|
340
|
+
updateMany(args: {
|
|
341
|
+
where: {
|
|
342
|
+
status?: {
|
|
343
|
+
in: string[];
|
|
344
|
+
};
|
|
345
|
+
validUntil?: {
|
|
346
|
+
lt: Date;
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
data: {
|
|
350
|
+
status?: string;
|
|
351
|
+
};
|
|
352
|
+
}): Promise<{
|
|
353
|
+
count: number;
|
|
354
|
+
}>;
|
|
355
|
+
}
|
|
356
|
+
interface PromoCodeRedemptionDelegateLike {
|
|
357
|
+
findUnique(args: {
|
|
358
|
+
where: {
|
|
359
|
+
id?: string;
|
|
360
|
+
subscriptionId?: string;
|
|
361
|
+
};
|
|
362
|
+
}): Promise<PromoCodeRedemptionRowLike | null>;
|
|
363
|
+
findMany(args?: {
|
|
364
|
+
where?: {
|
|
365
|
+
promoCodeId?: string;
|
|
366
|
+
};
|
|
367
|
+
orderBy?: {
|
|
368
|
+
redeemedAt?: SortDirection;
|
|
369
|
+
};
|
|
370
|
+
}): Promise<PromoCodeRedemptionRowLike[]>;
|
|
371
|
+
create(args: {
|
|
372
|
+
data: {
|
|
373
|
+
promoCodeId: string;
|
|
374
|
+
subscriptionId: string;
|
|
375
|
+
tenantId: string;
|
|
376
|
+
appliedValueType: string;
|
|
377
|
+
appliedValue: string;
|
|
378
|
+
appliedDurationType: string;
|
|
379
|
+
appliedDurationValue: number | null;
|
|
380
|
+
startsAt: Date;
|
|
381
|
+
endsAt: Date | null;
|
|
382
|
+
};
|
|
383
|
+
}): Promise<PromoCodeRedemptionRowLike>;
|
|
384
|
+
update(args: {
|
|
385
|
+
where: {
|
|
386
|
+
id: string;
|
|
387
|
+
};
|
|
388
|
+
data: {
|
|
389
|
+
status?: string;
|
|
390
|
+
reversedAt?: Date | null;
|
|
391
|
+
};
|
|
392
|
+
}): Promise<PromoCodeRedemptionRowLike>;
|
|
393
|
+
count(args?: {
|
|
394
|
+
where?: {
|
|
395
|
+
promoCodeId?: string;
|
|
396
|
+
status?: string;
|
|
397
|
+
};
|
|
398
|
+
}): Promise<number>;
|
|
399
|
+
updateMany(args: {
|
|
400
|
+
where: {
|
|
401
|
+
status?: string;
|
|
402
|
+
endsAt?: {
|
|
403
|
+
lt: Date;
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
data: {
|
|
407
|
+
status?: string;
|
|
408
|
+
};
|
|
409
|
+
}): Promise<{
|
|
410
|
+
count: number;
|
|
411
|
+
}>;
|
|
412
|
+
}
|
|
413
|
+
interface PromoCodeValidationLogDelegateLike {
|
|
414
|
+
create(args: {
|
|
415
|
+
data: {
|
|
416
|
+
promoCodeId: string | null;
|
|
417
|
+
codeAttempt: string;
|
|
418
|
+
result: string;
|
|
419
|
+
ipHash?: string | null;
|
|
420
|
+
sessionId?: string | null;
|
|
421
|
+
};
|
|
422
|
+
}): Promise<unknown>;
|
|
423
|
+
count(args?: {
|
|
424
|
+
where?: {
|
|
425
|
+
promoCodeId?: string;
|
|
426
|
+
result?: string;
|
|
427
|
+
};
|
|
428
|
+
}): Promise<number>;
|
|
429
|
+
}
|
|
430
|
+
interface AuditLogDelegateLike {
|
|
431
|
+
create(args: {
|
|
432
|
+
data: {
|
|
433
|
+
tenantId: string | null;
|
|
434
|
+
userId: string | null;
|
|
435
|
+
entity: string;
|
|
436
|
+
entityId: string;
|
|
437
|
+
action: string;
|
|
438
|
+
changes: unknown;
|
|
439
|
+
actorTag: string | null;
|
|
440
|
+
};
|
|
441
|
+
}): Promise<unknown>;
|
|
442
|
+
findMany(args?: {
|
|
443
|
+
where?: {
|
|
444
|
+
tenantId?: string;
|
|
445
|
+
userId?: string;
|
|
446
|
+
entity?: string;
|
|
447
|
+
entityId?: string;
|
|
448
|
+
action?: string;
|
|
449
|
+
actorTag?: string | {
|
|
450
|
+
startsWith: string;
|
|
451
|
+
};
|
|
452
|
+
createdAt?: {
|
|
453
|
+
gte?: Date;
|
|
454
|
+
lte?: Date;
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
orderBy?: {
|
|
458
|
+
createdAt?: SortDirection;
|
|
459
|
+
};
|
|
460
|
+
skip?: number;
|
|
461
|
+
take?: number;
|
|
462
|
+
}): Promise<AuditLogRowLike[]>;
|
|
463
|
+
count(args?: {
|
|
464
|
+
where?: {
|
|
465
|
+
createdAt?: {
|
|
466
|
+
gte?: Date;
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
}): Promise<number>;
|
|
470
|
+
}
|
|
471
|
+
interface SuperAdminUserDelegateLike {
|
|
472
|
+
findUnique(args: {
|
|
473
|
+
where: {
|
|
474
|
+
email: string;
|
|
475
|
+
};
|
|
476
|
+
}): Promise<SuperAdminUserRowLike | null>;
|
|
477
|
+
count(args?: {
|
|
478
|
+
where?: {
|
|
479
|
+
isActive?: boolean;
|
|
480
|
+
deletedAt?: null;
|
|
481
|
+
};
|
|
482
|
+
}): Promise<number>;
|
|
483
|
+
create(args: {
|
|
484
|
+
data: {
|
|
485
|
+
email: string;
|
|
486
|
+
passwordHash: string;
|
|
487
|
+
firstName?: string | null;
|
|
488
|
+
lastName?: string | null;
|
|
489
|
+
};
|
|
490
|
+
}): Promise<SuperAdminUserRowLike>;
|
|
491
|
+
}
|
|
492
|
+
interface SuperAdminMfaDelegateLike {
|
|
493
|
+
findUnique(args: {
|
|
494
|
+
where: {
|
|
495
|
+
userId: string;
|
|
496
|
+
};
|
|
497
|
+
}): Promise<{
|
|
498
|
+
userId: string;
|
|
499
|
+
secret: string | null;
|
|
500
|
+
enabledAt: Date | null;
|
|
501
|
+
updatedAt: Date;
|
|
502
|
+
} | null>;
|
|
503
|
+
upsert(args: {
|
|
504
|
+
where: {
|
|
505
|
+
userId: string;
|
|
506
|
+
};
|
|
507
|
+
create: {
|
|
508
|
+
userId: string;
|
|
509
|
+
secret: string | null;
|
|
510
|
+
enabledAt: Date | null;
|
|
511
|
+
};
|
|
512
|
+
update: {
|
|
513
|
+
secret: string | null;
|
|
514
|
+
enabledAt: Date | null;
|
|
515
|
+
};
|
|
516
|
+
}): Promise<unknown>;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Structural sub-interface of a Prisma transaction client
|
|
520
|
+
* (`Prisma.TransactionClient`): the table delegates plus raw access, without
|
|
521
|
+
* `$transaction`. Repository methods that accept an opaque
|
|
522
|
+
* `TransactionContext` cast it to this shape.
|
|
523
|
+
*/
|
|
524
|
+
interface PrismaTxLike {
|
|
525
|
+
subscription: SubscriptionDelegateLike;
|
|
526
|
+
planVersion: PlanVersionDelegateLike;
|
|
527
|
+
plan: PlanDelegateLike;
|
|
528
|
+
featureCatalogEntry: FeatureCatalogEntryDelegateLike;
|
|
529
|
+
promoCode: PromoCodeDelegateLike;
|
|
530
|
+
promoCodeRedemption: PromoCodeRedemptionDelegateLike;
|
|
531
|
+
promoCodeValidationLog: PromoCodeValidationLogDelegateLike;
|
|
532
|
+
auditLog: AuditLogDelegateLike;
|
|
533
|
+
superAdminUser: SuperAdminUserDelegateLike;
|
|
534
|
+
superAdminMfa: SuperAdminMfaDelegateLike;
|
|
535
|
+
$queryRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<unknown>;
|
|
536
|
+
$executeRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Structural sub-interface of `@prisma/client.PrismaClient`. The adapters
|
|
540
|
+
* expect only the delegates they actually use — no hard import on
|
|
541
|
+
* `@prisma/client`, so the package builds without a Prisma generate and can be
|
|
542
|
+
* mocked in tests.
|
|
543
|
+
*
|
|
544
|
+
* A consumer's `PrismaService extends PrismaClient` (generated from the
|
|
545
|
+
* canonical prisma-fragments schema) satisfies the interface automatically.
|
|
546
|
+
*/
|
|
547
|
+
interface PrismaLike extends PrismaTxLike {
|
|
548
|
+
$transaction<T>(fn: (tx: PrismaTxLike) => Promise<T>): Promise<T>;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
interface PrismaPersistenceOptions {
|
|
552
|
+
/**
|
|
553
|
+
* The app's Prisma client: either its injection token (typically the
|
|
554
|
+
* `PrismaService` class) — resolved through Nest DI at boot — or a ready
|
|
555
|
+
* `PrismaLike` instance (tests, non-DI scripts).
|
|
556
|
+
*/
|
|
557
|
+
client: PrismaLike | PersistenceInjectionToken;
|
|
558
|
+
/**
|
|
559
|
+
* App `PasswordHasher` (token or instance). Enables
|
|
560
|
+
* `core.superAdminProvisioning` (setup wizard / create-super-admin);
|
|
561
|
+
* without it the slice member stays absent.
|
|
562
|
+
*/
|
|
563
|
+
passwordHasher?: PasswordHasher | PersistenceInjectionToken;
|
|
564
|
+
/**
|
|
565
|
+
* Set to true when the app's Prisma middleware really lifts RLS
|
|
566
|
+
* (`SET LOCAL row_security = off`) while
|
|
567
|
+
* `AsyncLocalRlsBypassAdapter.isBypassActive()`. Only toggles the
|
|
568
|
+
* declared `rowLevelSecurity` capability — the adapter cannot verify the
|
|
569
|
+
* middleware. Default false.
|
|
570
|
+
*/
|
|
571
|
+
rlsIntegration?: boolean;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Builds the `SaasicatPersistenceAdapter` bundle for Prisma + PostgreSQL on
|
|
575
|
+
* the canonical schema (`@saasicat/spec` prisma-fragments):
|
|
576
|
+
*
|
|
577
|
+
* ```ts
|
|
578
|
+
* SaasPlatformModule.forRoot({
|
|
579
|
+
* persistence: prismaPersistence({ client: PrismaService }),
|
|
580
|
+
* // ...
|
|
581
|
+
* });
|
|
582
|
+
* ```
|
|
583
|
+
*
|
|
584
|
+
* Slices not shipped by this adapter (contracts, bundles, registration,
|
|
585
|
+
* tenant-billing write ports) stay absent — consumers keep providing custom
|
|
586
|
+
* adapters for those features.
|
|
587
|
+
*/
|
|
588
|
+
declare function prismaPersistence(options: PrismaPersistenceOptions): SaasicatPersistenceAdapter;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* `TransactionRunner` over `prisma.$transaction`. The interactive transaction
|
|
592
|
+
* client is passed through as the opaque `TransactionContext`; every
|
|
593
|
+
* repository in this package resolves it back via `resolveClient`.
|
|
594
|
+
*/
|
|
595
|
+
declare class PrismaTransactionRunner implements TransactionRunner {
|
|
596
|
+
private readonly prisma;
|
|
597
|
+
constructor(prisma: PrismaLike);
|
|
598
|
+
run<T>(fn: (tx: TransactionContext) => Promise<T>): Promise<T>;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Default implementation of `MfaPort` against the `SuperAdminMfa` table
|
|
603
|
+
* from the platform Prisma fragment.
|
|
604
|
+
*
|
|
605
|
+
* Schema assumption:
|
|
606
|
+
*
|
|
607
|
+
* ```prisma
|
|
608
|
+
* model SuperAdminMfa {
|
|
609
|
+
* userId String @id
|
|
610
|
+
* secret String?
|
|
611
|
+
* enabledAt DateTime?
|
|
612
|
+
* updatedAt DateTime @updatedAt
|
|
613
|
+
* }
|
|
614
|
+
* ```
|
|
615
|
+
*/
|
|
616
|
+
declare class PrismaMfaAdapter implements MfaPort {
|
|
617
|
+
private readonly prisma;
|
|
618
|
+
constructor(prisma: PrismaLike);
|
|
619
|
+
getSecret(userId: string): Promise<string | null>;
|
|
620
|
+
setSecret(userId: string, secret: string | null): Promise<void>;
|
|
621
|
+
isEnabled(userId: string): Promise<boolean>;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** `'web:<email>:<sessionId>'` / `'cli:<email>:<host>'` — audit-event.schema.json ActorTagPattern. */
|
|
625
|
+
declare function buildActorTag(actor: AdminActor): string;
|
|
626
|
+
/**
|
|
627
|
+
* Default implementation of `AuditPort` against the canonical `audit_logs`
|
|
628
|
+
* table (`@saasicat/spec` prisma-fragments/04-audit-log.prisma).
|
|
629
|
+
*
|
|
630
|
+
* SuperAdmin actions are platform-global, so `tenantId` stays null; the
|
|
631
|
+
* actor lands as `userId` + `actorTag`.
|
|
632
|
+
*/
|
|
633
|
+
declare class PrismaAuditAdapter implements AuditPort {
|
|
634
|
+
private readonly prisma;
|
|
635
|
+
constructor(prisma: PrismaLike);
|
|
636
|
+
write(input: {
|
|
637
|
+
actor: AdminActor;
|
|
638
|
+
entity: string;
|
|
639
|
+
entityId: string;
|
|
640
|
+
action: string;
|
|
641
|
+
changes?: Record<string, unknown>;
|
|
642
|
+
}): Promise<void>;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* `AuditQueryPort` against the canonical `audit_logs` table. Powers
|
|
647
|
+
* `<app> audit tail` and the admin audit pages.
|
|
648
|
+
*/
|
|
649
|
+
declare class PrismaAuditQueryAdapter implements AuditQueryPort {
|
|
650
|
+
private readonly prisma;
|
|
651
|
+
constructor(prisma: PrismaLike);
|
|
652
|
+
list(filter: AuditQuery): Promise<AuditEntry[]>;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** `AuditStatsPort` against the canonical `audit_logs` table. */
|
|
656
|
+
declare class PrismaAuditStatsAdapter implements AuditStatsPort {
|
|
657
|
+
private readonly prisma;
|
|
658
|
+
constructor(prisma: PrismaLike);
|
|
659
|
+
countSince(since: Date): Promise<number>;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Default implementation of `RlsBypassPort` via `node:async_hooks`.
|
|
664
|
+
*
|
|
665
|
+
* Sets `bypass: true` for the duration of the given callback in the current
|
|
666
|
+
* async context. Your PrismaService reads `isBypassActive()` (e.g. in a
|
|
667
|
+
* Prisma middleware or an interceptor) and issues `SET LOCAL row_security
|
|
668
|
+
* = off` in the current transaction when active.
|
|
669
|
+
*
|
|
670
|
+
* Example middleware:
|
|
671
|
+
*
|
|
672
|
+
* ```ts
|
|
673
|
+
* constructor(private readonly rls: AsyncLocalRlsBypassAdapter) {
|
|
674
|
+
* super();
|
|
675
|
+
* this.$use(async (params, next) => {
|
|
676
|
+
* if (this.rls.isBypassActive() && params.action.startsWith('find')) {
|
|
677
|
+
* // Bypass mode active — disable RLS for this query.
|
|
678
|
+
* await this.$executeRawUnsafe('SET LOCAL row_security = off');
|
|
679
|
+
* }
|
|
680
|
+
* return next(params);
|
|
681
|
+
* });
|
|
682
|
+
* }
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
declare class AsyncLocalRlsBypassAdapter implements RlsBypassPort {
|
|
686
|
+
private readonly storage;
|
|
687
|
+
runWithBypass<T>(fn: () => Promise<T>): Promise<T>;
|
|
688
|
+
/**
|
|
689
|
+
* `true` during the execution of a `runWithBypass(...)` callback.
|
|
690
|
+
* Query this in the PrismaService or an interceptor.
|
|
691
|
+
*/
|
|
692
|
+
isBypassActive(): boolean;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* `SubscriptionRepository` against the canonical `subscriptions` +
|
|
697
|
+
* `plan_versions` tables.
|
|
698
|
+
*
|
|
699
|
+
* Limitation: subscriptions that bind ONLY a `businessTypeVersionId` (no
|
|
700
|
+
* `planVersionId`) are not supported by this shipped adapter — the
|
|
701
|
+
* BusinessType composition needs app-specific aggregation. Such rows raise a
|
|
702
|
+
* descriptive error instead of returning wrong entitlements.
|
|
703
|
+
*
|
|
704
|
+
* `countByBundleVersionId` is deliberately not implemented (the
|
|
705
|
+
* `subscription_bundles` junction is not part of this adapter's slice);
|
|
706
|
+
* the platform then treats affected bundle versions as frozen (fail-closed).
|
|
707
|
+
*/
|
|
708
|
+
declare class PrismaSubscriptionRepository implements SubscriptionRepository {
|
|
709
|
+
private readonly prisma;
|
|
710
|
+
constructor(prisma: PrismaLike);
|
|
711
|
+
findByTenantId(tenantId: string): Promise<SubscriptionRecord | null>;
|
|
712
|
+
findByTenantIdLocked(tenantId: string, tx: TransactionContext): Promise<SubscriptionRecord | null>;
|
|
713
|
+
countByPlanVersionId(planVersionId: string): Promise<number>;
|
|
714
|
+
countActiveByPlanKey(_projectKey: string): Promise<Record<string, number>>;
|
|
715
|
+
private loadByTenantId;
|
|
716
|
+
private toRecord;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* `PlanVersionRepository` against the canonical `plan_versions` table.
|
|
721
|
+
*
|
|
722
|
+
* `findActive` is deliberately not implemented: the canonical schema carries
|
|
723
|
+
* no `validFrom`/`validUntil` columns yet, so time-aware resolution is not
|
|
724
|
+
* expressible — consumers fall back to `findLatestLive` (documented port
|
|
725
|
+
* behavior).
|
|
726
|
+
*/
|
|
727
|
+
declare class PrismaPlanVersionRepository implements PlanVersionRepository {
|
|
728
|
+
private readonly prisma;
|
|
729
|
+
constructor(prisma: PrismaLike);
|
|
730
|
+
findLatestLive(planId: string, tx?: TransactionContext): Promise<PlanVersionRecord | null>;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* `PromoCodeRepository` against the canonical `promo_codes` table.
|
|
735
|
+
*
|
|
736
|
+
* The availability-critical mutations (`claimSlot`, `releaseSlot`,
|
|
737
|
+
* `markExhaustedIfFull`) run as single atomic UPDATE statements — the
|
|
738
|
+
* column-to-column guard (`redemptionsCount < maxRedemptions`) is not
|
|
739
|
+
* expressible in the Prisma query API, so they use `$executeRaw`. The raw
|
|
740
|
+
* statements maintain `updatedAt` manually because they bypass Prisma's
|
|
741
|
+
* `@updatedAt`.
|
|
742
|
+
*/
|
|
743
|
+
declare class PrismaPromoCodeRepository implements PromoCodeRepository {
|
|
744
|
+
private readonly prisma;
|
|
745
|
+
constructor(prisma: PrismaLike);
|
|
746
|
+
findById(id: string): Promise<PromoCodeRecord | null>;
|
|
747
|
+
findByCode(code: string, tx?: TransactionContext): Promise<PromoCodeRecord | null>;
|
|
748
|
+
findMany(filter: PromoCodeFilter): Promise<PromoCodeRecord[]>;
|
|
749
|
+
create(data: CreatePromoCodeData): Promise<PromoCodeRecord>;
|
|
750
|
+
update(id: string, data: UpdatePromoCodeData): Promise<PromoCodeRecord>;
|
|
751
|
+
softDelete(id: string): Promise<void>;
|
|
752
|
+
claimSlot(id: string, tx?: TransactionContext): Promise<boolean>;
|
|
753
|
+
markExhaustedIfFull(id: string, tx?: TransactionContext): Promise<void>;
|
|
754
|
+
releaseSlot(id: string, tx?: TransactionContext): Promise<void>;
|
|
755
|
+
expireDueCodes(now: Date): Promise<number>;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* `PromoCodeRedemptionRepository` against the canonical
|
|
760
|
+
* `promo_code_redemptions` table. Double redemption per subscription is
|
|
761
|
+
* excluded by the `subscriptionId` unique constraint — a concurrent second
|
|
762
|
+
* `create` fails at the database.
|
|
763
|
+
*/
|
|
764
|
+
declare class PrismaPromoCodeRedemptionRepository implements PromoCodeRedemptionRepository {
|
|
765
|
+
private readonly prisma;
|
|
766
|
+
constructor(prisma: PrismaLike);
|
|
767
|
+
findBySubscription(subscriptionId: string, tx?: TransactionContext): Promise<PromoCodeRedemptionRecord | null>;
|
|
768
|
+
create(data: Omit<PromoCodeRedemptionRecord, 'id' | 'redeemedAt' | 'status' | 'reversedAt'>, tx?: TransactionContext): Promise<PromoCodeRedemptionRecord>;
|
|
769
|
+
setReversed(id: string, tx?: TransactionContext): Promise<PromoCodeRedemptionRecord>;
|
|
770
|
+
countByPromoCode(promoCodeId: string, status?: PromoCodeRedemptionStatus): Promise<number>;
|
|
771
|
+
listByPromoCode(promoCodeId: string): Promise<PromoCodeRedemptionListItem[]>;
|
|
772
|
+
expireDueRedemptions(now: Date): Promise<number>;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** `PromoCodeValidationLogRepository` against `promo_code_validation_logs`. */
|
|
776
|
+
declare class PrismaPromoCodeValidationLogRepository implements PromoCodeValidationLogRepository {
|
|
777
|
+
private readonly prisma;
|
|
778
|
+
constructor(prisma: PrismaLike);
|
|
779
|
+
log(args: {
|
|
780
|
+
promoCodeId: string | null;
|
|
781
|
+
codeAttempt: string;
|
|
782
|
+
result: string;
|
|
783
|
+
ipHash?: string;
|
|
784
|
+
sessionId?: string;
|
|
785
|
+
}): Promise<void>;
|
|
786
|
+
countValid(promoCodeId: string): Promise<number>;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** `PromoSubscriptionLookup` against the canonical `subscriptions` table. */
|
|
790
|
+
declare class PrismaPromoSubscriptionLookup implements PromoSubscriptionLookup {
|
|
791
|
+
private readonly prisma;
|
|
792
|
+
constructor(prisma: PrismaLike);
|
|
793
|
+
findById(subscriptionId: string, tx?: TransactionContext): Promise<{
|
|
794
|
+
id: string;
|
|
795
|
+
tenantId: string;
|
|
796
|
+
plan: string;
|
|
797
|
+
billingCycle: BillingCycle;
|
|
798
|
+
startedAt: Date | null;
|
|
799
|
+
} | null>;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Default `PromoRevenueDeductionAggregator` for apps without an
|
|
804
|
+
* `InvoiceDiscount` table: always reports `'0.00'` (the documented port
|
|
805
|
+
* fallback). Apps with invoice discounts replace it with their own adapter.
|
|
806
|
+
*/
|
|
807
|
+
declare class ZeroPromoRevenueDeductionAggregator implements PromoRevenueDeductionAggregator {
|
|
808
|
+
sumGrossForPromoCode(_promoCodeId: string): Promise<string>;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* DI token for the app's `PasswordHasher` implementation (argon2/bcrypt —
|
|
813
|
+
* the algorithm stays app-specific). `prismaPersistence({ passwordHasher })`
|
|
814
|
+
* wires it automatically; manual setups bind it themselves.
|
|
815
|
+
*/
|
|
816
|
+
declare const PASSWORD_HASHER_TOKEN: unique symbol;
|
|
817
|
+
/**
|
|
818
|
+
* `SuperAdminProvisioningPort` against the canonical `super_admin_users`
|
|
819
|
+
* table (prisma-fragments/10-super-admin.prisma). Backs the first-run setup
|
|
820
|
+
* wizard and `<app> user create-super-admin`.
|
|
821
|
+
*/
|
|
822
|
+
declare class PrismaSuperAdminBootstrapAdapter implements SuperAdminProvisioningPort {
|
|
823
|
+
private readonly prisma;
|
|
824
|
+
private readonly passwordHasher;
|
|
825
|
+
constructor(prisma: PrismaLike, passwordHasher: PasswordHasher);
|
|
826
|
+
countSuperAdmins(): Promise<number>;
|
|
827
|
+
createSuperAdmin(input: CreateSuperAdminCliInput): Promise<PlatformUserDto>;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* `PlanCatalogReadSink` against the canonical `plans`, `plan_versions` and
|
|
832
|
+
* `feature_catalog_entries` tables — DB hydration of the plan catalog at
|
|
833
|
+
* boot (`PlanCatalogModule.forRoot({ sink })`).
|
|
834
|
+
*
|
|
835
|
+
* `validFrom`/`validUntil` are reported as null: the canonical schema does
|
|
836
|
+
* not persist booking windows yet (see docs/data-model.md, "Known gaps").
|
|
837
|
+
*/
|
|
838
|
+
declare class PrismaPlanCatalogReadSink implements PlanCatalogReadSink {
|
|
839
|
+
private readonly prisma;
|
|
840
|
+
constructor(prisma: PrismaLike);
|
|
841
|
+
loadSnapshot(projectKey: string): Promise<PlanCatalogReadSnapshot>;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* `PlanCatalogImportSink` against the canonical catalog tables — the
|
|
846
|
+
* one-shot `saas.yaml → DB` import at boot.
|
|
847
|
+
*
|
|
848
|
+
* Idempotency per the port contract: existing rows (identity match) are
|
|
849
|
+
* skipped without error; only the created/skipped flags are reported.
|
|
850
|
+
*/
|
|
851
|
+
declare class PrismaPlanCatalogImportSink implements PlanCatalogImportSink {
|
|
852
|
+
private readonly prisma;
|
|
853
|
+
constructor(prisma: PrismaLike);
|
|
854
|
+
upsertPlan(input: UpsertPlanInput): Promise<UpsertResult>;
|
|
855
|
+
upsertPlanVersion(input: UpsertPlanVersionInput): Promise<UpsertResult>;
|
|
856
|
+
upsertFeatureCatalogEntry(input: UpsertFeatureCatalogEntryInput): Promise<UpsertResult>;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export { AsyncLocalRlsBypassAdapter, type DecimalLike, PASSWORD_HASHER_TOKEN, PRISMA_CLIENT_TOKEN, PrismaAuditAdapter, PrismaAuditQueryAdapter, PrismaAuditStatsAdapter, type PrismaLike, PrismaMfaAdapter, type PrismaPersistenceOptions, PrismaPlanCatalogImportSink, PrismaPlanCatalogReadSink, PrismaPlanVersionRepository, PrismaPromoCodeRedemptionRepository, PrismaPromoCodeRepository, PrismaPromoCodeValidationLogRepository, PrismaPromoSubscriptionLookup, PrismaSubscriptionRepository, PrismaSuperAdminBootstrapAdapter, PrismaTransactionRunner, type PrismaTxLike, ZeroPromoRevenueDeductionAggregator, buildActorTag, prismaPersistence };
|