@btravstack/prisma 0.42.0 → 0.44.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/README.md CHANGED
@@ -9,10 +9,10 @@
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
15
+ Install the required peers and Postgres polyfill, so the application holds a single
16
16
  copy of each. The `prisma` CLI is yours, as a dev dependency: the client is
17
17
  built from _your_ emitted contract, so there is none for this package to ship.
18
18
  Node `>=22.12`.
@@ -96,7 +96,7 @@ the per-query observation below comes from.
96
96
  lane, folded into the kernel's `GET /healthz` with nothing wired. `/readyz`
97
97
  does not read it: failing readiness on a dependency every replica shares
98
98
  removes them all at once.
99
- - **Every query handed to `Observers`**, as one `afterQuery` middleware, so
99
+ - **Every query handed to `Observers`**, as one SQL activity middleware, so
100
100
  composing `observability()` writes the failures as lines and `otel()` opens
101
101
  the spans and mints the instruments — with no flag here and no port list to
102
102
  satisfy when you compose neither. A middleware sees the ORM lane, the SQL
@@ -188,7 +188,7 @@ rather than the role `DATABASE_URL` carries. An application that migrates itself
188
188
  at boot races every other replica.
189
189
 
190
190
  **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
191
+ and ships no instrumentation package, so the SQL activity middleware is the
192
192
  whole seam and it already carries the runtime's own latency.
193
193
 
194
194
  **Transactions.** Commit boundaries belong to the adapter, spelled at the call —
package/dist/index.cjs CHANGED
@@ -4,61 +4,33 @@ let _btravstack_core = require("@btravstack/core");
4
4
  let _btravstack_di = require("@btravstack/di");
5
5
  let unthrown = require("unthrown");
6
6
  //#region src/instrument.ts
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.
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.
34
- */
7
+ /** Observe both SQL lanes from encoding until their statement activity ends. */
35
8
  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
- }));
44
- return Promise.resolve();
45
- };
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
- });
53
- return Promise.resolve();
54
- };
9
+ const key = Symbol("btravstack-query-observer");
55
10
  return {
56
11
  name: "btravstack-observers",
57
12
  familyId: "sql",
58
- beforeQuery: start,
59
- afterQuery: finish,
60
- beforeExecute: start,
61
- afterExecute: finish
13
+ beforeEncode: (plan, ctx) => {
14
+ ctx.state(key)["settle"] = (0, _btravstack_core.observe)(observers, {
15
+ component: "database",
16
+ name: "query",
17
+ attributes: { scope: ctx.scope ?? "runtime" },
18
+ ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
19
+ });
20
+ return Promise.resolve();
21
+ },
22
+ activityEnded: (_activity, result, ctx) => {
23
+ if (result.kind === "statement") {
24
+ const state = ctx.state(key);
25
+ const settle = state["settle"];
26
+ delete state["settle"];
27
+ settle?.({
28
+ outcome: result.outcome === "succeeded" ? "ok" : "error",
29
+ ...result.source === void 0 ? {} : { attributes: { source: result.source } }
30
+ });
31
+ }
32
+ return Promise.resolve();
33
+ }
62
34
  };
63
35
  };
64
36
  //#endregion
package/dist/index.d.cts CHANGED
@@ -1,39 +1,28 @@
1
1
  import { HealthChecks, Observers } from "@btravstack/core";
2
2
  import { Module, PortClassOf } from "@btravstack/di";
3
3
  //#region src/instrument.d.ts
4
- /**
5
- * A Prisma 8 middleware, structurally — `name`, `familyId` and the four hooks
6
- * this starter implements.
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.
11
- */
4
+ /** Structural Prisma 8 SQL hooks; no target-package import reaches consumers. */
12
5
  type SqlMiddlewareLike = {
13
6
  readonly name: string;
14
7
  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>;
8
+ readonly beforeEncode: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
9
+ readonly activityEnded: (activity: {
10
+ readonly kind: "statement" | "transaction";
11
+ }, result: Settled, ctx: MiddlewareContext) => Promise<void>;
19
12
  };
20
- /** The rendered statement, which both `before` hooks are handed. */
21
13
  type Plan = {
22
14
  readonly sql?: string;
23
15
  };
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
16
  type MiddlewareContext = {
30
- readonly planExecutionId: string;
31
17
  readonly scope?: "runtime" | "connection" | "transaction";
18
+ readonly state: (key: string | symbol) => Record<string | symbol, unknown>;
32
19
  };
33
- /** What an `after` hook reports. There is no error here: Prisma's shape carries none. */
34
20
  type Settled = {
35
- readonly completed: boolean;
21
+ readonly kind: "statement";
22
+ readonly outcome: "succeeded" | "failed" | "stopped";
36
23
  readonly source?: string;
24
+ } | {
25
+ readonly kind: "transaction";
37
26
  };
