@usebetterdev/tenant-core 0.1.0 → 0.2.0-beta.11
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 +7 -8
- package/dist/adapter.d.ts +3 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/better-tenant.d.ts +9 -11
- package/dist/better-tenant.d.ts.map +1 -1
- package/dist/index.cjs +171 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +171 -60
- package/dist/index.js.map +1 -1
- package/dist/request.d.ts.map +1 -1
- package/dist/resolver.d.ts.map +1 -1
- package/dist/telemetry.d.ts +8 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/types.d.ts +25 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @usebetterdev/tenant-core
|
|
2
2
|
|
|
3
|
-
Core library for request-scoped multi-tenancy with Postgres RLS. Provides tenant context (AsyncLocalStorage), resolver strategies, adapter contract, and tenant API. No database driver dependency — use with
|
|
3
|
+
Core library for request-scoped multi-tenancy with Postgres RLS. Provides tenant context (AsyncLocalStorage), resolver strategies, adapter contract, and tenant API. No database driver dependency — use with the Drizzle adapter or a custom adapter.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add @
|
|
8
|
+
pnpm add @usebetterdev/tenant
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { betterTenant
|
|
15
|
-
import { drizzleAdapter } from "
|
|
14
|
+
import { betterTenant } from "@usebetterdev/tenant";
|
|
15
|
+
import { drizzleAdapter } from "@usebetterdev/tenant/drizzle"; // or your adapter
|
|
16
16
|
|
|
17
17
|
const adapter = drizzleAdapter(db);
|
|
18
18
|
const tenant = betterTenant({
|
|
19
19
|
adapter,
|
|
20
20
|
tenantResolver: { header: "x-tenant-id" },
|
|
21
|
-
tenantTables: ["projects", "tasks"],
|
|
22
21
|
getTenantRepository: (database) => ({ create, update, list, delete }), // adapter provides this
|
|
23
22
|
});
|
|
24
23
|
|
|
@@ -38,9 +37,9 @@ const database = tenant.getDatabase(); // tenant-scoped DB handle (use only
|
|
|
38
37
|
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
39
38
|
| `betterTenant(config)` | Create instance with adapter, resolver, optional `getTenantRepository`. |
|
|
40
39
|
| `getContext()` | Current `TenantContext \| undefined` (tenantId, tenant?, database?). |
|
|
41
|
-
| `getDatabase()`
|
|
40
|
+
| `tenant.getDatabase()` | Tenant-scoped database handle, or `undefined` outside request scope. Use only this for tenant-scoped tables. |
|
|
42
41
|
| `runWithTenant(tenantId, fn)` | Run `fn` with context only (no DB). For tests or when adapter not used. |
|
|
43
|
-
| `runWithTenantAndDatabase(tenantId, adapter, fn)` | Set context and run `fn` with adapter
|
|
42
|
+
| `runWithTenantAndDatabase(tenantId, adapter, fn)` | Set context and run `fn` with adapter's tenant-scoped DB. Call from middleware. |
|
|
44
43
|
| `resolveTenant(request, config)` / `resolveTenantAsync` | Resolve tenant id from request (header, path, subdomain, jwt, custom). |
|
|
45
44
|
| `runAs(tenantId, adapter, fn)` | Same as request flow; use for cron or per-tenant jobs. |
|
|
46
45
|
| `runAsSystem(fn)` | Run with RLS bypass (adapter must implement `runAsSystem`). |
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TenantAdapter,
|
|
1
|
+
import type { TenantAdapter, RunWithTenantAndDatabaseOptions } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Returns the tenant-scoped database handle when inside a runWithTenantAndDatabase (or runAs) scope.
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@ import type { TenantAdapter, TenantScopedDatabase, RunWithTenantAndDatabaseOptio
|
|
|
6
6
|
* Config requires an adapter when tenant-scoped DB is used; getDatabase() is then available inside
|
|
7
7
|
* request handlers that ran via runWithTenantAndDatabase(tenantId, adapter, next).
|
|
8
8
|
*/
|
|
9
|
-
export declare function getDatabase():
|
|
9
|
+
export declare function getDatabase(): unknown;
|
|
10
10
|
/**
|
|
11
11
|
* Sets AsyncLocalStorage context and runs fn with the adapter's tenant-scoped DB.
|
|
12
12
|
*
|
|
@@ -20,5 +20,5 @@ export declare function getDatabase(): TenantScopedDatabase | undefined;
|
|
|
20
20
|
* When options.loadTenant is true and options.getTenantRepository and adapter.runAsSystem are set,
|
|
21
21
|
* loads the full Tenant row into context so getContext().tenant is available (or undefined if not found).
|
|
22
22
|
*/
|
|
23
|
-
export declare function runWithTenantAndDatabase<T>(tenantId: string, adapter: TenantAdapter, fn: (database:
|
|
23
|
+
export declare function runWithTenantAndDatabase<TDb, SDb, T>(tenantId: string, adapter: TenantAdapter<TDb, SDb>, fn: (database: TDb) => Promise<T>, options?: RunWithTenantAndDatabaseOptions<SDb>): Promise<T>;
|
|
24
24
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAGpB;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAGrC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EACxD,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,GAC7C,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TenantAdapter, Tenant,
|
|
1
|
+
import type { TenantAdapter, Tenant, TenantRepository } from "./types.js";
|
|
2
2
|
export interface CreateTenantData {
|
|
3
3
|
name: string;
|
|
4
4
|
slug: string;
|
|
@@ -14,7 +14,7 @@ export interface ListTenantsOptions {
|
|
|
14
14
|
/**
|
|
15
15
|
* Tenant CRUD API. All methods run via adapter.runAsSystem and getTenantRepository.
|
|
16
16
|
*/
|
|
17
|
-
export declare function createTenantApi(adapter: TenantAdapter, getTenantRepository: (database:
|
|
17
|
+
export declare function createTenantApi<TDb, SDb>(adapter: TenantAdapter<TDb, SDb>, getTenantRepository: (database: SDb) => TenantRepository): {
|
|
18
18
|
createTenant(data: CreateTenantData): Promise<Tenant>;
|
|
19
19
|
updateTenant(tenantId: string, data: UpdateTenantData): Promise<Tenant>;
|
|
20
20
|
listTenants(options?: ListTenantsOptions): Promise<Tenant[]>;
|
|
@@ -24,10 +24,10 @@ export declare function createTenantApi(adapter: TenantAdapter, getTenantReposit
|
|
|
24
24
|
* runAs(tenantId, fn): same as request flow — set context + run inside adapter.runWithTenant
|
|
25
25
|
* so code can use getDatabase() and run tenant-scoped queries. Use for cron, per-tenant migrations.
|
|
26
26
|
*/
|
|
27
|
-
export declare function runAs<T>(tenantId: string, adapter: TenantAdapter
|
|
27
|
+
export declare function runAs<TDb, T>(tenantId: string, adapter: TenantAdapter<TDb>, fn: (database: TDb) => Promise<T>): Promise<T>;
|
|
28
28
|
/**
|
|
29
29
|
* runAsSystem(fn): run fn with a DB handle that has RLS bypass.
|
|
30
30
|
* Optionally set context so getContext() reflects system mode.
|
|
31
31
|
*/
|
|
32
|
-
export declare function runAsSystem<T>(adapter: TenantAdapter, fn: (database:
|
|
32
|
+
export declare function runAsSystem<SDb, T>(adapter: TenantAdapter<unknown, SDb>, fn: (database: SDb) => Promise<T>): Promise<T>;
|
|
33
33
|
//# sourceMappingURL=api.d.ts.map
|
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,
|
|
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"}
|
package/dist/better-tenant.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type { BetterTenantConfig, Tenant, ResolvableRequest,
|
|
2
|
-
import {
|
|
3
|
-
import { getDatabase, runWithTenantAndDatabase } from "./adapter.js";
|
|
1
|
+
import type { BetterTenantConfig, Tenant, ResolvableRequest, TenantContext, TenantAdapter, RunWithTenantAndDatabaseOptions } from "./types.js";
|
|
2
|
+
import { runWithTenant } from "./context.js";
|
|
4
3
|
import { type HandleRequestOptions } from "./handle-request.js";
|
|
5
|
-
import { runAs } from "./api.js";
|
|
6
4
|
export interface TenantApi {
|
|
7
5
|
createTenant(data: {
|
|
8
6
|
name: string;
|
|
@@ -18,13 +16,13 @@ export interface TenantApi {
|
|
|
18
16
|
}): Promise<Tenant[]>;
|
|
19
17
|
deleteTenant(tenantId: string): Promise<void>;
|
|
20
18
|
}
|
|
21
|
-
export interface BetterTenantInstance {
|
|
22
|
-
getContext:
|
|
23
|
-
getDatabase:
|
|
19
|
+
export interface BetterTenantInstance<TDb = unknown, SDb = unknown> {
|
|
20
|
+
getContext: () => TenantContext | undefined;
|
|
21
|
+
getDatabase: () => TDb | undefined;
|
|
24
22
|
runWithTenant: typeof runWithTenant;
|
|
25
|
-
runWithTenantAndDatabase:
|
|
26
|
-
runAs:
|
|
27
|
-
runAsSystem: <T>(fn: (database:
|
|
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>;
|
|
25
|
+
runAsSystem: <T>(fn: (database: SDb) => Promise<T>) => Promise<T>;
|
|
28
26
|
resolveTenant: (request: ResolvableRequest) => string | undefined;
|
|
29
27
|
resolveTenantAsync: (request: ResolvableRequest) => Promise<string | undefined>;
|
|
30
28
|
handleRequest: <Req extends ResolvableRequest, Result>(request: Req, next: () => Promise<Result>, options?: Omit<HandleRequestOptions<Req, Result>, "resolveTenant" | "adapter">) => Promise<Result>;
|
|
@@ -36,5 +34,5 @@ export interface BetterTenantInstance {
|
|
|
36
34
|
* Creates the Better Tenant instance. Config must include adapter and tenantResolver.
|
|
37
35
|
* When getTenantRepository is provided, tenant.api is available for CRUD on the tenants table.
|
|
38
36
|
*/
|
|
39
|
-
export declare function betterTenant(config: BetterTenantConfig): BetterTenantInstance
|
|
37
|
+
export declare function betterTenant<TDb, SDb>(config: BetterTenantConfig<TDb, SDb>): BetterTenantInstance<TDb, SDb>;
|
|
40
38
|
//# 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,
|
|
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"}
|
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,6 @@ __export(index_exports, {
|
|
|
25
25
|
betterTenant: () => betterTenant,
|
|
26
26
|
createTenantApi: () => createTenantApi,
|
|
27
27
|
getContext: () => getContext,
|
|
28
|
-
getDatabase: () => getDatabase,
|
|
29
28
|
handleRequest: () => handleRequest,
|
|
30
29
|
resolveTenant: () => resolveTenant,
|
|
31
30
|
resolveTenantAsync: () => resolveTenantAsync,
|
|
@@ -55,7 +54,7 @@ function getTelemetryTenantConfig(config) {
|
|
|
55
54
|
jwt: !!r.jwt,
|
|
56
55
|
custom: !!r.custom
|
|
57
56
|
},
|
|
58
|
-
tenantTablesCount:
|
|
57
|
+
tenantTablesCount: 0,
|
|
59
58
|
hasGetTenantRepository: !!config.getTenantRepository,
|
|
60
59
|
loadTenant: config.loadTenant,
|
|
61
60
|
basePathSet: !!config.basePath,
|
|
@@ -72,7 +71,9 @@ function getTelemetryCliConfig(config) {
|
|
|
72
71
|
function getAnonymousId(cwd) {
|
|
73
72
|
try {
|
|
74
73
|
const pkgPath = (0, import_node_path.join)(cwd, "package.json");
|
|
75
|
-
if (!(0, import_node_fs.existsSync)(pkgPath))
|
|
74
|
+
if (!(0, import_node_fs.existsSync)(pkgPath)) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
76
77
|
const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
|
|
77
78
|
const name = typeof pkg?.name === "string" ? pkg.name : "";
|
|
78
79
|
const basePath = typeof pkg?.betterTenant?.basePath === "string" ? pkg.betterTenant.basePath : "";
|
|
@@ -101,22 +102,34 @@ function detectRuntime() {
|
|
|
101
102
|
}
|
|
102
103
|
function detectEnvironment() {
|
|
103
104
|
const env = process.env.NODE_ENV || "development";
|
|
104
|
-
if (env === "test")
|
|
105
|
+
if (env === "test") {
|
|
106
|
+
return "test";
|
|
107
|
+
}
|
|
105
108
|
if (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true" || process.env.GITLAB_CI === "true" || process.env.CIRCLECI === "true") {
|
|
106
109
|
return "ci";
|
|
107
110
|
}
|
|
108
|
-
if (env === "production")
|
|
111
|
+
if (env === "production") {
|
|
112
|
+
return "production";
|
|
113
|
+
}
|
|
109
114
|
return "development";
|
|
110
115
|
}
|
|
111
116
|
function detectFramework(cwd) {
|
|
112
117
|
try {
|
|
113
118
|
const pkgPath = (0, import_node_path.join)(cwd, "package.json");
|
|
114
|
-
if (!(0, import_node_fs.existsSync)(pkgPath))
|
|
119
|
+
if (!(0, import_node_fs.existsSync)(pkgPath)) {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
115
122
|
const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
|
|
116
123
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
117
|
-
if (deps["next"])
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
if (deps["next"]) {
|
|
125
|
+
return { name: "next", version: deps["next"] };
|
|
126
|
+
}
|
|
127
|
+
if (deps["hono"]) {
|
|
128
|
+
return { name: "hono", version: deps["hono"] };
|
|
129
|
+
}
|
|
130
|
+
if (deps["express"]) {
|
|
131
|
+
return { name: "express", version: deps["express"] };
|
|
132
|
+
}
|
|
120
133
|
return void 0;
|
|
121
134
|
} catch {
|
|
122
135
|
return void 0;
|
|
@@ -125,12 +138,17 @@ function detectFramework(cwd) {
|
|
|
125
138
|
function detectDatabase(cwd) {
|
|
126
139
|
try {
|
|
127
140
|
const pkgPath = (0, import_node_path.join)(cwd, "package.json");
|
|
128
|
-
if (!(0, import_node_fs.existsSync)(pkgPath))
|
|
141
|
+
if (!(0, import_node_fs.existsSync)(pkgPath)) {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
129
144
|
const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
|
|
130
145
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
131
|
-
if (deps["drizzle-orm"])
|
|
146
|
+
if (deps["drizzle-orm"]) {
|
|
132
147
|
return { name: "drizzle", version: deps["drizzle-orm"] };
|
|
133
|
-
|
|
148
|
+
}
|
|
149
|
+
if (deps["prisma"]) {
|
|
150
|
+
return { name: "prisma", version: deps["prisma"] };
|
|
151
|
+
}
|
|
134
152
|
return void 0;
|
|
135
153
|
} catch {
|
|
136
154
|
return void 0;
|
|
@@ -151,31 +169,46 @@ function detectSystem() {
|
|
|
151
169
|
}
|
|
152
170
|
function detectPackageManager() {
|
|
153
171
|
const ua = process.env.npm_config_user_agent;
|
|
154
|
-
if (!ua || typeof ua !== "string")
|
|
172
|
+
if (!ua || typeof ua !== "string") {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
155
175
|
const match = ua.match(/^(.+?)\/(\d+\.\d+\.\d+.*?)(?:\s|$)/);
|
|
156
176
|
if (match) {
|
|
157
177
|
const name = (match[1] ?? "unknown").toLowerCase();
|
|
158
178
|
const version = match[2];
|
|
159
179
|
return version ? { name, version } : { name };
|
|
160
180
|
}
|
|
161
|
-
if (ua.includes("pnpm"))
|
|
162
|
-
|
|
181
|
+
if (ua.includes("pnpm")) {
|
|
182
|
+
return { name: "pnpm" };
|
|
183
|
+
}
|
|
184
|
+
if (ua.includes("yarn")) {
|
|
185
|
+
return { name: "yarn" };
|
|
186
|
+
}
|
|
163
187
|
return { name: "npm" };
|
|
164
188
|
}
|
|
165
189
|
function isTelemetryEnabled(options) {
|
|
166
|
-
if (process.env.NODE_ENV === "test")
|
|
190
|
+
if (process.env.NODE_ENV === "test") {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
167
193
|
const env = process.env.BETTER_TENANT_TELEMETRY;
|
|
168
|
-
if (env === "0" || env?.toLowerCase() === "false")
|
|
169
|
-
|
|
194
|
+
if (env === "0" || env?.toLowerCase() === "false") {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (env === "1" || env?.toLowerCase() === "true") {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
170
200
|
return options?.enabled !== false;
|
|
171
201
|
}
|
|
172
202
|
function isDebugMode(options) {
|
|
173
|
-
if (process.env.BETTER_TENANT_TELEMETRY_DEBUG === "1")
|
|
203
|
+
if (process.env.BETTER_TENANT_TELEMETRY_DEBUG === "1") {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
174
206
|
return options?.debug === true;
|
|
175
207
|
}
|
|
176
208
|
function sendTelemetry(type, payload, options) {
|
|
177
|
-
if (!isTelemetryEnabled(options))
|
|
209
|
+
if (!isTelemetryEnabled(options)) {
|
|
178
210
|
return options?.wait ? Promise.resolve() : void 0;
|
|
211
|
+
}
|
|
179
212
|
const fullPayload = {
|
|
180
213
|
library: LIBRARY_ID,
|
|
181
214
|
type,
|
|
@@ -273,9 +306,10 @@ function getDatabase() {
|
|
|
273
306
|
async function runWithTenantAndDatabase(tenantId, adapter, fn, options) {
|
|
274
307
|
const result = await adapter.runWithTenant(tenantId, async (database) => {
|
|
275
308
|
let context = { tenantId, database };
|
|
276
|
-
|
|
309
|
+
const getTenantRepository = options?.getTenantRepository;
|
|
310
|
+
if (options?.loadTenant && getTenantRepository && adapter.runAsSystem) {
|
|
277
311
|
const tenant = await adapter.runAsSystem(
|
|
278
|
-
(systemDb) =>
|
|
312
|
+
(systemDb) => getTenantRepository(systemDb).getById(tenantId)
|
|
279
313
|
);
|
|
280
314
|
context = tenant != null ? { ...context, tenant } : context;
|
|
281
315
|
}
|
|
@@ -402,10 +436,7 @@ async function runAs(tenantId, adapter, fn) {
|
|
|
402
436
|
}
|
|
403
437
|
async function runAsSystem(adapter, fn) {
|
|
404
438
|
const run = requireRunAsSystem(adapter);
|
|
405
|
-
return runWithContext(
|
|
406
|
-
{ tenantId: "", isSystem: true },
|
|
407
|
-
() => run(fn)
|
|
408
|
-
);
|
|
439
|
+
return runWithContext({ tenantId: "", isSystem: true }, () => run(fn));
|
|
409
440
|
}
|
|
410
441
|
|
|
411
442
|
// src/resolver.ts
|
|
@@ -416,17 +447,23 @@ function getHeader(headers, name) {
|
|
|
416
447
|
return value2?.trim() || void 0;
|
|
417
448
|
}
|
|
418
449
|
const raw = headers[key] ?? headers[name];
|
|
419
|
-
if (raw === void 0)
|
|
450
|
+
if (raw === void 0) {
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
420
453
|
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
421
454
|
return (typeof value === "string" ? value : "").trim() || void 0;
|
|
422
455
|
}
|
|
423
456
|
function resolveFromHeader(request, headerName) {
|
|
424
|
-
if (!headerName)
|
|
457
|
+
if (!headerName) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
425
460
|
return getHeader(request.headers, headerName);
|
|
426
461
|
}
|
|
427
462
|
function resolveFromPath(request, pathConfig) {
|
|
428
463
|
const pathOrUrl = request.path ?? request.url;
|
|
429
|
-
if (!pathOrUrl)
|
|
464
|
+
if (!pathOrUrl) {
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
430
467
|
let path;
|
|
431
468
|
try {
|
|
432
469
|
path = pathOrUrl.startsWith("http") ? new URL(pathOrUrl).pathname : pathOrUrl;
|
|
@@ -446,11 +483,17 @@ function parseTenantSegmentIndex(pattern) {
|
|
|
446
483
|
}
|
|
447
484
|
function resolveFromSubdomain(request, config) {
|
|
448
485
|
const host = request.host ?? (request.url ? new URL(request.url).host : "");
|
|
449
|
-
if (!host)
|
|
486
|
+
if (!host) {
|
|
487
|
+
return void 0;
|
|
488
|
+
}
|
|
450
489
|
const hostname = host.split(":")[0];
|
|
451
|
-
if (!hostname)
|
|
490
|
+
if (!hostname) {
|
|
491
|
+
return void 0;
|
|
492
|
+
}
|
|
452
493
|
const parts = hostname.split(".");
|
|
453
|
-
if (parts.length <= 2)
|
|
494
|
+
if (parts.length <= 2) {
|
|
495
|
+
return void 0;
|
|
496
|
+
}
|
|
454
497
|
const index = config === true ? 0 : typeof config === "object" && config.segmentIndex !== void 0 ? config.segmentIndex : 0;
|
|
455
498
|
const value = parts[index];
|
|
456
499
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -458,86 +501,146 @@ function resolveFromSubdomain(request, config) {
|
|
|
458
501
|
function decodeJwtPayload(token) {
|
|
459
502
|
try {
|
|
460
503
|
const parts = token.split(".");
|
|
461
|
-
if (parts.length < 2)
|
|
504
|
+
if (parts.length < 2) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
462
507
|
const payload = parts[1];
|
|
463
|
-
if (!payload)
|
|
508
|
+
if (!payload) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
464
511
|
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
465
|
-
|
|
512
|
+
const parsed = JSON.parse(decoded);
|
|
513
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
return parsed;
|
|
466
517
|
} catch {
|
|
467
518
|
return null;
|
|
468
519
|
}
|
|
469
520
|
}
|
|
470
521
|
function resolveFromJwt(request, config) {
|
|
471
522
|
const getToken = request.getToken;
|
|
472
|
-
if (!getToken)
|
|
523
|
+
if (!getToken) {
|
|
524
|
+
return void 0;
|
|
525
|
+
}
|
|
473
526
|
const token = typeof getToken === "function" ? getToken() : getToken;
|
|
474
527
|
const value = token instanceof Promise ? void 0 : token ?? void 0;
|
|
475
528
|
const resolved = value ?? void 0;
|
|
476
|
-
if (!resolved)
|
|
529
|
+
if (!resolved) {
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
477
532
|
const claim = typeof config === "string" ? config : config.claim;
|
|
478
|
-
if (!claim)
|
|
479
|
-
|
|
480
|
-
|
|
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
|
+
}
|
|
481
550
|
const claimValue = payload[claim];
|
|
482
551
|
return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
|
|
483
552
|
}
|
|
484
553
|
async function resolveFromJwtAsync(request, config) {
|
|
485
554
|
const getToken = request.getToken;
|
|
486
|
-
if (!getToken)
|
|
555
|
+
if (!getToken) {
|
|
556
|
+
return void 0;
|
|
557
|
+
}
|
|
487
558
|
const token = typeof getToken === "function" ? getToken() : getToken;
|
|
488
559
|
const value = token instanceof Promise ? await token : token;
|
|
489
560
|
const resolved = value ?? void 0;
|
|
490
|
-
if (!resolved)
|
|
561
|
+
if (!resolved) {
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
491
564
|
const claim = typeof config === "string" ? config : config.claim;
|
|
492
|
-
if (!claim)
|
|
493
|
-
|
|
494
|
-
|
|
565
|
+
if (!claim) {
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
const verifyToken = typeof config === "object" ? config.verifyToken : void 0;
|
|
569
|
+
let payload;
|
|
570
|
+
if (verifyToken) {
|
|
571
|
+
payload = await verifyToken(resolved);
|
|
572
|
+
} else {
|
|
573
|
+
payload = decodeJwtPayload(resolved);
|
|
574
|
+
}
|
|
575
|
+
if (!payload) {
|
|
576
|
+
return void 0;
|
|
577
|
+
}
|
|
495
578
|
const claimValue = payload[claim];
|
|
496
579
|
return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
|
|
497
580
|
}
|
|
498
581
|
function resolveTenant(request, config) {
|
|
499
582
|
if (config.header !== void 0) {
|
|
500
583
|
const v = resolveFromHeader(request, config.header);
|
|
501
|
-
if (v !== void 0)
|
|
584
|
+
if (v !== void 0) {
|
|
585
|
+
return v;
|
|
586
|
+
}
|
|
502
587
|
}
|
|
503
588
|
if (config.path !== void 0) {
|
|
504
589
|
const v = resolveFromPath(request, config.path);
|
|
505
|
-
if (v !== void 0)
|
|
590
|
+
if (v !== void 0) {
|
|
591
|
+
return v;
|
|
592
|
+
}
|
|
506
593
|
}
|
|
507
594
|
if (config.subdomain !== void 0 && config.subdomain !== false) {
|
|
508
595
|
const v = resolveFromSubdomain(request, config.subdomain);
|
|
509
|
-
if (v !== void 0)
|
|
596
|
+
if (v !== void 0) {
|
|
597
|
+
return v;
|
|
598
|
+
}
|
|
510
599
|
}
|
|
511
600
|
if (config.jwt !== void 0) {
|
|
512
601
|
const v = resolveFromJwt(request, config.jwt);
|
|
513
|
-
if (v !== void 0)
|
|
602
|
+
if (v !== void 0) {
|
|
603
|
+
return v;
|
|
604
|
+
}
|
|
514
605
|
}
|
|
515
606
|
if (config.custom !== void 0) {
|
|
516
607
|
const v = config.custom(request);
|
|
517
|
-
if (typeof v === "string" && v.length > 0)
|
|
608
|
+
if (typeof v === "string" && v.length > 0) {
|
|
609
|
+
return v;
|
|
610
|
+
}
|
|
518
611
|
}
|
|
519
612
|
return void 0;
|
|
520
613
|
}
|
|
521
614
|
async function resolveTenantAsync(request, config) {
|
|
522
615
|
if (config.header !== void 0) {
|
|
523
616
|
const v = resolveFromHeader(request, config.header);
|
|
524
|
-
if (v !== void 0)
|
|
617
|
+
if (v !== void 0) {
|
|
618
|
+
return v;
|
|
619
|
+
}
|
|
525
620
|
}
|
|
526
621
|
if (config.path !== void 0) {
|
|
527
622
|
const v = resolveFromPath(request, config.path);
|
|
528
|
-
if (v !== void 0)
|
|
623
|
+
if (v !== void 0) {
|
|
624
|
+
return v;
|
|
625
|
+
}
|
|
529
626
|
}
|
|
530
627
|
if (config.subdomain !== void 0 && config.subdomain !== false) {
|
|
531
628
|
const v = resolveFromSubdomain(request, config.subdomain);
|
|
532
|
-
if (v !== void 0)
|
|
629
|
+
if (v !== void 0) {
|
|
630
|
+
return v;
|
|
631
|
+
}
|
|
533
632
|
}
|
|
534
633
|
if (config.jwt !== void 0) {
|
|
535
634
|
const v = await resolveFromJwtAsync(request, config.jwt);
|
|
536
|
-
if (v !== void 0)
|
|
635
|
+
if (v !== void 0) {
|
|
636
|
+
return v;
|
|
637
|
+
}
|
|
537
638
|
}
|
|
538
639
|
if (config.custom !== void 0) {
|
|
539
640
|
const v = await Promise.resolve(config.custom(request));
|
|
540
|
-
if (typeof v === "string" && v.length > 0)
|
|
641
|
+
if (typeof v === "string" && v.length > 0) {
|
|
642
|
+
return v;
|
|
643
|
+
}
|
|
541
644
|
}
|
|
542
645
|
return void 0;
|
|
543
646
|
}
|
|
@@ -551,7 +654,7 @@ function betterTenant(config) {
|
|
|
551
654
|
const runWithTenantAndDatabaseOptions = shouldLoadTenant ? { loadTenant: true, getTenantRepository } : void 0;
|
|
552
655
|
return {
|
|
553
656
|
getContext,
|
|
554
|
-
getDatabase,
|
|
657
|
+
getDatabase: () => getDatabase(),
|
|
555
658
|
runWithTenant,
|
|
556
659
|
runWithTenantAndDatabase: (tenantId, _adapter, fn) => runWithTenantAndDatabase(tenantId, adapter, fn, runWithTenantAndDatabaseOptions),
|
|
557
660
|
runAs: (tenantId, _adapter, fn) => runAs(tenantId, adapter, fn),
|
|
@@ -585,12 +688,16 @@ function isFetchRequest(input) {
|
|
|
585
688
|
return typeof Request !== "undefined" && input instanceof Request;
|
|
586
689
|
}
|
|
587
690
|
function normalizeHost(raw) {
|
|
588
|
-
if (!raw)
|
|
691
|
+
if (!raw) {
|
|
692
|
+
return void 0;
|
|
693
|
+
}
|
|
589
694
|
const value = raw.split(":")[0]?.trim();
|
|
590
695
|
return value || void 0;
|
|
591
696
|
}
|
|
592
697
|
function normalizeHeaders(headers) {
|
|
593
|
-
if (!headers)
|
|
698
|
+
if (!headers) {
|
|
699
|
+
return {};
|
|
700
|
+
}
|
|
594
701
|
const normalized = {};
|
|
595
702
|
for (const [key, value] of Object.entries(headers)) {
|
|
596
703
|
const normalizedKey = key.toLowerCase();
|
|
@@ -607,7 +714,9 @@ function normalizeHeaders(headers) {
|
|
|
607
714
|
return normalized;
|
|
608
715
|
}
|
|
609
716
|
function pathWithoutQuery(value) {
|
|
610
|
-
if (!value)
|
|
717
|
+
if (!value) {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
611
720
|
const [pathname] = value.split("?");
|
|
612
721
|
return pathname || void 0;
|
|
613
722
|
}
|
|
@@ -641,7 +750,9 @@ function toResolvableRequest(request) {
|
|
|
641
750
|
const hostFromHeaders = headers.host;
|
|
642
751
|
const hostValue = Array.isArray(hostFromHeaders) ? hostFromHeaders[0] : hostFromHeaders;
|
|
643
752
|
const host = normalizeHost(request.hostname ?? request.host ?? hostValue);
|
|
644
|
-
const path = pathWithoutQuery(
|
|
753
|
+
const path = pathWithoutQuery(
|
|
754
|
+
request.path ?? request.originalUrl ?? request.url
|
|
755
|
+
);
|
|
645
756
|
const resolved = {
|
|
646
757
|
headers
|
|
647
758
|
};
|
|
@@ -663,7 +774,6 @@ function toResolvableRequest(request) {
|
|
|
663
774
|
betterTenant,
|
|
664
775
|
createTenantApi,
|
|
665
776
|
getContext,
|
|
666
|
-
getDatabase,
|
|
667
777
|
handleRequest,
|
|
668
778
|
resolveTenant,
|
|
669
779
|
resolveTenantAsync,
|