@saasicat/adapter-prisma 0.26.1 → 1.0.0-rc.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 +90 -201
- package/README.md +4 -4
- package/dist/.build-stamp +1 -0
- package/dist/index.cjs +149 -55
- package/dist/index.d.cts +100 -5
- package/dist/index.d.ts +100 -5
- package/dist/index.js +147 -55
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdminResourcesPort, AdminTenantListFilter, AdminTenantListRow, AdminTenantDetail, AdminTenantStateResult, AdminUserListFilter, AdminUserListRow, AdminAuditListFilter, AuditEntry, AdminSubscriptionListRow, BundleRepository, TransactionContext, BundleVersionRow, BundleListFilter, BundleRow, CreateBundleData, UpdateBundleData, CreateBundleVersionDraftData, UpdateBundleVersionDraftData, VersionChange, PersistenceInjectionToken, PasswordHasher,
|
|
1
|
+
import { AdminResourcesPort, AdminTenantListFilter, AdminTenantListRow, AdminTenantDetail, AdminTenantStateResult, AdminUserListFilter, AdminUserListRow, AdminAuditListFilter, AuditEntry, AdminSubscriptionListRow, BundleRepository, TransactionContext, BundleVersionRow, BundleListFilter, BundleRow, CreateBundleData, UpdateBundleData, CreateBundleVersionDraftData, UpdateBundleVersionDraftData, VersionChange, PersistenceInjectionToken, PasswordHasher, SaaSiCatPersistenceAdapter, TransactionRunner, MfaPort, AuditPort, AdminActor, AuditQueryPort, AuditQuery, AuditStatsPort, RlsBypassPort, SubscriptionRepository, SubscriptionRecord, SubscriptionUsagePort, SubscriptionUsageRecord, SubscriptionBundleRepository, SubscriptionBundleRecord, CreateSubscriptionBundleData, CancelSubscriptionBundleData, TenantSubscriptionWritePort, ApplyOnboardingSelectionInput, RedeemPromoInTransactionCallback, ApplyOnboardingSelectionResult, ImmediatePlanChangeInput, ScheduledPlanChangeInput, 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, PlanRepository, PlanListFilter, PlanRow, CreatePlanData, UpdatePlanData, PlanVersionRow, CreatePlanVersionDraftData, UpdatePlanVersionDraftData, CatalogEntryRepository, CatalogEntryFilter, CapabilityCatalogEntryRow, FeatureCatalogEntryRow, QuotaCatalogEntryRow, UpsertCapabilityEntryData, UpsertFeatureEntryData, UpsertQuotaEntryData, SetCatalogEntryReviewData, CatalogEntryI18n, UpdateCatalogEntryBaseData, MarketingProjectionRepository, MarketingProjectionFilter, MarketingProjectionRow, CreateMarketingProjectionData, UpdateMarketingProjectionData, MarketingSettingsRepository, MarketingSettingsRow, UpdateMarketingSettingsData, PromotionRepository, PromotionFilter, PromotionRow, CreatePromotionData, UpdatePromotionData, SubscriptionContractRepository, SubscriptionContractFilter, SubscriptionContractRecord, CreateSubscriptionContractData, TerminateSubscriptionContractData } from '@saasicat/types';
|
|
2
2
|
|
|
3
3
|
declare const PRISMA_CLIENT_TOKEN: unique symbol;
|
|
4
4
|
/** Prisma `Decimal` values arrive as objects with `toString()`; tests may use plain values. */
|
|
@@ -703,6 +703,81 @@ declare function createPrismaPlanBindingResolver(options?: PrismaPlanBindingOpti
|
|
|
703
703
|
/** Resolves a configurable Prisma delegate and fails early on misspellings. */
|
|
704
704
|
declare function getPrismaDelegate<Row>(client: unknown, delegateName: string): PrismaModelDelegateLike<Row>;
|
|
705
705
|
|
|
706
|
+
/** The Prisma delegates the adapter needs, by the name your client uses. */
|
|
707
|
+
interface AdminResourcesDelegates {
|
|
708
|
+
/** Default `tenant`. */
|
|
709
|
+
tenant?: string;
|
|
710
|
+
/** Default `user`. */
|
|
711
|
+
user?: string;
|
|
712
|
+
/** Default `subscription`. */
|
|
713
|
+
subscription?: string;
|
|
714
|
+
}
|
|
715
|
+
/** The scalar and relation fields the adapter reads, by your name for them. */
|
|
716
|
+
interface AdminResourcesFields {
|
|
717
|
+
tenant?: {
|
|
718
|
+
/** Default `slug` — the stable identifier every admin route addresses. */
|
|
719
|
+
slug?: string;
|
|
720
|
+
/** Default `name`. */
|
|
721
|
+
name?: string;
|
|
722
|
+
/** Default `isActive`. */
|
|
723
|
+
isActive?: string;
|
|
724
|
+
/** Default `deletedAt`. */
|
|
725
|
+
deletedAt?: string;
|
|
726
|
+
/** The tenant→users relation. Default `users`. */
|
|
727
|
+
users?: string;
|
|
728
|
+
/**
|
|
729
|
+
* The tenant→subscription relation. Default `subscription`.
|
|
730
|
+
*
|
|
731
|
+
* On YOUR model, so your name for it: Prisma names a relation field
|
|
732
|
+
* after what it points at, and an app whose tenant is `Organization`
|
|
733
|
+
* has usually not renamed this one — but it may have.
|
|
734
|
+
*/
|
|
735
|
+
subscription?: string;
|
|
736
|
+
};
|
|
737
|
+
user?: {
|
|
738
|
+
/** Default `email`. */
|
|
739
|
+
email?: string;
|
|
740
|
+
/** Default `firstName`. */
|
|
741
|
+
firstName?: string;
|
|
742
|
+
/** Default `lastName`. */
|
|
743
|
+
lastName?: string;
|
|
744
|
+
/** Default `isActive`. */
|
|
745
|
+
isActive?: string;
|
|
746
|
+
/**
|
|
747
|
+
* The user→tenant relation. Default `tenant`.
|
|
748
|
+
*
|
|
749
|
+
* The one the documented example gets wrong by omission: an app whose
|
|
750
|
+
* tenant model is `Organization` calls this field `organization`, by
|
|
751
|
+
* the same Prisma convention that named the delegate.
|
|
752
|
+
*/
|
|
753
|
+
tenant?: string;
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/** The mapping with every default filled in — what the adapter actually reads. */
|
|
757
|
+
interface ResolvedAdminResourcesSchema {
|
|
758
|
+
readonly delegates: Required<AdminResourcesDelegates>;
|
|
759
|
+
readonly tenant: Required<NonNullable<AdminResourcesFields['tenant']>>;
|
|
760
|
+
readonly user: Required<NonNullable<AdminResourcesFields['user']>>;
|
|
761
|
+
}
|
|
762
|
+
declare function resolveAdminResourcesSchema(delegates?: AdminResourcesDelegates, fields?: AdminResourcesFields): ResolvedAdminResourcesSchema;
|
|
763
|
+
/**
|
|
764
|
+
* Refuses a mapping that names a delegate the client does not have.
|
|
765
|
+
*
|
|
766
|
+
* At construction, which is boot: a mapped delegate is resolved lazily, so a
|
|
767
|
+
* typo used to surface as a 500 on the tenants page long after deployment —
|
|
768
|
+
* and only if somebody opened it. The message lists what the client does have,
|
|
769
|
+
* because the answer is almost always a near miss (`organisation` for
|
|
770
|
+
* `organization`, `users` for `user`).
|
|
771
|
+
*
|
|
772
|
+
* Only for an EXPLICIT mapping. `prismaPersistence()` builds this adapter for
|
|
773
|
+
* every bundle, including for applications that never switch `adminResources`
|
|
774
|
+
* on, and several of them have no `tenant` delegate at all — asserting the
|
|
775
|
+
* defaults here would refuse to construct a bundle nobody was going to ask
|
|
776
|
+
* about. Those keep failing where they always did, at the first request, with
|
|
777
|
+
* a message that now names the way out.
|
|
778
|
+
*/
|
|
779
|
+
declare function assertDelegatesExist(client: unknown, schema: ResolvedAdminResourcesSchema): void;
|
|
780
|
+
|
|
706
781
|
interface PrismaAdminResourcesOptions {
|
|
707
782
|
/**
|
|
708
783
|
* Relation counters exposed on tenant rows and tenant details. The names
|
|
@@ -710,6 +785,13 @@ interface PrismaAdminResourcesOptions {
|
|
|
710
785
|
*/
|
|
711
786
|
tenantMetrics?: readonly string[];
|
|
712
787
|
listLimit?: number;
|
|
788
|
+
/**
|
|
789
|
+
* Delegate names, when the app's models are not called `tenant`, `user`
|
|
790
|
+
* and `subscription`. Checked against the client at construction.
|
|
791
|
+
*/
|
|
792
|
+
delegates?: AdminResourcesDelegates;
|
|
793
|
+
/** Field names, when the app's columns differ. See `AdminResourcesFields`. */
|
|
794
|
+
fields?: AdminResourcesFields;
|
|
713
795
|
}
|
|
714
796
|
/**
|
|
715
797
|
* Convention-based adapter for the standard SuperAdmin resource pages.
|
|
@@ -724,6 +806,7 @@ declare class PrismaAdminResourcesAdapter implements AdminResourcesPort {
|
|
|
724
806
|
private readonly metrics;
|
|
725
807
|
private readonly listLimit;
|
|
726
808
|
private readonly audit;
|
|
809
|
+
private readonly schema;
|
|
727
810
|
constructor(prisma: PrismaLike, options?: PrismaAdminResourcesOptions);
|
|
728
811
|
listTenants(filter: AdminTenantListFilter): Promise<AdminTenantListRow[]>;
|
|
729
812
|
getTenantDetail(slug: string): Promise<AdminTenantDetail | null>;
|
|
@@ -734,6 +817,18 @@ declare class PrismaAdminResourcesAdapter implements AdminResourcesPort {
|
|
|
734
817
|
private tenants;
|
|
735
818
|
private users;
|
|
736
819
|
private subscriptions;
|
|
820
|
+
/** The subscription relation of a tenant row, under whatever it is called. */
|
|
821
|
+
private subscriptionOf;
|
|
822
|
+
/** The tenant slug on a user row, through both mapped names. */
|
|
823
|
+
private tenantSlugOf;
|
|
824
|
+
/**
|
|
825
|
+
* Reads a mapped field off a row.
|
|
826
|
+
*
|
|
827
|
+
* The rows come back keyed by the APP's field names, so every read of a
|
|
828
|
+
* mapped field goes through here — and the row type stays the shape the
|
|
829
|
+
* adapter promises rather than becoming `Record<string, unknown>`.
|
|
830
|
+
*/
|
|
831
|
+
private read;
|
|
737
832
|
}
|
|
738
833
|
|
|
739
834
|
/**
|
|
@@ -859,11 +954,11 @@ interface PrismaPersistenceOptions {
|
|
|
859
954
|
bundle?: PrismaBundleRepositoryOptions;
|
|
860
955
|
}
|
|
861
956
|
/**
|
|
862
|
-
* Builds the `
|
|
957
|
+
* Builds the `SaaSiCatPersistenceAdapter` bundle for Prisma + PostgreSQL on
|
|
863
958
|
* the canonical schema (`@saasicat/spec` prisma-fragments):
|
|
864
959
|
*
|
|
865
960
|
* ```ts
|
|
866
|
-
*
|
|
961
|
+
* SaaSiCatModule.forRoot({
|
|
867
962
|
* persistence: prismaPersistence({ client: PrismaService }),
|
|
868
963
|
* // ...
|
|
869
964
|
* });
|
|
@@ -873,7 +968,7 @@ interface PrismaPersistenceOptions {
|
|
|
873
968
|
* tenant-billing, promo and plan-catalog persistence. App-specific behavior
|
|
874
969
|
* such as quota counters and authentication remains in the consumer.
|
|
875
970
|
*/
|
|
876
|
-
declare function prismaPersistence(options: PrismaPersistenceOptions):
|
|
971
|
+
declare function prismaPersistence(options: PrismaPersistenceOptions): SaaSiCatPersistenceAdapter;
|
|
877
972
|
|
|
878
973
|
/**
|
|
879
974
|
* `TransactionRunner` over `prisma.$transaction`. The interactive transaction
|
|
@@ -1461,4 +1556,4 @@ declare class PrismaSubscriptionContractRepository implements SubscriptionContra
|
|
|
1461
1556
|
terminate(contractId: string, data: TerminateSubscriptionContractData): Promise<SubscriptionContractRecord>;
|
|
1462
1557
|
}
|
|
1463
1558
|
|
|
1464
|
-
export { AsyncLocalRlsBypassAdapter, type DecimalLike, PASSWORD_HASHER_TOKEN, PRISMA_BUNDLE_REPOSITORY_OPTIONS, PRISMA_CLIENT_TOKEN, PRISMA_SCHEMA_OPTIONS_TOKEN, PrismaAdminResourcesAdapter, type PrismaAdminResourcesOptions, PrismaAuditAdapter, PrismaAuditQueryAdapter, PrismaAuditStatsAdapter, PrismaBundleRepository, type PrismaBundleRepositoryOptions, PrismaCatalogEntryRepository, type PrismaLike, PrismaMarketingProjectionRepository, PrismaMarketingSettingsRepository, PrismaMfaAdapter, type PrismaModelDelegateLike, type PrismaPersistenceOptions, type PrismaPlanBindingMode, type PrismaPlanBindingOptions, type PrismaPlanBindingResolver, PrismaPlanCatalogImportSink, PrismaPlanCatalogReadSink, type PrismaPlanDelegateOptions, PrismaPlanRepository, type PrismaPlanVersionFieldCapabilities, type PrismaPlanVersionFieldOptions, PrismaPlanVersionRepository, PrismaPromoCodeRedemptionRepository, PrismaPromoCodeRepository, PrismaPromoCodeValidationLogRepository, PrismaPromoSubscriptionLookup, PrismaPromotionRepository, type PrismaSchemaOptions, PrismaSubscriptionBundleRepository, PrismaSubscriptionContractRepository, PrismaSubscriptionRepository, PrismaSubscriptionUsageAdapter, PrismaSuperAdminBootstrapAdapter, type PrismaTenantSubscriptionOptions, PrismaTenantSubscriptionWriteAdapter, PrismaTransactionRunner, type PrismaTxLike, type ResolvedPrismaSchemaOptions, ZeroPromoRevenueDeductionAggregator, buildActorTag, createPrismaPlanBindingResolver, getPrismaDelegate, prismaPersistence, resolvePrismaSchemaOptions };
|
|
1559
|
+
export { type AdminResourcesDelegates, type AdminResourcesFields, AsyncLocalRlsBypassAdapter, type DecimalLike, PASSWORD_HASHER_TOKEN, PRISMA_BUNDLE_REPOSITORY_OPTIONS, PRISMA_CLIENT_TOKEN, PRISMA_SCHEMA_OPTIONS_TOKEN, PrismaAdminResourcesAdapter, type PrismaAdminResourcesOptions, PrismaAuditAdapter, PrismaAuditQueryAdapter, PrismaAuditStatsAdapter, PrismaBundleRepository, type PrismaBundleRepositoryOptions, PrismaCatalogEntryRepository, type PrismaLike, PrismaMarketingProjectionRepository, PrismaMarketingSettingsRepository, PrismaMfaAdapter, type PrismaModelDelegateLike, type PrismaPersistenceOptions, type PrismaPlanBindingMode, type PrismaPlanBindingOptions, type PrismaPlanBindingResolver, PrismaPlanCatalogImportSink, PrismaPlanCatalogReadSink, type PrismaPlanDelegateOptions, PrismaPlanRepository, type PrismaPlanVersionFieldCapabilities, type PrismaPlanVersionFieldOptions, PrismaPlanVersionRepository, PrismaPromoCodeRedemptionRepository, PrismaPromoCodeRepository, PrismaPromoCodeValidationLogRepository, PrismaPromoSubscriptionLookup, PrismaPromotionRepository, type PrismaSchemaOptions, PrismaSubscriptionBundleRepository, PrismaSubscriptionContractRepository, PrismaSubscriptionRepository, PrismaSubscriptionUsageAdapter, PrismaSuperAdminBootstrapAdapter, type PrismaTenantSubscriptionOptions, PrismaTenantSubscriptionWriteAdapter, PrismaTransactionRunner, type PrismaTxLike, type ResolvedAdminResourcesSchema, type ResolvedPrismaSchemaOptions, ZeroPromoRevenueDeductionAggregator, assertDelegatesExist, buildActorTag, createPrismaPlanBindingResolver, getPrismaDelegate, prismaPersistence, resolveAdminResourcesSchema, resolvePrismaSchemaOptions };
|
package/dist/index.js
CHANGED
|
@@ -351,7 +351,7 @@ __name(createPrismaPlanBindingResolver, "createPrismaPlanBindingResolver");
|
|
|
351
351
|
function getPrismaDelegate(client, delegateName) {
|
|
352
352
|
const delegate = client?.[delegateName];
|
|
353
353
|
if (!delegate || typeof delegate !== "object") {
|
|
354
|
-
throw new Error(`Prisma client has no '${delegateName}' delegate.`);
|
|
354
|
+
throw new Error(`Prisma client has no '${delegateName}' delegate. If your model is called something else, map it \u2014 \`adminResources: { delegates: { tenant: "organization" } }\` for the SuperAdmin resources, \`schema\` for the plan binding.`);
|
|
355
355
|
}
|
|
356
356
|
return delegate;
|
|
357
357
|
}
|
|
@@ -375,6 +375,65 @@ function asPlanIdentityClient(client) {
|
|
|
375
375
|
}
|
|
376
376
|
__name(asPlanIdentityClient, "asPlanIdentityClient");
|
|
377
377
|
|
|
378
|
+
// src/admin-resources-schema.ts
|
|
379
|
+
var DEFAULTS = {
|
|
380
|
+
delegates: {
|
|
381
|
+
tenant: "tenant",
|
|
382
|
+
user: "user",
|
|
383
|
+
subscription: "subscription"
|
|
384
|
+
},
|
|
385
|
+
tenant: {
|
|
386
|
+
slug: "slug",
|
|
387
|
+
name: "name",
|
|
388
|
+
isActive: "isActive",
|
|
389
|
+
deletedAt: "deletedAt",
|
|
390
|
+
users: "users",
|
|
391
|
+
subscription: "subscription"
|
|
392
|
+
},
|
|
393
|
+
user: {
|
|
394
|
+
email: "email",
|
|
395
|
+
firstName: "firstName",
|
|
396
|
+
lastName: "lastName",
|
|
397
|
+
isActive: "isActive",
|
|
398
|
+
tenant: "tenant"
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
function resolveAdminResourcesSchema(delegates = {}, fields = {}) {
|
|
402
|
+
return {
|
|
403
|
+
delegates: {
|
|
404
|
+
...DEFAULTS.delegates,
|
|
405
|
+
...delegates
|
|
406
|
+
},
|
|
407
|
+
tenant: {
|
|
408
|
+
...DEFAULTS.tenant,
|
|
409
|
+
...fields.tenant
|
|
410
|
+
},
|
|
411
|
+
user: {
|
|
412
|
+
...DEFAULTS.user,
|
|
413
|
+
...fields.user
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
__name(resolveAdminResourcesSchema, "resolveAdminResourcesSchema");
|
|
418
|
+
function assertDelegatesExist(client, schema) {
|
|
419
|
+
const available = new Set(delegateNamesOf(client));
|
|
420
|
+
const missing = Object.entries(schema.delegates).filter(([, delegateName]) => !available.has(delegateName)).map(([role, delegateName]) => `${role} \u2192 '${delegateName}'`);
|
|
421
|
+
if (missing.length === 0) return;
|
|
422
|
+
throw new Error(`PrismaAdminResourcesAdapter is mapped to delegates the Prisma client does not have: ${missing.join(", ")}. The client offers: ${[
|
|
423
|
+
...available
|
|
424
|
+
].sort().join(", ") || "(none)"}. Map them with \`adminResources: { delegates: { tenant: "organization" } }\`, or implement \`AdminResourcesPort\` yourself if the shape differs rather than the names.`);
|
|
425
|
+
}
|
|
426
|
+
__name(assertDelegatesExist, "assertDelegatesExist");
|
|
427
|
+
function delegateNamesOf(client) {
|
|
428
|
+
if (!client || typeof client !== "object") return [];
|
|
429
|
+
return Object.keys(client).filter((key) => {
|
|
430
|
+
if (key.startsWith("$") || key.startsWith("_")) return false;
|
|
431
|
+
const value = client[key];
|
|
432
|
+
return typeof value === "object" && value !== null && typeof value.findMany === "function";
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
__name(delegateNamesOf, "delegateNamesOf");
|
|
436
|
+
|
|
378
437
|
// src/prisma-admin-resources.adapter.ts
|
|
379
438
|
var DEFAULT_LIST_LIMIT = 200;
|
|
380
439
|
var DEFAULT_AUDIT_LIMIT = 100;
|
|
@@ -386,41 +445,45 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
386
445
|
metrics;
|
|
387
446
|
listLimit;
|
|
388
447
|
audit;
|
|
448
|
+
schema;
|
|
389
449
|
constructor(prisma, options = {}) {
|
|
390
450
|
this.prisma = prisma;
|
|
391
|
-
this.metrics = options.tenantMetrics ?? [
|
|
392
|
-
"users"
|
|
393
|
-
];
|
|
394
451
|
this.listLimit = options.listLimit ?? DEFAULT_LIST_LIMIT;
|
|
395
452
|
this.audit = new PrismaAuditQueryAdapter(prisma);
|
|
453
|
+
this.schema = resolveAdminResourcesSchema(options.delegates, options.fields);
|
|
454
|
+
this.metrics = options.tenantMetrics ?? [
|
|
455
|
+
this.schema.tenant.users
|
|
456
|
+
];
|
|
457
|
+
if (options.delegates) assertDelegatesExist(prisma, this.schema);
|
|
396
458
|
}
|
|
397
459
|
async listTenants(filter) {
|
|
460
|
+
const t = this.schema.tenant;
|
|
398
461
|
const where = {};
|
|
399
462
|
const active = parseActiveFilter(filter.status);
|
|
400
|
-
if (active !== void 0) where.isActive = active;
|
|
463
|
+
if (active !== void 0) where[t.isActive] = active;
|
|
401
464
|
if (filter.search) {
|
|
402
465
|
where.OR = [
|
|
403
466
|
{
|
|
404
|
-
slug: {
|
|
467
|
+
[t.slug]: {
|
|
405
468
|
contains: filter.search,
|
|
406
469
|
mode: "insensitive"
|
|
407
470
|
}
|
|
408
471
|
},
|
|
409
472
|
{
|
|
410
|
-
name: {
|
|
473
|
+
[t.name]: {
|
|
411
474
|
contains: filter.search,
|
|
412
475
|
mode: "insensitive"
|
|
413
476
|
}
|
|
414
477
|
}
|
|
415
478
|
];
|
|
416
479
|
}
|
|
417
|
-
if (filter.plan) where.subscription = {
|
|
480
|
+
if (filter.plan) where[t.subscription] = {
|
|
418
481
|
plan: filter.plan
|
|
419
482
|
};
|
|
420
483
|
const rows = await this.tenants().findMany({
|
|
421
484
|
where,
|
|
422
485
|
include: {
|
|
423
|
-
subscription: {
|
|
486
|
+
[t.subscription]: {
|
|
424
487
|
select: {
|
|
425
488
|
plan: true,
|
|
426
489
|
status: true
|
|
@@ -437,24 +500,25 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
437
500
|
});
|
|
438
501
|
return rows.map((row) => ({
|
|
439
502
|
id: row.id,
|
|
440
|
-
slug: row.slug,
|
|
441
|
-
name: row.name,
|
|
442
|
-
isActive: row.isActive,
|
|
443
|
-
deletedAt: row.deletedAt?.toISOString() ?? null,
|
|
444
|
-
plan: row
|
|
445
|
-
status: row
|
|
503
|
+
slug: this.read(row, t.slug) ?? "",
|
|
504
|
+
name: this.read(row, t.name) ?? "",
|
|
505
|
+
isActive: this.read(row, t.isActive) ?? true,
|
|
506
|
+
deletedAt: this.read(row, t.deletedAt)?.toISOString() ?? null,
|
|
507
|
+
plan: this.subscriptionOf(row)?.plan ?? null,
|
|
508
|
+
status: this.subscriptionOf(row)?.status ?? null,
|
|
446
509
|
createdAt: row.createdAt.toISOString(),
|
|
447
510
|
...row._count ?? {}
|
|
448
511
|
}));
|
|
449
512
|
}
|
|
450
513
|
async getTenantDetail(slug) {
|
|
514
|
+
const t = this.schema.tenant;
|
|
451
515
|
const row = await this.tenants().findUnique({
|
|
452
516
|
where: {
|
|
453
|
-
slug
|
|
517
|
+
[t.slug]: slug
|
|
454
518
|
},
|
|
455
519
|
include: {
|
|
456
|
-
subscription: true,
|
|
457
|
-
users: {
|
|
520
|
+
[t.subscription]: true,
|
|
521
|
+
[t.users]: {
|
|
458
522
|
orderBy: {
|
|
459
523
|
createdAt: "asc"
|
|
460
524
|
}
|
|
@@ -467,31 +531,32 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
467
531
|
if (!row) return null;
|
|
468
532
|
return {
|
|
469
533
|
id: row.id,
|
|
470
|
-
slug: row.slug,
|
|
471
|
-
name: row.name,
|
|
472
|
-
isActive: row.isActive,
|
|
473
|
-
subscription: row
|
|
474
|
-
plan: row.
|
|
475
|
-
status: row.
|
|
476
|
-
billingCycle: row.
|
|
477
|
-
isPilot: row.
|
|
478
|
-
trialEndsAt: row.
|
|
479
|
-
pilotEndsAt: row.
|
|
534
|
+
slug: this.read(row, t.slug) ?? slug,
|
|
535
|
+
name: this.read(row, t.name) ?? "",
|
|
536
|
+
isActive: this.read(row, t.isActive) ?? true,
|
|
537
|
+
subscription: this.subscriptionOf(row) ? {
|
|
538
|
+
plan: this.subscriptionOf(row).plan,
|
|
539
|
+
status: this.subscriptionOf(row).status,
|
|
540
|
+
billingCycle: this.subscriptionOf(row).billingCycle,
|
|
541
|
+
isPilot: this.subscriptionOf(row).isPilot,
|
|
542
|
+
trialEndsAt: this.subscriptionOf(row).trialEndsAt?.toISOString() ?? null,
|
|
543
|
+
pilotEndsAt: this.subscriptionOf(row).pilotEndsAt?.toISOString() ?? null
|
|
480
544
|
} : null,
|
|
481
|
-
users: (row.users ?? []).map((user) => ({
|
|
545
|
+
users: (this.read(row, t.users) ?? []).map((user) => ({
|
|
482
546
|
id: user.id,
|
|
483
|
-
email: user.email,
|
|
484
|
-
firstName: user.firstName ?? void 0,
|
|
485
|
-
lastName: user.lastName ?? void 0,
|
|
547
|
+
email: this.read(user, this.schema.user.email) ?? "",
|
|
548
|
+
firstName: this.read(user, this.schema.user.firstName) ?? void 0,
|
|
549
|
+
lastName: this.read(user, this.schema.user.lastName) ?? void 0,
|
|
486
550
|
createdAt: user.createdAt.toISOString()
|
|
487
551
|
})),
|
|
488
552
|
counts: row._count ?? {}
|
|
489
553
|
};
|
|
490
554
|
}
|
|
491
555
|
async setTenantActive(slug, active, subscriptionStatus) {
|
|
556
|
+
const t = this.schema.tenant;
|
|
492
557
|
const tenant = await this.tenants().findUnique({
|
|
493
558
|
where: {
|
|
494
|
-
slug
|
|
559
|
+
[t.slug]: slug
|
|
495
560
|
}
|
|
496
561
|
});
|
|
497
562
|
if (!tenant) return null;
|
|
@@ -500,7 +565,7 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
500
565
|
id: tenant.id
|
|
501
566
|
},
|
|
502
567
|
data: {
|
|
503
|
-
isActive: active
|
|
568
|
+
[t.isActive]: active
|
|
504
569
|
}
|
|
505
570
|
});
|
|
506
571
|
await this.subscriptions().updateMany({
|
|
@@ -520,20 +585,22 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
520
585
|
};
|
|
521
586
|
}
|
|
522
587
|
async listUsers(filter) {
|
|
588
|
+
const u = this.schema.user;
|
|
589
|
+
const tenantSlug = this.schema.tenant.slug;
|
|
523
590
|
const where = {};
|
|
524
|
-
if (filter.q) where.email = {
|
|
591
|
+
if (filter.q) where[u.email] = {
|
|
525
592
|
contains: filter.q,
|
|
526
593
|
mode: "insensitive"
|
|
527
594
|
};
|
|
528
|
-
if (filter.tenant) where.tenant = {
|
|
529
|
-
|
|
595
|
+
if (filter.tenant) where[u.tenant] = {
|
|
596
|
+
[tenantSlug]: filter.tenant
|
|
530
597
|
};
|
|
531
598
|
const rows = await this.users().findMany({
|
|
532
599
|
where,
|
|
533
600
|
include: {
|
|
534
|
-
tenant: {
|
|
601
|
+
[u.tenant]: {
|
|
535
602
|
select: {
|
|
536
|
-
|
|
603
|
+
[tenantSlug]: true
|
|
537
604
|
}
|
|
538
605
|
}
|
|
539
606
|
},
|
|
@@ -544,12 +611,12 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
544
611
|
});
|
|
545
612
|
return rows.map((row) => ({
|
|
546
613
|
id: row.id,
|
|
547
|
-
email: row.email,
|
|
548
|
-
firstName: row.firstName ?? "",
|
|
549
|
-
lastName: row.lastName ?? "",
|
|
614
|
+
email: this.read(row, u.email) ?? "",
|
|
615
|
+
firstName: this.read(row, u.firstName) ?? "",
|
|
616
|
+
lastName: this.read(row, u.lastName) ?? "",
|
|
550
617
|
role: row.platformRole ?? row.role ?? "MEMBER",
|
|
551
|
-
isActive: row.isActive ?? true,
|
|
552
|
-
tenantSlug: row.tenant
|
|
618
|
+
isActive: this.read(row, u.isActive) ?? true,
|
|
619
|
+
tenantSlug: this.tenantSlugOf(row, u.tenant, tenantSlug),
|
|
553
620
|
lastLoginAt: row.lastLoginAt?.toISOString() ?? null,
|
|
554
621
|
createdAt: row.createdAt.toISOString()
|
|
555
622
|
}));
|
|
@@ -564,12 +631,13 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
564
631
|
});
|
|
565
632
|
}
|
|
566
633
|
async listSubscriptions() {
|
|
634
|
+
const t = this.schema.tenant;
|
|
567
635
|
const rows = await this.subscriptions().findMany({
|
|
568
636
|
include: {
|
|
569
637
|
tenant: {
|
|
570
638
|
select: {
|
|
571
|
-
slug: true,
|
|
572
|
-
name: true
|
|
639
|
+
[t.slug]: true,
|
|
640
|
+
[t.name]: true
|
|
573
641
|
}
|
|
574
642
|
},
|
|
575
643
|
planVersion: {
|
|
@@ -585,7 +653,10 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
585
653
|
});
|
|
586
654
|
return rows.map((row) => ({
|
|
587
655
|
id: row.id,
|
|
588
|
-
tenant:
|
|
656
|
+
tenant: {
|
|
657
|
+
slug: this.read(row.tenant, t.slug) ?? "",
|
|
658
|
+
name: this.read(row.tenant, t.name) ?? ""
|
|
659
|
+
},
|
|
589
660
|
plan: row.plan,
|
|
590
661
|
status: row.status,
|
|
591
662
|
billingCycle: row.billingCycle,
|
|
@@ -594,13 +665,32 @@ var PrismaAdminResourcesAdapter = class {
|
|
|
594
665
|
}));
|
|
595
666
|
}
|
|
596
667
|
tenants() {
|
|
597
|
-
return getPrismaDelegate(this.prisma,
|
|
668
|
+
return getPrismaDelegate(this.prisma, this.schema.delegates.tenant);
|
|
598
669
|
}
|
|
599
670
|
users() {
|
|
600
|
-
return getPrismaDelegate(this.prisma,
|
|
671
|
+
return getPrismaDelegate(this.prisma, this.schema.delegates.user);
|
|
601
672
|
}
|
|
602
673
|
subscriptions() {
|
|
603
|
-
return getPrismaDelegate(this.prisma,
|
|
674
|
+
return getPrismaDelegate(this.prisma, this.schema.delegates.subscription);
|
|
675
|
+
}
|
|
676
|
+
/** The subscription relation of a tenant row, under whatever it is called. */
|
|
677
|
+
subscriptionOf(row) {
|
|
678
|
+
return this.read(row, this.schema.tenant.subscription) ?? null;
|
|
679
|
+
}
|
|
680
|
+
/** The tenant slug on a user row, through both mapped names. */
|
|
681
|
+
tenantSlugOf(row, relation, slugField) {
|
|
682
|
+
const tenant = this.read(row, relation);
|
|
683
|
+
return tenant ? this.read(tenant, slugField) ?? null : null;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Reads a mapped field off a row.
|
|
687
|
+
*
|
|
688
|
+
* The rows come back keyed by the APP's field names, so every read of a
|
|
689
|
+
* mapped field goes through here — and the row type stays the shape the
|
|
690
|
+
* adapter promises rather than becoming `Record<string, unknown>`.
|
|
691
|
+
*/
|
|
692
|
+
read(row, field) {
|
|
693
|
+
return row[field];
|
|
604
694
|
}
|
|
605
695
|
};
|
|
606
696
|
function metricSelect(metrics) {
|
|
@@ -1980,7 +2070,7 @@ PrismaMfaAdapter = _ts_decorate9([
|
|
|
1980
2070
|
])
|
|
1981
2071
|
], PrismaMfaAdapter);
|
|
1982
2072
|
|
|
1983
|
-
// src/prisma-plan-catalog-import-sink.ts
|
|
2073
|
+
// src/prisma-plan-catalog-import-sink.adapter.ts
|
|
1984
2074
|
import { Inject as Inject9, Injectable as Injectable10, Optional as Optional2 } from "@nestjs/common";
|
|
1985
2075
|
function _ts_decorate10(decorators, target, key, desc) {
|
|
1986
2076
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2134,7 +2224,7 @@ PrismaPlanCatalogImportSink = _ts_decorate10([
|
|
|
2134
2224
|
])
|
|
2135
2225
|
], PrismaPlanCatalogImportSink);
|
|
2136
2226
|
|
|
2137
|
-
// src/prisma-plan-catalog-read-sink.ts
|
|
2227
|
+
// src/prisma-plan-catalog-read-sink.adapter.ts
|
|
2138
2228
|
import { Inject as Inject10, Injectable as Injectable11, Optional as Optional3 } from "@nestjs/common";
|
|
2139
2229
|
function _ts_decorate11(decorators, target, key, desc) {
|
|
2140
2230
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3564,7 +3654,7 @@ PrismaPromoCodeValidationLogRepository = _ts_decorate17([
|
|
|
3564
3654
|
])
|
|
3565
3655
|
], PrismaPromoCodeValidationLogRepository);
|
|
3566
3656
|
|
|
3567
|
-
// src/prisma-promo-subscription-lookup.ts
|
|
3657
|
+
// src/prisma-promo-subscription-lookup.adapter.ts
|
|
3568
3658
|
import { Inject as Inject17, Injectable as Injectable18 } from "@nestjs/common";
|
|
3569
3659
|
function _ts_decorate18(decorators, target, key, desc) {
|
|
3570
3660
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -4762,7 +4852,7 @@ function clearedPendingVersionData() {
|
|
|
4762
4852
|
}
|
|
4763
4853
|
__name(clearedPendingVersionData, "clearedPendingVersionData");
|
|
4764
4854
|
|
|
4765
|
-
// src/prisma-transaction-runner.ts
|
|
4855
|
+
// src/prisma-transaction-runner.adapter.ts
|
|
4766
4856
|
import { Inject as Inject24, Injectable as Injectable25 } from "@nestjs/common";
|
|
4767
4857
|
function _ts_decorate25(decorators, target, key, desc) {
|
|
4768
4858
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -4803,7 +4893,7 @@ PrismaTransactionRunner = _ts_decorate25([
|
|
|
4803
4893
|
])
|
|
4804
4894
|
], PrismaTransactionRunner);
|
|
4805
4895
|
|
|
4806
|
-
// src/zero-promo-revenue-aggregator.ts
|
|
4896
|
+
// src/zero-promo-revenue-aggregator.adapter.ts
|
|
4807
4897
|
import { Injectable as Injectable26 } from "@nestjs/common";
|
|
4808
4898
|
function _ts_decorate26(decorators, target, key, desc) {
|
|
4809
4899
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -4956,9 +5046,11 @@ export {
|
|
|
4956
5046
|
PrismaTenantSubscriptionWriteAdapter,
|
|
4957
5047
|
PrismaTransactionRunner,
|
|
4958
5048
|
ZeroPromoRevenueDeductionAggregator,
|
|
5049
|
+
assertDelegatesExist,
|
|
4959
5050
|
buildActorTag,
|
|
4960
5051
|
createPrismaPlanBindingResolver,
|
|
4961
5052
|
getPrismaDelegate,
|
|
4962
5053
|
prismaPersistence,
|
|
5054
|
+
resolveAdminResourcesSchema,
|
|
4963
5055
|
resolvePrismaSchemaOptions
|
|
4964
5056
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/adapter-prisma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
4
4
|
"description": "Prisma + PostgreSQL persistence for SaaSiCat: one standard bundle for core, catalog, entitlement, tenant billing, Admin resources and promo, plus individual adapters for custom compositions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@saasicat/types": "^0.
|
|
25
|
+
"@saasicat/types": "^1.0.0-rc.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@nestjs/common": "^11.0.0"
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"@prisma/client": "^6.19.0",
|
|
35
35
|
"tsup": "^8.0.0",
|
|
36
36
|
"typescript": "^6.0.0",
|
|
37
|
-
"@saasicat/persistence-testing": "^0.
|
|
38
|
-
"@saasicat/spec": "^0.
|
|
37
|
+
"@saasicat/persistence-testing": "^1.0.0-rc.0",
|
|
38
|
+
"@saasicat/spec": "^1.0.0-rc.0"
|
|
39
39
|
},
|
|
40
|
-
"license": "
|
|
40
|
+
"license": "PolyForm-Shield-1.0.0",
|
|
41
41
|
"author": "Taci Uelker",
|
|
42
42
|
"homepage": "https://github.com/uelker70/saasicat",
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
45
45
|
"url": "https://github.com/uelker70/saasicat.git",
|
|
46
|
-
"directory": "packages/
|
|
46
|
+
"directory": "packages/adapter-prisma"
|
|
47
47
|
},
|
|
48
48
|
"bugs": {
|
|
49
49
|
"url": "https://github.com/uelker70/saasicat/issues"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "node ../../scripts/build-and-prune.mjs \"tsup src/index.ts --format esm,cjs --dts --external @saasicat/types --external @nestjs/common\"",
|
|
56
56
|
"pretest": "pnpm run build",
|
|
57
|
-
"test": "node --test tests
|
|
58
|
-
"test:integration": "node --test tests
|
|
57
|
+
"test": "node --test 'tests/*.test.js'",
|
|
58
|
+
"test:integration": "node --test 'tests/integration/*.test.js'"
|
|
59
59
|
}
|
|
60
60
|
}
|