@usebetterdev/tenant-core 0.2.0-beta.11 → 0.2.0-beta.12

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/README.md CHANGED
@@ -12,18 +12,11 @@ pnpm add @usebetterdev/tenant
12
12
 
13
13
  ```ts
14
14
  import { betterTenant } from "@usebetterdev/tenant";
15
- import { drizzleAdapter } from "@usebetterdev/tenant/drizzle"; // or your adapter
15
+ import { drizzleDatabase } from "@usebetterdev/tenant/drizzle"; // or prismaDatabase
16
16
 
17
- const adapter = drizzleAdapter(db);
18
17
  const tenant = betterTenant({
19
- adapter,
18
+ database: drizzleDatabase(db),
20
19
  tenantResolver: { header: "x-tenant-id" },
21
- getTenantRepository: (database) => ({ create, update, list, delete }), // adapter provides this
22
- });
23
-
24
- // In middleware: resolve tenantId (e.g. tenant.resolveTenant(request)), then:
25
- await tenant.runWithTenantAndDatabase(tenantId, adapter, async () => {
26
- await next();
27
20
  });
28
21
 
29
22
  // In handlers:
@@ -35,15 +28,16 @@ const database = tenant.getDatabase(); // tenant-scoped DB handle (use only
35
28
 
36
29
  | API | Description |
37
30
  | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
38
- | `betterTenant(config)` | Create instance with adapter, resolver, optional `getTenantRepository`. |
31
+ | `betterTenant(config)` | Create instance with `database` provider and resolver. |
39
32
  | `getContext()` | Current `TenantContext \| undefined` (tenantId, tenant?, database?). |
40
33
  | `tenant.getDatabase()` | Tenant-scoped database handle, or `undefined` outside request scope. Use only this for tenant-scoped tables. |
41
34
  | `runWithTenant(tenantId, fn)` | Run `fn` with context only (no DB). For tests or when adapter not used. |
42
- | `runWithTenantAndDatabase(tenantId, adapter, fn)` | Set context and run `fn` with adapter's tenant-scoped DB. Call from middleware. |
43
- | `resolveTenant(request, config)` / `resolveTenantAsync` | Resolve tenant id from request (header, path, subdomain, jwt, custom). |
44
- | `runAs(tenantId, adapter, fn)` | Same as request flow; use for cron or per-tenant jobs. |
35
+ | `tenant.runWithTenantAndDatabase(tenantId, fn)` | Set context and run `fn` with adapter's tenant-scoped DB. Call from middleware. |
36
+ | `resolveTenant(request, config)` | Resolve tenant id from request (header, path, subdomain, jwt, custom). Returns `Promise<string \| undefined>`. |
37
+ | `tenant.runAs(tenantId, fn)` | Same as request flow; use for cron or per-tenant jobs. |
45
38
  | `runAsSystem(fn)` | Run with RLS bypass (adapter must implement `runAsSystem`). |
46
- | `tenant.api` | `createTenant`, `updateTenant`, `listTenants`, `deleteTenant` (when `getTenantRepository` is set). |
39
+ | `tenant.api` | `createTenant`, `updateTenant`, `listTenants`, `deleteTenant`. |
40
+ | `TenantRepository.getBySlug(slug)` | Look up a tenant by slug. Used internally for slug-to-UUID auto-resolution. Returns `Tenant \| null`. |
47
41
 
48
42
  ## Adapter contract
49
43
 
@@ -58,6 +52,26 @@ Types: `TenantScopedDatabase` (opaque handle), `SystemDatabase` (opaque handle f
58
52
 
59
53
  Resolution order: **header → path → subdomain → jwt → custom**. Configure `tenantResolver` with e.g. `header: "x-tenant-id"`, `path: "/t/:tenantId"`, `subdomain: true`, `jwt: { claim: "tenant_id" }`, or `custom: (req) => ...`.
60
54
 
55
+ ### Slug-to-UUID resolution
56
+
57
+ After the resolver extracts an identifier, it is automatically normalized to a UUID:
58
+
59
+ - **UUID** — passes through unchanged.
60
+ - **Slug** (e.g. `"acme"`) — looked up in the tenants table via `getBySlug`.
61
+ - **Custom transform** — if `resolveToId` is set, it is called instead of the above (takes full precedence).
62
+
63
+ This means subdomain resolution (`"acme"`) or header-based slugs automatically resolve to UUIDs.
64
+
65
+ ### `resolveToId`
66
+
67
+ Optional transform added to `TenantResolverConfig`:
68
+
69
+ ```ts
70
+ resolveToId?: (identifier: string) => string | Promise<string>;
71
+ ```
72
+
73
+ Use this to map custom domains, external IDs, or any non-slug identifier to a tenant UUID.
74
+
61
75
  ## Telemetry
62
76
 
63
77
  Anonymous telemetry is **on by default** to help improve the library. Data is sent to `https://telemetry.usebetter.dev` and includes runtime, framework, and redacted config (no PII or secrets). Telemetry is disabled in `NODE_ENV=test`.
