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

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;AAM1E,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;2BASzC,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))
@@ -365,7 +363,6 @@ async function handleRequest(request, next, options) {
365
363
 
366
364
  // src/api.ts
367
365
  var DEFAULT_LIST_LIMIT = 50;
368
- var MAX_LIST_LIMIT = 50;
369
366
  function requireRunAsSystem(adapter) {
370
367
  if (!adapter.runAsSystem) {
371
368
  throw new Error(
@@ -374,17 +371,7 @@ function requireRunAsSystem(adapter) {
374
371
  }
375
372
  return adapter.runAsSystem;
376
373
  }
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
374
  function createTenantApi(adapter, getTenantRepository) {
386
- const runAsSystem2 = requireRunAsSystem(adapter);
387
- const getRepository = requireTenantRepository(getTenantRepository);
388
375
  return {
389
376
  async createTenant(data) {
390
377
  if (!data.name?.trim()) {
@@ -393,8 +380,9 @@ function createTenantApi(adapter, getTenantRepository) {
393
380
  if (!data.slug?.trim()) {
394
381
  throw new Error("better-tenant: createTenant requires slug");
395
382
  }
383
+ const runAsSystem2 = requireRunAsSystem(adapter);
396
384
  return runAsSystem2(
397
- (database) => getRepository(database).create({
385
+ (database) => getTenantRepository(database).create({
398
386
  name: data.name.trim(),
399
387
  slug: data.slug.trim()
400
388
  })
@@ -404,29 +392,29 @@ function createTenantApi(adapter, getTenantRepository) {
404
392
  if (!tenantId?.trim()) {
405
393
  throw new Error("better-tenant: updateTenant requires tenantId");
406
394
  }
395
+ const runAsSystem2 = requireRunAsSystem(adapter);
407
396
  return runAsSystem2(
408
- (database) => getRepository(database).update(tenantId, {
397
+ (database) => getTenantRepository(database).update(tenantId, {
409
398
  ...data.name !== void 0 && { name: data.name },
410
399
  ...data.slug !== void 0 && { slug: data.slug }
411
400
  })
412
401
  );
413
402
  },
414
403
  async listTenants(options = {}) {
415
- const limit = Math.min(
416
- options.limit ?? DEFAULT_LIST_LIMIT,
417
- MAX_LIST_LIMIT
418
- );
404
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT;
419
405
  const offset = Math.max(0, options.offset ?? 0);
406
+ const runAsSystem2 = requireRunAsSystem(adapter);
420
407
  return runAsSystem2(
421
- (database) => getRepository(database).list({ limit, offset })
408
+ (database) => getTenantRepository(database).list({ limit, offset })
422
409
  );
423
410
  },
424
411
  async deleteTenant(tenantId) {
425
412
  if (!tenantId?.trim()) {
426
413
  throw new Error("better-tenant: deleteTenant requires tenantId");
427
414
  }
415
+ const runAsSystem2 = requireRunAsSystem(adapter);
428
416
  return runAsSystem2(
429
- (database) => getRepository(database).delete(tenantId)
417
+ (database) => getTenantRepository(database).delete(tenantId)
430
418
  );
431
419
  }
432
420
  };
@@ -436,7 +424,7 @@ async function runAs(tenantId, adapter, fn) {
436
424
  }
437
425
  async function runAsSystem(adapter, fn) {
438
426
  const run = requireRunAsSystem(adapter);
439
- return runWithContext({ tenantId: "", isSystem: true }, () => run(fn));
427
+ return runWithContext({ isSystem: true }, () => run(fn));
440
428
  }
441
429
 
442
430
  // src/resolver.ts
@@ -518,39 +506,7 @@ function decodeJwtPayload(token) {
518
506
  return null;
519
507
  }
520
508
  }
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) {
509
+ async function resolveFromJwt(request, config) {
554
510
  const getToken = request.getToken;
555
511
  if (!getToken) {
556
512
  return void 0;
@@ -578,40 +534,28 @@ async function resolveFromJwtAsync(request, config) {
578
534
  const claimValue = payload[claim];
579
535
  return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
580
536
  }
581
- function resolveTenant(request, config) {
537
+ function describeStrategies(config) {
538
+ const strategies = [];
582
539
  if (config.header !== void 0) {
583
- const v = resolveFromHeader(request, config.header);
584
- if (v !== void 0) {
585
- return v;
586
- }
540
+ strategies.push(`header '${config.header}'`);
587
541
  }
588
542
  if (config.path !== void 0) {
589
- const v = resolveFromPath(request, config.path);
590
- if (v !== void 0) {
591
- return v;
592
- }
543
+ const pattern = typeof config.path === "string" ? config.path : config.path.pattern;
544
+ strategies.push(`path '${pattern}'`);
593
545
  }
594
546
  if (config.subdomain !== void 0 && config.subdomain !== false) {
595
- const v = resolveFromSubdomain(request, config.subdomain);
596
- if (v !== void 0) {
597
- return v;
598
- }
547
+ strategies.push("subdomain");
599
548
  }
600
549
  if (config.jwt !== void 0) {
601
- const v = resolveFromJwt(request, config.jwt);
602
- if (v !== void 0) {
603
- return v;
604
- }
550
+ const claim = typeof config.jwt === "string" ? config.jwt : config.jwt.claim;
551
+ strategies.push(`jwt claim '${claim}'`);
605
552
  }
606
553
  if (config.custom !== void 0) {
607
- const v = config.custom(request);
608
- if (typeof v === "string" && v.length > 0) {
609
- return v;
610
- }
554
+ strategies.push("custom resolver");
611
555
  }
612
- return void 0;
556
+ return strategies;
613
557
  }
614
- async function resolveTenantAsync(request, config) {
558
+ async function resolveTenant(request, config) {
615
559
  if (config.header !== void 0) {
616
560
  const v = resolveFromHeader(request, config.header);
617
561
  if (v !== void 0) {
@@ -631,7 +575,7 @@ async function resolveTenantAsync(request, config) {
631
575
  }
632
576
  }
633
577
  if (config.jwt !== void 0) {
634
- const v = await resolveFromJwtAsync(request, config.jwt);
578
+ const v = await resolveFromJwt(request, config.jwt);
635
579
  if (v !== void 0) {
636
580
  return v;
637
581
  }
@@ -646,40 +590,58 @@ async function resolveTenantAsync(request, config) {
646
590
  }
647
591
 
648
592
  // src/better-tenant.ts
593
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
594
+ async function resolveIdentifierToId(identifier, resolverConfig, adapter, getTenantRepository) {
595
+ if (resolverConfig.resolveToId) {
596
+ return resolverConfig.resolveToId(identifier);
597
+ }
598
+ if (UUID_RE.test(identifier)) {
599
+ return identifier;
600
+ }
601
+ if (adapter.runAsSystem) {
602
+ const tenant = await adapter.runAsSystem(
603
+ (systemDb) => getTenantRepository(systemDb).getBySlug(identifier)
604
+ );
605
+ return tenant?.id;
606
+ }
607
+ return identifier;
608
+ }
649
609
  function betterTenant(config) {
650
- const { adapter, tenantResolver, getTenantRepository, loadTenant } = config;
610
+ const { database, tenantResolver, loadTenant } = config;
611
+ const { adapter, getTenantRepository } = database;
651
612
  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;
613
+ const api = createTenantApi(adapter, getTenantRepository);
614
+ const runWithTenantAndDatabaseOptions = loadTenant !== false ? { loadTenant: true, getTenantRepository } : void 0;
615
+ const resolverStrategies = describeStrategies(tenantResolver);
616
+ async function resolveAndNormalize(request) {
617
+ const raw = await resolveTenant(request, tenantResolver);
618
+ if (!raw) return void 0;
619
+ return resolveIdentifierToId(
620
+ raw,
621
+ tenantResolver,
622
+ adapter,
623
+ getTenantRepository
624
+ );
625
+ }
655
626
  return {
656
627
  getContext,
657
628
  getDatabase: () => getDatabase(),
658
629
  runWithTenant,
659
- runWithTenantAndDatabase: (tenantId, _adapter, fn) => runWithTenantAndDatabase(tenantId, adapter, fn, runWithTenantAndDatabaseOptions),
660
- runAs: (tenantId, _adapter, fn) => runAs(tenantId, adapter, fn),
630
+ runAs: (tenantId, fn) => runWithTenantAndDatabase(
631
+ tenantId,
632
+ adapter,
633
+ fn,
634
+ runWithTenantAndDatabaseOptions
635
+ ),
661
636
  runAsSystem: (fn) => runAsSystem(adapter, fn),
662
- resolveTenant: (request) => resolveTenant(request, tenantResolver),
663
- resolveTenantAsync: (request) => resolveTenantAsync(request, tenantResolver),
637
+ resolveTenant: resolveAndNormalize,
638
+ resolverStrategies,
664
639
  handleRequest: (request, next, options) => handleRequest(request, next, {
665
640
  ...options,
666
- resolveTenant: (input) => resolveTenantAsync(input, tenantResolver),
641
+ resolveTenant: resolveAndNormalize,
667
642
  adapter
668
643
  }),
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()
644
+ api
683
645
  };
684
646
  }
685
647
 
@@ -773,14 +735,13 @@ function toResolvableRequest(request) {
773
735
  TenantNotResolvedError,
774
736
  betterTenant,
775
737
  createTenantApi,
738
+ describeStrategies,
776
739
  getContext,
777
740
  handleRequest,
778
741
  resolveTenant,
779
- resolveTenantAsync,
780
742
  runAs,
781
743
  runAsSystem,
782
744
  runWithTenant,
783
- runWithTenantAndDatabase,
784
745
  sendCliTelemetry,
785
746
  toResolvableRequest
786
747
  });