@prisma-next/framework-components 0.7.0 → 0.8.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.
@@ -58,6 +58,27 @@ export interface RuntimeMiddlewareContext {
58
58
  * cancels.
59
59
  */
60
60
  readonly signal?: AbortSignal;
61
+ /**
62
+ * Identifies the queryable scope this execution is running under.
63
+ *
64
+ * - `'runtime'` — top-level `runtime.execute(plan)`. The default scope
65
+ * used by the standard read/write paths.
66
+ * - `'connection'` — `connection.execute(plan)` after
67
+ * `runtime.connection()` checked out a connection from the pool.
68
+ * - `'transaction'` — `transaction.execute(plan)` inside an explicit
69
+ * transaction, or a query routed through `withTransaction`.
70
+ *
71
+ * Middleware that should only act at the top level read this field to
72
+ * bypass non-runtime scopes. The cache middleware uses it to skip
73
+ * caching inside transactions (where read-after-write coherence is the
74
+ * caller's expectation) and dedicated connections (where the user has
75
+ * explicitly stepped outside the shared cache surface). Observers that
76
+ * don't care about the scope can ignore the field.
77
+ *
78
+ * Family runtimes populate this at context-construction time per
79
+ * scope. Existing middleware that ignore the field are unaffected.
80
+ */
81
+ readonly scope: 'runtime' | 'connection' | 'transaction';
61
82
  }
62
83
 
63
84
  export interface AfterExecuteResult {
@@ -201,6 +222,33 @@ export interface RuntimeMiddleware<
201
222
  ): Promise<void>;
202
223
  }
203
224
 
225
+ /**
226
+ * Cross-family middleware — one that doesn't constrain `familyId` or
227
+ * `targetId` and is therefore compatible with any family runtime's
228
+ * middleware array (`SqlMiddleware[]`, `MongoMiddleware[]`, etc.).
229
+ *
230
+ * The intersection `RuntimeMiddleware & { familyId?: undefined; targetId?: undefined }`
231
+ * pins both optional properties to exactly `undefined` (intersecting
232
+ * `string | undefined` with `undefined` collapses to `undefined`). Under
233
+ * `exactOptionalPropertyTypes: true`, the plain `RuntimeMiddleware` shape
234
+ * — with `familyId?: string` — is *not* assignable to `SqlMiddleware`
235
+ * (which narrows `familyId?: 'sql'`) because `string` is wider than
236
+ * `'sql'`. Pinning the property to `undefined` makes the value a subtype
237
+ * of every narrowed variant: `undefined` extends both `'sql' | undefined`
238
+ * and `'mongo' | undefined`, so a `CrossFamilyMiddleware` value drops
239
+ * into a SQL or Mongo middleware slot without a cast.
240
+ *
241
+ * Cross-family middleware factories (`createCacheMiddleware`, future
242
+ * `audit` / OTel middleware) declare this as their return type so the
243
+ * cross-family typing is named once rather than re-spelled at every call
244
+ * site.
245
+ */
246
+ export type CrossFamilyMiddleware<TPlan extends QueryPlan = QueryPlan> =
247
+ RuntimeMiddleware<TPlan> & {
248
+ readonly familyId?: undefined;
249
+ readonly targetId?: undefined;
250
+ };
251
+
204
252
  /**
205
253
  * Optional per-`execute` options accepted by every family runtime.
206
254
  *
@@ -212,6 +260,7 @@ export interface RuntimeMiddleware<
212
260
  */
213
261
  export interface RuntimeExecuteOptions {
214
262
  readonly signal?: AbortSignal;
263
+ readonly scope?: 'runtime' | 'connection' | 'transaction';
215
264
  }
216
265
 
