@cedarjs/tenancy 8.0.0-canary.3552 → 8.0.0-canary.3556
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/prismaExtension.d.ts +34 -13
- package/dist/prismaExtension.d.ts.map +1 -1
- package/dist/prismaExtension.js +22 -20
- package/package.json +11 -11
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export * from './context.js';
|
|
|
3
3
|
export * from './auth.js';
|
|
4
4
|
export * from './permissions.js';
|
|
5
5
|
export { createTenancyExtension } from './prismaExtension.js';
|
|
6
|
-
export type { ModelNamesFor, TenancyConfig } from './prismaExtension.js';
|
|
6
|
+
export type { ModelNamesFor, TenancyConfig, UnscopedClient, } from './prismaExtension.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,sBAAsB,CAAA"}
|
|
@@ -57,29 +57,50 @@ interface ParsedModelFields {
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function parseListFieldsFromSchema(schemaText: string): Map<string, ParsedModelFields>;
|
|
59
59
|
declare function throwRawQueryBlocked(): never;
|
|
60
|
+
/**
|
|
61
|
+
* A client the escape hatches can be called on. Prisma's interactive
|
|
62
|
+
* transaction client has no `$extends`, so a client built for it couldn't
|
|
63
|
+
* stay on the transaction's connection; leaving `$extends` out of its type
|
|
64
|
+
* makes `tx.$forOrg()` a type error, and `isTransactionClient` refuses it at
|
|
65
|
+
* runtime.
|
|
66
|
+
*/
|
|
67
|
+
type ExtendableClient = {
|
|
68
|
+
$extends: unknown;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The client methods a scoped client blocks (see `throwRawQueryBlocked`) and
|
|
72
|
+
* an unscoped one allows.
|
|
73
|
+
*/
|
|
74
|
+
type RawSqlMethod = '$queryRaw' | '$queryRawUnsafe' | '$executeRaw' | '$executeRawUnsafe';
|
|
75
|
+
/**
|
|
76
|
+
* The type of `db.$withoutTenant()`: `db`'s own type, with raw SQL typed the
|
|
77
|
+
* way the un-extended client types it instead of as always throwing. Every
|
|
78
|
+
* other member, the models and `$transaction` included, has exactly `db`'s
|
|
79
|
+
* type, so code can take either client without the two forming a union
|
|
80
|
+
* TypeScript can't call model methods on.
|
|
81
|
+
*/
|
|
82
|
+
export type UnscopedClient<TDb, TClient> = Omit<TDb, RawSqlMethod | '$forOrg' | '$withoutTenant'> & Pick<TClient, Extract<keyof TClient, RawSqlMethod>> & {
|
|
83
|
+
$forOrg(this: ExtendableClient, organizationId: string): TDb;
|
|
84
|
+
$withoutTenant(this: ExtendableClient): UnscopedClient<TDb, TClient>;
|
|
85
|
+
};
|
|
60
86
|
/**
|
|
61
87
|
* Builds a Prisma Client extension that scopes every operation on a
|
|
62
88
|
* tenant-owned model to the tenant id `config.getTenantId()` (or the
|
|
63
89
|
* default reader of `context.currentOrg?.id`) returns, and adds
|
|
64
90
|
* `db.$forOrg(organizationId)` / `db.$withoutTenant()` escape hatches.
|
|
65
91
|
*
|
|
92
|
+
* Every client the escape hatches return carries them too, and
|
|
93
|
+
* `db.$forOrg(id)` has the same type as `db`: TypeScript takes that type from
|
|
94
|
+
* the client the method is called on (`this`). So the tenancy extension
|
|
95
|
+
* should be the last one `db` is extended with; one added after it would be
|
|
96
|
+
* in the type of a `$forOrg` client but not on the client itself.
|
|
97
|
+
*
|
|
66
98
|
* See `docs/implementation-plans/2026-08-26-multi-tenancy.md` ("Layer 1")
|
|
67
99
|
* for the full behavior table this implements.
|
|
68
100
|
*/
|
|
69
101
|
export declare function createTenancyExtension<TClient>(config: TenancyConfig<TClient>): (client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/client").InternalArgs<{}, {}, {}, {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* context — for background jobs, webhooks, and other code that
|
|
73
|
-
* knows the tenant but doesn't run inside a request.
|
|
74
|
-
*/
|
|
75
|
-
$forOrg(organizationId: string): TClient;
|
|
76
|
-
/**
|
|
77
|
-
* An unscoped client for code that intentionally reads or writes
|
|
78
|
-
* across organizations: seeds, data migrations, admin tooling.
|
|
79
|
-
* Nothing here is scoped, and raw SQL is allowed.
|
|
80
|
-
*/
|
|
81
|
-
$withoutTenant(): TClient;
|
|
82
|
-
}> & import("@prisma/client/runtime/client").InternalArgs<{}, {}, {}, {
|
|
102
|
+
$forOrg: <T extends ExtendableClient>(this: T, organizationId: string) => T;
|
|
103
|
+
$withoutTenant: <T extends ExtendableClient>(this: T) => UnscopedClient<T, TClient>;
|
|
83
104
|
$queryRaw: typeof throwRawQueryBlocked;
|
|
84
105
|
$queryRawUnsafe: typeof throwRawQueryBlocked;
|
|
85
106
|
$executeRaw: typeof throwRawQueryBlocked;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prismaExtension.d.ts","sourceRoot":"","sources":["../src/prismaExtension.ts"],"names":[],"mappings":"AAQA,KAAK,uBAAuB,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,GACpD,KAAK,GACL,CAAC,SAAS,MAAM,GACd,KAAK,GACL,CAAC,CAAA;AAEP;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,OAAO,IAAI,uBAAuB,CAAC,MAAM,OAAO,CAAC,CAAA;AAE3E,MAAM,WAAW,aAAa,CAAC,OAAO;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG;QAAE,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,CAAA;KAAE,CAAA;IAC1E;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;CACvC;AA4ID;;;;;;GAMG;AACH,UAAU,iBAAiB;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,GACjB,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAwChC;AAiwBD,iBAAS,oBAAoB,IAAI,KAAK,CAErC;
|
|
1
|
+
{"version":3,"file":"prismaExtension.d.ts","sourceRoot":"","sources":["../src/prismaExtension.ts"],"names":[],"mappings":"AAQA,KAAK,uBAAuB,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,GACpD,KAAK,GACL,CAAC,SAAS,MAAM,GACd,KAAK,GACL,CAAC,CAAA;AAEP;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,OAAO,IAAI,uBAAuB,CAAC,MAAM,OAAO,CAAC,CAAA;AAE3E,MAAM,WAAW,aAAa,CAAC,OAAO;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG;QAAE,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,CAAA;KAAE,CAAA;IAC1E;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;CACvC;AA4ID;;;;;;GAMG;AACH,UAAU,iBAAiB;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,GACjB,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAwChC;AAiwBD,iBAAS,oBAAoB,IAAI,KAAK,CAErC;AAQD;;;;;;GAMG;AACH,KAAK,gBAAgB,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAA;AAQ7C;;;GAGG;AACH,KAAK,YAAY,GACf,WAAW,GAAG,iBAAiB,GAAG,aAAa,GAAG,mBAAmB,CAAA;AAEvE;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,GAAG,EAAE,OAAO,IAAI,IAAI,CAC7C,GAAG,EACH,YAAY,GAAG,SAAS,GAAG,gBAAgB,CAC5C,GACC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,OAAO,EAAE,YAAY,CAAC,CAAC,GAAG;IACpD,OAAO,CAAC,IAAI,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,GAAG,GAAG,CAAA;IAC5D,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;CACrE,CAAA;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAC5C,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC;cA8BX,CAAC,SAAS,gBAAgB,QACnC,CAAC,kBACS,MAAM,KACrB,CAAC;qBAaoB,CAAC,SAAS,gBAAgB,QAC1C,CAAC,KACN,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;;;;;0DA2DhC"}
|
package/dist/prismaExtension.js
CHANGED
|
@@ -504,6 +504,10 @@ const RAW_QUERY_MESSAGE = "Raw SQL is not allowed on a tenant-scoped client. Use
|
|
|
504
504
|
function throwRawQueryBlocked() {
|
|
505
505
|
throw new TenantScopeError(RAW_QUERY_MESSAGE);
|
|
506
506
|
}
|
|
507
|
+
const TRANSACTION_MESSAGE = "$forOrg() and $withoutTenant() can't be called on a transaction client: the client they return would run its queries outside the transaction. Start the transaction on the client you need instead, e.g. db.$forOrg(organizationId).$transaction(async (tx) => ...).";
|
|
508
|
+
function isTransactionClient(client) {
|
|
509
|
+
return typeof client === "object" && client !== null && !("$extends" in client);
|
|
510
|
+
}
|
|
507
511
|
function createTenancyExtension(config) {
|
|
508
512
|
const tenantField = config.tenantField ?? "organizationId";
|
|
509
513
|
const getTenantId = config.getTenantId ?? (() => getCurrentOrg()?.id);
|
|
@@ -514,6 +518,18 @@ function createTenancyExtension(config) {
|
|
|
514
518
|
runtimeDataModel
|
|
515
519
|
);
|
|
516
520
|
const listFieldsByModel = getListFieldsByModel(client);
|
|
521
|
+
function $forOrg(organizationId) {
|
|
522
|
+
if (isTransactionClient(this)) {
|
|
523
|
+
throw new TenantScopeError(TRANSACTION_MESSAGE);
|
|
524
|
+
}
|
|
525
|
+
return buildScopedClient(() => organizationId);
|
|
526
|
+
}
|
|
527
|
+
function $withoutTenant() {
|
|
528
|
+
if (isTransactionClient(this)) {
|
|
529
|
+
throw new TenantScopeError(TRANSACTION_MESSAGE);
|
|
530
|
+
}
|
|
531
|
+
return unscopedClient;
|
|
532
|
+
}
|
|
517
533
|
function buildScopedClient(tenantIdSource) {
|
|
518
534
|
const ctx = {
|
|
519
535
|
tenantField,
|
|
@@ -547,6 +563,8 @@ function createTenancyExtension(config) {
|
|
|
547
563
|
$executeRawUnsafe: throwRawQueryBlocked
|
|
548
564
|
},
|
|
549
565
|
client: {
|
|
566
|
+
$forOrg,
|
|
567
|
+
$withoutTenant,
|
|
550
568
|
$queryRaw: throwRawQueryBlocked,
|
|
551
569
|
$queryRawUnsafe: throwRawQueryBlocked,
|
|
552
570
|
$executeRaw: throwRawQueryBlocked,
|
|
@@ -554,27 +572,11 @@ function createTenancyExtension(config) {
|
|
|
554
572
|
}
|
|
555
573
|
});
|
|
556
574
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
client: {
|
|
560
|
-
/**
|
|
561
|
-
* A client scoped to `organizationId` regardless of request
|
|
562
|
-
* context — for background jobs, webhooks, and other code that
|
|
563
|
-
* knows the tenant but doesn't run inside a request.
|
|
564
|
-
*/
|
|
565
|
-
$forOrg(organizationId) {
|
|
566
|
-
return buildScopedClient(() => organizationId);
|
|
567
|
-
},
|
|
568
|
-
/**
|
|
569
|
-
* An unscoped client for code that intentionally reads or writes
|
|
570
|
-
* across organizations: seeds, data migrations, admin tooling.
|
|
571
|
-
* Nothing here is scoped, and raw SQL is allowed.
|
|
572
|
-
*/
|
|
573
|
-
$withoutTenant() {
|
|
574
|
-
return client;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
575
|
+
const unscopedClient = client.$extends({
|
|
576
|
+
name: "cedarjs-tenancy-unscoped",
|
|
577
|
+
client: { $forOrg, $withoutTenant }
|
|
577
578
|
});
|
|
579
|
+
return buildScopedClient(getTenantId);
|
|
578
580
|
});
|
|
579
581
|
}
|
|
580
582
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/tenancy",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3556",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"test:watch": "vitest watch"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
49
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
47
|
+
"@cedarjs/api": "8.0.0-canary.3556",
|
|
48
|
+
"@cedarjs/context": "8.0.0-canary.3556",
|
|
49
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3556"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@apollo/client": "4.3.5",
|
|
53
53
|
"@arethetypeswrong/cli": "0.18.5",
|
|
54
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
55
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
56
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
57
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
54
|
+
"@cedarjs/auth": "8.0.0-canary.3556",
|
|
55
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3556",
|
|
56
|
+
"@cedarjs/router": "8.0.0-canary.3556",
|
|
57
|
+
"@cedarjs/web": "8.0.0-canary.3556",
|
|
58
58
|
"@prisma/adapter-better-sqlite3": "7.10.1",
|
|
59
59
|
"@prisma/client": "7.10.1",
|
|
60
60
|
"@testing-library/dom": "10.4.2",
|
|
@@ -80,9 +80,9 @@
|
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"@apollo/client": "4.3.5",
|
|
83
|
-
"@cedarjs/auth": "8.0.0-canary.
|
|
84
|
-
"@cedarjs/router": "8.0.0-canary.
|
|
85
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
83
|
+
"@cedarjs/auth": "8.0.0-canary.3556",
|
|
84
|
+
"@cedarjs/router": "8.0.0-canary.3556",
|
|
85
|
+
"@cedarjs/web": "8.0.0-canary.3556",
|
|
86
86
|
"@prisma/client": "7.10.1",
|
|
87
87
|
"react": "19.3.1"
|
|
88
88
|
},
|