@usebetterdev/tenant-prisma 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 ADDED
@@ -0,0 +1,57 @@
1
+ # @usebetterdev/tenant-prisma
2
+
3
+ Prisma adapter for [@usebetterdev/tenant](https://github.com/usebetter-dev/usebetter). Runs each tenant scope in an interactive transaction with `SET LOCAL app.current_tenant = '<uuid>'` so Postgres RLS applies.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @usebetterdev/tenant @prisma/client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { PrismaClient } from "@prisma/client";
15
+ import { betterTenant } from "@usebetterdev/tenant";
16
+ import { prismaDatabase } from "@usebetterdev/tenant/prisma";
17
+
18
+ const prisma = new PrismaClient();
19
+
20
+ const tenant = betterTenant({
21
+ database: prismaDatabase(prisma),
22
+ tenantResolver: { header: "x-tenant-id" },
23
+ });
24
+
25
+ // tenant.api.createTenant, updateTenant, listTenants, deleteTenant now work (via runAsSystem).
26
+ // In handlers: tenant.getDatabase() returns the Prisma transaction client.
27
+ ```
28
+
29
+ `prismaDatabase(prisma)` bundles the adapter and tenant repository into a single `database` provider. If you use a custom table name, pass it via the `tableName` option:
30
+
31
+ ```ts
32
+ import { prismaDatabase } from "@usebetterdev/tenant/prisma";
33
+
34
+ const tenant = betterTenant({
35
+ database: prismaDatabase(prisma, { tableName: "my_tenants" }),
36
+ tenantResolver: { header: "x-tenant-id" },
37
+ });
38
+ ```
39
+
40
+ The tenants table must match the CLI-generated schema: `id` (UUID), `name`, `slug`, `created_at`.
41
+
42
+ ## Behaviour
43
+
44
+ - **runWithTenant(tenantId, fn)** — Opens an interactive `$transaction`, runs `SELECT set_config('app.current_tenant', tenantId, true)`, then calls `fn(tx)`. The `tx` handle is the Prisma transaction client; use only this for tenant-scoped tables so RLS sees the tenant.
45
+ - **runAsSystem(fn)** — Opens an interactive `$transaction`, runs `SELECT set_config('app.bypass_rls', 'true', true)`, then calls `fn(tx)`. Use for admin/cron only (e.g. tenant.api.\*). RLS policies must allow rows when `current_setting('app.bypass_rls', true) = 'true'` (CLI generates this bypass policy).
46
+ - **loadTenant** — The full Tenant row is loaded by default into `getContext().tenant`. Set `loadTenant: false` to opt out (saves one query per request).
47
+ - The handle passed to `fn` is the Prisma interactive transaction client — all generated model methods (`db.user.findMany()`, etc.) are available on it.
48
+
49
+ ## Testing
50
+
51
+ Integration tests use [@testcontainers/postgresql](https://www.npmjs.com/package/@testcontainers/postgresql): a Postgres container is started automatically when you run tests (Docker required). If Docker is unavailable, the tests are skipped. You can instead set `DATABASE_URL` to run the same tests against an existing database.
52
+
53
+ **Run integration tests:** From the repo root, run `pnpm run test:integration` or `pnpm run test --filter @usebetterdev/tenant-prisma`.
54
+
55
+ ## Peer dependency
56
+
57
+ Requires `@prisma/client` (>= 5.0.0). Install it in your app when using this adapter.
package/dist/index.cjs ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ prismaDatabase: () => prismaDatabase
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/adapter.ts
28
+ function prismaAdapter(prisma) {
29
+ return {
30
+ async runWithTenant(tenantId, fn) {
31
+ return prisma.$transaction(async (tx) => {
32
+ await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;
33
+ return fn(tx);
34
+ });
35
+ },
36
+ async runAsSystem(fn) {
37
+ return prisma.$transaction(async (tx) => {
38
+ await tx.$executeRaw`SELECT set_config('app.bypass_rls', 'true', true)`;
39
+ return fn(tx);
40
+ });
41
+ }
42
+ };
43
+ }
44
+
45
+ // src/repository.ts
46
+ function rowToTenant(row) {
47
+ const createdAt = row.created_at ?? row.createdAt;
48
+ if (createdAt === void 0 || createdAt === null) {
49
+ throw new Error("better-tenant: row missing created_at / createdAt");
50
+ }
51
+ return {
52
+ id: String(row.id),
53
+ name: String(row.name),
54
+ slug: String(row.slug),
55
+ createdAt,
56
+ ...row
57
+ };
58
+ }
59
+ function assertSafeIdentifier(name) {
60
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
61
+ throw new Error(
62
+ `better-tenant: invalid table name "${name}" \u2014 must be [a-zA-Z_][a-zA-Z0-9_]*`
63
+ );
64
+ }
65
+ }
66
+ function createGetTenantRepository(tableName = "tenants") {
67
+ assertSafeIdentifier(tableName);
68
+ return (database) => {
69
+ const tx = database;
70
+ return {
71
+ async create(data) {
72
+ const rows = await tx.$queryRawUnsafe(
73
+ `INSERT INTO ${tableName} (name, slug) VALUES ($1, $2) RETURNING *`,
74
+ data.name,
75
+ data.slug
76
+ );
77
+ const row = rows[0];
78
+ if (!row || typeof row !== "object") {
79
+ throw new Error("better-tenant: createTenant failed to return row");
80
+ }
81
+ return rowToTenant(row);
82
+ },
83
+ async getById(tenantId) {
84
+ const rows = await tx.$queryRawUnsafe(
85
+ `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,
86
+ tenantId
87
+ );
88
+ const row = rows[0];
89
+ if (!row || typeof row !== "object") {
90
+ return null;
91
+ }
92
+ return rowToTenant(row);
93
+ },
94
+ async getBySlug(slug) {
95
+ const rows = await tx.$queryRawUnsafe(
96
+ `SELECT * FROM ${tableName} WHERE slug = $1 LIMIT 1`,
97
+ slug
98
+ );
99
+ const row = rows[0];
100
+ if (!row || typeof row !== "object") {
101
+ return null;
102
+ }
103
+ return rowToTenant(row);
104
+ },
105
+ async update(tenantId, data) {
106
+ const setClauses = [];
107
+ const values = [];
108
+ if (data.name !== void 0) {
109
+ values.push(data.name);
110
+ setClauses.push(`name = $${String(values.length)}`);
111
+ }
112
+ if (data.slug !== void 0) {
113
+ values.push(data.slug);
114
+ setClauses.push(`slug = $${String(values.length)}`);
115
+ }
116
+ if (setClauses.length === 0) {
117
+ const rows2 = await tx.$queryRawUnsafe(
118
+ `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,
119
+ tenantId
120
+ );
121
+ const row2 = rows2[0];
122
+ if (!row2 || typeof row2 !== "object") {
123
+ throw new Error("better-tenant: tenant not found");
124
+ }
125
+ return rowToTenant(row2);
126
+ }
127
+ values.push(tenantId);
128
+ const query = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE id = $${String(values.length)}::uuid RETURNING *`;
129
+ const rows = await tx.$queryRawUnsafe(
130
+ query,
131
+ ...values
132
+ );
133
+ const row = rows[0];
134
+ if (!row || typeof row !== "object") {
135
+ throw new Error("better-tenant: tenant not found");
136
+ }
137
+ return rowToTenant(row);
138
+ },
139
+ async list(options = {}) {
140
+ const limit = options.limit ?? 50;
141
+ const offset = Math.max(0, options.offset ?? 0);
142
+ const rows = await tx.$queryRawUnsafe(
143
+ `SELECT * FROM ${tableName} ORDER BY created_at ASC LIMIT $1 OFFSET $2`,
144
+ limit,
145
+ offset
146
+ );
147
+ return rows.map(rowToTenant);
148
+ },
149
+ async delete(tenantId) {
150
+ await tx.$executeRawUnsafe(
151
+ `DELETE FROM ${tableName} WHERE id = $1::uuid`,
152
+ tenantId
153
+ );
154
+ }
155
+ };
156
+ };
157
+ }
158
+
159
+ // src/database.ts
160
+ function prismaDatabase(prisma, options) {
161
+ return {
162
+ adapter: prismaAdapter(prisma),
163
+ getTenantRepository: createGetTenantRepository(options?.tableName)
164
+ };
165
+ }
166
+ // Annotate the CommonJS export names for ESM import in node:
167
+ 0 && (module.exports = {
168
+ prismaDatabase
169
+ });
170
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/repository.ts","../src/database.ts"],"sourcesContent":["export { prismaDatabase } from \"./database.js\";\nexport type { PrismaClientLike, PrismaTransactionClient } from \"./types.js\";\n","import type { TenantAdapter } from \"@usebetterdev/tenant-core\";\nimport type { PrismaClientLike, PrismaTransactionClient } from \"./types.js\";\n\n/**\n * Creates a TenantAdapter for Prisma (PostgreSQL).\n *\n * Uses an interactive transaction per runWithTenant:\n * BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n *\n * The handle passed to fn is the Prisma transaction client; use only this\n * handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = 'true'\n * so RLS policies that allow when current_setting('app.bypass_rls', true) = 'true'\n * permit access (CLI generates this bypass policy).\n *\n * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)\n * @returns TenantAdapter implementation\n */\nexport function prismaAdapter(\n prisma: PrismaClientLike,\n): TenantAdapter<PrismaTransactionClient, PrismaTransactionClient> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: PrismaTransactionClient) => Promise<T>,\n ): Promise<T> {\n return prisma.$transaction(async (tx: PrismaTransactionClient) => {\n await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: PrismaTransactionClient) => Promise<T>,\n ): Promise<T> {\n return prisma.$transaction(async (tx: PrismaTransactionClient) => {\n await tx.$executeRaw`SELECT set_config('app.bypass_rls', 'true', true)`;\n return fn(tx);\n });\n },\n };\n}\n","import type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport type { PrismaTransactionClient } from \"./types.js\";\n\n/**\n * Converts a raw DB row to the Tenant shape expected by core.\n */\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.created_at ?? row.createdAt;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Validate table name to prevent SQL injection. Only allows [a-zA-Z_][a-zA-Z0-9_]*.\n */\nfunction assertSafeIdentifier(name: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new Error(\n `better-tenant: invalid table name \"${name}\" — must be [a-zA-Z_][a-zA-Z0-9_]*`,\n );\n }\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n *\n * Uses raw SQL ($queryRawUnsafe / $executeRawUnsafe) on the Prisma transaction\n * client so the adapter works without requiring a generated `Tenant` model in\n * the user's Prisma schema. The tenants table must match the CLI-generated shape\n * (id UUID, name TEXT, slug TEXT, created_at TIMESTAMPTZ).\n *\n * Table name is validated at creation time to prevent injection — only\n * alphanumeric + underscore identifiers are accepted.\n *\n * @param tableName - SQL table name (default: \"tenants\")\n */\nexport function createGetTenantRepository(\n tableName = \"tenants\",\n): (database: PrismaTransactionClient) => TenantRepository {\n assertSafeIdentifier(tableName);\n\n return (database: PrismaTransactionClient) => {\n const tx = database;\n\n return {\n async create(data) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `INSERT INTO ${tableName} (name, slug) VALUES ($1, $2) RETURNING *`,\n data.name,\n data.slug,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row);\n },\n\n async getById(tenantId: string) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,\n tenantId,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row);\n },\n\n async getBySlug(slug: string) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE slug = $1 LIMIT 1`,\n slug,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row);\n },\n\n async update(tenantId, data) {\n const setClauses: string[] = [];\n const values: unknown[] = [];\n\n if (data.name !== undefined) {\n values.push(data.name);\n setClauses.push(`name = $${String(values.length)}`);\n }\n if (data.slug !== undefined) {\n values.push(data.slug);\n setClauses.push(`slug = $${String(values.length)}`);\n }\n\n if (setClauses.length === 0) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,\n tenantId,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row);\n }\n\n values.push(tenantId);\n const query = `UPDATE ${tableName} SET ${setClauses.join(\", \")} WHERE id = $${String(values.length)}::uuid RETURNING *`;\n\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n query,\n ...values,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} ORDER BY created_at ASC LIMIT $1 OFFSET $2`,\n limit,\n offset,\n );\n return rows.map(rowToTenant);\n },\n\n async delete(tenantId) {\n await tx.$executeRawUnsafe(\n `DELETE FROM ${tableName} WHERE id = $1::uuid`,\n tenantId,\n );\n },\n };\n };\n}\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { prismaAdapter } from \"./adapter.js\";\nimport type { PrismaClientLike, PrismaTransactionClient } from \"./types.js\";\nimport { createGetTenantRepository } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Prisma (PostgreSQL).\n *\n * Bundles prismaAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: prismaDatabase(prisma) })`.\n *\n * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)\n * @param options - Optional: custom table name (default: \"tenants\")\n */\nexport function prismaDatabase(\n prisma: PrismaClientLike,\n options?: { tableName?: string },\n): DatabaseProvider<PrismaTransactionClient, PrismaTransactionClient> {\n return {\n adapter: prismaAdapter(prisma),\n getTenantRepository: createGetTenantRepository(options?.tableName),\n };\n}\n"],"mappings":"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","names":["rows","row"]}
@@ -0,0 +1,40 @@
1
+ import { DatabaseProvider } from '@usebetterdev/tenant-core';
2
+
3
+ /**
4
+ * Minimal Prisma transaction client shape.
5
+ *
6
+ * This is the handle passed into `$transaction(async (tx) => ...)`.
7
+ * We define our own interface to avoid a hard dependency on a generated `@prisma/client`.
8
+ */
9
+ type PrismaTransactionClient = {
10
+ $executeRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<number>;
11
+ $queryRaw<T = unknown>(query: TemplateStringsArray, ...values: unknown[]): Promise<T>;
12
+ $queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T>;
13
+ $executeRawUnsafe(query: string, ...values: unknown[]): Promise<number>;
14
+ };
15
+ /**
16
+ * Minimal PrismaClient shape: must support interactive `$transaction`.
17
+ *
18
+ * Matches `new PrismaClient()` — no generated model types required at build time.
19
+ */
20
+ type PrismaClientLike = PrismaTransactionClient & {
21
+ $transaction<T>(fn: (tx: PrismaTransactionClient) => Promise<T>, options?: {
22
+ maxWait?: number;
23
+ timeout?: number;
24
+ }): Promise<T>;
25
+ };
26
+
27
+ /**
28
+ * Creates a DatabaseProvider for Prisma (PostgreSQL).
29
+ *
30
+ * Bundles prismaAdapter + createGetTenantRepository into a single config value
31
+ * for use with `betterTenant({ database: prismaDatabase(prisma) })`.
32
+ *
33
+ * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)
34
+ * @param options - Optional: custom table name (default: "tenants")
35
+ */
36
+ declare function prismaDatabase(prisma: PrismaClientLike, options?: {
37
+ tableName?: string;
38
+ }): DatabaseProvider<PrismaTransactionClient, PrismaTransactionClient>;
39
+
40
+ export { type PrismaClientLike, type PrismaTransactionClient, prismaDatabase };
@@ -0,0 +1,40 @@
1
+ import { DatabaseProvider } from '@usebetterdev/tenant-core';
2
+
3
+ /**
4
+ * Minimal Prisma transaction client shape.
5
+ *
6
+ * This is the handle passed into `$transaction(async (tx) => ...)`.
7
+ * We define our own interface to avoid a hard dependency on a generated `@prisma/client`.
8
+ */
9
+ type PrismaTransactionClient = {
10
+ $executeRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<number>;
11
+ $queryRaw<T = unknown>(query: TemplateStringsArray, ...values: unknown[]): Promise<T>;
12
+ $queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T>;
13
+ $executeRawUnsafe(query: string, ...values: unknown[]): Promise<number>;
14
+ };
15
+ /**
16
+ * Minimal PrismaClient shape: must support interactive `$transaction`.
17
+ *
18
+ * Matches `new PrismaClient()` — no generated model types required at build time.
19
+ */
20
+ type PrismaClientLike = PrismaTransactionClient & {
21
+ $transaction<T>(fn: (tx: PrismaTransactionClient) => Promise<T>, options?: {
22
+ maxWait?: number;
23
+ timeout?: number;
24
+ }): Promise<T>;
25
+ };
26
+
27
+ /**
28
+ * Creates a DatabaseProvider for Prisma (PostgreSQL).
29
+ *
30
+ * Bundles prismaAdapter + createGetTenantRepository into a single config value
31
+ * for use with `betterTenant({ database: prismaDatabase(prisma) })`.
32
+ *
33
+ * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)
34
+ * @param options - Optional: custom table name (default: "tenants")
35
+ */
36
+ declare function prismaDatabase(prisma: PrismaClientLike, options?: {
37
+ tableName?: string;
38
+ }): DatabaseProvider<PrismaTransactionClient, PrismaTransactionClient>;
39
+
40
+ export { type PrismaClientLike, type PrismaTransactionClient, prismaDatabase };
package/dist/index.js ADDED
@@ -0,0 +1,143 @@
1
+ // src/adapter.ts
2
+ function prismaAdapter(prisma) {
3
+ return {
4
+ async runWithTenant(tenantId, fn) {
5
+ return prisma.$transaction(async (tx) => {
6
+ await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;
7
+ return fn(tx);
8
+ });
9
+ },
10
+ async runAsSystem(fn) {
11
+ return prisma.$transaction(async (tx) => {
12
+ await tx.$executeRaw`SELECT set_config('app.bypass_rls', 'true', true)`;
13
+ return fn(tx);
14
+ });
15
+ }
16
+ };
17
+ }
18
+
19
+ // src/repository.ts
20
+ function rowToTenant(row) {
21
+ const createdAt = row.created_at ?? row.createdAt;
22
+ if (createdAt === void 0 || createdAt === null) {
23
+ throw new Error("better-tenant: row missing created_at / createdAt");
24
+ }
25
+ return {
26
+ id: String(row.id),
27
+ name: String(row.name),
28
+ slug: String(row.slug),
29
+ createdAt,
30
+ ...row
31
+ };
32
+ }
33
+ function assertSafeIdentifier(name) {
34
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
35
+ throw new Error(
36
+ `better-tenant: invalid table name "${name}" \u2014 must be [a-zA-Z_][a-zA-Z0-9_]*`
37
+ );
38
+ }
39
+ }
40
+ function createGetTenantRepository(tableName = "tenants") {
41
+ assertSafeIdentifier(tableName);
42
+ return (database) => {
43
+ const tx = database;
44
+ return {
45
+ async create(data) {
46
+ const rows = await tx.$queryRawUnsafe(
47
+ `INSERT INTO ${tableName} (name, slug) VALUES ($1, $2) RETURNING *`,
48
+ data.name,
49
+ data.slug
50
+ );
51
+ const row = rows[0];
52
+ if (!row || typeof row !== "object") {
53
+ throw new Error("better-tenant: createTenant failed to return row");
54
+ }
55
+ return rowToTenant(row);
56
+ },
57
+ async getById(tenantId) {
58
+ const rows = await tx.$queryRawUnsafe(
59
+ `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,
60
+ tenantId
61
+ );
62
+ const row = rows[0];
63
+ if (!row || typeof row !== "object") {
64
+ return null;
65
+ }
66
+ return rowToTenant(row);
67
+ },
68
+ async getBySlug(slug) {
69
+ const rows = await tx.$queryRawUnsafe(
70
+ `SELECT * FROM ${tableName} WHERE slug = $1 LIMIT 1`,
71
+ slug
72
+ );
73
+ const row = rows[0];
74
+ if (!row || typeof row !== "object") {
75
+ return null;
76
+ }
77
+ return rowToTenant(row);
78
+ },
79
+ async update(tenantId, data) {
80
+ const setClauses = [];
81
+ const values = [];
82
+ if (data.name !== void 0) {
83
+ values.push(data.name);
84
+ setClauses.push(`name = $${String(values.length)}`);
85
+ }
86
+ if (data.slug !== void 0) {
87
+ values.push(data.slug);
88
+ setClauses.push(`slug = $${String(values.length)}`);
89
+ }
90
+ if (setClauses.length === 0) {
91
+ const rows2 = await tx.$queryRawUnsafe(
92
+ `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,
93
+ tenantId
94
+ );
95
+ const row2 = rows2[0];
96
+ if (!row2 || typeof row2 !== "object") {
97
+ throw new Error("better-tenant: tenant not found");
98
+ }
99
+ return rowToTenant(row2);
100
+ }
101
+ values.push(tenantId);
102
+ const query = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE id = $${String(values.length)}::uuid RETURNING *`;
103
+ const rows = await tx.$queryRawUnsafe(
104
+ query,
105
+ ...values
106
+ );
107
+ const row = rows[0];
108
+ if (!row || typeof row !== "object") {
109
+ throw new Error("better-tenant: tenant not found");
110
+ }
111
+ return rowToTenant(row);
112
+ },
113
+ async list(options = {}) {
114
+ const limit = options.limit ?? 50;
115
+ const offset = Math.max(0, options.offset ?? 0);
116
+ const rows = await tx.$queryRawUnsafe(
117
+ `SELECT * FROM ${tableName} ORDER BY created_at ASC LIMIT $1 OFFSET $2`,
118
+ limit,
119
+ offset
120
+ );
121
+ return rows.map(rowToTenant);
122
+ },
123
+ async delete(tenantId) {
124
+ await tx.$executeRawUnsafe(
125
+ `DELETE FROM ${tableName} WHERE id = $1::uuid`,
126
+ tenantId
127
+ );
128
+ }
129
+ };
130
+ };
131
+ }
132
+
133
+ // src/database.ts
134
+ function prismaDatabase(prisma, options) {
135
+ return {
136
+ adapter: prismaAdapter(prisma),
137
+ getTenantRepository: createGetTenantRepository(options?.tableName)
138
+ };
139
+ }
140
+ export {
141
+ prismaDatabase
142
+ };
143
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapter.ts","../src/repository.ts","../src/database.ts"],"sourcesContent":["import type { TenantAdapter } from \"@usebetterdev/tenant-core\";\nimport type { PrismaClientLike, PrismaTransactionClient } from \"./types.js\";\n\n/**\n * Creates a TenantAdapter for Prisma (PostgreSQL).\n *\n * Uses an interactive transaction per runWithTenant:\n * BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n *\n * The handle passed to fn is the Prisma transaction client; use only this\n * handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = 'true'\n * so RLS policies that allow when current_setting('app.bypass_rls', true) = 'true'\n * permit access (CLI generates this bypass policy).\n *\n * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)\n * @returns TenantAdapter implementation\n */\nexport function prismaAdapter(\n prisma: PrismaClientLike,\n): TenantAdapter<PrismaTransactionClient, PrismaTransactionClient> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: PrismaTransactionClient) => Promise<T>,\n ): Promise<T> {\n return prisma.$transaction(async (tx: PrismaTransactionClient) => {\n await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: PrismaTransactionClient) => Promise<T>,\n ): Promise<T> {\n return prisma.$transaction(async (tx: PrismaTransactionClient) => {\n await tx.$executeRaw`SELECT set_config('app.bypass_rls', 'true', true)`;\n return fn(tx);\n });\n },\n };\n}\n","import type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport type { PrismaTransactionClient } from \"./types.js\";\n\n/**\n * Converts a raw DB row to the Tenant shape expected by core.\n */\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.created_at ?? row.createdAt;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Validate table name to prevent SQL injection. Only allows [a-zA-Z_][a-zA-Z0-9_]*.\n */\nfunction assertSafeIdentifier(name: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new Error(\n `better-tenant: invalid table name \"${name}\" — must be [a-zA-Z_][a-zA-Z0-9_]*`,\n );\n }\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n *\n * Uses raw SQL ($queryRawUnsafe / $executeRawUnsafe) on the Prisma transaction\n * client so the adapter works without requiring a generated `Tenant` model in\n * the user's Prisma schema. The tenants table must match the CLI-generated shape\n * (id UUID, name TEXT, slug TEXT, created_at TIMESTAMPTZ).\n *\n * Table name is validated at creation time to prevent injection — only\n * alphanumeric + underscore identifiers are accepted.\n *\n * @param tableName - SQL table name (default: \"tenants\")\n */\nexport function createGetTenantRepository(\n tableName = \"tenants\",\n): (database: PrismaTransactionClient) => TenantRepository {\n assertSafeIdentifier(tableName);\n\n return (database: PrismaTransactionClient) => {\n const tx = database;\n\n return {\n async create(data) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `INSERT INTO ${tableName} (name, slug) VALUES ($1, $2) RETURNING *`,\n data.name,\n data.slug,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row);\n },\n\n async getById(tenantId: string) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,\n tenantId,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row);\n },\n\n async getBySlug(slug: string) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE slug = $1 LIMIT 1`,\n slug,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row);\n },\n\n async update(tenantId, data) {\n const setClauses: string[] = [];\n const values: unknown[] = [];\n\n if (data.name !== undefined) {\n values.push(data.name);\n setClauses.push(`name = $${String(values.length)}`);\n }\n if (data.slug !== undefined) {\n values.push(data.slug);\n setClauses.push(`slug = $${String(values.length)}`);\n }\n\n if (setClauses.length === 0) {\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} WHERE id = $1::uuid LIMIT 1`,\n tenantId,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row);\n }\n\n values.push(tenantId);\n const query = `UPDATE ${tableName} SET ${setClauses.join(\", \")} WHERE id = $${String(values.length)}::uuid RETURNING *`;\n\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n query,\n ...values,\n );\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await tx.$queryRawUnsafe<Record<string, unknown>[]>(\n `SELECT * FROM ${tableName} ORDER BY created_at ASC LIMIT $1 OFFSET $2`,\n limit,\n offset,\n );\n return rows.map(rowToTenant);\n },\n\n async delete(tenantId) {\n await tx.$executeRawUnsafe(\n `DELETE FROM ${tableName} WHERE id = $1::uuid`,\n tenantId,\n );\n },\n };\n };\n}\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { prismaAdapter } from \"./adapter.js\";\nimport type { PrismaClientLike, PrismaTransactionClient } from \"./types.js\";\nimport { createGetTenantRepository } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Prisma (PostgreSQL).\n *\n * Bundles prismaAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: prismaDatabase(prisma) })`.\n *\n * @param prisma - PrismaClient instance (or anything matching PrismaClientLike)\n * @param options - Optional: custom table name (default: \"tenants\")\n */\nexport function prismaDatabase(\n prisma: PrismaClientLike,\n options?: { tableName?: string },\n): DatabaseProvider<PrismaTransactionClient, PrismaTransactionClient> {\n return {\n adapter: prismaAdapter(prisma),\n getTenantRepository: createGetTenantRepository(options?.tableName),\n };\n}\n"],"mappings":";AAmBO,SAAS,cACd,QACiE;AACjE,SAAO;AAAA,IACL,MAAM,cACJ,UACA,IACY;AACZ,aAAO,OAAO,aAAa,OAAO,OAAgC;AAChE,cAAM,GAAG,sDAAsD,QAAQ;AACvE,eAAO,GAAG,EAAE;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,YACJ,IACY;AACZ,aAAO,OAAO,aAAa,OAAO,OAAgC;AAChE,cAAM,GAAG;AACT,eAAO,GAAG,EAAE;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACpCA,SAAS,YAAY,KAAsC;AACzD,QAAM,YAAY,IAAI,cAAc,IAAI;AACxC,MAAI,cAAc,UAAa,cAAc,MAAM;AACjD,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,SAAO;AAAA,IACL,IAAI,OAAO,IAAI,EAAE;AAAA,IACjB,MAAM,OAAO,IAAI,IAAI;AAAA,IACrB,MAAM,OAAO,IAAI,IAAI;AAAA,IACrB;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAKA,SAAS,qBAAqB,MAAoB;AAChD,MAAI,CAAC,2BAA2B,KAAK,IAAI,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,sCAAsC,IAAI;AAAA,IAC5C;AAAA,EACF;AACF;AAeO,SAAS,0BACd,YAAY,WAC6C;AACzD,uBAAqB,SAAS;AAE9B,SAAO,CAAC,aAAsC;AAC5C,UAAM,KAAK;AAEX,WAAO;AAAA,MACL,MAAM,OAAO,MAAM;AACjB,cAAM,OAAO,MAAM,GAAG;AAAA,UACpB,eAAe,SAAS;AAAA,UACxB,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AACA,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,gBAAM,IAAI,MAAM,kDAAkD;AAAA,QACpE;AACA,eAAO,YAAY,GAAG;AAAA,MACxB;AAAA,MAEA,MAAM,QAAQ,UAAkB;AAC9B,cAAM,OAAO,MAAM,GAAG;AAAA,UACpB,iBAAiB,SAAS;AAAA,UAC1B;AAAA,QACF;AACA,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,iBAAO;AAAA,QACT;AACA,eAAO,YAAY,GAAG;AAAA,MACxB;AAAA,MAEA,MAAM,UAAU,MAAc;AAC5B,cAAM,OAAO,MAAM,GAAG;AAAA,UACpB,iBAAiB,SAAS;AAAA,UAC1B;AAAA,QACF;AACA,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,iBAAO;AAAA,QACT;AACA,eAAO,YAAY,GAAG;AAAA,MACxB;AAAA,MAEA,MAAM,OAAO,UAAU,MAAM;AAC3B,cAAM,aAAuB,CAAC;AAC9B,cAAM,SAAoB,CAAC;AAE3B,YAAI,KAAK,SAAS,QAAW;AAC3B,iBAAO,KAAK,KAAK,IAAI;AACrB,qBAAW,KAAK,WAAW,OAAO,OAAO,MAAM,CAAC,EAAE;AAAA,QACpD;AACA,YAAI,KAAK,SAAS,QAAW;AAC3B,iBAAO,KAAK,KAAK,IAAI;AACrB,qBAAW,KAAK,WAAW,OAAO,OAAO,MAAM,CAAC,EAAE;AAAA,QACpD;AAEA,YAAI,WAAW,WAAW,GAAG;AAC3B,gBAAMA,QAAO,MAAM,GAAG;AAAA,YACpB,iBAAiB,SAAS;AAAA,YAC1B;AAAA,UACF;AACA,gBAAMC,OAAMD,MAAK,CAAC;AAClB,cAAI,CAACC,QAAO,OAAOA,SAAQ,UAAU;AACnC,kBAAM,IAAI,MAAM,iCAAiC;AAAA,UACnD;AACA,iBAAO,YAAYA,IAAG;AAAA,QACxB;AAEA,eAAO,KAAK,QAAQ;AACpB,cAAM,QAAQ,UAAU,SAAS,QAAQ,WAAW,KAAK,IAAI,CAAC,gBAAgB,OAAO,OAAO,MAAM,CAAC;AAEnG,cAAM,OAAO,MAAM,GAAG;AAAA,UACpB;AAAA,UACA,GAAG;AAAA,QACL;AACA,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,gBAAM,IAAI,MAAM,iCAAiC;AAAA,QACnD;AACA,eAAO,YAAY,GAAG;AAAA,MACxB;AAAA,MAEA,MAAM,KAAK,UAAU,CAAC,GAAG;AACvB,cAAM,QAAQ,QAAQ,SAAS;AAC/B,cAAM,SAAS,KAAK,IAAI,GAAG,QAAQ,UAAU,CAAC;AAC9C,cAAM,OAAO,MAAM,GAAG;AAAA,UACpB,iBAAiB,SAAS;AAAA,UAC1B;AAAA,UACA;AAAA,QACF;AACA,eAAO,KAAK,IAAI,WAAW;AAAA,MAC7B;AAAA,MAEA,MAAM,OAAO,UAAU;AACrB,cAAM,GAAG;AAAA,UACP,eAAe,SAAS;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACtIO,SAAS,eACd,QACA,SACoE;AACpE,SAAO;AAAA,IACL,SAAS,cAAc,MAAM;AAAA,IAC7B,qBAAqB,0BAA0B,SAAS,SAAS;AAAA,EACnE;AACF;","names":["rows","row"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@usebetterdev/tenant-prisma",
3
+ "version": "0.2.0-beta.16",
4
+ "repository": "github:usebetter-dev/usebetter",
5
+ "bugs": "https://github.com/usebetter-dev/usebetter/issues",
6
+ "homepage": "https://github.com/usebetter-dev/usebetter#readme",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "lint": "oxlint",
29
+ "test": "vitest run",
30
+ "test:integration": "vitest run -c vitest.integration.config.ts",
31
+ "typecheck": "tsc --noEmit"
32
+ },
33
+ "dependencies": {
34
+ "@usebetterdev/tenant-core": "workspace:*"
35
+ },
36
+ "peerDependencies": {
37
+ "@prisma/client": ">=5.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@prisma/client": {
41
+ "optional": false
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@usebetterdev/test-utils": "workspace:*",
46
+ "@testcontainers/postgresql": "^11.11.0",
47
+ "@types/node": "^22.10.0",
48
+ "@types/pg": "^8.11.0",
49
+ "pg": "^8.13.0",
50
+ "tsup": "^8.3.5",
51
+ "typescript": "~5.7.2",
52
+ "vitest": "^2.1.6"
53
+ },
54
+ "engines": {
55
+ "node": ">=22"
56
+ }
57
+ }