@btravstack/prisma 0.43.0 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,11 +9,20 @@
9
9
 
10
10
  ```sh
11
11
  pnpm add @btravstack/prisma @btravstack/core @btravstack/config @btravstack/di unthrown \
12
- @prisma/orm-postgres@8.0.0-rc.11
12
+ @prisma/orm-postgres@8.0.0-rc.18 temporal-polyfill@1.0.4
13
13
  ```
14
14
 
15
- Five peer dependencies — install every one, so the application holds a single
16
- copy of each. The `prisma` CLI is yours, as a dev dependency: the client is
15
+ Install the required peers, so the application holds a single copy of each;
16
+ `temporal-polyfill` is the Postgres package's own peer from rc.14. The
17
+ `@prisma/orm-postgres` peer is `^8.0.0-rc.11`, and both ends are tested: an
18
+ application already on rc.11 keeps it.
19
+
20
+ **Upgrading with released migrations:** upgrade `@btravstack/prisma` and its
21
+ `@btravstack/*` peers, and leave Prisma where it is. No contract, migration or
22
+ database marker changes. Upgrading Prisma itself from rc.11 rewrites every
23
+ released migration and re-signs every database, which is Prisma's path rather
24
+ than this package's; see
25
+ [Supported Prisma trains](https://btravstack.github.io/btravstack/reference/prisma#supported-prisma-trains). The `prisma` CLI is yours, as a dev dependency: the client is
17
26
  built from _your_ emitted contract, so there is none for this package to ship.
18
27
  Node `>=22.12`.
19
28
 
@@ -96,7 +105,7 @@ the per-query observation below comes from.
96
105
  lane, folded into the kernel's `GET /healthz` with nothing wired. `/readyz`
97
106
  does not read it: failing readiness on a dependency every replica shares
98
107
  removes them all at once.
99
- - **Every query handed to `Observers`**, as one `afterQuery` middleware, so
108
+ - **Every query handed to `Observers`**, as one statement middleware, so
100
109
  composing `observability()` writes the failures as lines and `otel()` opens
101
110
  the spans and mints the instruments — with no flag here and no port list to
102
111
  satisfy when you compose neither. A middleware sees the ORM lane, the SQL
@@ -188,7 +197,7 @@ rather than the role `DATABASE_URL` carries. An application that migrates itself
188
197
  at boot races every other replica.
189
198
 
190
199
  **Engine-level tracing.** There is no engine: Prisma 8 is a TypeScript runtime
191
- and ships no instrumentation package, so the `afterQuery` middleware is the
200
+ and ships no instrumentation package, so the statement middleware is the
192
201
  whole seam and it already carries the runtime's own latency.
193
202
 
194
203
  **Transactions.** Commit boundaries belong to the adapter, spelled at the call —
package/dist/index.cjs CHANGED
@@ -5,60 +5,62 @@ let _btravstack_di = require("@btravstack/di");
5
5
  let unthrown = require("unthrown");
6
6
  //#region src/instrument.ts
7
7
  /**
8
- * Every query, handed to the graph's observers: a span that spans the query, a
9
- * count of how it came out, and a log line if it failed.
8
+ * Every statement, handed to the graph's observers: a span around it, a count
9
+ * of how it came out, and a log line if it failed.
10
10
  *
11
- * **This is why a client the starter cannot name can be instrumented at all.**
12
- * `middleware` is a construction option rather than a wrapper, and a hook sees
13
- * every statement on every lane — the ORM's, the SQL builder's and the raw
14
- * one — so it never needs to know the contract, which is the thing this package
15
- * cannot see.
16
- *
17
- * **It starts the operation in `beforeQuery` and settles it in `afterQuery`**,
18
- * paired by `ctx.planExecutionId`, because that is what `Observers` is for: the
19
- * port is called at the START and answers a finisher, so an observer can open a
20
- * span the query runs *inside*. Settling a span reconstructed afterwards from a
21
- * duration would make it the parent of nothing. It is also why the runtime's
22
- * own `latencyMs` is not reported — the observer started the operation, so the
23
- * duration is its own measurement rather than something to pass along.
24
- *
25
- * **Both lanes, because there are two.** A read settles through
26
- * `afterQuery`; a write with no `RETURNING` — a SQL-builder `delete()` run
27
- * through `runtime().execute(plan)` — settles through `afterExecute` and would
28
- * otherwise go unobserved entirely.
29
- *
30
- * `completed` is what settles the outcome, and a failed statement does reach
31
- * the hook: measured against a real database, a duplicate insert arrives as
32
- * `afterQuery … completed=false` before the rejection surfaces. That is what
33
- * keeps the errors half of RED honest.
11
+ * The operation STARTS before the driver runs and settles after it, because
12
+ * `Observers` answers a finisher precisely so a span can be opened around the
13
+ * work. On rc.18 the finisher rides the activity's own `ctx.state`. On rc.11 it
14
+ * is keyed WEAKLY by the operation's context, the one object every hook of a
15
+ * statement is handed: a statement refused after `beforeQuery` (a later
16
+ * middleware, a codec, an abort) never reaches `afterQuery`, and a map keyed by
17
+ * `planExecutionId` would keep its finisher for the client's lifetime.
34
18
  */
35
19
  const queryObserver = (observers) => {
36
- const open = /* @__PURE__ */ new Map();
37
- const start = (plan, ctx) => {
38
- open.set(ctx.planExecutionId, (0, _btravstack_core.observe)(observers, {
39
- component: "database",
40
- name: "query",
41
- attributes: { scope: ctx.scope ?? "runtime" },
42
- ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
43
- }));
20
+ const key = Symbol("btravstack-query-observer");
21
+ const inFlight = /* @__PURE__ */ new WeakMap();
22
+ const start = (plan, scope) => (0, _btravstack_core.observe)(observers, {
23
+ component: "database",
24
+ name: "query",
25
+ attributes: { scope: scope ?? "runtime" },
26
+ ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
27
+ });
28
+ const settle = (finish, ok, source) => {
29
+ finish?.({
30
+ outcome: ok ? "ok" : "error",
31
+ ...source === void 0 ? {} : { attributes: { source } }
32
+ });
33
+ };
34
+ const beforePlan = (plan, ctx) => {
35
+ inFlight.set(ctx, start(plan, ctx.scope));
44
36
  return Promise.resolve();
45
37
  };
46
- const finish = (_plan, result, ctx) => {
47
- const settle = open.get(ctx.planExecutionId);
48
- open.delete(ctx.planExecutionId);
49
- settle?.({
50
- outcome: result.completed ? "ok" : "error",
51
- ...result.source === void 0 ? {} : { attributes: { source: result.source } }
52
- });
38
+ const afterPlan = (_plan, result, ctx) => {
39
+ const finish = inFlight.get(ctx);
40
+ inFlight.delete(ctx);
41
+ settle(finish, result.completed, result.source);
53
42
  return Promise.resolve();
54
43
  };
55
44
  return {
56
45
  name: "btravstack-observers",
57
46
  familyId: "sql",
58
- beforeQuery: start,
59
- afterQuery: finish,
60
- beforeExecute: start,
61
- afterExecute: finish
47
+ beforeEncode: (plan, ctx) => {
48
+ ctx.state(key)["settle"] = start(plan, ctx.scope);
49
+ return Promise.resolve();
50
+ },
51
+ activityEnded: (_activity, result, ctx) => {
52
+ if (result.kind === "statement") {
53
+ const state = ctx.state(key);
54
+ const finish = state["settle"];
55
+ delete state["settle"];
56
+ settle(finish, result.outcome === "succeeded", result.source);
57
+ }
58
+ return Promise.resolve();
59
+ },
60
+ beforeQuery: beforePlan,
61
+ afterQuery: afterPlan,
62
+ beforeExecute: beforePlan,
63
+ afterExecute: afterPlan
62
64
  };
63
65
  };
64
66
  //#endregion
package/dist/index.d.cts CHANGED
@@ -2,36 +2,51 @@ import { HealthChecks, Observers } from "@btravstack/core";
2
2
  import { Module, PortClassOf } from "@btravstack/di";
3
3
  //#region src/instrument.d.ts
4
4
  /**
5
- * A Prisma 8 middleware, structurally — `name`, `familyId` and the four hooks
6
- * this starter implements.
5
+ * A Prisma 8 SQL middleware, structurally, with the hooks of BOTH release
6
+ * trains the peer range admits.
7
7
  *
8
- * Declared here rather than imported from `@prisma/orm-postgres/family-runtime`
9
- * so the option's type does not drag the target package into a consumer that
10
- * only reads this module's types. A real `SqlMiddleware` satisfies it.
8
+ * Prisma 8.0.0-rc.18 replaced the per-statement hooks: `beforeQuery` and
9
+ * `beforeExecute` became `beforeEncode`, `afterQuery` and `afterExecute`
10
+ * became `activityEnded`. The two sets are disjoint and neither runtime refuses
11
+ * a hook it does not know, so one object serves both: each runtime calls its
12
+ * own pair and never the other's. Declared here rather than imported so the
13
+ * option's type drags no target package into a consumer.
11
14
  */
12
15
  type SqlMiddlewareLike = {
13
16
  readonly name: string;
14
17
  readonly familyId: "sql";
15
- readonly beforeQuery: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
16
- readonly afterQuery: (plan: Plan, result: Settled, ctx: MiddlewareContext) => Promise<void>;
17
- readonly beforeExecute: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
18
- readonly afterExecute: (plan: Plan, result: Settled, ctx: MiddlewareContext) => Promise<void>;
18
+ /** rc.18 and later: before the statement's parameters are encoded, on both lanes. */
19
+ readonly beforeEncode: (plan: Plan, ctx: ActivityContext) => Promise<void>;
20
+ /** rc.18 and later: a statement or a transaction ended. */
21
+ readonly activityEnded: (activity: {
22
+ readonly kind: "statement" | "transaction";
23
+ }, result: ActivityEnded, ctx: ActivityContext) => Promise<void>;
24
+ /** rc.11 to rc.17: a statement read for its rows. */
25
+ readonly beforeQuery: (plan: Plan, ctx: PlanContext) => Promise<void>;
26
+ readonly afterQuery: (plan: Plan, result: Completed, ctx: PlanContext) => Promise<void>;
27
+ /** rc.11 to rc.17: a statement run for its statistics, such as a write with no `RETURNING`. */
28
+ readonly beforeExecute: (plan: Plan, ctx: PlanContext) => Promise<void>;
29
+ readonly afterExecute: (plan: Plan, result: Completed, ctx: PlanContext) => Promise<void>;
19
30
  };
20
- /** The rendered statement, which both `before` hooks are handed. */
21
31
  type Plan = {
22
32
  readonly sql?: string;
23
33
  };
24
- /**
25
- * What every hook is handed. `planExecutionId` is the runtime's own correlation
26
- * id — its TSDoc names tracing and timing as the reason it exists — and it is
27
- * what pairs a `before` with its `after`.
28
- */
29
- type MiddlewareContext = {
30
- readonly planExecutionId: string;
31
- readonly scope?: "runtime" | "connection" | "transaction";
34
+ type Scope = "runtime" | "connection" | "transaction";
35
+ type ActivityContext = {
36
+ readonly scope?: Scope;
37
+ readonly state: (key: string | symbol) => Record<string | symbol, unknown>;
38
+ };
39
+ type ActivityEnded = {
40
+ readonly kind: "statement";
41
+ readonly outcome: "succeeded" | "failed" | "stopped";
42
+ readonly source?: string;
43
+ } | {
44
+ readonly kind: "transaction";
45
+ };
46
+ type PlanContext = {
47
+ readonly scope?: Scope;
32
48
  };
33
- /** What an `after` hook reports. There is no error here: Prisma's shape carries none. */
34
- type Settled = {
49
+ type Completed = {
35
50
  readonly completed: boolean;
36
51
  readonly source?: string;
37
52
  };
@@ -98,9 +113,9 @@ type PrismaBinding = {
98
113
  /** `DATABASE_URL`, read through `Config` rather than by the application. */
99
114
  readonly url: string;
100
115
  /**
101
- * The starter's own middleware — one `afterQuery` hook reporting every
102
- * query to `Observers`. Spread it into the client's `middleware` array
103
- * beside any of the application's own.
116
+ * The starter's own middleware — the statement hooks of every supported
117
+ * Prisma train, reporting every query to `Observers`. Spread it into the
118
+ * client's `middleware` array beside any of the application's own.
104
119
  */
105
120
  readonly middleware: readonly SqlMiddlewareLike[];
106
121
  };
package/dist/index.d.mts CHANGED
@@ -2,36 +2,51 @@ import { HealthChecks, Observers } from "@btravstack/core";
2
2
  import { Module, PortClassOf } from "@btravstack/di";
3
3
  //#region src/instrument.d.ts
4
4
  /**
5
- * A Prisma 8 middleware, structurally — `name`, `familyId` and the four hooks
6
- * this starter implements.
5
+ * A Prisma 8 SQL middleware, structurally, with the hooks of BOTH release
6
+ * trains the peer range admits.
7
7
  *
8
- * Declared here rather than imported from `@prisma/orm-postgres/family-runtime`
9
- * so the option's type does not drag the target package into a consumer that
10
- * only reads this module's types. A real `SqlMiddleware` satisfies it.
8
+ * Prisma 8.0.0-rc.18 replaced the per-statement hooks: `beforeQuery` and
9
+ * `beforeExecute` became `beforeEncode`, `afterQuery` and `afterExecute`
10
+ * became `activityEnded`. The two sets are disjoint and neither runtime refuses
11
+ * a hook it does not know, so one object serves both: each runtime calls its
12
+ * own pair and never the other's. Declared here rather than imported so the
13
+ * option's type drags no target package into a consumer.
11
14
  */
12
15
  type SqlMiddlewareLike = {
13
16
  readonly name: string;
14
17
  readonly familyId: "sql";
15
- readonly beforeQuery: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
16
- readonly afterQuery: (plan: Plan, result: Settled, ctx: MiddlewareContext) => Promise<void>;
17
- readonly beforeExecute: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
18
- readonly afterExecute: (plan: Plan, result: Settled, ctx: MiddlewareContext) => Promise<void>;
18
+ /** rc.18 and later: before the statement's parameters are encoded, on both lanes. */
19
+ readonly beforeEncode: (plan: Plan, ctx: ActivityContext) => Promise<void>;
20
+ /** rc.18 and later: a statement or a transaction ended. */
21
+ readonly activityEnded: (activity: {
22
+ readonly kind: "statement" | "transaction";
23
+ }, result: ActivityEnded, ctx: ActivityContext) => Promise<void>;
24
+ /** rc.11 to rc.17: a statement read for its rows. */
25
+ readonly beforeQuery: (plan: Plan, ctx: PlanContext) => Promise<void>;
26
+ readonly afterQuery: (plan: Plan, result: Completed, ctx: PlanContext) => Promise<void>;
27
+ /** rc.11 to rc.17: a statement run for its statistics, such as a write with no `RETURNING`. */
28
+ readonly beforeExecute: (plan: Plan, ctx: PlanContext) => Promise<void>;
29
+ readonly afterExecute: (plan: Plan, result: Completed, ctx: PlanContext) => Promise<void>;
19
30
  };
20
- /** The rendered statement, which both `before` hooks are handed. */
21
31
  type Plan = {
22
32
  readonly sql?: string;
23
33
  };
24
- /**
25
- * What every hook is handed. `planExecutionId` is the runtime's own correlation
26
- * id — its TSDoc names tracing and timing as the reason it exists — and it is
27
- * what pairs a `before` with its `after`.
28
- */
29
- type MiddlewareContext = {
30
- readonly planExecutionId: string;
31
- readonly scope?: "runtime" | "connection" | "transaction";
34
+ type Scope = "runtime" | "connection" | "transaction";
35
+ type ActivityContext = {
36
+ readonly scope?: Scope;
37
+ readonly state: (key: string | symbol) => Record<string | symbol, unknown>;
38
+ };
39
+ type ActivityEnded = {
40
+ readonly kind: "statement";
41
+ readonly outcome: "succeeded" | "failed" | "stopped";
42
+ readonly source?: string;
43
+ } | {
44
+ readonly kind: "transaction";
45
+ };
46
+ type PlanContext = {
47
+ readonly scope?: Scope;
32
48
  };
33
- /** What an `after` hook reports. There is no error here: Prisma's shape carries none. */
34
- type Settled = {
49
+ type Completed = {
35
50
  readonly completed: boolean;
36
51
  readonly source?: string;
37
52
  };
@@ -98,9 +113,9 @@ type PrismaBinding = {
98
113
  /** `DATABASE_URL`, read through `Config` rather than by the application. */
99
114
  readonly url: string;
100
115
  /**
101
- * The starter's own middleware — one `afterQuery` hook reporting every
102
- * query to `Observers`. Spread it into the client's `middleware` array
103
- * beside any of the application's own.
116
+ * The starter's own middleware — the statement hooks of every supported
117
+ * Prisma train, reporting every query to `Observers`. Spread it into the
118
+ * client's `middleware` array beside any of the application's own.
104
119
  */
105
120
  readonly middleware: readonly SqlMiddlewareLike[];
106
121
  };
package/dist/index.mjs CHANGED
@@ -4,60 +4,62 @@ import { Module, Port, Provider } from "@btravstack/di";
4
4
  import { TaggedError, fromPromise, fromSafePromise } from "unthrown";
5
5
  //#region src/instrument.ts
6
6
  /**
7
- * Every query, handed to the graph's observers: a span that spans the query, a
8
- * count of how it came out, and a log line if it failed.
7
+ * Every statement, handed to the graph's observers: a span around it, a count
8
+ * of how it came out, and a log line if it failed.
9
9
  *
10
- * **This is why a client the starter cannot name can be instrumented at all.**
11
- * `middleware` is a construction option rather than a wrapper, and a hook sees
12
- * every statement on every lane — the ORM's, the SQL builder's and the raw
13
- * one — so it never needs to know the contract, which is the thing this package
14
- * cannot see.
15
- *
16
- * **It starts the operation in `beforeQuery` and settles it in `afterQuery`**,
17
- * paired by `ctx.planExecutionId`, because that is what `Observers` is for: the
18
- * port is called at the START and answers a finisher, so an observer can open a
19
- * span the query runs *inside*. Settling a span reconstructed afterwards from a
20
- * duration would make it the parent of nothing. It is also why the runtime's
21
- * own `latencyMs` is not reported — the observer started the operation, so the
22
- * duration is its own measurement rather than something to pass along.
23
- *
24
- * **Both lanes, because there are two.** A read settles through
25
- * `afterQuery`; a write with no `RETURNING` — a SQL-builder `delete()` run
26
- * through `runtime().execute(plan)` — settles through `afterExecute` and would
27
- * otherwise go unobserved entirely.
28
- *
29
- * `completed` is what settles the outcome, and a failed statement does reach
30
- * the hook: measured against a real database, a duplicate insert arrives as
31
- * `afterQuery … completed=false` before the rejection surfaces. That is what
32
- * keeps the errors half of RED honest.
10
+ * The operation STARTS before the driver runs and settles after it, because
11
+ * `Observers` answers a finisher precisely so a span can be opened around the
12
+ * work. On rc.18 the finisher rides the activity's own `ctx.state`. On rc.11 it
13
+ * is keyed WEAKLY by the operation's context, the one object every hook of a
14
+ * statement is handed: a statement refused after `beforeQuery` (a later
15
+ * middleware, a codec, an abort) never reaches `afterQuery`, and a map keyed by
16
+ * `planExecutionId` would keep its finisher for the client's lifetime.
33
17
  */
34
18
  const queryObserver = (observers) => {
35
- const open = /* @__PURE__ */ new Map();
36
- const start = (plan, ctx) => {
37
- open.set(ctx.planExecutionId, observe(observers, {
38
- component: "database",
39
- name: "query",
40
- attributes: { scope: ctx.scope ?? "runtime" },
41
- ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
42
- }));
19
+ const key = Symbol("btravstack-query-observer");
20
+ const inFlight = /* @__PURE__ */ new WeakMap();
21
+ const start = (plan, scope) => observe(observers, {
22
+ component: "database",
23
+ name: "query",
24
+ attributes: { scope: scope ?? "runtime" },
25
+ ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
26
+ });
27
+ const settle = (finish, ok, source) => {
28
+ finish?.({
29
+ outcome: ok ? "ok" : "error",
30
+ ...source === void 0 ? {} : { attributes: { source } }
31
+ });
32
+ };
33
+ const beforePlan = (plan, ctx) => {
34
+ inFlight.set(ctx, start(plan, ctx.scope));
43
35
  return Promise.resolve();
44
36
  };
45
- const finish = (_plan, result, ctx) => {
46
- const settle = open.get(ctx.planExecutionId);
47
- open.delete(ctx.planExecutionId);
48
- settle?.({
49
- outcome: result.completed ? "ok" : "error",
50
- ...result.source === void 0 ? {} : { attributes: { source: result.source } }
51
- });
37
+ const afterPlan = (_plan, result, ctx) => {
38
+ const finish = inFlight.get(ctx);
39
+ inFlight.delete(ctx);
40
+ settle(finish, result.completed, result.source);
52
41
  return Promise.resolve();
53
42
  };
54
43
  return {
55
44
  name: "btravstack-observers",
56
45
  familyId: "sql",
57
- beforeQuery: start,
58
- afterQuery: finish,
59
- beforeExecute: start,
60
- afterExecute: finish
46
+ beforeEncode: (plan, ctx) => {
47
+ ctx.state(key)["settle"] = start(plan, ctx.scope);
48
+ return Promise.resolve();
49
+ },
50
+ activityEnded: (_activity, result, ctx) => {
51
+ if (result.kind === "statement") {
52
+ const state = ctx.state(key);
53
+ const finish = state["settle"];
54
+ delete state["settle"];
55
+ settle(finish, result.outcome === "succeeded", result.source);
56
+ }
57
+ return Promise.resolve();
58
+ },
59
+ beforeQuery: beforePlan,
60
+ afterQuery: afterPlan,
61
+ beforeExecute: beforePlan,
62
+ afterExecute: afterPlan
61
63
  };
62
64
  };
63
65
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/prisma",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "description": "The Prisma starter for @btravstack/core: DATABASE_URL through Config, the Postgres driver adapter, and a client whose pool is the application scope's",
5
5
  "keywords": [
6
6
  "database",
@@ -63,14 +63,15 @@
63
63
  "./package.json": "./package.json"
64
64
  },
65
65
  "devDependencies": {
66
- "@btravstack/config": "0.43.0",
67
- "@btravstack/core": "0.43.0",
68
- "@btravstack/di": "0.43.0",
66
+ "@btravstack/config": "0.44.1",
67
+ "@btravstack/core": "0.44.1",
68
+ "@btravstack/di": "0.44.1",
69
69
  "@btravstack/tsconfig": "0.4.0",
70
- "@prisma/orm-postgres": "8.0.0-rc.11",
70
+ "@prisma/orm-postgres": "8.0.0-rc.18",
71
71
  "@types/node": "26.6.5",
72
72
  "@unthrown/vitest": "5.12.1",
73
73
  "@vitest/coverage-v8": "5.0.3",
74
+ "temporal-polyfill": "1.0.4",
74
75
  "tsdown": "0.23.1",
75
76
  "typescript": "7.0.2",
76
77
  "typescript-consumer": "npm:typescript@5.9.3",
@@ -78,9 +79,9 @@
78
79
  "vitest": "5.0.3"
79
80
  },
80
81
  "peerDependencies": {
81
- "@btravstack/config": "^0.43.0",
82
- "@btravstack/core": "^0.43.0",
83
- "@btravstack/di": "^0.43.0",
82
+ "@btravstack/config": "^0.44.1",
83
+ "@btravstack/core": "^0.44.1",
84
+ "@btravstack/di": "^0.44.1",
84
85
  "@prisma/orm-postgres": "^8.0.0-rc.11",
85
86
  "unthrown": "^5.0.0"
86
87
  },