@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/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, 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';
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 `SaasicatPersistenceAdapter` bundle for Prisma + PostgreSQL on
957
+ * Builds the `SaaSiCatPersistenceAdapter` bundle for Prisma + PostgreSQL on
863
958
  * the canonical schema (`@saasicat/spec` prisma-fragments):
864
959
  *
865
960
  * ```ts
866
- * SaasPlatformModule.forRoot({
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): SaasicatPersistenceAdapter;
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.subscription?.plan ?? null,
445
- status: row.subscription?.status ?? null,
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.subscription ? {
474
- plan: row.subscription.plan,
475
- status: row.subscription.status,
476
- billingCycle: row.subscription.billingCycle,
477
- isPilot: row.subscription.isPilot,
478
- trialEndsAt: row.subscription.trialEndsAt?.toISOString() ?? null,
479
- pilotEndsAt: row.subscription.pilotEndsAt?.toISOString() ?? null
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
- slug: filter.tenant
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
- slug: true
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?.slug ?? null,
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: row.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, "tenant");
668
+ return getPrismaDelegate(this.prisma, this.schema.delegates.tenant);
598
669
  }
599
670
  users() {
600
- return getPrismaDelegate(this.prisma, "user");
671
+ return getPrismaDelegate(this.prisma, this.schema.delegates.user);
601
672
  }
602
673
  subscriptions() {
603
- return getPrismaDelegate(this.prisma, "subscription");
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.26.1",
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.26.1"
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.26.1",
38
- "@saasicat/spec": "^0.26.1"
37
+ "@saasicat/persistence-testing": "^1.0.0-rc.0",
38
+ "@saasicat/spec": "^1.0.0-rc.0"
39
39
  },
40
- "license": "Apache-2.0",
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/saas-platform-adapter-prisma"
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/**/*.test.js",
58
- "test:integration": "node --test tests-integration/**/*.test.js"
57
+ "test": "node --test 'tests/*.test.js'",
58
+ "test:integration": "node --test 'tests/integration/*.test.js'"
59
59
  }
60
60
  }