38
27
  //#endregion
39
28
  //#region src/prisma.d.ts
package/dist/index.d.mts CHANGED
@@ -1,39 +1,28 @@
1
1
  import { HealthChecks, Observers } from "@btravstack/core";
2
2
  import { Module, PortClassOf } from "@btravstack/di";
3
3
  //#region src/instrument.d.ts
4
- /**
5
- * A Prisma 8 middleware, structurally — `name`, `familyId` and the four hooks
6
- * this starter implements.
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.
11
- */
4
+ /** Structural Prisma 8 SQL hooks; no target-package import reaches consumers. */
12
5
  type SqlMiddlewareLike = {
13
6
  readonly name: string;
14
7
  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>;
8
+ readonly beforeEncode: (plan: Plan, ctx: MiddlewareContext) => Promise<void>;
9
+ readonly activityEnded: (activity: {
10
+ readonly kind: "statement" | "transaction";
11
+ }, result: Settled, ctx: MiddlewareContext) => Promise<void>;
19
12
  };
20
- /** The rendered statement, which both `before` hooks are handed. */
21
13
  type Plan = {
22
14
  readonly sql?: string;
23
15
  };
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
16
  type MiddlewareContext = {
30
- readonly planExecutionId: string;
31
17
  readonly scope?: "runtime" | "connection" | "transaction";
18
+ readonly state: (key: string | symbol) => Record<string | symbol, unknown>;
32
19
  };
33
- /** What an `after` hook reports. There is no error here: Prisma's shape carries none. */
34
20
  type Settled = {
35
- readonly completed: boolean;
21
+ readonly kind: "statement";
22
+ readonly outcome: "succeeded" | "failed" | "stopped";
36
23
  readonly source?: string;
24
+ } | {
25
+ readonly kind: "transaction";
37
26
  };
38
27
  //#endregion
39
28
  //#region src/prisma.d.ts
package/dist/index.mjs CHANGED
@@ -3,61 +3,33 @@ import { HealthCheckFailed, HealthChecks, Observers, noObserverMember, observe }
3
3
  import { Module, Port, Provider } from "@btravstack/di";
4
4
  import { TaggedError, fromPromise, fromSafePromise } from "unthrown";
5
5
  //#region src/instrument.ts
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.
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.
33
- */
6
+ /** Observe both SQL lanes from encoding until their statement activity ends. */
34
7
  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
- }));
43
- return Promise.resolve();
44
- };
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
- });
52
- return Promise.resolve();
53
- };
8
+ const key = Symbol("btravstack-query-observer");
54
9
  return {
55
10
  name: "btravstack-observers",
56
11
  familyId: "sql",
57
- beforeQuery: start,
58
- afterQuery: finish,
59
- beforeExecute: start,
60
- afterExecute: finish
12
+ beforeEncode: (plan, ctx) => {
13
+ ctx.state(key)["settle"] = observe(observers, {
14
+ component: "database",
15
+ name: "query",
16
+ attributes: { scope: ctx.scope ?? "runtime" },
17
+ ...plan.sql === void 0 ? {} : { details: { sql: plan.sql } }
18
+ });
19
+ return Promise.resolve();
20
+ },
21
+ activityEnded: (_activity, result, ctx) => {
22
+ if (result.kind === "statement") {
23
+ const state = ctx.state(key);
24
+ const settle = state["settle"];
25
+ delete state["settle"];
26
+ settle?.({
27
+ outcome: result.outcome === "succeeded" ? "ok" : "error",
28
+ ...result.source === void 0 ? {} : { attributes: { source: result.source } }
29
+ });
30
+ }
31
+ return Promise.resolve();
32
+ }
61
33
  };
62
34
  };
63
35
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/prisma",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
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.42.0",
67
- "@btravstack/core": "0.42.0",
68
- "@btravstack/di": "0.42.0",
66
+ "@btravstack/config": "0.44.0",
67
+ "@btravstack/core": "0.44.0",
68
+ "@btravstack/di": "0.44.0",
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,10 +79,10 @@
78
79
  "vitest": "5.0.3"
79
80
  },
80
81
  "peerDependencies": {
81
- "@btravstack/config": "^0.42.0",
82
- "@btravstack/core": "^0.42.0",
83
- "@btravstack/di": "^0.42.0",
84
- "@prisma/orm-postgres": "^8.0.0-rc.11",
82
+ "@btravstack/config": "^0.44.0",
83
+ "@btravstack/core": "^0.44.0",
84
+ "@btravstack/di": "^0.44.0",
85
+ "@prisma/orm-postgres": "^8.0.0-rc.18",
85
86
  "unthrown": "^5.0.0"
86
87
  },
87
88
  "engines": {