217
266
  /**
@@ -1,3 +1,11 @@
1
+ export type {
2
+ AnnotationHandle,
3
+ AnnotationValue,
4
+ DefineAnnotationOptions,
5
+ OperationKind,
6
+ ValidAnnotations,
7
+ } from '../annotations';
8
+ export { assertAnnotationsApplicable, defineAnnotation } from '../annotations';
1
9
  export { AsyncIterableResult } from '../execution/async-iterable-result';
2
10
  export { runBeforeExecuteChain } from '../execution/before-execute-chain';
3
11
  export type { ExecutionPlan, QueryPlan, ResultType } from '../execution/query-plan';
@@ -14,6 +22,7 @@ export {
14
22
  } from '../execution/runtime-error';
15
23
  export type {
16
24
  AfterExecuteResult,
25
+ CrossFamilyMiddleware,
17
26
  InterceptResult,
18
27
  ParamRefMutator,
19
28
  RuntimeExecuteOptions,
@@ -23,3 +32,5 @@ export type {
23
32
  RuntimeMiddlewareContext,
24
33
  } from '../execution/runtime-middleware';
25
34
  export { checkMiddlewareCompatibility } from '../execution/runtime-middleware';
35
+ export type { LaneMetaBuilder, MetaBuilder } from '../meta-builder';
36
+ export { createMetaBuilder } from '../meta-builder';
@@ -0,0 +1,101 @@
1
+ import {
2
+ type AnnotationValue,
3
+ assertAnnotationsApplicable,
4
+ type OperationKind,
5
+ } from './annotations';
6
+
7
+ /**
8
+ * Per-terminal meta configurator handed to user callbacks. The terminal's
9
+ * operation kind `K` is fixed by the terminal that constructed the builder;
10
+ * `annotate(...)` accepts only annotations whose declared `Kinds` include
11
+ * `K`.
12
+ *
13
+ * The conditional parameter type
14
+ * `K extends Kinds ? AnnotationValue<P, Kinds> : never` collapses to `never`
15
+ * for inapplicable annotations, surfacing the mismatch as a type error at
16
+ * the call site of `meta.annotate(...)`. No variadic-tuple inference is
17
+ * involved — TypeScript infers `Kinds` from the annotation argument and
18
+ * checks the conditional directly.
19
+ *
20
+ * The runtime gate inside `annotate` (via
21
+ * `assertAnnotationsApplicable`) catches cast / `any` / dynamic bypasses
22
+ * and throws `RUNTIME.ANNOTATION_INAPPLICABLE`.
23
+ *
24
+ * `annotate` returns the builder for chaining; the return value of the
25
+ * configurator callback is unused, so both block-body and expression-body
26
+ * callbacks compile.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * await db.User.find({ id }, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
31
+ * await db.User.create(input, (meta) => {
32
+ * meta.annotate(auditAnnotation({ actor: 'system' }));
33
+ * meta.annotate(otelAnnotation({ traceId }));
34
+ * });
35
+ * ```
36
+ */
37
+ export interface MetaBuilder<K extends OperationKind> {
38
+ annotate<P, Kinds extends OperationKind>(
39
+ annotation: K extends Kinds ? AnnotationValue<P, Kinds> : never,
40
+ ): this;
41
+ }
42
+
43
+ /**
44
+ * Lane-side view of a meta builder. Extends the public `MetaBuilder<K>`
45
+ * surface with `annotations` so lane terminals can read the recorded map
46
+ * after invoking the user configurator.
47
+ *
48
+ * Lane terminals construct one of these via `createMetaBuilder(kind, terminalName)`,
49
+ * pass it to the user callback as `MetaBuilder<K>` (the narrower public
50
+ * view), then read `meta.annotations` to thread the recorded values into
51
+ * `plan.meta.annotations`.
52
+ */
53
+ export interface LaneMetaBuilder<K extends OperationKind> extends MetaBuilder<K> {
54
+ readonly annotations: ReadonlyMap<string, AnnotationValue<unknown, OperationKind>>;
55
+ }
56
+
57
+ class MetaBuilderImpl<K extends OperationKind> implements LaneMetaBuilder<K> {
58
+ readonly #kind: K;
59
+ readonly #terminalName: string;
60
+ readonly #annotations = new Map<string, AnnotationValue<unknown, OperationKind>>();
61
+
62
+ constructor(kind: K, terminalName: string) {
63
+ this.#kind = kind;
64
+ this.#terminalName = terminalName;
65
+ }
66
+
67
+ get annotations(): ReadonlyMap<string, AnnotationValue<unknown, OperationKind>> {
68
+ return this.#annotations;
69
+ }
70
+
71
+ annotate<P, Kinds extends OperationKind>(
72
+ annotation: K extends Kinds ? AnnotationValue<P, Kinds> : never,
73
+ ): this {
74
+ // Inside the body, the conditional `K extends Kinds ? AnnotationValue<P, Kinds> : never`
75
+ // is opaque to TypeScript — it can't pick a branch without a concrete
76
+ // K. Widen to the structural shape so we can call into the runtime
77
+ // gate. The runtime gate (assertAnnotationsApplicable) is what
78
+ // catches cast bypasses where the conditional would have resolved to
79
+ // `never` had the type checker been allowed to specialise.
80
+ const value = annotation as AnnotationValue<unknown, OperationKind>;
81
+ assertAnnotationsApplicable([value], this.#kind, this.#terminalName);
82
+ this.#annotations.set(value.namespace, value);
83
+ return this;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Construct a lane-side meta builder for a terminal of operation kind `K`.
89
+ *
90
+ * Lane terminals call this with their `kind` (`'read'` or `'write'`) and a
91
+ * `terminalName` for error messages, hand the resulting builder to the
92
+ * user-supplied configurator callback (typed as `MetaBuilder<K>`, the
93
+ * narrower public view), and read `meta.annotations` afterwards to thread
94
+ * the recorded values into `plan.meta.annotations`.
95
+ */
96
+ export function createMetaBuilder<K extends OperationKind>(
97
+ kind: K,
98
+ terminalName: string,
99
+ ): LaneMetaBuilder<K> {
100
+ return new MetaBuilderImpl(kind, terminalName);
101
+ }