@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 +28 -14
- package/dist/api.d.ts +2 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/better-tenant.d.ts +7 -10
- package/dist/better-tenant.d.ts.map +1 -1
- package/dist/index.cjs +65 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -98
- package/dist/index.js.map +1 -1
- package/dist/resolver.d.ts +6 -5
- package/dist/resolver.d.ts.map +1 -1
- package/dist/telemetry.d.ts +1 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/types.d.ts +13 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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 {
|
|
15
|
+
import { drizzleDatabase } from "@usebetterdev/tenant/drizzle"; // or prismaDatabase
|
|
16
16
|
|
|
17
|
-
const adapter = drizzleAdapter(db);
|
|
18
17
|
const tenant = betterTenant({
|
|
19
|
-
|
|
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
|
|
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,
|
|
43
|
-
| `resolveTenant(request, config)`
|
|
44
|
-
| `runAs(tenantId,
|
|
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
|
|
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;
|
|
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"}
|
package/dist/better-tenant.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BetterTenantConfig, Tenant, ResolvableRequest, TenantContext
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
api: TenantApi;
|
|
31
|
-
};
|
|
29
|
+
api: TenantApi;
|
|
32
30
|
}
|
|
33
31
|
/**
|
|
34
|
-
* Creates the Better Tenant instance. Config must include adapter and
|
|
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,
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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({
|
|
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
|
|
541
|
+
function describeStrategies(config) {
|
|
542
|
+
const strategies = [];
|
|
582
543
|
if (config.header !== void 0) {
|
|
583
|
-
|
|
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
|
|
590
|
-
|
|
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
|
-
|
|
596
|
-
if (v !== void 0) {
|
|
597
|
-
return v;
|
|
598
|
-
}
|
|
551
|
+
strategies.push("subdomain");
|
|
599
552
|
}
|
|
600
553
|
if (config.jwt !== void 0) {
|
|
601
|
-
const
|
|
602
|
-
|
|
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
|
-
|
|
608
|
-
if (typeof v === "string" && v.length > 0) {
|
|
609
|
-
return v;
|
|
610
|
-
}
|
|
558
|
+
strategies.push("custom resolver");
|
|
611
559
|
}
|
|
612
|
-
return
|
|
560
|
+
return strategies;
|
|
613
561
|
}
|
|
614
|
-
async function
|
|
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
|
|
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 {
|
|
614
|
+
const { database, tenantResolver, loadTenant } = config;
|
|
615
|
+
const { adapter, getTenantRepository } = database;
|
|
651
616
|
sendInitTelemetry(config, config.telemetry);
|
|
652
|
-
const api =
|
|
653
|
-
const
|
|
654
|
-
const
|
|
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
|
-
|
|
660
|
-
|
|
634
|
+
runAs: (tenantId, fn) => runWithTenantAndDatabase(
|
|
635
|
+
tenantId,
|
|
636
|
+
adapter,
|
|
637
|
+
fn,
|
|
638
|
+
runWithTenantAndDatabaseOptions
|
|
639
|
+
),
|
|
661
640
|
runAsSystem: (fn) => runAsSystem(adapter, fn),
|
|
662
|
-
resolveTenant:
|
|
663
|
-
|
|
641
|
+
resolveTenant: resolveAndNormalize,
|
|
642
|
+
resolverStrategies,
|
|
664
643
|
handleRequest: (request, next, options) => handleRequest(request, next, {
|
|
665
644
|
...options,
|
|
666
|
-
resolveTenant:
|
|
645
|
+
resolveTenant: resolveAndNormalize,
|
|
667
646
|
adapter
|
|
668
647
|
}),
|
|
669
|
-
|
|
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
|
});
|