@usebetterdev/tenant-core 0.2.0-beta.9 → 0.2.1-beta.1
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 +66 -105
- 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 +65 -103
- 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;
|
|
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"}
|
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))
|
|
@@ -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) =>
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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({
|
|
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
|
|
537
|
+
function describeStrategies(config) {
|
|
538
|
+
const strategies = [];
|
|
582
539
|
if (config.header !== void 0) {
|
|
583
|
-
|
|
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
|
|
590
|
-
|
|
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
|
-
|
|
596
|
-
if (v !== void 0) {
|
|
597
|
-
return v;
|
|
598
|
-
}
|
|
547
|
+
strategies.push("subdomain");
|
|
599
548
|
}
|
|
600
549
|
if (config.jwt !== void 0) {
|
|
601
|
-
const
|
|
602
|
-
|
|
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
|
-
|
|
608
|
-
if (typeof v === "string" && v.length > 0) {
|
|
609
|
-
return v;
|
|
610
|
-
}
|
|
554
|
+
strategies.push("custom resolver");
|
|
611
555
|
}
|
|
612
|
-
return
|
|
556
|
+
return strategies;
|
|
613
557
|
}
|
|
614
|
-
async function
|
|
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
|
|
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 {
|
|
610
|
+
const { database, tenantResolver, loadTenant } = config;
|
|
611
|
+
const { adapter, getTenantRepository } = database;
|
|
651
612
|
sendInitTelemetry(config, config.telemetry);
|
|
652
|
-
const api =
|
|
653
|
-
const
|
|
654
|
-
const
|
|
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
|
-
|
|
660
|
-
|
|
630
|
+
runAs: (tenantId, fn) => runWithTenantAndDatabase(
|
|
631
|
+
tenantId,
|
|
632
|
+
adapter,
|
|
633
|
+
fn,
|
|
634
|
+
runWithTenantAndDatabaseOptions
|
|
635
|
+
),
|
|
661
636
|
runAsSystem: (fn) => runAsSystem(adapter, fn),
|
|
662
|
-
resolveTenant:
|
|
663
|
-
|
|
637
|
+
resolveTenant: resolveAndNormalize,
|
|
638
|
+
resolverStrategies,
|
|
664
639
|
handleRequest: (request, next, options) => handleRequest(request, next, {
|
|
665
640
|
...options,
|
|
666
|
-
resolveTenant:
|
|
641
|
+
resolveTenant: resolveAndNormalize,
|
|
667
642
|
adapter
|
|
668
643
|
}),
|
|
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()
|
|
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
|
});
|