@12-apps/prisma 4.0.0 → 5.0.0
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/actor-context.js +1 -1
- package/dist/index.js +12 -12
- package/package.json +13 -13
- package/prisma/migrations/20260731210000_shift_delete_guard/migration.sql +1 -1
- package/prisma/migrations/20260812150000_add_mcp_oauth_tables/migration.sql +6 -6
- package/prisma/migrations/20260812150000_add_onboarding_states/migration.sql +3 -3
- package/prisma/migrations/20260813140000_add_notification_tables/migration.sql +1 -1
- package/prisma/schema/mcp.prisma +1 -1
- package/prisma/schema/notifications.prisma +1 -1
- package/prisma/schema/onboarding.prisma +1 -1
- package/src/actor-context.ts +2 -2
- package/src/index.ts +12 -12
package/dist/actor-context.js
CHANGED
|
@@ -27,7 +27,7 @@ const node_async_hooks_1 = require("node:async_hooks");
|
|
|
27
27
|
// module) can't create a second store whose context is invisible to closures
|
|
28
28
|
// captured against the first.
|
|
29
29
|
const globalStore = globalThis;
|
|
30
|
-
const store = () => (globalStore.
|
|
30
|
+
const store = () => (globalStore.__12appsPrismaActorStore ??= new node_async_hooks_1.AsyncLocalStorage());
|
|
31
31
|
/**
|
|
32
32
|
* The REAL human behind `onBehalfOfUserId`, derived (never accepted) from the
|
|
33
33
|
* stamp that declares the impersonation (FUT-458).
|
package/dist/index.js
CHANGED
|
@@ -182,25 +182,25 @@ const createPostgresClient = async () => {
|
|
|
182
182
|
* module-load failure.
|
|
183
183
|
*/
|
|
184
184
|
const getPrismaClient = async () => {
|
|
185
|
-
if (globalStore.
|
|
186
|
-
return globalStore.
|
|
187
|
-
if (globalStore.
|
|
188
|
-
return globalStore.
|
|
185
|
+
if (globalStore.__12appsPrisma)
|
|
186
|
+
return globalStore.__12appsPrisma;
|
|
187
|
+
if (globalStore.__12appsPrismaInit)
|
|
188
|
+
return globalStore.__12appsPrismaInit;
|
|
189
189
|
const pglite = resolvePglite();
|
|
190
190
|
const init = (pglite ? createPgliteClient(pglite.dataDir) : createPostgresClient())
|
|
191
191
|
.then((client) => {
|
|
192
|
-
globalStore.
|
|
193
|
-
globalStore.
|
|
192
|
+
globalStore.__12appsPrisma = client;
|
|
193
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
194
194
|
return client;
|
|
195
195
|
})
|
|
196
196
|
.catch((error) => {
|
|
197
|
-
globalStore.
|
|
197
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
198
198
|
throw new Error(`Prisma client not available (${pglite ? 'PGlite' : 'PostgreSQL'} mode). ` +
|
|
199
199
|
'Run "pnpm --filter @12-apps/prisma prisma generate" from the ' +
|
|
200
200
|
'monorepo root, or "pnpm prisma generate" from packages/prisma. ' +
|
|
201
201
|
`Cause: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
202
202
|
});
|
|
203
|
-
globalStore.
|
|
203
|
+
globalStore.__12appsPrismaInit = init;
|
|
204
204
|
return init;
|
|
205
205
|
};
|
|
206
206
|
exports.getPrismaClient = getPrismaClient;
|
|
@@ -208,15 +208,15 @@ exports.getPrismaClient = getPrismaClient;
|
|
|
208
208
|
* Set a custom Prisma client instance (for testing).
|
|
209
209
|
*/
|
|
210
210
|
const setPrismaClient = (client) => {
|
|
211
|
-
globalStore.
|
|
212
|
-
globalStore.
|
|
211
|
+
globalStore.__12appsPrisma = client;
|
|
212
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
213
213
|
};
|
|
214
214
|
exports.setPrismaClient = setPrismaClient;
|
|
215
215
|
/**
|
|
216
216
|
* Reset the Prisma client instance (for testing).
|
|
217
217
|
*/
|
|
218
218
|
const resetPrismaClient = () => {
|
|
219
|
-
globalStore.
|
|
220
|
-
globalStore.
|
|
219
|
+
globalStore.__12appsPrisma = undefined;
|
|
220
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
221
221
|
};
|
|
222
222
|
exports.resetPrismaClient = resetPrismaClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/prisma",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Prisma host: the multi-file schema folder, the plugin migration seam, and the shared PrismaClient singleton with its audit / append-only extensions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -59,20 +59,20 @@
|
|
|
59
59
|
"pglite-prisma-adapter": "0.7.2"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@12-apps/audit": "^
|
|
63
|
-
"@12-apps/entitlements": "^
|
|
64
|
-
"@12-apps/entity-lifecycle": "^
|
|
62
|
+
"@12-apps/audit": "^4.0.0",
|
|
63
|
+
"@12-apps/entitlements": "^3.0.0",
|
|
64
|
+
"@12-apps/entity-lifecycle": "^4.0.0",
|
|
65
65
|
"@12-apps/eslint-config": "^1.20.0",
|
|
66
|
-
"@12-apps/jobs": "^
|
|
67
|
-
"@12-apps/mcp": "^
|
|
68
|
-
"@12-apps/notifications": "^
|
|
69
|
-
"@12-apps/onboarding": "^
|
|
70
|
-
"@12-apps/payments-backend": "^
|
|
66
|
+
"@12-apps/jobs": "^4.0.0",
|
|
67
|
+
"@12-apps/mcp": "^3.0.0",
|
|
68
|
+
"@12-apps/notifications": "^4.0.0",
|
|
69
|
+
"@12-apps/onboarding": "^2.0.0",
|
|
70
|
+
"@12-apps/payments-backend": "^4.0.0",
|
|
71
71
|
"@12-apps/product-research": "^2.0.0",
|
|
72
|
-
"@12-apps/rbac": "^
|
|
73
|
-
"@12-apps/realtime": "^
|
|
74
|
-
"@12-apps/report-builder": "^
|
|
75
|
-
"@12-apps/shift": "^
|
|
72
|
+
"@12-apps/rbac": "^4.0.0",
|
|
73
|
+
"@12-apps/realtime": "^2.0.0",
|
|
74
|
+
"@12-apps/report-builder": "^5.0.0",
|
|
75
|
+
"@12-apps/shift": "^3.0.0",
|
|
76
76
|
"@12-apps/typescript-config": "^1.20.0",
|
|
77
77
|
"@types/node": "^22.10.6",
|
|
78
78
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
-- with no foreign key (that is what keeps the package host-agnostic), so a host
|
|
6
6
|
-- that deletes its tenant row and then sweeps the remaining by-value tables by
|
|
7
7
|
-- `client_id` could never remove a shift: the RAISE aborted the whole
|
|
8
|
-
-- transaction.
|
|
8
|
+
-- transaction. The origin host's demo-store reset does exactly that on EVERY deploy,
|
|
9
9
|
-- so a single shift row would have wedged the deploy step.
|
|
10
10
|
--
|
|
11
11
|
-- Deletability is host policy, not a package invariant. What the guard actually
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
-- Authorization codes are deliberately absent: they are STATELESS signed blobs,
|
|
19
19
|
-- so there is no table to create and nothing to sweep.
|
|
20
20
|
--
|
|
21
|
-
-- The columns, defaults, indexes and CHECK are
|
|
21
|
+
-- The columns, defaults, indexes and CHECK are the origin host's
|
|
22
22
|
-- `20260713120000_add_oauth_client_refresh`,
|
|
23
23
|
-- `20260715180000_add_onboarding_state_mcp_connection` (the mcp_connections half
|
|
24
24
|
-- — the onboarding half belongs to @12-apps/onboarding) and
|
|
25
25
|
-- `20260720120000_add_mcp_connection_host` verbatim, minus the FK to `users`:
|
|
26
26
|
-- this package cannot know the name of a host's user table, and a host that has
|
|
27
|
-
-- one keeps its own constraint (
|
|
27
|
+
-- one keeps its own constraint (the origin host's is ON DELETE CASCADE).
|
|
28
28
|
--
|
|
29
29
|
-- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block for
|
|
30
30
|
-- the CHECK, which has no IF NOT EXISTS form). That is what makes adoption by a
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
-- the difference bites exactly the host this file is written for: `CREATE TABLE IF
|
|
36
36
|
-- NOT EXISTS` skips the whole table, columns included, so a host holding an OLDER
|
|
37
37
|
-- shape of one of these tables silently keeps it. Each table below is therefore
|
|
38
|
-
-- followed by a guarded `ADD COLUMN` for every column that reached
|
|
38
|
+
-- followed by a guarded `ADD COLUMN` for every column that reached the origin host in a
|
|
39
39
|
-- LATER migration than its own CREATE. The full audit: `oauth_refresh_tokens
|
|
40
40
|
-- .user_sub` (`20260713150000_add_oauth_refresh_user_sub`) and `mcp_connections
|
|
41
41
|
-- .host` (`20260720120000_add_mcp_connection_host`). `oauth_clients` needs none —
|
|
@@ -112,10 +112,10 @@ CREATE INDEX IF NOT EXISTS "oauth_refresh_tokens_user_email_client_id_idx"
|
|
|
112
112
|
-- `CREATE TABLE IF NOT EXISTS` skips the WHOLE table, so a host that already holds
|
|
113
113
|
-- `oauth_refresh_tokens` in an OLDER SHAPE gets none of the columns declared above
|
|
114
114
|
-- — statement-level guarding is not the same as column-level guarding. That is
|
|
115
|
-
-- precisely how
|
|
115
|
+
-- precisely how the origin host's own history ran: `user_sub` arrived in a SECOND
|
|
116
116
|
-- migration (FUT-105, `20260713150000_add_oauth_refresh_user_sub`), so a host
|
|
117
117
|
-- frozen before it would adopt this file, skip the CREATE, never get the column,
|
|
118
|
-
-- and then fail on every refresh the package serves. Mirror
|
|
118
|
+
-- and then fail on every refresh the package serves. Mirror the origin host's pair
|
|
119
119
|
-- verbatim — guarded add with a backfill default to satisfy NOT NULL, then drop
|
|
120
120
|
-- the default so the column matches the Prisma schema (`String`, no default).
|
|
121
121
|
-- Both statements are no-ops on a fresh host and on a replay.
|
|
@@ -147,6 +147,6 @@ CREATE INDEX IF NOT EXISTS "mcp_connections_last_active_at_idx"
|
|
|
147
147
|
ON "mcp_connections"("last_active_at");
|
|
148
148
|
|
|
149
149
|
-- A host adopting this migration where `mcp_connections` predates the `host`
|
|
150
|
-
-- column (
|
|
150
|
+
-- column (the origin host added it in a later migration) gets it here; a fresh host
|
|
151
151
|
-- already has it from the CREATE above, so the guard makes both cases a no-op.
|
|
152
152
|
ALTER TABLE "mcp_connections" ADD COLUMN IF NOT EXISTS "host" TEXT;
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
-- the package and copied into a host's migrations folder by its
|
|
3
3
|
-- plugin-migration sync.
|
|
4
4
|
--
|
|
5
|
-
-- The columns, defaults, indexes and the status CHECK are
|
|
5
|
+
-- The columns, defaults, indexes and the status CHECK are the origin host's
|
|
6
6
|
-- `20260715180000_add_onboarding_state_mcp_connection` verbatim, minus two
|
|
7
7
|
-- things that are the HOST's vocabulary rather than the package's:
|
|
8
8
|
--
|
|
9
9
|
-- * the FKs to `users` / `clients` — this package cannot know the name of a
|
|
10
10
|
-- host's user or tenant table. A host that has them keeps its own
|
|
11
|
-
-- constraints (
|
|
11
|
+
-- constraints (the origin host's are ON DELETE CASCADE) and they stay
|
|
12
12
|
-- compatible with everything the package writes;
|
|
13
13
|
-- * the `mcp_connections` half of that migration, which belongs to
|
|
14
14
|
-- @12-apps/mcp and ships in ITS folder.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
-- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block
|
|
17
17
|
-- for the CHECK, which has no IF NOT EXISTS form). That is what makes adoption
|
|
18
18
|
-- by a host that ALREADY has the table a no-op instead of a failed deploy —
|
|
19
|
-
--
|
|
19
|
+
-- the origin host applies this and nothing changes, no `prisma migrate resolve`
|
|
20
20
|
-- dance required. It is also what lets the PGlite provisioner replay it into an
|
|
21
21
|
-- existing schema, which is how the harness and the integration suites run.
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--
|
|
25
25
|
-- ============================ REPLAY SAFETY ================================
|
|
26
26
|
-- Every statement is guarded, because the first adopters ALREADY HAVE these
|
|
27
|
-
-- tables:
|
|
27
|
+
-- tables: the origin host created them by hand before the package existed, so this
|
|
28
28
|
-- migration must be a no-op there and correct on an empty database.
|
|
29
29
|
--
|
|
30
30
|
-- The guards are per COLUMN, not per table. `CREATE TABLE IF NOT EXISTS` alone
|
package/prisma/schema/mcp.prisma
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Host-agnostic by design (the entity-lifecycle / rbac doctrine): `user_id` is a
|
|
13
13
|
// by-value scalar with NO relation, because this package cannot know the name of
|
|
14
|
-
// the host's user model. The host's own migration may add the FK (
|
|
14
|
+
// the host's user model. The host's own migration may add the FK (the origin host's
|
|
15
15
|
// is ON DELETE CASCADE). Note there is deliberately no `oauth_codes` table:
|
|
16
16
|
// authorization codes are STATELESS signed blobs, so there is nothing to store
|
|
17
17
|
// and nothing to sweep.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// and for a stronger reason: the category set is HOST vocabulary (`categories`
|
|
23
23
|
// on the server config), so a closed set in the schema would be wrong for every
|
|
24
24
|
// adopter but the first. A host that wants its own taxonomy enforced adds the
|
|
25
|
-
// CHECK in a migration of its own —
|
|
25
|
+
// CHECK in a migration of its own — the origin host does. Only `channel` and `status`
|
|
26
26
|
// on the delivery row are closed here, because those two are the LIBRARY's.
|
|
27
27
|
model Notification {
|
|
28
28
|
id String @id @default(uuid())
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// Host-agnostic by design (the entity-lifecycle / report-builder doctrine):
|
|
13
13
|
// - `user_id` and `client_id` are by-value scalars, NOT relations — this
|
|
14
14
|
// package cannot know the name of the host's user or tenant model. The
|
|
15
|
-
// host's own migration may add the FK constraints (
|
|
15
|
+
// host's own migration may add the FK constraints (the origin host has both,
|
|
16
16
|
// ON DELETE CASCADE).
|
|
17
17
|
// - `status` is a String with a DB CHECK rather than a Prisma enum (the
|
|
18
18
|
// house String+CHECK pattern); the four values are the package's own
|
package/src/actor-context.ts
CHANGED
|
@@ -93,11 +93,11 @@ export interface ActorContext extends ActorAttributionSnapshot {
|
|
|
93
93
|
// module) can't create a second store whose context is invisible to closures
|
|
94
94
|
// captured against the first.
|
|
95
95
|
const globalStore = globalThis as unknown as {
|
|
96
|
-
|
|
96
|
+
__12appsPrismaActorStore?: AsyncLocalStorage<ActorContext>;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
const store = (): AsyncLocalStorage<ActorContext> =>
|
|
100
|
-
(globalStore.
|
|
100
|
+
(globalStore.__12appsPrismaActorStore ??= new AsyncLocalStorage<ActorContext>());
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* The REAL human behind `onBehalfOfUserId`, derived (never accepted) from the
|
package/src/index.ts
CHANGED
|
@@ -63,8 +63,8 @@ export { normalizeSearchText } from './search-normalize';
|
|
|
63
63
|
// a second PGlite instance against the same dataDir — PGlite holds a single
|
|
64
64
|
// exclusive connection, and a duplicate would deadlock or corrupt the store.
|
|
65
65
|
const globalStore = globalThis as unknown as {
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
__12appsPrisma?: PrismaClient;
|
|
67
|
+
__12appsPrismaInit?: Promise<PrismaClient>;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -196,20 +196,20 @@ const createPostgresClient = async (): Promise<PrismaClient> => {
|
|
|
196
196
|
* module-load failure.
|
|
197
197
|
*/
|
|
198
198
|
export const getPrismaClient = async (): Promise<PrismaClient> => {
|
|
199
|
-
if (globalStore.
|
|
200
|
-
if (globalStore.
|
|
199
|
+
if (globalStore.__12appsPrisma) return globalStore.__12appsPrisma;
|
|
200
|
+
if (globalStore.__12appsPrismaInit) return globalStore.__12appsPrismaInit;
|
|
201
201
|
|
|
202
202
|
const pglite = resolvePglite();
|
|
203
203
|
const init = (
|
|
204
204
|
pglite ? createPgliteClient(pglite.dataDir) : createPostgresClient()
|
|
205
205
|
)
|
|
206
206
|
.then((client) => {
|
|
207
|
-
globalStore.
|
|
208
|
-
globalStore.
|
|
207
|
+
globalStore.__12appsPrisma = client;
|
|
208
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
209
209
|
return client;
|
|
210
210
|
})
|
|
211
211
|
.catch((error: unknown) => {
|
|
212
|
-
globalStore.
|
|
212
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
213
213
|
throw new Error(
|
|
214
214
|
`Prisma client not available (${pglite ? 'PGlite' : 'PostgreSQL'} mode). ` +
|
|
215
215
|
'Run "pnpm --filter @12-apps/prisma prisma generate" from the ' +
|
|
@@ -219,7 +219,7 @@ export const getPrismaClient = async (): Promise<PrismaClient> => {
|
|
|
219
219
|
);
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
globalStore.
|
|
222
|
+
globalStore.__12appsPrismaInit = init;
|
|
223
223
|
return init;
|
|
224
224
|
};
|
|
225
225
|
|
|
@@ -227,14 +227,14 @@ export const getPrismaClient = async (): Promise<PrismaClient> => {
|
|
|
227
227
|
* Set a custom Prisma client instance (for testing).
|
|
228
228
|
*/
|
|
229
229
|
export const setPrismaClient = (client: PrismaClient): void => {
|
|
230
|
-
globalStore.
|
|
231
|
-
globalStore.
|
|
230
|
+
globalStore.__12appsPrisma = client;
|
|
231
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
232
232
|
};
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
235
|
* Reset the Prisma client instance (for testing).
|
|
236
236
|
*/
|
|
237
237
|
export const resetPrismaClient = (): void => {
|
|
238
|
-
globalStore.
|
|
239
|
-
globalStore.
|
|
238
|
+
globalStore.__12appsPrisma = undefined;
|
|
239
|
+
globalStore.__12appsPrismaInit = undefined;
|
|
240
240
|
};
|