package/dist/api.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface ListTenantsOptions {
13
13
  }
14
14
  /**
15
15
  * Tenant CRUD API. All methods run via adapter.runAsSystem and getTenantRepository.
16
+ * The runAsSystem check is deferred to call time so that betterTenant() can be
17
+ * constructed with adapters that don't implement runAsSystem (e.g. test mocks).
16
18
  */
17
19
  export declare function createTenantApi<TDb, SDb>(adapter: TenantAdapter<TDb, SDb>, getTenantRepository: (database: SDb) => TenantRepository): {
18
20
  createTenant(data: CreateTenantData): Promise<Tenant>;
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAO1E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EACtC,OAAO,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAChC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,gBAAgB;uBAM7B,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;2BAgB/C,MAAM,QACV,gBAAgB,GACrB,OAAO,CAAC,MAAM,CAAC;0BAYS,kBAAkB,GAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;2BAWzC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;EAStD;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,CAAC,EACtC,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,EACpC,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAGZ"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAO1E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EACtC,OAAO,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAChC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,gBAAgB;uBAG7B,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;2BAiB/C,MAAM,QACV,gBAAgB,GACrB,OAAO,CAAC,MAAM,CAAC;0BAaS,kBAAkB,GAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;2BAYzC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;EAUtD;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,CAAC,EACtC,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,EACpC,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAGZ"}
@@ -1,4 +1,4 @@
1
- import type { BetterTenantConfig, Tenant, ResolvableRequest, TenantContext, TenantAdapter, RunWithTenantAndDatabaseOptions } from "./types.js";
1
+ import type { BetterTenantConfig, Tenant, ResolvableRequest, TenantContext } from "./types.js";
2
2
  import { runWithTenant } from "./context.js";
3
3
  import { type HandleRequestOptions } from "./handle-request.js";
