Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds a per-execute CodecCallContext (with optional AbortSignal), threads it through codec encode/decode and lowering paths, extends runtime.execute to accept RuntimeExecuteOptions (signal), implements abort/error envelopes (RUNTIME.ABORTED) and raceAgainstAbort, and adds tests, ADRs, and docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Runtime
participant Codec
participant Driver
Client->>Runtime: execute(plan, { signal })
alt signal.aborted at entry
Runtime->>Client: Reject RUNTIME.ABORTED (phase: "stream")
else
Runtime->>Runtime: create CodecCallContext(signal)
Runtime->>Codec: encode(param, ctx)
Codec-->>Runtime: encoded param
Runtime->>Driver: execute(query)
Driver-->>Runtime: yields rows
loop per row
Runtime->>Runtime: check signal
Runtime->>Codec: decode(wireRow, ctx)
alt signal fires during decode
Runtime->>Client: Reject RUNTIME.ABORTED (phase: "decode")
else
Runtime->>Client: yield row
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/middleware-telemetry
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
docs/reference/framework-gaps.md (1)
445-446: ⚡ Quick winAvoid volatile “latest stable X.Y.Z” wording in durable docs
The “latest stable
0.4.2” claim will stale quickly. Prefer either a dated snapshot (“as of 2026-04-29”) or a link to npm dist-tags/version history.As per coding guidelines: “Keep docs current (READMEs, rules, links) as defined in
.cursor/rules/doc-maintenance.mdc.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/framework-gaps.md` around lines 445 - 446, Replace the volatile phrase "latest stable `0.4.2`" in the paragraph (the quoted text containing “latest stable `0.4.2`”) with a durable alternative: either annotate it as a dated snapshot (e.g., "version 0.4.2 (as of 2026-04-29)") or replace it with a link to the npm dist-tags/version history for the `@prisma-next` packages; also add a short adherence note referencing the doc-maintenance guideline ".cursor/rules/doc-maintenance.mdc" to signal how to keep this claim updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/reference/framework-gaps.md`:
- Around line 417-434: Update the G10 gap text to reflect that cancellation
tokens are now present but not fully propagated: replace the sentence claiming
the codec interface lacks a cancellation token with wording that references
CodecCallContext.signal and the execute(..., { signal }) API and states the
remaining limitation is that the signal is not yet fully threaded through all
runtime dispatch paths (e.g., some codec runtime call-sites or dispatch layers
still ignore or fail to forward the signal). Keep the rest of the rationale and
recommended solution (Expose AbortSignal on EncodeContext/DecodeContext and
forward to SDK calls) but emphasize “not yet fully threaded through all runtime
dispatch paths” as the current gap.
- Around line 613-621: The fenced code block containing the package tree that
starts with "@prisma-next/extension-template" is missing a language tag and
triggers MD040; update that block by adding a language hint (e.g., add "text"
after the opening triple backticks) so the tree layout is treated as plain text,
leaving the contents (the src/ tree, tsup.config.ts and package.json lines)
unchanged.
- Around line 3-7: The two companion links at the top
(`[./system-design-review.md](./system-design-review.md)`,
`[./code-review.md](./code-review.md)` and
`[./walkthrough.md](./walkthrough.md)`) are currently wrapped in backticks and
render as code; remove the surrounding backticks so they become normal Markdown
links (e.g., replace the inline code tokens around those link tokens in
docs/reference/framework-gaps.md) to make them clickable and follow the
doc-maintenance guideline.
In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts`:
- Around line 79-82: The current runtimeAborted function overwrites explicit
falsy causes (null, false, 0, '') by using the nullish coalescing expression
cause ?? new DOMException(...); change the resolution logic so only an undefined
cause is replaced with the synthetic AbortError (e.g., test cause === undefined)
and preserve any other explicit falsy values; update the resolvedCause
assignment in runtimeAborted (which constructs the envelope via runtimeError and
RUNTIME_ABORTED) to use this undefined-only check so real cancellation details
are not lost.
In
`@packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts`:
- Around line 96-111: The test currently only asserts the thrown value has a
matching cause; update it to also assert the error is the runtime abort error by
checking isRuntimeError(observed) and that observed.code === RUNTIME.ABORTED (in
the same branch where you check observed.cause). Ensure you reference the same
RUNTIME constant used elsewhere in the tests and keep the existing
observed.cause assertion so the test both pins the error class via
isRuntimeError/observed.code and validates the cause.
In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts`:
- Around line 69-74: Update the author-facing function type signatures so the
second `ctx` parameter is optional: change the two-argument overloads for
`encode` and `decode` in `encode?` / `decode` definitions to accept `ctx?:
CodecCallContext` instead of `ctx: CodecCallContext`, and do the same for the
other occurrences referenced around lines 86-113 (the other encode/decode
overload blocks and any related callback type aliases) so callers and
implementations can safely be invoked with `ctx === undefined`.
In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts`:
- Around line 287-296: The current identity fallback for config.encode (assigned
to userEncode) can lie about TWire; change the fallback so identity is only used
when TWire and TInput are actually interchangeable and otherwise require/throw:
update the userEncode definition (and related CtxEncode/CtxDecode typing) to use
a conditional type or overload that yields the identity function only when TWire
extends TInput, and for the non-interchangeable case set the fallback to a
runtime-throwing stub (e.g., a function that throws "encode not implemented for
differing TWire") so callers cannot accidentally get the wrong wire shape; keep
references to CtxEncode, CtxDecode, userEncode, config.encode and ensure
userDecode remains typed as before.
---
Nitpick comments:
In `@docs/reference/framework-gaps.md`:
- Around line 445-446: Replace the volatile phrase "latest stable `0.4.2`" in
the paragraph (the quoted text containing “latest stable `0.4.2`”) with a
durable alternative: either annotate it as a dated snapshot (e.g., "version
0.4.2 (as of 2026-04-29)") or replace it with a link to the npm
dist-tags/version history for the `@prisma-next` packages; also add a short
adherence note referencing the doc-maintenance guideline
".cursor/rules/doc-maintenance.mdc" to signal how to keep this claim updated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 35e26dff-b324-40ec-b427-6bca07c64bf0
⛔ Files ignored due to path filters (2)
projects/codec-call-context/plan.mdis excluded by!projects/**projects/codec-call-context/spec.mdis excluded by!projects/**
📒 Files selected for processing (17)
docs/reference/framework-gaps.mdpackages/1-framework/1-core/framework-components/src/codec-types.tspackages/1-framework/1-core/framework-components/src/exports/codec.tspackages/1-framework/1-core/framework-components/src/exports/runtime.tspackages/1-framework/1-core/framework-components/src/runtime-core.tspackages/1-framework/1-core/framework-components/src/runtime-error.tspackages/1-framework/1-core/framework-components/src/runtime-middleware.tspackages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.tspackages/1-framework/1-core/framework-components/test/runtime-aborted.test.tspackages/1-framework/1-core/framework-components/test/runtime-core-options.test.tspackages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.tspackages/2-mongo-family/1-foundation/mongo-codec/src/codecs.tspackages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.tspackages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.tspackages/2-sql/4-lanes/relational-core/src/ast/codec-types.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts (1)
123-151: ⚡ Quick winAdd
manifestto the stub control-plane descriptors.
targetDescriptorandadapterDescriptorare both descriptor fixtures, but neither includesmanifest. That makes the test shape drift from the repo’s required control-plane descriptor contract.As per coding guidelines, "All control plane descriptors must include
manifestproperty of typeExtensionPackManifest".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts` around lines 123 - 151, The test descriptors targetDescriptor and adapterDescriptor are missing the required manifest property; update both objects to include a manifest: ExtensionPackManifest (matching the control-plane contract) — e.g., add a manifest field to targetDescriptor and adapterDescriptor with a minimal valid ExtensionPackManifest object (name/version and any required fields) so the fixtures conform to the expected descriptor shape used by the control plane.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/2-sql/5-runtime/src/codecs/race-against-abort.ts`:
- Around line 36-42: The abort listener registration in the abortPromise may
miss already-aborted signals; before calling signal.addEventListener in the
abortPromise factory (and before creating onAbort), check signal.aborted and if
true immediately set sentinel.reason and reject with sentinel, otherwise proceed
to create onAbort and register it; ensure you still use { once: true } and keep
the onAbort/sentinel logic unchanged so AbortSignal.aborted short-circuits
synchronous aborts for the abortPromise used in raceAgainstAbort.
In `@packages/2-sql/5-runtime/src/sql-runtime.ts`:
- Around line 309-315: The current for-await loop lets the runtime call
stream.next() before the between-row abort check, so change the loop to manually
drive the async iterator from stream (use const iterator =
stream[Symbol.asyncIterator]() and an explicit while loop) and check
signal?.aborted and throw runtimeAborted('stream', signal.reason) before each
await iterator.next(); after awaiting, handle done/value as before and process
rawRow. This ensures the abort is observed before requesting the next row while
preserving existing handling of rawRow and errors.
---
Nitpick comments:
In `@packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts`:
- Around line 123-151: The test descriptors targetDescriptor and
adapterDescriptor are missing the required manifest property; update both
objects to include a manifest: ExtensionPackManifest (matching the control-plane
contract) — e.g., add a manifest field to targetDescriptor and adapterDescriptor
with a minimal valid ExtensionPackManifest object (name/version and any required
fields) so the fixtures conform to the expected descriptor shape used by the
control plane.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: f3d18d9e-30a1-4680-9dbc-3483328f0542
📒 Files selected for processing (8)
packages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/2-sql/5-runtime/src/codecs/race-against-abort.tspackages/2-sql/5-runtime/src/sql-runtime.tspackages/2-sql/5-runtime/test/codec-decode-ctx.test.tspackages/2-sql/5-runtime/test/codec-encode-ctx.test.tspackages/2-sql/5-runtime/test/sql-runtime-abort.test.tstest/integration/test/sql-builder/execution-abort.test.ts
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
packages/1-framework/1-core/framework-components/src/race-against-abort.ts (1)
41-47:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle already-aborted signals before installing the listener.
If
signalwas aborted whileworkwas being created, this listener will never fire retroactively, soPromise.race()falls through toworkinstead of surfacingRUNTIME.ABORTED. That path is reachable from callers likeresolveValue(), which buildPromise.all(...)before invoking this helper.Suggested fix
const abortPromise = new Promise<never>((_, reject) => { + if (signal.aborted) { + sentinel.reason = signal.reason; + reject(sentinel); + return; + } + onAbort = () => { sentinel.reason = signal.reason; reject(sentinel); }; signal.addEventListener('abort', onAbort, { once: true }); });If an AbortSignal is already aborted, does adding an 'abort' event listener later invoke it immediately, or must code check signal.aborted first?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts` around lines 41 - 47, The abort listener must handle the case where signal is already aborted before the listener is attached: inside the Promise constructor that creates abortPromise (the block that declares onAbort, sentinel, and calls signal.addEventListener), first check if signal.aborted and if so set sentinel.reason = signal.reason and immediately reject(sentinel); otherwise register the onAbort listener as before (signal.addEventListener('abort', onAbort, { once: true })). This ensures abortPromise rejects immediately when signal.aborted is true instead of relying on a retroactive event.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts`:
- Around line 83-89: The test's use of countAbortListeners() is ineffective
because it only increments on actual abort; replace that check by spying on the
signal's event listener registration/removal: wrap or spy on
controller.signal.addEventListener and controller.signal.removeEventListener (or
capture the callback passed to addEventListener) when calling
raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode') and assert
that addEventListener was called once and removeEventListener was called with
the same callback (or that the captured callback is no longer present), and
apply the same change to the other related test block referenced (lines 110-126)
to verify listeners are actually removed.
In `@packages/2-sql/5-runtime/src/codecs/decoding.ts`:
- Around line 206-210: The current construction of cellCtx can propagate a stale
column when ref is undefined because it returns rowCtx unchanged; update the
logic that builds cellCtx (the SqlCodecCallContext creation using rowCtx and
ref) so that when ref is falsy you return a copy/clone of rowCtx with the column
property explicitly set to undefined (or omitted) instead of returning rowCtx
as-is; modify the branch that currently does `: rowCtx` to produce `{ ...rowCtx,
column: undefined }` (or equivalent) so codecs never see an inherited column for
unresolved aliases.
In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts`:
- Around line 53-59: The codec.encode call in the MongoParamRef branch is
awaited directly and thus ignores mid-flight aborts; update the branch handling
MongoParamRef (where it checks value.codecId, codecs, and calls codec.encode) to
make the encode call abort-aware by racing the codec.encode promise against the
ctx.signal abort (or using a small helper that listens to ctx.signal) and, if
the signal is aborted, immediately reject/throw the RUNTIME.ABORTED error so the
abort surfaces to callers (this change should be applied in the same branch used
by MongoAdapterImpl.#resolveDocument that handles bare MongoParamRef values).
---
Duplicate comments:
In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`:
- Around line 41-47: The abort listener must handle the case where signal is
already aborted before the listener is attached: inside the Promise constructor
that creates abortPromise (the block that declares onAbort, sentinel, and calls
signal.addEventListener), first check if signal.aborted and if so set
sentinel.reason = signal.reason and immediately reject(sentinel); otherwise
register the onAbort listener as before (signal.addEventListener('abort',
onAbort, { once: true })). This ensures abortPromise rejects immediately when
signal.aborted is true instead of relying on a retroactive event.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: b14efbbf-4757-44cd-a83a-e20ded649935
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/codec-call-context/plan.mdis excluded by!projects/**
📒 Files selected for processing (15)
packages/1-framework/1-core/framework-components/src/exports/runtime.tspackages/1-framework/1-core/framework-components/src/race-against-abort.tspackages/1-framework/1-core/framework-components/test/race-against-abort.test.tspackages/2-mongo-family/6-transport/mongo-lowering/package.jsonpackages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.tspackages/2-mongo-family/7-runtime/src/mongo-runtime.tspackages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.tspackages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/3-mongo-target/2-mongo-adapter/src/lowering.tspackages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.tspackages/3-mongo-target/2-mongo-adapter/src/resolve-value.tspackages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.tspackages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.tstest/integration/test/mongo/execution-abort.test.ts
✅ Files skipped from review due to trivial changes (2)
- packages/2-mongo-family/6-transport/mongo-lowering/package.json
- packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.ts
| if (value instanceof MongoParamRef) { | ||
| if (value.codecId && codecs) { | ||
| const codec = codecs.get(value.codecId); | ||
| if (codec?.encode) { | ||
| try { | ||
| return await codec.encode(value.value); | ||
| return await codec.encode(value.value, ctx); | ||
| } catch (error) { |
There was a problem hiding this comment.
Top-level scalar encodes still ignore mid-flight aborts.
This branch awaits codec.encode() directly. When the lowered value is a bare MongoParamRef—for example a simple field filter or any leaf reached from MongoAdapterImpl.#resolveDocument()—there is no surrounding raced Promise.all, so an abort during codec.encode() waits for the codec to finish instead of surfacing RUNTIME.ABORTED.
Suggested fix
if (codec?.encode) {
try {
- return await codec.encode(value.value, ctx);
+ const encoded = codec.encode(value.value, ctx);
+ return signal ? await raceAgainstAbort(encoded, signal, 'encode') : await encoded;
} catch (error) {
wrapEncodeFailure(error, value, codec.id);
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts` around lines 53
- 59, The codec.encode call in the MongoParamRef branch is awaited directly and
thus ignores mid-flight aborts; update the branch handling MongoParamRef (where
it checks value.codecId, codecs, and calls codec.encode) to make the encode call
abort-aware by racing the codec.encode promise against the ctx.signal abort (or
using a small helper that listens to ctx.signal) and, if the signal is aborted,
immediately reject/throw the RUNTIME.ABORTED error so the abort surfaces to
callers (this change should be applied in the same branch used by
MongoAdapterImpl.#resolveDocument that handles bare MongoParamRef values).
4568fe7 to
89c30d6
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (10)
packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts (2)
69-74:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake the author-facing
ctxparameter optional.
mongoCodec()can call these two-arg callbacks withctx === undefinedwhen no signal is supplied, so the signatures needctx?: CodecCallContext.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts` around lines 69 - 74, The encode/decode function types in codecs.ts currently require a second parameter ctx: CodecCallContext but mongoCodec can invoke them with ctx === undefined, so update both union variants for encode and decode to accept an optional second parameter (ctx?: CodecCallContext) instead of a required one; modify the signatures that reference encode and decode in the codec type (the two-arg overloads) to use ctx? so callers that omit a signal are correctly typed.
88-90:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon't let the identity fallback lie about
TWire.Omitting
encodestill returns the input value verbatim, so a specializedTWirecan compile while emitting the wrong wire shape.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts` around lines 88 - 90, The current identity fallback for userEncode (using (config.encode as CtxEncode | undefined) ?? ((value: TInput) => value as unknown as TWire)) falsely promises it produces TWire; remove that identity fallback and treat encode as optional so callers must handle absence or provide a real encoder. Change userEncode to obtain only (config.encode as CtxEncode | undefined) (no default function) and update any code that calls userEncode to check for undefined or to use a safe conversion path; likewise ensure userDecode comes from (config.decode as CtxDecode | undefined) if symmetrical handling is needed. This prevents the compile-time lie about TWire and ensures correct runtime wire shape handling.packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts (2)
271-276:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake the SQL author-facing
ctxparameter optional.The two-arg
codec()overloads can be called without a signal, so these author signatures needctx?: SqlCodecCallContext.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` around lines 271 - 276, The encode/decode two-argument function signatures should make the ctx parameter optional so author-facing overloads accept calls without a context; update the types in codec-related definitions (the encode?: and decode: union branches) to use (value: TInput, ctx?: SqlCodecCallContext) => ... and (wire: TWire, ctx?: SqlCodecCallContext) => ... respectively, ensuring all codec() overloads that accept a ctx match this optional signature.
294-296:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon't let the SQL identity fallback lie about
TWire.The default
encodestill returnsvalueunchanged, so a codec with a specializedTWirecan compile while producing the wrong runtime shape.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` around lines 294 - 296, The current fallback makes userEncode default to an identity function ((value: TInput) => value as unknown as TWire) which misrepresents TWire when it differs; change the fallback so that userEncode is not silently the identity for mismatched TWire—either require config.encode be provided for codecs where TWire !== TInput (i.e., remove the unconditional identity fallback) or replace the fallback with a runtime guard that throws a clear error when encode is missing and TWire differs; update the code around userEncode, CtxEncode, config.encode (and leave userDecode/config.decode handling intact) so codecs cannot compile with a misleading identity encode for a specialized TWire.packages/1-framework/1-core/framework-components/src/race-against-abort.ts (1)
41-47:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard already-aborted signals inside the helper.
AbortSignaldoes not replay abort events to late listeners, so this race can hang if the signal is already aborted when the helper is called. Checksignal.abortedbefore registering the listener and reject immediately.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts` around lines 41 - 47, The helper currently always registers an abort listener which can hang if the AbortSignal is already aborted; update the abortPromise creation (and the onAbort/sentinel logic) to first check signal.aborted and, if true, set sentinel.reason = signal.reason and immediately reject(sentinel), otherwise create onAbort and call signal.addEventListener('abort', onAbort, { once: true }); ensure the code path that rejects uses the same sentinel object so downstream comparisons still work.docs/reference/framework-gaps.md (2)
619-627:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a fence language.
The package-tree block still trips MD040;
textis enough here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/framework-gaps.md` around lines 619 - 627, The fenced package-tree block starting with "@prisma-next/extension-template" is missing a fence language which triggers MD040; update that block to use a language tag (e.g., add "text" after the opening triple backticks) so the code fence is explicitly annotated and the linter stops flagging it.
3-7:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUnwrap the Markdown links.
The backticks make the companion links render as code instead of clickable links.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/framework-gaps.md` around lines 3 - 7, The Markdown links in the opening paragraph are wrapped in backticks so they render as inline code rather than clickable links; update the text by removing the backticks around `[./system-design-review.md](./system-design-review.md)`, `[./code-review.md](./code-review.md)`, and `[./walkthrough.md](./walkthrough.md)` (and any other backticked link tokens in that paragraph) so they are plain Markdown links, verify they render as clickable links in the resulting HTML, and keep the surrounding punctuation and capitalization intact.packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts (1)
83-89:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAssert listener removal directly.
countAbortListeners()always returns0, so this can pass even if the abort listener leaks. Spy onaddEventListener/removeEventListeneror capture the callback and verify it is detached.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts` around lines 83 - 89, The test currently relies on countAbortListeners() which always returns 0 and can hide listener leaks; update the test for raceAgainstAbort to spy on the signal's addEventListener/removeEventListener (or stub capture the actual callback passed) and assert that removeEventListener is called with the same callback when the raced promise resolves; specifically, wrap the AbortController.signal, spy on its addEventListener/removeEventListener methods or replace them with instrumented functions in the test, call raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode'), and verify removeEventListener was invoked (with the previously captured callback) to ensure the listener is detached.packages/1-framework/1-core/framework-components/src/runtime-error.ts (1)
79-82:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPreserve explicit falsy abort reasons.
cause ?? ...replacesnull,false,0, and''with a syntheticAbortError, so explicit cancellation detail gets lost.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts` around lines 79 - 82, The runtimeAborted function currently uses the nullish coalescing operator to set resolvedCause which incorrectly replaces explicit falsy values (null, false, 0, ''), so change the fallback logic to only substitute when cause is strictly undefined (e.g., if cause === undefined then use new DOMException(...), otherwise keep cause) in runtimeAborted; update the resolvedCause assignment in that function (and ensure the returned envelope still attaches { cause: resolvedCause } as before), referencing runtimeAborted, resolvedCause, runtimeError, and RUNTIME_ABORTED to locate the change.packages/2-sql/5-runtime/src/codecs/decoding.ts (1)
200-204:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClear
columnwhenrefis missing.Returning
rowCtxunchanged still leaks any inheritedcolumninto aggregate/computed cells, so the new “column undefined” contract is violated on reused contexts.Suggested fix
const cellCtx: SqlCodecCallContext | undefined = rowCtx ? ref ? { ...rowCtx, column: { table: ref.table, name: ref.column } } - : rowCtx + : { ...rowCtx, column: undefined } : undefined;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-sql/5-runtime/src/codecs/decoding.ts` around lines 200 - 204, The current creation of cellCtx can leak an existing rowCtx.column when ref is falsy; change the logic so that when rowCtx exists but ref is missing you explicitly clear column (e.g., produce { ...rowCtx, column: undefined }) instead of returning rowCtx unchanged; update the expression that builds cellCtx (referencing cellCtx, SqlCodecCallContext, rowCtx, ref, column) so both branches explicitly set column (either the {table,name} object when ref is present or undefined when not).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/2-sql/5-runtime/src/codecs/encoding.ts`:
- Around line 122-129: The zero-parameter fast-return in encodeParams skips
cancellation checks; move the abort check (inspect ctx?.signal and throw
runtimeAborted('encode', signal.reason) if signal.aborted) above the early
return for plan.params.length === 0 so an already-aborted context yields
RUNTIME.ABORTED; update the code paths in encodeParams to reference the same
signal logic (ctx?.signal and runtimeAborted) before returning plan.params.
In `@packages/2-sql/5-runtime/test/codec-decode-ctx.test.ts`:
- Around line 142-196: The tests currently call decodeRow with undefined for the
reusable context, so they don't catch a regression where a previously-populated
SqlCodecCallContext.column leaks into unresolved projections; create a reusable
SqlCodecCallContext object (e.g., assign a dummy column value) and pass it as
the fourth argument to decodeRow in both tests (the calls around decodeRow(...,
undefined, { signal })). This seeds the context with a stale column value so the
assertions that ctx.column is cleared (observed?.column toBeUndefined) will fail
if the runtime doesn't reset column for AggregateExpr and non-column-ref
ProjectionItem cases; update both test cases to use the seeded context.
In `@packages/2-sql/5-runtime/test/codec-encode-ctx.test.ts`:
- Around line 141-171: Add a regression test that calls encodeParams with an
empty plan to ensure an already-aborted signal short-circuits even when there
are zero parameters; specifically, create an AbortController, call
controller.abort(reason), then call encodeParams([], registry, { signal:
controller.signal }) (using the same createCodecRegistry()/registry setup used
elsewhere) and assert it rejects with code 'RUNTIME.ABORTED' and details.phase
=== 'encode' and that no codec encode functions (e.g., those registered via
codec(...) and registry.register(...)) are invoked—this mirrors the existing
"already-aborted" test but uses buildPlan([]) / an empty params array to cover
the zero-parameter early-return path.
In `@packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts`:
- Around line 37-39: The test currently asserts deep equality with
expect(observed).toEqual([ctx]) which allows a cloned context to pass; update
the assertion to verify identity preservation by asserting the observed entry is
the exact same CodecCallContext instance produced earlier (e.g., replace the
deep-equality check with an assertion that observed[0] is the same object as ctx
using toBe, optionally also asserting observed.length === 1). This change should
be applied in the resolveValue test where MongoParamRef('hello', { codecId:
'test/observe@1' }), registry, ctx and the observed array are used.
---
Duplicate comments:
In `@docs/reference/framework-gaps.md`:
- Around line 619-627: The fenced package-tree block starting with
"@prisma-next/extension-template" is missing a fence language which triggers
MD040; update that block to use a language tag (e.g., add "text" after the
opening triple backticks) so the code fence is explicitly annotated and the
linter stops flagging it.
- Around line 3-7: The Markdown links in the opening paragraph are wrapped in
backticks so they render as inline code rather than clickable links; update the
text by removing the backticks around
`[./system-design-review.md](./system-design-review.md)`,
`[./code-review.md](./code-review.md)`, and
`[./walkthrough.md](./walkthrough.md)` (and any other backticked link tokens in
that paragraph) so they are plain Markdown links, verify they render as
clickable links in the resulting HTML, and keep the surrounding punctuation and
capitalization intact.
In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`:
- Around line 41-47: The helper currently always registers an abort listener
which can hang if the AbortSignal is already aborted; update the abortPromise
creation (and the onAbort/sentinel logic) to first check signal.aborted and, if
true, set sentinel.reason = signal.reason and immediately reject(sentinel),
otherwise create onAbort and call signal.addEventListener('abort', onAbort, {
once: true }); ensure the code path that rejects uses the same sentinel object
so downstream comparisons still work.
In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts`:
- Around line 79-82: The runtimeAborted function currently uses the nullish
coalescing operator to set resolvedCause which incorrectly replaces explicit
falsy values (null, false, 0, ''), so change the fallback logic to only
substitute when cause is strictly undefined (e.g., if cause === undefined then
use new DOMException(...), otherwise keep cause) in runtimeAborted; update the
resolvedCause assignment in that function (and ensure the returned envelope
still attaches { cause: resolvedCause } as before), referencing runtimeAborted,
resolvedCause, runtimeError, and RUNTIME_ABORTED to locate the change.
In
`@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts`:
- Around line 83-89: The test currently relies on countAbortListeners() which
always returns 0 and can hide listener leaks; update the test for
raceAgainstAbort to spy on the signal's addEventListener/removeEventListener (or
stub capture the actual callback passed) and assert that removeEventListener is
called with the same callback when the raced promise resolves; specifically,
wrap the AbortController.signal, spy on its addEventListener/removeEventListener
methods or replace them with instrumented functions in the test, call
raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode'), and verify
removeEventListener was invoked (with the previously captured callback) to
ensure the listener is detached.
In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts`:
- Around line 69-74: The encode/decode function types in codecs.ts currently
require a second parameter ctx: CodecCallContext but mongoCodec can invoke them
with ctx === undefined, so update both union variants for encode and decode to
accept an optional second parameter (ctx?: CodecCallContext) instead of a
required one; modify the signatures that reference encode and decode in the
codec type (the two-arg overloads) to use ctx? so callers that omit a signal are
correctly typed.
- Around line 88-90: The current identity fallback for userEncode (using
(config.encode as CtxEncode | undefined) ?? ((value: TInput) => value as unknown
as TWire)) falsely promises it produces TWire; remove that identity fallback and
treat encode as optional so callers must handle absence or provide a real
encoder. Change userEncode to obtain only (config.encode as CtxEncode |
undefined) (no default function) and update any code that calls userEncode to
check for undefined or to use a safe conversion path; likewise ensure userDecode
comes from (config.decode as CtxDecode | undefined) if symmetrical handling is
needed. This prevents the compile-time lie about TWire and ensures correct
runtime wire shape handling.
In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts`:
- Around line 271-276: The encode/decode two-argument function signatures should
make the ctx parameter optional so author-facing overloads accept calls without
a context; update the types in codec-related definitions (the encode?: and
decode: union branches) to use (value: TInput, ctx?: SqlCodecCallContext) => ...
and (wire: TWire, ctx?: SqlCodecCallContext) => ... respectively, ensuring all
codec() overloads that accept a ctx match this optional signature.
- Around line 294-296: The current fallback makes userEncode default to an
identity function ((value: TInput) => value as unknown as TWire) which
misrepresents TWire when it differs; change the fallback so that userEncode is
not silently the identity for mismatched TWire—either require config.encode be
provided for codecs where TWire !== TInput (i.e., remove the unconditional
identity fallback) or replace the fallback with a runtime guard that throws a
clear error when encode is missing and TWire differs; update the code around
userEncode, CtxEncode, config.encode (and leave userDecode/config.decode
handling intact) so codecs cannot compile with a misleading identity encode for
a specialized TWire.
In `@packages/2-sql/5-runtime/src/codecs/decoding.ts`:
- Around line 200-204: The current creation of cellCtx can leak an existing
rowCtx.column when ref is falsy; change the logic so that when rowCtx exists but
ref is missing you explicitly clear column (e.g., produce { ...rowCtx, column:
undefined }) instead of returning rowCtx unchanged; update the expression that
builds cellCtx (referencing cellCtx, SqlCodecCallContext, rowCtx, ref, column)
so both branches explicitly set column (either the {table,name} object when ref
is present or undefined when not).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 291f58ff-809b-4073-becc-3129ad430078
📒 Files selected for processing (39)
docs/architecture docs/adrs/ADR 204 - Single-Path Async Codec Runtime.mddocs/architecture docs/adrs/ADR 207 - Codec call context per-query AbortSignal and column metadata.mddocs/reference/framework-gaps.mdpackages/1-framework/1-core/framework-components/README.mdpackages/1-framework/1-core/framework-components/src/codec-types.tspackages/1-framework/1-core/framework-components/src/exports/codec.tspackages/1-framework/1-core/framework-components/src/exports/runtime.tspackages/1-framework/1-core/framework-components/src/race-against-abort.tspackages/1-framework/1-core/framework-components/src/runtime-core.tspackages/1-framework/1-core/framework-components/src/runtime-error.tspackages/1-framework/1-core/framework-components/src/runtime-middleware.tspackages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.tspackages/1-framework/1-core/framework-components/test/race-against-abort.test.tspackages/1-framework/1-core/framework-components/test/runtime-aborted.test.tspackages/1-framework/1-core/framework-components/test/runtime-core-options.test.tspackages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.tspackages/2-mongo-family/1-foundation/mongo-codec/README.mdpackages/2-mongo-family/1-foundation/mongo-codec/src/codecs.tspackages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.tspackages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.tspackages/2-mongo-family/6-transport/mongo-lowering/package.jsonpackages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.tspackages/2-mongo-family/7-runtime/src/mongo-runtime.tspackages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.tspackages/2-sql/4-lanes/relational-core/README.mdpackages/2-sql/4-lanes/relational-core/src/ast/codec-types.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.tspackages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/2-sql/5-runtime/src/sql-runtime.tspackages/2-sql/5-runtime/test/codec-decode-ctx.test.tspackages/2-sql/5-runtime/test/codec-encode-ctx.test.tspackages/2-sql/5-runtime/test/sql-runtime-abort.test.tspackages/3-mongo-target/2-mongo-adapter/src/lowering.tspackages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.tspackages/3-mongo-target/2-mongo-adapter/src/resolve-value.tspackages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.tspackages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts
✅ Files skipped from review due to trivial changes (7)
- packages/2-mongo-family/6-transport/mongo-lowering/package.json
- packages/1-framework/1-core/framework-components/src/exports/codec.ts
- packages/1-framework/1-core/framework-components/test/runtime-aborted.test.ts
- packages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.ts
- packages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.ts
- packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.ts
- packages/1-framework/1-core/framework-components/README.md
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.ts
- packages/2-mongo-family/1-foundation/mongo-codec/README.md
- packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts
- packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.ts
- packages/1-framework/1-core/framework-components/src/runtime-middleware.ts
- packages/1-framework/1-core/framework-components/src/exports/runtime.ts
- packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.ts
- packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.ts
- packages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.ts
- docs/architecture docs/adrs/ADR 207 - Codec call context per-query AbortSignal and column metadata.md
- packages/1-framework/1-core/framework-components/src/codec-types.ts
- packages/2-mongo-family/7-runtime/src/mongo-runtime.ts
- packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts
- packages/2-sql/5-runtime/src/sql-runtime.ts
| const ctx: CodecCallContext = { signal: new AbortController().signal }; | ||
| await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx); | ||
| expect(observed).toEqual([ctx]); |
There was a problem hiding this comment.
Assert the same context object, not just deep equality.
toEqual([ctx]) can still pass if resolveValue() clones the context object and reuses the same signal. Use toBe on the observed value so this test actually proves identity preservation.
♻️ Proposed fix
- expect(observed).toEqual([ctx]);
+ expect(observed).toHaveLength(1);
+ expect(observed[0]).toBe(ctx);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ctx: CodecCallContext = { signal: new AbortController().signal }; | |
| await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx); | |
| expect(observed).toEqual([ctx]); | |
| const ctx: CodecCallContext = { signal: new AbortController().signal }; | |
| await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx); | |
| expect(observed).toHaveLength(1); | |
| expect(observed[0]).toBe(ctx); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts`
around lines 37 - 39, The test currently asserts deep equality with
expect(observed).toEqual([ctx]) which allows a cloned context to pass; update
the assertion to verify identity preservation by asserting the observed entry is
the exact same CodecCallContext instance produced earlier (e.g., replace the
deep-equality check with an assertion that observed[0] is the same object as ctx
using toBe, optionally also asserting observed.length === 1). This change should
be applied in the resolveValue test where MongoParamRef('hello', { codecId:
'test/observe@1' }), registry, ctx and the observed array are used.
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
wmadden
left a comment
There was a problem hiding this comment.
Many locations now encode ctx as optional, which is wrong. We control it. While the options the user provides to execute() are optional, every call site flowing from that should take a non-optional ctx.
Similarly, adapter methods take an optional codecs option. That's stupid. It's not optional. You'll need to address this since the following option is ctx, which should not be optional.
| */ | ||
| export function runtimeAborted(phase: RuntimeAbortedPhase, cause?: unknown): RuntimeErrorEnvelope { | ||
| const resolvedCause = | ||
| cause === undefined ? new DOMException('The operation was aborted.', 'AbortError') : cause; |
There was a problem hiding this comment.
Why is this a DOMException? We don't necessarily run in the browser
| encode?: | ||
| | ((value: TInput) => TWire | Promise<TWire>) | ||
| | ((value: TInput, ctx: CodecCallContext) => TWire | Promise<TWire>); | ||
| decode: | ||
| | ((wire: TWire) => TInput | Promise<TInput>) | ||
| | ((wire: TWire, ctx: CodecCallContext) => TInput | Promise<TInput>); |
There was a problem hiding this comment.
This union is unnecessary and will complicate any type inference based on the encode/decode functions. Simply use an optional final argument
| See [ADR 204 — Single-Path Async Codec Runtime](../../../../docs/architecture%20docs/adrs/ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md) for the codec runtime's async boundary contract. | ||
| ### Codec call context (`ctx`) | ||
|
|
||
| Codec authors may optionally take a second `ctx?: CodecCallContext` argument on `encode`. The Mongo runtime threads one context per `mongoRuntime.execute(plan, { signal })` call. Mongo uses the framework `CodecCallContext` directly (signal-only); column metadata is SQL-family-specific and isn't part of Mongo's per-call shape today. |
There was a problem hiding this comment.
You're overcomplicating this. "Codecs receive a second options argument..."
|
|
||
| > **Note.** Mongo's read path doesn't go through `codec.decode` (per ADR 204 cross-family scope notes), so the `decode` signature above accepts `ctx` for parity with the codec interface but the runtime doesn't currently invoke `decode` on the Mongo read side. Encode-side `ctx.signal` is observed at every recursion level of `resolveValue` so a mid-encode abort surfaces as `RUNTIME.ABORTED { phase: 'encode' }`. | ||
|
|
||
| Existing single-arg authors (e.g. `(v: string) => v`) continue to compile and run unchanged. Aborts surface to the caller as `RUNTIME.ABORTED`; codec bodies that ignore the signal complete in the background (cooperative cancellation). |
There was a problem hiding this comment.
Again, overcomplicated. All codecs receive the arg, you may ignore it.
This is unnecessary because TS permits a function which accepts only one arg to be assigned to a function signature which accepts two (but not the other way around)
| paramRef: { readonly codecId?: string; readonly name?: string }, | ||
| paramIndex: number, | ||
| registry: CodecRegistry, | ||
| ctx?: SqlCodecCallContext, |
There was a problem hiding this comment.
This is wrong. The context arg is non-optional, what's optional is recipients doing anything with it.
| const settled = signal | ||
| ? await raceAgainstAbort(Promise.all(tasks), signal, 'encode') | ||
| : await Promise.all(tasks); |
There was a problem hiding this comment.
This would be simpler if raceAgainstAbort() treated the abort signal as optional. Then callers could collapse all these branches
| // Pre-check at entry: an already-aborted caller observes | ||
| // RUNTIME.ABORTED on the first `next()` without any work being done. | ||
| if (signal?.aborted) { | ||
| throw runtimeAborted('stream', signal.reason); | ||
| } |
There was a problem hiding this comment.
This precheck is repeated often, too. It's a good candidate for a utility function alongside raceAgainstAbort()
| export async function lowerFilter( | ||
| filter: MongoFilterExpr, | ||
| codecs?: MongoCodecRegistry, | ||
| ctx?: CodecCallContext, |
There was a problem hiding this comment.
Non-optional - both this and codecs
| export async function lowerStage( | ||
| stage: MongoPipelineStage, | ||
| codecs?: MongoCodecRegistry, | ||
| ctx?: CodecCallContext, |
There was a problem hiding this comment.
All these ctx usages should be non-optional. We control the calling context. codecs will NEVER be undefined, and neither will ctx.
| } | ||
|
|
||
| async #resolveDocument(expr: MongoExpr): Promise<Document> { | ||
| async #resolveDocument(expr: MongoExpr, ctx?: CodecCallContext): Promise<Document> { |
There was a problem hiding this comment.
Same for this and all references to ctx on this branch. Non-optional.
…lback) The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:` optional and installed an identity fallback `(value) => value as unknown as TWire` when omitted. The cast is unsound — an author who declares `TInput=number, TWire=string` and forgets `encode` silently produces a runtime that returns numbers where the wire shape promised strings. Audit confirms zero in-tree consumers of the identity fallback: every existing codec definition (SQL core, postgres adapter, sqlite adapter, mongo adapter, pgvector extension) declares an explicit `encode`. Making `encode` required is therefore non-disruptive and closes the type hole. `decode` was already required for the same reason. The standalone `encode` identity fallback is gone; the `encodeJson`/`decodeJson` identity defaults (gated on `TInput extends JsonValue`) remain — those defaults are sound because the conditional input constraint guarantees the contract artifact stays JSON-safe. Resolves CodeRabbit C7 on PR #400.
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.
Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.
`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.
Resolves CodeRabbit C6 on PR #400.
…site The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present. Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again. Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry). Resolves the user's review summary on PR #400.
…ng signature Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land. `codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
…lback) The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:` optional and installed an identity fallback `(value) => value as unknown as TWire` when omitted. The cast is unsound — an author who declares `TInput=number, TWire=string` and forgets `encode` silently produces a runtime that returns numbers where the wire shape promised strings. Audit confirms zero in-tree consumers of the identity fallback: every existing codec definition (SQL core, postgres adapter, sqlite adapter, mongo adapter, pgvector extension) declares an explicit `encode`. Making `encode` required is therefore non-disruptive and closes the type hole. `decode` was already required for the same reason. The standalone `encode` identity fallback is gone; the `encodeJson`/`decodeJson` identity defaults (gated on `TInput extends JsonValue`) remain — those defaults are sound because the conditional input constraint guarantees the contract artifact stays JSON-safe. Resolves CodeRabbit C7 on PR #400.
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.
Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.
`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.
Resolves CodeRabbit C6 on PR #400.
…site The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present. Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again. Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry). Resolves the user's review summary on PR #400.
…ng signature Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land. `codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
80bd348 to
e7507f1
Compare
…ference
Captures the shaping output for TML-2330: introduce CodecCallContext
({ signal?, column? }) threaded through codec dispatch sites, scope
narrowed from the original ticket sketch (no concurrency cap, no bulk
codec interface). Adds the framework-gaps reference documenting the
CipherStash integration findings that motivate this work and the
sibling tickets (TML-2359 middleware param transformation, TML-2360
envelope-codec extension + pattern doc).
Introduce `CodecCallContext` as the named, future-extensible per-call
context the runtime threads to every codec.encode / codec.decode
invocation. Two fields land in this commit:
- `signal?: AbortSignal` for per-query cancellation.
- `column?: { table, name }` for decode-side column identity (encode
call sites pass undefined; encode-time column context is the
middleware's domain).
`Codec.encode` / `Codec.decode` accept the context as an optional
second argument. The arg is strictly additive: existing single-arg
call sites continue to compile and behave identically. ADR 204's
walk-back constraints are preserved (no TRuntime generic, no
runtime/kind discriminator, no conditional return types).
Project: codec-call-context.
Phase m1 (T1.1, T1.3, T1.4).
The SQL `codec()` factory now accepts author functions of either arity for `encode` and `decode`: - `(value) => …` — single-arg authors lift unchanged. - `(value, ctx) => …` — ctx-bearing authors receive a CodecCallContext instance forwarded through the lift; AbortSignal identity is preserved across calls. The encode/decode config types are expressed as a union of the two arities so TypeScript continues to infer TInput from the user's function signature without widening (e.g. when the author writes `(value: string | Date): string` for normalization). The factory widens to the two-arg shape internally so the lift can forward `ctx` to either author shape. Project: codec-call-context. Phase m1 (T1.2 SQL, T1.5).
The Mongo codec factory now mirrors `codec()` in sql-relational-core: author functions for `encode` and `decode` may take either `(value)` or `(value, ctx: CodecCallContext)`. The factory lifts both shapes uniformly and forwards ctx (with AbortSignal identity preserved) through the lifted methods. Project: codec-call-context. Phase m1 (T1.2 Mongo, T1.6).
Add `RUNTIME_ABORTED` (the canonical code) and `runtimeAborted(phase, cause?)`, a tiny helper colocated with `runtimeError`. The helper produces a runtime-error envelope with `details.phase` set to one of `'encode' | 'decode' | 'stream'` and `cause` set to the supplied abort reason (falling back to an `AbortError` DOMException so the envelope always carries a cause). Pre-existing in-repo discrepancy: runtime error codes are not enumerated in a central registry; they are string literals at call sites. T1.7 asks to "add `RUNTIME.ABORTED` to the runtime error code list ... wherever envelope codes are enumerated." The closest match is exporting a typed constant + a typed helper from `runtime-error.ts`, which is what lands here. The helper is the construction site that m2/m3 will throw from. Project: codec-call-context. Phase m1 (T1.7).
…tions
Extend the family-agnostic runtime entry point to accept an optional
`{ signal?: AbortSignal }` second argument:
- `RuntimeExecutor<TPlan>.execute(plan, options?)` — interface gains
the options arg via the new `RuntimeExecuteOptions` type.
- `RuntimeCore.execute(plan, options?)` — pre-checks `signal.aborted`
at entry and throws `RUNTIME.ABORTED { phase: 'stream' }` (with
`signal.reason` as cause) before any work begins. When a signal is
present, constructs a `CodecCallContext = { signal }` and forwards
it to the abstract `lower(plan, ctx?)` so concrete subclasses
(SQL/Mongo) can thread the signal to their codec dispatch sites in
m2/m3.
Existing `runtime.execute(plan)` call sites continue to compile —
options is optional and the default no-signal path is structurally
identical to today. Concrete `lower(plan)` overrides remain
compatible (the new `ctx?` parameter is optional).
Project: codec-call-context.
Phase m1 (T1.8, T1.9).
The framework Codec interface is target-agnostic and cannot encode SQL-specific (table, column) addressing. Move the column field off the framework CodecCallContext and into a SqlCodecCallContext defined in the SQL layer, with the SQL Codec interface narrowing encode/decode to use it. Mongo continues to use the framework CodecCallContext directly (no extension introduced). Spec FR2/FR4/FR9 + AC and plan M1 T1.4/T1.5 + M2/M3 dispatch tasks updated accordingly.
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
…ion (TML-2324) Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot. - git mv the ADR file 207 -> 209 - update the heading inside the ADR - update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment) Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
…ion (TML-2324) Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot. - git mv the ADR file 207 -> 209 - update the heading inside the ADR - update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment) Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
…h middleware-param-transform cross-refs Rewrites envelope-codec-extension.spec.md against the post-#402 codec API and the locked Project 1 scope: - Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema for { equality, freeTextSearch }, registered separately from the codec body itself (mirrors pgvector's plumbing for length). - Constrains the column-type surface to EncryptedString only (3 argument shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber, and other types from scope — those land in Project 2 alongside their codec round-trip / search-operator / migration tests. - Adds the EQL bundle install via databaseDependencies.init, sourced from the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for CREATE EXTENSION vector. - Adds operator lowering for eq and ilike, deferring the canonical SQL function names to the first-attempt's operation-templates.ts. - Native type is eql_v2_encrypted (not text), reflecting EQL's actual storage type. - Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests for round-trip, bulk amortization, nullable handling, and cancellation. Refreshes middleware-param-transform.spec.md cross-references: - Path depth bumped (../../ → ../../../) for the new specs/ subdirectory location. - Adds back-reference to the umbrella spec. - Updates the cipherstash-extension cross-reference to point at the new envelope-codec-extension.spec.md path. - Annotates ADR 207 reference as forthcoming with PR #400.
… with high-level plan Restructure the cipherstash-integration project from a single-spec layout into a three-component umbrella: - project-1/: searchable-encryption MVP (the previous umbrella spec rescoped as Project 1, plus its 5 task specs moved under specs/) - project-2/: planner-driven DDL + expanded surface (new stub) - sql-raw-factory/: public raw\`...\` template-literal factory (moved from projects/sql-raw-factory/) New artifacts at the umbrella level: - spec.md: scope of the umbrella, why three components, cross-component design decisions (RawSqlExpr ownership, DataTransformOperation choice, end-to-end-tested-only scope), in-flight framework dependency status (PRs #400/#402 merged 2026-05-01; #404/#409 still open). - plan.md: component-level sequencing. Phase A is Project 1 (critical path, gated externally on #404 + #409); phase B is sql-raw-factory and Project 2 in parallel afterwards, each with its own gating story (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing; Project 2 blocks on Project 1 + TML-2338 + TML-2339). Project 1's spec is reframed: drop "this is the umbrella" language, add a header pointing to the new umbrella, repath all relative references for the new directory depth (3-up from project-1/, 4-up from project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously- open question about Project 2's on-disk slug. No content changes to the per-task specs beyond reference repaths.
…nsion
Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:
- Write side: users construct envelopes from plaintext
(EncryptedString.from('me@example.com')); the extension's
middleware walks ParamRefs in beforeExecute, batches all envelope
plaintexts into one bulkEncrypt({ signal }) call, replaces values
with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
ciphertext + the column handle (table, column, KMS metadata)
supplied by SqlCodecCallContext.column. Users call
await row.email.decrypt() per cell, or use bulk-decrypt
utilities (decryptAll, post-buffering) to amortize across many
envelopes.
Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).
Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.
Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
…h middleware-param-transform cross-refs Rewrites envelope-codec-extension.spec.md against the post-#402 codec API and the locked Project 1 scope: - Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema for { equality, freeTextSearch }, registered separately from the codec body itself (mirrors pgvector's plumbing for length). - Constrains the column-type surface to EncryptedString only (3 argument shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber, and other types from scope — those land in Project 2 alongside their codec round-trip / search-operator / migration tests. - Adds the EQL bundle install via databaseDependencies.init, sourced from the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for CREATE EXTENSION vector. - Adds operator lowering for eq and ilike, deferring the canonical SQL function names to the first-attempt's operation-templates.ts. - Native type is eql_v2_encrypted (not text), reflecting EQL's actual storage type. - Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests for round-trip, bulk amortization, nullable handling, and cancellation. Refreshes middleware-param-transform.spec.md cross-references: - Path depth bumped (../../ → ../../../) for the new specs/ subdirectory location. - Adds back-reference to the umbrella spec. - Updates the cipherstash-extension cross-reference to point at the new envelope-codec-extension.spec.md path. - Annotates ADR 207 reference as forthcoming with PR #400.
… with high-level plan Restructure the cipherstash-integration project from a single-spec layout into a three-component umbrella: - project-1/: searchable-encryption MVP (the previous umbrella spec rescoped as Project 1, plus its 5 task specs moved under specs/) - project-2/: planner-driven DDL + expanded surface (new stub) - sql-raw-factory/: public raw\`...\` template-literal factory (moved from projects/sql-raw-factory/) New artifacts at the umbrella level: - spec.md: scope of the umbrella, why three components, cross-component design decisions (RawSqlExpr ownership, DataTransformOperation choice, end-to-end-tested-only scope), in-flight framework dependency status (PRs #400/#402 merged 2026-05-01; #404/#409 still open). - plan.md: component-level sequencing. Phase A is Project 1 (critical path, gated externally on #404 + #409); phase B is sql-raw-factory and Project 2 in parallel afterwards, each with its own gating story (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing; Project 2 blocks on Project 1 + TML-2338 + TML-2339). Project 1's spec is reframed: drop "this is the umbrella" language, add a header pointing to the new umbrella, repath all relative references for the new directory depth (3-up from project-1/, 4-up from project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously- open question about Project 2's on-disk slug. No content changes to the per-task specs beyond reference repaths.
…nsion
Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:
- Write side: users construct envelopes from plaintext
(EncryptedString.from('me@example.com')); the extension's
middleware walks ParamRefs in beforeExecute, batches all envelope
plaintexts into one bulkEncrypt({ signal }) call, replaces values
with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
ciphertext + the column handle (table, column, KMS metadata)
supplied by SqlCodecCallContext.column. Users call
await row.email.decrypt() per cell, or use bulk-decrypt
utilities (decryptAll, post-buffering) to amortize across many
envelopes.
Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).
Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.
Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
…h middleware-param-transform cross-refs Rewrites envelope-codec-extension.spec.md against the post-#402 codec API and the locked Project 1 scope: - Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema for { equality, freeTextSearch }, registered separately from the codec body itself (mirrors pgvector's plumbing for length). - Constrains the column-type surface to EncryptedString only (3 argument shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber, and other types from scope — those land in Project 2 alongside their codec round-trip / search-operator / migration tests. - Adds the EQL bundle install via databaseDependencies.init, sourced from the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for CREATE EXTENSION vector. - Adds operator lowering for eq and ilike, deferring the canonical SQL function names to the first-attempt's operation-templates.ts. - Native type is eql_v2_encrypted (not text), reflecting EQL's actual storage type. - Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests for round-trip, bulk amortization, nullable handling, and cancellation. Refreshes middleware-param-transform.spec.md cross-references: - Path depth bumped (../../ → ../../../) for the new specs/ subdirectory location. - Adds back-reference to the umbrella spec. - Updates the cipherstash-extension cross-reference to point at the new envelope-codec-extension.spec.md path. - Annotates ADR 207 reference as forthcoming with PR #400.
… with high-level plan Restructure the cipherstash-integration project from a single-spec layout into a three-component umbrella: - project-1/: searchable-encryption MVP (the previous umbrella spec rescoped as Project 1, plus its 5 task specs moved under specs/) - project-2/: planner-driven DDL + expanded surface (new stub) - sql-raw-factory/: public raw\`...\` template-literal factory (moved from projects/sql-raw-factory/) New artifacts at the umbrella level: - spec.md: scope of the umbrella, why three components, cross-component design decisions (RawSqlExpr ownership, DataTransformOperation choice, end-to-end-tested-only scope), in-flight framework dependency status (PRs #400/#402 merged 2026-05-01; #404/#409 still open). - plan.md: component-level sequencing. Phase A is Project 1 (critical path, gated externally on #404 + #409); phase B is sql-raw-factory and Project 2 in parallel afterwards, each with its own gating story (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing; Project 2 blocks on Project 1 + TML-2338 + TML-2339). Project 1's spec is reframed: drop "this is the umbrella" language, add a header pointing to the new umbrella, repath all relative references for the new directory depth (3-up from project-1/, 4-up from project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously- open question about Project 2's on-disk slug. No content changes to the per-task specs beyond reference repaths.
…nsion
Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:
- Write side: users construct envelopes from plaintext
(EncryptedString.from('me@example.com')); the extension's
middleware walks ParamRefs in beforeExecute, batches all envelope
plaintexts into one bulkEncrypt({ signal }) call, replaces values
with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
ciphertext + the column handle (table, column, KMS metadata)
supplied by SqlCodecCallContext.column. Users call
await row.email.decrypt() per cell, or use bulk-decrypt
utilities (decryptAll, post-buffering) to amortize across many
envelopes.
Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).
Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.
Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
…h middleware-param-transform cross-refs Rewrites envelope-codec-extension.spec.md against the post-#402 codec API and the locked Project 1 scope: - Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema for { equality, freeTextSearch }, registered separately from the codec body itself (mirrors pgvector's plumbing for length). - Constrains the column-type surface to EncryptedString only (3 argument shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber, and other types from scope — those land in Project 2 alongside their codec round-trip / search-operator / migration tests. - Adds the EQL bundle install via databaseDependencies.init, sourced from the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for CREATE EXTENSION vector. - Adds operator lowering for eq and ilike, deferring the canonical SQL function names to the first-attempt's operation-templates.ts. - Native type is eql_v2_encrypted (not text), reflecting EQL's actual storage type. - Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests for round-trip, bulk amortization, nullable handling, and cancellation. Refreshes middleware-param-transform.spec.md cross-references: - Path depth bumped (../../ → ../../../) for the new specs/ subdirectory location. - Adds back-reference to the umbrella spec. - Updates the cipherstash-extension cross-reference to point at the new envelope-codec-extension.spec.md path. - Annotates ADR 207 reference as forthcoming with PR #400.
… with high-level plan Restructure the cipherstash-integration project from a single-spec layout into a three-component umbrella: - project-1/: searchable-encryption MVP (the previous umbrella spec rescoped as Project 1, plus its 5 task specs moved under specs/) - project-2/: planner-driven DDL + expanded surface (new stub) - sql-raw-factory/: public raw\`...\` template-literal factory (moved from projects/sql-raw-factory/) New artifacts at the umbrella level: - spec.md: scope of the umbrella, why three components, cross-component design decisions (RawSqlExpr ownership, DataTransformOperation choice, end-to-end-tested-only scope), in-flight framework dependency status (PRs #400/#402 merged 2026-05-01; #404/#409 still open). - plan.md: component-level sequencing. Phase A is Project 1 (critical path, gated externally on #404 + #409); phase B is sql-raw-factory and Project 2 in parallel afterwards, each with its own gating story (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing; Project 2 blocks on Project 1 + TML-2338 + TML-2339). Project 1's spec is reframed: drop "this is the umbrella" language, add a header pointing to the new umbrella, repath all relative references for the new directory depth (3-up from project-1/, 4-up from project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously- open question about Project 2's on-disk slug. No content changes to the per-task specs beyond reference repaths.
…lback) The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:` optional and installed an identity fallback `(value) => value as unknown as TWire` when omitted. The cast is unsound — an author who declares `TInput=number, TWire=string` and forgets `encode` silently produces a runtime that returns numbers where the wire shape promised strings. Audit confirms zero in-tree consumers of the identity fallback: every existing codec definition (SQL core, postgres adapter, sqlite adapter, mongo adapter, pgvector extension) declares an explicit `encode`. Making `encode` required is therefore non-disruptive and closes the type hole. `decode` was already required for the same reason. The standalone `encode` identity fallback is gone; the `encodeJson`/`decodeJson` identity defaults (gated on `TInput extends JsonValue`) remain — those defaults are sound because the conditional input constraint guarantees the contract artifact stays JSON-safe. Resolves CodeRabbit C7 on PR #400.
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.
Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.
`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.
Resolves CodeRabbit C6 on PR #400.
…site The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present. Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again. Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry). Resolves the user's review summary on PR #400.
…ng signature Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land. `codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)
The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.
The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.
Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.
Phase E TE.1-TE.5 (codec-registry-unification project).
cfe1b6d ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5. Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`. Phase E TE.6-TE.7 (codec-registry-unification project).
…ion (TML-2324) Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot. - git mv the ADR file 207 -> 209 - update the heading inside the ADR - update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment) Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
…nsion
Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:
- Write side: users construct envelopes from plaintext
(EncryptedString.from('me@example.com')); the extension's
middleware walks ParamRefs in beforeExecute, batches all envelope
plaintexts into one bulkEncrypt({ signal }) call, replaces values
with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
ciphertext + the column handle (table, column, KMS metadata)
supplied by SqlCodecCallContext.column. Users call
await row.email.decrypt() per cell, or use bulk-decrypt
utilities (decryptAll, post-buffering) to amortize across many
envelopes.
Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).
Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.
Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
…h middleware-param-transform cross-refs Rewrites envelope-codec-extension.spec.md against the post-#402 codec API and the locked Project 1 scope: - Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema for { equality, freeTextSearch }, registered separately from the codec body itself (mirrors pgvector's plumbing for length). - Constrains the column-type surface to EncryptedString only (3 argument shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber, and other types from scope — those land in Project 2 alongside their codec round-trip / search-operator / migration tests. - Adds the EQL bundle install via databaseDependencies.init, sourced from the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for CREATE EXTENSION vector. - Adds operator lowering for eq and ilike, deferring the canonical SQL function names to the first-attempt's operation-templates.ts. - Native type is eql_v2_encrypted (not text), reflecting EQL's actual storage type. - Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests for round-trip, bulk amortization, nullable handling, and cancellation. Refreshes middleware-param-transform.spec.md cross-references: - Path depth bumped (../../ → ../../../) for the new specs/ subdirectory location. - Adds back-reference to the umbrella spec. - Updates the cipherstash-extension cross-reference to point at the new envelope-codec-extension.spec.md path. - Annotates ADR 207 reference as forthcoming with PR #400.
… with high-level plan Restructure the cipherstash-integration project from a single-spec layout into a three-component umbrella: - project-1/: searchable-encryption MVP (the previous umbrella spec rescoped as Project 1, plus its 5 task specs moved under specs/) - project-2/: planner-driven DDL + expanded surface (new stub) - sql-raw-factory/: public raw\`...\` template-literal factory (moved from projects/sql-raw-factory/) New artifacts at the umbrella level: - spec.md: scope of the umbrella, why three components, cross-component design decisions (RawSqlExpr ownership, DataTransformOperation choice, end-to-end-tested-only scope), in-flight framework dependency status (PRs #400/#402 merged 2026-05-01; #404/#409 still open). - plan.md: component-level sequencing. Phase A is Project 1 (critical path, gated externally on #404 + #409); phase B is sql-raw-factory and Project 2 in parallel afterwards, each with its own gating story (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing; Project 2 blocks on Project 1 + TML-2338 + TML-2339). Project 1's spec is reframed: drop "this is the umbrella" language, add a header pointing to the new umbrella, repath all relative references for the new directory depth (3-up from project-1/, 4-up from project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously- open question about Project 2's on-disk slug. No content changes to the per-task specs beyond reference repaths.
closes TML-2330
Intent
Introduce
CodecCallContext— a per-query, per-call context object the runtime threads to everycodec.encode/codec.decodecall — and admit an optional{ signal }option on the frameworkRuntimeCore.executeentry point. This is the foundation for KMS-backed codecs (e.g. CipherStash) to cancel in-flight network calls when the caller aborts; m2/m3 will thread the signal through the SQL and Mongo dispatch sites end-to-end.The shape is named (not inlined) so future fields land additively without breaking codec author signatures.
Change map
CodecCallContextinpackages/1-framework/1-core/framework-components/src/codec-types.ts—{ readonly signal?: AbortSignal }, family-agnostic. The frameworkCodec.encode/Codec.decodeadmit it as an optional second argument.SqlCodecCallContext extends CodecCallContextinpackages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts— addsreadonly column?: SqlColumnRef({ table, name }). The SQLCodecinterface narrowsencode/decodeto use it.CodecCallContextdirectly; no Mongo extension introduced (the Mongo read path doesn't go throughcodec.decodeper ADR 204, and the encode site has no per-call concept that needs to extend the framework type today).codec()andmongoCodec()factories — accept author functions of either arity ((value)or(value, ctx)).RUNTIME.ABORTEDenvelope helper inpackages/1-framework/1-core/framework-components/src/runtime-error.ts—RUNTIME_ABORTEDconstant,RuntimeAbortedPhase = 'encode' | 'decode' | 'stream', andruntimeAborted(phase, cause?)envelope builder. Falls back to a synthesizedDOMException('AbortError')when no reason is supplied.RuntimeCore.execute(plan, options?)inpackages/1-framework/1-core/framework-components/src/runtime-core.ts— accepts{ signal? }, builds a single per-executeCodecCallContext, forwards it to the abstractlower(plan, ctx?), and pre-checkssignal.abortedat firstnext()to throwRUNTIME.ABORTED { phase: 'stream' }for already-cancelled callers.The story
The natural shape is a single
CodecCallContextobject the runtime constructs once perexecute()and threads through every codec dispatch site. Two motivations converge on this seam: per-query cancellation (so codecs that perform network I/O can forwardAbortSignalto their underlying SDK), and decode-side column identity (so codecs can construct return values that carry(table, name)for downstream bulk operations like envelope-pattern decryption).The framework-vs-family layering ruled where each piece lives.
signalis per-query cancellation, genuinely framework-shaped — every family wants it.columnis rooted in SQL's(table, column)addressing model and would not generalise to Mongo (paths/keys), document stores, or graph stores; it lives on a SQL-family extension. TypeScript's bivariant handling of method-syntax declarations admits the SQL parameter-narrowing under strict mode without any cast — pinned by type tests so a future change to arrow-property syntax fails loudly.The factory work is a separate concern: a naive single-arg-to-two-arg widening at the user-facing config type widens
TInputfor codecs whose author signatures are non-trivial (e.g.sqlTimestampCodec's(value: string | Date): string), which then breaks the conditionalJsonRoundTripConfig. The fix is a union of two arities at the user surface, widened to a uniform internal shape so the lift forwardsctxthrough either author shape unchanged.The
RUNTIME.ABORTEDhelper lands in m1 (rather than m2 as originally planned) becauseRuntimeCore.execute's entry pre-check needs a construction site for the throw, making the helper a m1 prerequisite rather than a m2 deliverable.Behavior changes & evidence
A —
Codec.encode/Codec.decodeadmit a second optionalCodecCallContextargumentencode(value): Promise<TWire>anddecode(wire): Promise<TInput>.encode(value, ctx?: CodecCallContext): Promise<TWire>anddecode(wire, ctx?: CodecCallContext): Promise<TInput>at the framework layer;ctx?: SqlCodecCallContextat the SQL layer.framework-components/src/codec-types.ts (L24–L82),relational-core/src/ast/codec-types.ts (L26–L113).framework-components/test/codec-call-context.types.test-d.ts(framework signal-only + walk-back constraints),relational-core/test/ast/codec-factory-ctx.types.test-d.ts(SqlColumnRef,SqlCodecCallContext extends CodecCallContext, SQLCodecnarrows ctx).B —
codec()andmongoCodec()factories accept either author arity and lift uniformly(value)/(wire)signatures only.(value)or(value, ctx)(and(wire)or(wire, ctx)) and lift both shapes through a uniform internal(value, ctx) => userFn(value, ctx)adapter. Sync vs async authoring continues to work identically. SQL ctx-bearing authors getSqlCodecCallContext; Mongo ctx-bearing authors get the frameworkCodecCallContext.relational-core/src/ast/codec-types.ts (L226–L296), Mongomongo-codec/src/codecs.ts (L59–L120).relational-core/test/ast/codec-factory-ctx.test.ts(single-arg back-compat, ctx forwarded for both encode and decode authors,AbortSignalidentity preserved through the lift, identity-default for omitted encode still works),mongo-codec/test/codecs-ctx.test.ts.C —
RUNTIME.ABORTEDenvelope helperRUNTIME_ABORTED = 'RUNTIME.ABORTED' as const,RuntimeAbortedPhase = 'encode' | 'decode' | 'stream',runtimeAborted(phase, cause?)builds an envelope withdetails: { phase }and acausethat falls back tonew DOMException('The operation was aborted.', 'AbortError')when no reason is supplied.framework-components/src/runtime-error.ts (L8–L83).framework-components/test/runtime-aborted.test.ts(code constant, envelope shape, phase recording, supplied cause,DOMExceptionfallback, string-reason pass-through).D —
RuntimeExecutor<TPlan>+RuntimeCore.executeaccept{ signal }and pre-check abortexecute<Row>(plan): AsyncIterableResult<Row>, no abort handling.execute<Row>(plan, options?: { readonly signal?: AbortSignal }): AsyncIterableResult<Row>.RuntimeCore.executebuilds a single per-executeCodecCallContextfromoptions.signal, forwards it to the abstractlower(plan, ctx?), and pre-checkssignal.abortedat firstnext(). If already aborted, throwsRUNTIME.ABORTED { phase: 'stream' }withsignal.reasonascause; nolower/ norunDriver/ no codec calls are made.framework-components/src/runtime-middleware.ts (L46–L73),framework-components/src/runtime-core.ts (L73–L127).framework-components/test/runtime-core-options.types.test-d.ts(all four call shapes typecheck),framework-components/test/runtime-core-options.test.ts(back-compat, ctx threaded intolower()carrying the signal, already-aborted entry rejects with zerolowercalls,causeattached).Compatibility / migration / risk
sqlChar,sqlVarchar,sqlInt,sqlFloat,sqlText,sqlTimestamp, etc.) compile unchanged. Existingruntime.execute(plan)call sites compile unchanged.Codecretains exactly four type parameters; noTRuntimegeneric, nokind/runtimediscriminator, no conditional return types, no exported sync-vs-async predicates, noCodec.contextalias. All pinned by negative-form type tests per ADR 204's walk-back framing.Codecinterfaces declareencode/decodewith method syntax, so TypeScript's bivariant method handling admits the SQL parameter-narrowing under strict mode. Pinned by type-tests; converting the declarations to arrow-property syntax in a future round would unwind the layering correction.pnpm lint:depsreports zero violations across 661 modules;framework-components/src/codec-types.tsimports only@prisma-next/contract/types; no SQL-package imports anywhere inframework-components/.Follow-ups
SqlCodecCallContextthroughencodeParams/decodeRow/executeAgainstQueryable; populatesctx.column = { table, name }on decode; racesPromise.allagainst the signal; throwsRUNTIME.ABORTED { phase: 'encode' | 'decode' | 'stream' }at the right boundaries. Integration test against an aborting fetch.CodecCallContextthroughresolveValuerecursively. (Mongo decode is out of scope per ADR 204; encode-only signal coverage.)Non-goals (intentionally out of scope)
Promise.allcodec dispatch (framework-gaps G4-option-2). Today'sPromise.allshape is preserved unchanged; deferred to a separate ticket.bulkEncode/bulkDecode). Out of scope. Bulk-encrypt at write time is solved at the middleware layer (separate ticket); bulk-decrypt at read time is solved by extension-owned envelope objects + helpers operating onctx.column-bearing return values.signaltopg/ MongoDB driver so an in-flight query is killed at the connection level). Worth doing later; the codec story is independently useful.executesignal covers the headline use case;AbortSignal.any(...)composition with transaction-scoped signals is a future enhancement.Summary by CodeRabbit
New Features
Documentation
Tests