4
4
  export interface TenantApi {
@@ -20,19 +20,16 @@ export interface BetterTenantInstance<TDb = unknown, SDb = unknown> {
20
20
  getContext: () => TenantContext | undefined;
21
21
  getDatabase: () => TDb | undefined;
22
22
  runWithTenant: typeof runWithTenant;
23
- runWithTenantAndDatabase: <T>(tenantId: string, adapter: TenantAdapter<TDb, SDb>, fn: (database: TDb) => Promise<T>, options?: RunWithTenantAndDatabaseOptions<SDb>) => Promise<T>;
24
- runAs: <T>(tenantId: string, adapter: TenantAdapter<TDb>, fn: (database: TDb) => Promise<T>) => Promise<T>;
23
+ runAs: <T>(tenantId: string, fn: (database: TDb) => Promise<T>) => Promise<T>;
25
24
  runAsSystem: <T>(fn: (database: SDb) => Promise<T>) => Promise<T>;
26
- resolveTenant: (request: ResolvableRequest) => string | undefined;
27
- resolveTenantAsync: (request: ResolvableRequest) => Promise<string | undefined>;
25
+ resolveTenant: (request: ResolvableRequest) => Promise<string | undefined>;
26
+ /** Human-readable descriptions of the configured resolution strategies. */
27
+ resolverStrategies: string[];
28
28
  handleRequest: <Req extends ResolvableRequest, Result>(request: Req, next: () => Promise<Result>, options?: Omit<HandleRequestOptions<Req, Result>, "resolveTenant" | "adapter">) => Promise<Result>;
29
- tenant: {
30
- api: TenantApi;
31
- };
29
+ api: TenantApi;
32
30
  }
33
31
  /**
34
- * Creates the Better Tenant instance. Config must include adapter and tenantResolver.
35
- * When getTenantRepository is provided, tenant.api is available for CRUD on the tenants table.
32
+ * Creates the Better Tenant instance. Config must include adapter, tenantResolver, and getTenantRepository.
36
33
  */
37
34
  export declare function betterTenant<TDb, SDb>(config: BetterTenantConfig<TDb, SDb>): BetterTenantInstance<TDb, SDb>;
38
35
  //# sourceMappingURL=better-tenant.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"better-tenant.d.ts","sourceRoot":"","sources":["../src/better-tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,MAAM,EACN,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAc,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,YAAY,CACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB,CAAC,GAAG,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO;IAChE,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IAC5C,WAAW,EAAE,MAAM,GAAG,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,wBAAwB,EAAE,CAAC,CAAC,EAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAChC,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EACjC,OAAO,CAAC,EAAE,+BAA+B,CAAC,GAAG,CAAC,KAC3C,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,EACP,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAC3B,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAC9B,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAClE,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,MAAM,GAAG,SAAS,CAAC;IAClE,kBAAkB,EAAE,CAClB,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjC,aAAa,EAAE,CAAC,GAAG,SAAS,iBAAiB,EAAE,MAAM,EACnD,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAC3B,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,KAC3E,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE;QAAE,GAAG,EAAE,SAAS,CAAA;KAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAmC3G"}
1
+ {"version":3,"file":"better-tenant.d.ts","sourceRoot":"","sources":["../src/better-tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,MAAM,EAIN,iBAAiB,EACjB,aAAa,EAEd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAc,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAwC7B,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,YAAY,CACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB,CAAC,GAAG,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO;IAChE,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IAC5C,WAAW,EAAE,MAAM,GAAG,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9E,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAClE,aAAa,EAAE,CACb,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjC,2EAA2E;IAC3E,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,EAAE,CAAC,GAAG,SAAS,iBAAiB,EAAE,MAAM,EACnD,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAC3B,OAAO,CAAC,EAAE,IAAI,CACZ,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,EACjC,eAAe,GAAG,SAAS,CAC5B,KACE,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,GAAG,EAAE,SAAS,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EACnC,MAAM,EAAE,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,GACnC,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAqDhC"}
package/dist/index.cjs CHANGED
@@ -24,14 +24,13 @@ __export(index_exports, {
24
24
  TenantNotResolvedError: () => TenantNotResolvedError,
25
25
  betterTenant: () => betterTenant,
26
26
  createTenantApi: () => createTenantApi,
27
+ describeStrategies: () => describeStrategies,
27
28
  getContext: () => getContext,
28
29
  handleRequest: () => handleRequest,
29
30
  resolveTenant: () => resolveTenant,
30
- resolveTenantAsync: () => resolveTenantAsync,
31
31
  runAs: () => runAs,
32
32
  runAsSystem: () => runAsSystem,
33
33
  runWithTenant: () => runWithTenant,
34
- runWithTenantAndDatabase: () => runWithTenantAndDatabase,
35
34
  sendCliTelemetry: () => sendCliTelemetry,
36
35
  toResolvableRequest: () => toResolvableRequest
37
36
  });
@@ -55,7 +54,6 @@ function getTelemetryTenantConfig(config) {
55
54
  custom: !!r.custom
56
55
  },
57
56
  tenantTablesCount: 0,
58
- hasGetTenantRepository: !!config.getTenantRepository,
59
57
  loadTenant: config.loadTenant,
60
58
  basePathSet: !!config.basePath,
61
59
  plugins: (config.plugins ?? []).map((p) => String(p.id))
@@ -374,17 +372,7 @@ function requireRunAsSystem(adapter) {
374
372
  }
375
373
  return adapter.runAsSystem;
376
374
  }
377
- function requireTenantRepository(getTenantRepository) {
378
- if (!getTenantRepository) {
379
- throw new Error(
380
- "better-tenant: tenant.api requires getTenantRepository in config (adapter provides CRUD for tenants table)"
381
- );
382
- }
383
- return getTenantRepository;
384
- }
385
375
  function createTenantApi(adapter, getTenantRepository) {
386
- const runAsSystem2 = requireRunAsSystem(adapter);
387
- const getRepository = requireTenantRepository(getTenantRepository);
388
376
  return {
389
377
  async createTenant(data) {
390
378
  if (!data.name?.trim()) {
@@ -393,8 +381,9 @@ function createTenantApi(adapter, getTenantRepository) {
393
381
  if (!data.slug?.trim()) {
394
382
  throw new Error("better-tenant: createTenant requires slug");
395
383
  }
384
+ const runAsSystem2 = requireRunAsSystem(adapter);
396
385
  return runAsSystem2(
397
- (database) => getRepository(database).create({
386
+ (database) => getTenantRepository(database).create({
398
387
  name: data.name.trim(),
399
388
  slug: data.slug.trim()
400
389
  })
@@ -404,8 +393,9 @@ function createTenantApi(adapter, getTenantRepository) {
404
393
  if (!tenantId?.trim()) {
405
394
  throw new Error("better-tenant: updateTenant requires tenantId");
406
395
  }
396
+ const runAsSystem2 = requireRunAsSystem(adapter);
407
397
  return runAsSystem2(
408
- (database) => getRepository(database).update(tenantId, {
398
+ (database) => getTenantRepository(database).update(tenantId, {
409
399
  ...data.name !== void 0 && { name: data.name },
410
400
  ...data.slug !== void 0 && { slug: data.slug }
411
401
  })
@@ -417,16 +407,18 @@ function createTenantApi(adapter, getTenantRepository) {
417
407
  MAX_LIST_LIMIT
418
408
  );
419
409
  const offset = Math.max(0, options.offset ?? 0);
410
+ const runAsSystem2 = requireRunAsSystem(adapter);
420
411
  return runAsSystem2(
421
- (database) => getRepository(database).list({ limit, offset })
412
+ (database) => getTenantRepository(database).list({ limit, offset })
422
413
  );
423
414
  },
424
415
  async deleteTenant(tenantId) {
425
416
  if (!tenantId?.trim()) {
426
417
  throw new Error("better-tenant: deleteTenant requires tenantId");
427
418
  }
419
+ const runAsSystem2 = requireRunAsSystem(adapter);
428
420
  return runAsSystem2(
429
- (database) => getRepository(database).delete(tenantId)
421
+ (database) => getTenantRepository(database).delete(tenantId)
430
422
  );
431
423
  }
432
424
  };
@@ -436,7 +428,7 @@ async function runAs(tenantId, adapter, fn) {
436
428
  }
437
429
  async function runAsSystem(adapter, fn) {
438
430
  const run = requireRunAsSystem(adapter);
439
- return runWithContext({ tenantId: "", isSystem: true }, () => run(fn));
431
+ return runWithContext({ isSystem: true }, () => run(fn));
440
432
  }
441
433
 
442
434
  // src/resolver.ts
@@ -518,39 +510,7 @@ function decodeJwtPayload(token) {
518
510
  return null;
519
511
  }
520
512
  }
521
- function resolveFromJwt(request, config) {
522
- const getToken = request.getToken;
523
- if (!getToken) {
524
- return void 0;
525
- }
526
- const token = typeof getToken === "function" ? getToken() : getToken;
527
- const value = token instanceof Promise ? void 0 : token ?? void 0;
528
- const resolved = value ?? void 0;
529
- if (!resolved) {
530
- return void 0;
531
- }
532
- const claim = typeof config === "string" ? config : config.claim;
533
- if (!claim) {
534
- return void 0;
535
- }
536
- const verifyToken = typeof config === "object" ? config.verifyToken : void 0;
537
- let payload;
538
- if (verifyToken) {
539
- const result = verifyToken(resolved);
540
- if (result instanceof Promise) {
541
- return void 0;
542
- }
543
- payload = result;
544
- } else {
545
- payload = decodeJwtPayload(resolved);
546
- }
547
- if (!payload) {
548
- return void 0;
549
- }
550
- const claimValue = payload[claim];
551
- return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
552
- }
553
- async function resolveFromJwtAsync(request, config) {
513
+ async function resolveFromJwt(request, config) {
554
514
  const getToken = request.getToken;
555
515
  if (!getToken) {
556
516
  return void 0;
@@ -578,40 +538,28 @@ async function resolveFromJwtAsync(request, config) {
578
538
  const claimValue = payload[claim];
579
539
  return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
580
540
  }
581
- function resolveTenant(request, config) {
541
+ function describeStrategies(config) {
542
+ const strategies = [];
582
543
  if (config.header !== void 0) {
583
- const v = resolveFromHeader(request, config.header);
584
- if (v !== void 0) {
585
- return v;
586
- }
544
+ strategies.push(`header '${config.header}'`);
587
545
  }
588
546
  if (config.path !== void 0) {
589
- const v = resolveFromPath(request, config.path);
590
- if (v !== void 0) {
591
- return v;
592
- }
547
+ const pattern = typeof config.path === "string" ? config.path : config.path.pattern;
548
+ strategies.push(`path '${pattern}'`);
593
549
  }
594
550
  if (config.subdomain !== void 0 && config.subdomain !== false) {
595
- const v = resolveFromSubdomain(request, config.subdomain);
596
- if (v !== void 0) {
597
- return v;
598
- }
551
+ strategies.push("subdomain");
599
552
  }
600
553
  if (config.jwt !== void 0) {
601
- const v = resolveFromJwt(request, config.jwt);
602
- if (v !== void 0) {
603
- return v;
604
- }
554
+ const claim = typeof config.jwt === "string" ? config.jwt : config.jwt.claim;
555
+ strategies.push(`jwt claim '${claim}'`);
605
556
  }
606
557
  if (config.custom !== void 0) {
607
- const v = config.custom(request);
608
- if (typeof v === "string" && v.length > 0) {
609
- return v;
610
- }
558
+ strategies.push("custom resolver");
611
559
  }
612
- return void 0;
560
+ return strategies;
613
561
  }
614
- async function resolveTenantAsync(request, config) {
562
+ async function resolveTenant(request, config) {
615
563
  if (config.header !== void 0) {
616
564
  const v = resolveFromHeader(request, config.header);
617
565
  if (v !== void 0) {
@@ -631,7 +579,7 @@ async function resolveTenantAsync(request, config) {
631
579
  }
632
580
  }
633
581
  if (config.jwt !== void 0) {
634
- const v = await resolveFromJwtAsync(request, config.jwt);
582
+ const v = await resolveFromJwt(request, config.jwt);
635
583
  if (v !== void 0) {
636
584
  return v;
637
585
  }
@@ -646,40 +594,58 @@ async function resolveTenantAsync(request, config) {
646
594
  }
647
595
 
648
596
  // src/better-tenant.ts
597
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
598
+ async function resolveIdentifierToId(identifier, resolverConfig, adapter, getTenantRepository) {
599
+ if (resolverConfig.resolveToId) {
600
+ return resolverConfig.resolveToId(identifier);
601
+ }
602
+ if (UUID_RE.test(identifier)) {
603
+ return identifier;
604
+ }
605
+ if (adapter.runAsSystem) {
606
+ const tenant = await adapter.runAsSystem(
607
+ (systemDb) => getTenantRepository(systemDb).getBySlug(identifier)
608
+ );
609
+ return tenant?.id;
610
+ }
611
+ return identifier;
612
+ }
649
613
  function betterTenant(config) {
650
- const { adapter, tenantResolver, getTenantRepository, loadTenant } = config;
614
+ const { database, tenantResolver, loadTenant } = config;
615
+ const { adapter, getTenantRepository } = database;
651
616
  sendInitTelemetry(config, config.telemetry);
652
- const api = getTenantRepository ? createTenantApi(adapter, getTenantRepository) : createStubTenantApi();
653
- const shouldLoadTenant = getTenantRepository != null && loadTenant !== false;
654
- const runWithTenantAndDatabaseOptions = shouldLoadTenant ? { loadTenant: true, getTenantRepository } : void 0;
617
+ const api = createTenantApi(adapter, getTenantRepository);
618
+ const runWithTenantAndDatabaseOptions = loadTenant !== false ? { loadTenant: true, getTenantRepository } : void 0;
619
+ const resolverStrategies = describeStrategies(tenantResolver);
620
+ async function resolveAndNormalize(request) {
621
+ const raw = await resolveTenant(request, tenantResolver);
622
+ if (!raw) return void 0;
623
+ return resolveIdentifierToId(
624
+ raw,
625
+ tenantResolver,
626
+ adapter,
627
+ getTenantRepository
628
+ );
629
+ }
655
630
  return {
656
631
  getContext,
657
632
  getDatabase: () => getDatabase(),
658
633
  runWithTenant,
659
- runWithTenantAndDatabase: (tenantId, _adapter, fn) => runWithTenantAndDatabase(tenantId, adapter, fn, runWithTenantAndDatabaseOptions),
660
- runAs: (tenantId, _adapter, fn) => runAs(tenantId, adapter, fn),
634
+ runAs: (tenantId, fn) => runWithTenantAndDatabase(
635
+ tenantId,
636
+ adapter,
637
+ fn,
638
+ runWithTenantAndDatabaseOptions
639
+ ),
661
640
  runAsSystem: (fn) => runAsSystem(adapter, fn),
662
- resolveTenant: (request) => resolveTenant(request, tenantResolver),
663
- resolveTenantAsync: (request) => resolveTenantAsync(request, tenantResolver),
641
+ resolveTenant: resolveAndNormalize,
642
+ resolverStrategies,
664
643
  handleRequest: (request, next, options) => handleRequest(request, next, {
665
644
  ...options,
666
- resolveTenant: (input) => resolveTenantAsync(input, tenantResolver),
645
+ resolveTenant: resolveAndNormalize,
667
646
  adapter
668
647
  }),
669
- tenant: { api }
670
- };
671
- }
672
- function createStubTenantApi() {
673
- const err = () => {
674
- throw new Error(
675
- "better-tenant: tenant.api requires getTenantRepository in config"
676
- );
677
- };
678
- return {
679
- createTenant: () => err(),
680
- updateTenant: () => err(),
681
- listTenants: () => err(),
682
- deleteTenant: () => err()
648
+ api
683
649
  };
684
650
  }
685
651
 
@@ -773,14 +739,13 @@ function toResolvableRequest(request) {
773
739
  TenantNotResolvedError,
774
740
  betterTenant,
775
741
  createTenantApi,
742
+ describeStrategies,
776
743
  getContext,
777
744
  handleRequest,
778
745
  resolveTenant,
779
- resolveTenantAsync,
780
746
  runAs,
781
747
  runAsSystem,
782
748
  runWithTenant,
783
- runWithTenantAndDatabase,
784
749
  sendCliTelemetry,
785
750
  toResolvableRequest
786
751
  });