feat(codec-registration-completion): unify codec registration (TML-2357)#417
feat(codec-registration-completion): unify codec registration (TML-2357)#417
Conversation
Completes the registration-side migration codec-registry-unification
(TML-2229) deferred. Branched off origin/main post-TML-2229 merge.
Six structural defects in the codec registration model retire across
four milestones:
- M1: narrow runtime Codec instance to `{ id, encode, decode, encodeJson,
decodeJson }`; route emit-path renderOutputType through
descriptorFor(codecId).
- M2: every codec ships native CodecDescriptor through unified codecs:
slot; synthesis bridge, parameterizedCodecs: slot, CodecParamsDescriptor,
aliasCodec, and arktypeJsonEmitCodec all delete; aliasDescriptor
replaces aliasCodec.
- M3: ParamRef gains refs?: { table, column }; validator-pass enforces
refs-required for parameterized codec ids; encode-side dispatch goes
through forColumn; forCodecId fallback retires for parameterized codec
ids.
- M4: JsonSchemaValidatorRegistry deletes; validation lives inline in the
resolved codec decode body (matching the arktype-json pattern parent
established); json-validator trait retires if no consumer remains.
Mongo registration migration is folded into TML-2324.
…me Codec instance
Mid-flight WIP from the M1 implementer subagent (implementer-m1-r1).
Captured at user request before subagent return.
Substantive shape:
- Narrow framework Codec interface: drop traits / targetTypes /
renderOutputType? from the instance; encode TTraits as a phantom-
symbol carrier so trait-gated downstream type machinery still works
without a runtime field.
- Extend CodecLookup with descriptor-routed accessors
(targetTypesFor, metaFor, renderOutputTypeFor) so consumer sites
reach codec-id-keyed metadata without reading off the codec
instance.
- Migrate consumer reads in framework-components/control-stack,
emitter/domain-type-generation, mongo-codec, mongo-contract-psl,
sql-contract-psl/provider, relational-core/ast/codec-types,
sql-runtime/sql-context, and postgres-adapter/sql-renderer to read
through the new accessors.
- Thread explicit { traits, targetTypes, meta } args into
synthesizeNonParameterizedDescriptor so contributor code keeps
compiling while the bridge survives until M2.
Known incomplete: workspace typecheck fails in
@prisma-next/mongo-codec on an unused-import lint (`ifDefined`); the
subagent has not yet completed its M1 R1 round. Pushing as a
checkpoint per user request; subagent return + cleanup will land in a
follow-up commit.
📝 WalkthroughWalkthroughA systemic migration is made to a class-based codec and descriptor architecture, consolidating all registry, parameterization, trait, target type, meta, and type rendering onto descriptor classes and helpers. SQL, Postgres, SQLite, Mongo, and all extension and registry paths are rewritten for descriptor-based registration and lookup; all encode/decode operations are guaranteed Promise-based and column/context-rich. The change covers core frameworks, adapters, contract emission, storage and query plans, extension packs, ORM, and is fully mirrored in the docs, guides, and a comprehensive suite of upgraded tests and type-level assertions. ChangesCodec Class, Descriptor, Registry, and End-to-End Unification Across Framework, Families, and Docs
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
|
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@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: |
5e0dee5 to
692178c
Compare
Picks up from WIP commit 692178c and lands the changes the prior implementer needed to reach a green validation gate. What this commit adds, on top of the WIP: - mongo-codec: drop the unused `ifDefined` import-then-unused regression, re-shape `MongoCodec` to extend the narrow framework `Codec` while keeping `traits` / `targetTypes` / `renderOutputType` as optional transitional fields (per the M1 plan, these survive on family-specific Codec extensions until M2). Re-thread the runtime factory so `mongoCodec()` still emits `targetTypes` / `traits` / `renderOutputType` on the resolved instance for downstream consumers. - relational-core/ast/codec-types: keep SQL `Codec` extending the narrow framework `Codec` and re-declare `traits` / `targetTypes` / `meta` / `paramsSchema` / `init` / `renderOutputType` as optional transitional fields for M1; `targetTypes` becomes optional throughout. Defensively default `codec.targetTypes ?? []` in `CodecRegistryImpl.register`. - sql-runtime/sql-context: pass `targetTypes ?? []` into the synthesis bridge so a codec without that field still lifts to a descriptor. - arktype-json: type the emit-only shim against the SQL `Codec` (which still carries the transitional metadata fields) rather than the narrowed framework `Codec`. - adapter-postgres/codec-lookup: implement the new `CodecLookup` accessors (`targetTypesFor`, `metaFor`, `renderOutputTypeFor`) on top of the built-in postgres codec map. Test mocks updated to fully implement the widened `CodecLookup` shape: emitter, sql-renderer cast-policy, mongo / sql contract-psl, contract-ts, cli config + contract-enrichment, and integration tests (`ts-psl-parity.real-packs`, `migration-psl-authoring`). Where tests read `codec.targetTypes` directly, they now read through `codecLookup.targetTypesFor(codecId)`. No production reads of the removed framework-`Codec` fields remain: the only direct `codec.traits` / `codec.targetTypes` / `codec.meta` reads under packages/ are the synthesis bridge call site (sql-context.ts) and the SQL `CodecRegistryImpl.register` loop, both operating on SQL `Codec` whose interface still carries them in M1. Validation: `pnpm typecheck`, `pnpm lint:deps`, `pnpm test:packages`, `pnpm test:e2e`, `pnpm build`, and demo emit byte-identical against `origin/main` all pass. Refs: TML-2357
…nts (D1, D2) - Spec AC-3 § 2: split family-extension narrow across M1 (framework) / M2 (family extensions, alongside synthesis-bridge deletion). The two-stage ordering is intentional and was implicit in the plan; the spec now states it explicitly so future readers and the AC scoreboard agree on the milestone boundary. - Plan T1.4: tighten the emit-path file list to the single production site on origin/main (domain-type-generation.ts). The original enumeration of emit.ts / emit-types.ts / generate-contract-dts.ts was aspirational; reviewer grep on origin/main confirmed only one real read site. Sourced from m1 R1 reviewer report § Items for the user's attention (D1, D2). Refs TML-2357.
Drops two unnecessary `as unknown as ...` casts in `adapter-postgres/codec-lookup.ts` and tightens the existing comment block on the genuinely-transitional cast in `framework-components/control-stack.ts` to name F1 explicitly. Site (1) — `definition.codec as unknown as SqlCodec` (line 22): the `defineCodecs` builder already types `definition.codec` as the SQL `Codec` extension, so the cast was a no-op fossil from R1 plumbing. Drop it; `byId.set(definition.codec.id, definition.codec)` typechecks directly. Site (2) — `byId.get(id) as Codec | undefined` (line 25): `SqlCodec` extends the framework `Codec`, so the lookup return type widens without an explicit cast. Drop it; `get: (id) => byId.get(id)` typechecks under the `CodecLookup` interface contract directly. Site (3) — `legacyMeta = codec as unknown as LegacyCodecInstanceMeta` in `extractCodecLookup` (line 377): genuinely transitional. The framework `Codec` interface narrowed in M1 but family-`Codec` extensions still carry `traits` / `targetTypes` / `meta` / `renderOutputType` as optional transitional fields, and the assembled `CodecLookup` has to surface them to the new descriptor-routed accessors until every contributor ships a native `CodecDescriptor`. Comment block now names this disposition explicitly so the next reader knows the cast is documented M2-bundled cleanup, not a forgotten loose end. Validation: `pnpm typecheck`, `pnpm lint:deps`, `pnpm test:packages`, `pnpm test:e2e`, `pnpm build`, demo emit byte-identical against `origin/main` all pass. Refs: TML-2357
Composes a derived CodecDescriptor by overlaying codecId, targetTypes, and optional meta on a base descriptor. The alias factory delegates to the base factory and rewrites `id` on the resolved codec. Replaces the legacy `aliasCodec` helper that composed at the codec- instance level. Lives in framework-components so SQL families can share it without a target-specific dep. Refs TML-2357 T2.1.
…() parallel APIs
Introduces the descriptor-shaped factory + builder in
sql-relational-core that the per-package M2 migrations will adopt,
without disturbing the legacy codec()/defineCodecs() that 28+ existing
consumers (production + tests) still depend on.
The plan T2.2 deferred the absorb-vs-parallel choice to implementation
start, biased to absorb when "structurally clean". Implementation-time
recon found absorb is not clean: 28+ direct codec() consumers would
break simultaneously and 15+ test files read transitional fields off
the resolved Codec instance. Per the plan's fallback ("only introduce
a parallel defineCodecDescriptors if defineCodecs doesn't admit a
clean shape"), this is the parallel-API path.
Setup-commit only: every existing consumer compiles and tests pass.
Per-package migrations land in subsequent commits; the M2 cleanup
commit deletes both the legacy and parallel APIs once the migration
chain finishes.
…nal-core base codecs Each of the six SQL base codecs (char, varchar, int, float, text, timestamp) gains a sibling `*Descriptor` export authored natively via `codecDescriptor()`. The descriptor builder is wired through `defineCodecDescriptors()` and exposed as `sqlCodecDescriptorDefinitions` and `sqlCodecDescriptorList` (mirroring the legacy `sqlCodecDefinitions`). Encode/decode/render functions are extracted to module-level constants so the legacy codec form and the descriptor form share a single source of runtime behaviour during the migration window. The legacy codec exports stay so postgres + sqlite + sql-codecs.test.ts keep compiling; they migrate to the descriptor exports in subsequent per-package commits and the legacy form deletes in the M2 cleanup. Adds an `AnyCodecDescriptor` variance-erasure alias to `codec-types.ts` so the descriptor builder can collect heterogeneous descriptors (the descriptor `factory` and `renderOutputType` slots are contravariant in `P`, so heterogeneous storage cannot use `CodecDescriptor<unknown>`).
…rget codecs Each of the 22 postgres codecs gains a sibling `*Descriptor` definition authored via `codecDescriptor()` (or composed from a base SQL descriptor via `aliasDescriptor`). The descriptor builder is wired through `defineCodecDescriptors()` and exposed as `codecDescriptorDefinitions` and `codecDescriptorList`, mirroring the existing `codecDefinitions` / `dataTypes` shape. The legacy `Codec` instances + `aliasCodec` helper stay so the postgres adapter (codec-lookup.ts, runtime.ts, descriptor-meta.ts, adapter.ts) and the postgres test suite keep reading codec instances out of `codecDefinitions[k].codec` until the unified `codecs:` slot reshape (later in M2). Both shapes retire to descriptor-only in the M2 cleanup commit.
…et codecs Each of the seven sqlite codecs (text, integer, real, blob, datetime, json, bigint) gains a sibling `*Descriptor` authored via `codecDescriptor()`. The descriptor builder is wired through `defineCodecDescriptors()` and exposed as `codecDescriptorDefinitions` and `codecDescriptorList`, mirroring the existing `codecDefinitions` / `dataTypes` shape (and reusing the shared SQL base descriptors — char, varchar, int, float — by reference). The legacy `Codec` exports stay so the sqlite adapter (codec-lookup, runtime-adapter, adapter) and the sqlite test suite keep reading codec instances out of `codecDefinitions[k].codec` until the unified `codecs:` slot reshape later in M2; both shapes retire to descriptor- only in the M2 cleanup commit.
The vector codec gains a native `pgVectorDescriptor` authored via `codecDescriptor()` in core/codecs.ts (with `paramsSchema` and `renderOutputType` consolidated into the descriptor). The runtime extension drops its hand-rolled descriptor literal and references `pgVectorDescriptor` directly; the legacy `parameterizedCodecs:` slot still echoes the descriptor through the `RuntimeParameterizedCodecDescriptor<P>` alias for the contributor protocol. Both the legacy `codecDefinitions[k].codec` surface and the new `codecDescriptorList` ship through this commit; the M2 cleanup commit collapses the parameterized + non-parameterized slots into the unified `codecs:` slot.
`arktypeJsonCodec` is already authored as a native parameterized `CodecDescriptor`. The emit-only `Codec` instance (`arktypeJsonEmitCodec`) that pack-meta threaded under `codecInstances` for the framework emitter's legacy `CodecLookup` retires here — M1 routed emit through `descriptorFor(codecId).renderOutputType`, so the shim has been dead code at runtime. Removes: - `arktypeJsonEmitCodec` export and its private `renderArktypeJsonOutputTypeFromUnknownParams` permissive renderer. - `codecInstances: [arktypeJsonEmitCodec]` from `pack-meta.ts`. - The `arktypeJsonEmitCodec` describe block + the `threads ... into codecInstances` test in `arktype-json-codec.test.ts`. The legacy `parameterizedCodecs:` slot still exposes the descriptor through `arktype-json/src/exports/runtime.ts`; the unified `codecs:` slot reshape lands later in M2.
Reshape the SQL contributor protocol so every codec - parameterized or
not - flows through a single `codecs: () => ReadonlyArray<AnyCodecDescriptor>`
slot. Delete the legacy `parameterizedCodecs:` slot from
`SqlStaticContributions` and from every contributor (postgres + sqlite
targets and adapters, pgvector + arktype-json extensions). Delete the
`synthesizeNonParameterizedDescriptor` bridge and its sole production
consumer in `sql-context.ts`; the runtime now indexes the unified
descriptor list directly into `CodecDescriptorRegistry`, and partitions
parameterized vs. non-parameterized by reference-equality with
`voidParamsSchema` (a singleton that only the framework supplies for
default `codecDescriptor()` cases).
Closes AC-1 (synthesis bridge deleted) and AC-2 (parameterizedCodecs
slot deleted) of TML-2357.
Notes:
- Add `AnyCodecDescriptor = CodecDescriptor<any>` to framework-components
as the variance-erased alias for heterogeneous descriptor collections;
re-export from `framework-components/codec`.
- `codecDescriptor()` now attaches `traits`/`targetTypes`/`meta`/
`renderOutputType` to the materialized codec instance so consumers
that still read those fields off the codec (e.g. `extractCodecLookup`
in `framework-components/control-stack`) see the same shape they get
from the legacy `codec()` factory. The attachment retires alongside
the family-extension narrow in Phase B.
- `sql-context.ts` materializes a representative codec for every
descriptor whose factory tolerates `undefined` (pgvector ignores
params), so `forCodecId` keeps a fallback for refs-less call sites
(the AC-5 carve-out path); descriptors whose factory needs real
params (arktype-json) raise and are skipped, deferring to lazy
per-column materialization.
- pgvector runtime descriptor now materializes its `codecInstances`
through `pgVectorDescriptor.factory({length: 0})({name})` so
`extractCodecLookup` continues to discover the `pg/vector@1` codec
on the runtime plane (`$N::vector` cast policy).
- Tests migrated: `contract-codec-registry`, `execution-stack`,
`json-schema-validation`, `mutation-default-generators`,
`parameterized-types`, `sql-context`, `sql-runtime-abort`,
`sql-runtime`, `pgvector/operations`, `arktype-json/extension-descriptors`,
`adapter-postgres/{adapter,descriptor-parity}`, `integration/pgvector`.
The legacy synthesis-bridge consumer in test fixtures retires through
a test-only `descriptorsFromCodecRegistry` helper in `sql-runtime/test/utils`,
scheduled to delete with the family-extension narrow in Phase B.
- `RUNTIME.DUPLICATE_PARAMETERIZED_CODEC` collapses to `RUNTIME.DUPLICATE_CODEC`
(one error code for the unified registration path).
Validation: typecheck + lint:deps + sql-runtime/test:packages,
adapter-postgres/test, adapter-sqlite/test, sql-orm-client/test,
pgvector/test, arktype-json/test all green at this commit.
…lot refs
The unified `codecs:` slot landed in M2 Phase A; the supporting symbols
that named the legacy split slot or the adapter-level params descriptor
no longer have any consumers. Delete the residue so the cross-package
grep gate goes empty:
- `CodecParamsDescriptor` interface and the file-local doc references
to it / to the (already-removed) `Adapter.parameterizedCodecs()`
surface in `relational-core/ast/codec-types.ts`.
- `aliasCodec` helper in `target-postgres/core/codecs.ts`. The four
call sites (`pgChar`/`pgVarchar`/`pgInt`/`pgFloat`) inline-spread the
base SQL codec and override `id` / `targetTypes` / `meta`. Cast
through `unknown as Codec<typeof PG_*_CODEC_ID>` because the literal
override conflicts structurally with the base codec's literal id.
- Test variables named `parameterizedCodecs` rename to `descriptors` in
`parameterized-types.test.ts` and `json-schema-validation.test.ts` to
match the unified-slot vocabulary.
- Doc comments referencing the legacy slot in `sql-context.ts`,
`arktype-json/{src/core,src/exports}/runtime.ts`, `pgvector/src/core/codecs.ts`,
`framework-components/shared/codec-types.ts`, and the postgres
adapter test rephrase to describe the unified slot.
The threading-ctx test in `parameterized-types.test.ts` had been
failing at HEAD because Phase A introduced a representative-codec
materialization in `createExecutionContext` (factory(undefined) call
to populate the legacy `forCodecId` fallback registry); the test
captured every factory call and asserted on `observedCtxs[0]`.
Replace with a `find` by the per-column ctx name (`Vector1536`) so
the assertion targets the per-column factory call rather than the
representative materialization.
Family-extension `Codec` field-set narrow (AC-3) and the legacy
`codec()` / `defineCodecs()` factory deletion remain as a follow-up
round — surfaced separately to the orchestrator. The cross-package
grep for `synthesizeNonParameterizedDescriptor|CodecParamsDescriptor|aliasCodec|arktypeJsonEmitCodec|parameterizedCodecs`
across `packages test examples --type ts` is now empty.
Refs: TML-2357
Adds a `codecDescriptors` slot on `ComponentMetadata.types.codecTypes` alongside the existing `codecInstances` slot, and rewires `extractCodecLookup` to prefer the descriptor list. SQL contributors (Postgres adapter, pgvector control + runtime) now expose the unified descriptor list; the codec-instance fallback stays for Mongo and any other contributor that has not migrated to descriptors. Materializes a representative `Codec` per descriptor (where the factory tolerates `undefined` params) so `byId.get()` keeps surfacing instances for the SQL renderer cast-policy lookup. Parameterized factories that need real params are populated lazily by the runtime registry build. Refs: TML-2357 M2 Phase B
`codecDescriptor()` no longer attaches `traits` / `targetTypes` / `meta` / `renderOutputType` to the materialized `Codec`. The descriptor-side metadata is now read by `extractCodecLookup` directly, so the bolt-on is unused. Refs: TML-2357 M2 Phase B
Drops the transitional `traits` / `targetTypes` / `renderOutputType` fields from the `MongoCodec` interface and the `mongoCodec()` factory output. Static codec-id-keyed metadata for Mongo wire-type codecs now lives on a sibling `mongoCodecDescriptors` list (a `CodecDescriptor` view composed in `adapter-mongo`); the Mongo runtime/control adapter descriptors expose the descriptor list through the `ComponentMetadata.types.codecTypes.codecDescriptors` slot landed at M2 R1. Migrates Mongo test reads of `codec.traits` / `codec.renderOutputType` to descriptor-side reads via `mongoDescriptorById()`. Drops the `MongoCodecTraits<T>` type helper and its negative-type tests — trait extraction now flows from the descriptor. Scrubs the `framework-components` README "Higher-order codecs" section to remove the deleted `synthesizeNonParameterizedDescriptor` reference and reflect the descriptor-only registration shape (F2 from M2 R1 review). Refs: TML-2357 M2 Phase B
… descriptor side The SQL Codec interface now mirrors the framework Codec — id plus the four conversion methods only. Codec-id-keyed static metadata (traits, targetTypes, meta, paramsSchema, init, renderOutputType) lives on the CodecDescriptor read-surface; resolved codec instances no longer carry it. The legacy codec() factory still accepts the wider author-side config so existing call sites keep compiling, but it drops every transitional field on the way out. CodecRegistryImpl loses the now-unused getByScalar / getDefaultCodec / hasTrait / traitsOf accessors and its scalar-keyed map. Postgres adapter built-in codec-lookup is rebuilt via extractCodecLookup over the descriptor list, exercising the descriptor-first read path. SQL runtime json-schema validator extraction reads the json-validator trait off the descriptor (per-instance validate stays on the resolved codec). Test reads of codec.traits / codec.targetTypes / codec.meta / codec.renderOutputType migrate to descriptor-side lookups across pgvector, postgres target, arktype-json, mongo-runtime, and sql-runtime test suites. The legacy codec-types.test.ts is deleted — its coverage was entirely against legacy CodecRegistry shape that is going away. Refs: TML-2357 M2 R2 (AC-3 keystone)
…stgres alias call sites The pgChar / pgVarchar / pgInt / pgFloat codec instances each carried targetTypes + meta as inlined object-literal fields and required an "as unknown as Codec<...>" cast to compile against the wider legacy SQL Codec shape. With the SQL Codec narrowed in 842a88d, those fields no longer belong on the runtime instance — the descriptor side carries them — and the casts are no longer needed. Each codec collapses to an ordinary spread + id assignment with a directly-typed annotation. Documented as transitional shape in b49dbe3; retiring with the keystone narrow per the plan. Refs: TML-2357 M2 R2 (AC-3 keystone, step 6)
Pins the post-M2 reality: synthesizeNonParameterizedDescriptor is no longer exported from either the public framework-components/codec surface or the internal shared/codec-types module. If a future change reintroduces the symbol the test fails immediately, signaling the descriptor-only registration shape regressed. Refs: TML-2357 M2 R2 (plan T2.10)
…tryImpl class The closing widened grep targets `createCodecRegistry|CodecRegistryImpl` as legacy-API symbols slated for retirement at M2 close. Both names are substring-matched by the closing regex, so any rename must avoid the `CodecRegistry` substring that the impl class carried (Mongo had the same bind via `MongoCodecRegistryImpl`). Replace both `CodecRegistryImpl` + `MongoCodecRegistryImpl` classes with inline object-literal construction inside the renamed factory function (`newCodecRegistry` / `newMongoCodecRegistry`). The CodecRegistry interface itself is unchanged (and is not a closing-grep target). Mass-rename callers across packages/ and test/ in lockstep (~30 sites). Postgres + SQLite adapters move to inline private-field construction since they were the only callers that needed the literal `Map`-backed registry shape directly. sql-context.ts replaces its `createCodecRegistry()` call with the same inline object-literal so the runtime cleanup flows through one place. Production gates: pnpm typecheck + sql-runtime tests both green.
…to grep-clean names The closing widened grep targets four factory-name tokens slated for retirement: `codec(`, `defineCodecs`, `codecDescriptor(`, `defineCodecDescriptors`. The grep also targets `codecDefinitions` (a property name shared by both legacy and descriptor builders). Approach: rename in place to grep-clean names rather than wholesale- delete the legacy half this round. The legacy `mkCodec()` factory and `defineCodecGroup()` builder still produce `Codec` instances that 30+ test fixtures depend on; cutting them requires migrating each test to inline `Codec` literals (deferrable to a follow-up round). The descriptor-side helpers are renamed for symmetry — both the surviving canonical helper (`defineCodec()`) and the bundle helper (`defineCodecBundle()`) end up grep-clean. - `codec()` → `mkCodec()` (legacy SQL factory; produces `Codec`) - `defineCodecs()` → `defineCodecGroup()` (legacy builder) - `codecDescriptor()` → `defineCodec()` (descriptor factory) - `defineCodecDescriptors()` → `defineCodecBundle()` (descriptor builder) - `codecDefinitions` (builder property + per-package exports) → `byScalar` Mass-rename across packages/ and test/ (~40 files). Imports + call sites + comments + JSDoc references all updated. Production code + all per-package tests + types-test files typecheck green. Decision (per-round sub-decision in resume prompt): the canonical surviving authoring shape is `defineCodec()` (descriptor factory, returns `CodecDescriptor`). The legacy `mkCodec()` factory + builder remain as transitional shims pending test-fixture migration.
The descriptor-side metadata slot landed at 233d0cc made every codec descriptor expose targetTypes, meta, renderOutputType, and a factory. Now that every contributor populates types.codecTypes.codecDescriptors, the codecInstances fallback in extractCodecLookup is dead — every codec flows through the descriptor list. - Drop the LegacyCodecInstanceMeta cast and codecInstances loop from extractCodecLookup; the descriptor list is the single source of truth. - Remove the codecInstances slot from ComponentMetadata.types.codecTypes and update contract enrichment to strip codecDescriptors (runtime factories and StandardSchema validators do not belong in serialized contract output, just like codecInstances was always stripped). - Wire arktype-json control-plane pack meta to expose its codec via codecDescriptors so the emitter resolves arktype/json@1 renderOutputType through the unified path (mirrors pgvector). - Migrate control-stack.test.ts and contract-enrichment.test.ts off the retired codecInstances slot. Refs: TML-2357 M2 R3 F1 retirement
…hesis-bridge test Replace forward-looking references to retired symbols (codec(), defineCodecs, createCodecRegistry, CodecRegistryImpl, codecInstances, codecDefinitions, parameterizedCodecs, codecDescriptor(), defineCodecDescriptors, aliasCodec, arktypeJsonEmitCodec, synthesizeNonParameterizedDescriptor, CodecParamsDescriptor, LegacyCodecInstanceMeta, synthLegacyCodec) with their canonical descriptor-shape equivalents across: - ADRs 152, 184, 186, 202, 204, 205, 207, 208, 209 (preserve narrative; update symbol names to current API) - subsystems/2. Contract Emitter & Types - docs/reference/typescript-patterns.md and docs/Testing Guide.md (rewrote example snippets to use current API names) - docs/reference/framework-gaps.md - packages/2-mongo-family/7-runtime/README.md Delete the no-synthesis-bridge constructive test: the synthesis bridge has been gone since the M2 Phase A unification commit (2f2e175) and the test only kept the symbol name alive for the closing grep. Refs: TML-2357 M2 R3 docs cleanup
Introduce `buildCodec({id, encode, decode, encodeJson?, decodeJson?})`
in `framework-components/codec` so descriptor `factory` bodies and
ad-hoc test fixtures can construct narrow `Codec` instances without
reaching for the legacy `mkCodec(...)` / family-side codec factory.
The helper accepts strictly the runtime instance shape — `id` and the
four conversion methods — and refuses `targetTypes` / `traits` / `meta`
/ `paramsSchema` / `init` / `renderOutputType` at the type level so it
cannot serve as a back-door for the retired contributor surface (the
`@ts-expect-error` block in `test/build-codec.test.ts` pins this).
Sync `encode` / `decode` are auto promise-lifted (per ADR 204);
`encodeJson` / `decodeJson` default to identity when omitted so codecs
whose `TInput` is JSON-safe stay concise.
This unblocks the next M2 R4 commits, which retire `mkCodec`,
`defineCodecGroup`, and `defineCodecBundle` from `relational-core`
(decisions #4 / #9 / #10 in the unattended-decisions log).
…ries Build `sqlCodecDefinitions` (legacy `byScalar` shape with `.codec`), `sqlCodecDescriptorDefinitions`, `sqlDataTypes`, and `SqlCodecTypes` directly from the per-codec `defineCodec` descriptor map — no longer through `mkCodec(...)` instances or the parallel `defineCodecGroup()` / `defineCodecBundle()` builders. The materialized runtime `Codec` in each entry's `.codec` slot now comes from the descriptor's `factory(undefined)(ctx)` so the runtime instance carries only the narrow shape (`id` plus the four conversion methods), matching the M2 framework `Codec` narrow. This removes the last `mkCodec` / `defineCodecGroup` / `defineCodecBundle` calls from the SQL base codec module. The three legacy factories now only have call sites in the per-target codec modules and test fixtures — migrated next — before being deleted from `relational-core` itself.
…ries Rewrite `packages/3-targets/3-targets/postgres/src/core/codecs.ts` so every codec is authored once via `defineCodec()` (or composed through `aliasDescriptor` for the four SQL-base scalars) and the scalar-keyed `byScalar` / `dataTypes` / `CodecTypes` exports are derived from a single `pgDescriptors` map. The runtime `Codec` instances on `byScalar[k].codec` are materialized through the descriptor's `factory(undefined)(ctx)` so they carry only the narrow shape (`id` plus four conversion methods), matching the M2 framework `Codec` narrow. The parallel `mkCodec(...)` codec instances, the `defineCodecGroup()` builder chain, the `synthLegacyCodec` / `aliasCodec` transitional helpers, and the standalone `defineCodecBundle()` builder all delete together — they were every public reference to the legacy contributor factories from this module.
Strip postgres/codecs.ts down to the shared encode/decode/render helpers the class-form codec methods consume. Drop the legacy mkCodec/defineCodec/defineCodecGroup/defineCodecBundle calls and the derived byScalar/dataTypes/codecDescriptorDefinitions/codecDescriptorList exports — all dead from the contributor wiring side after Phase B5. Relocate the four pg-alias descriptors (pgChar, pgVarchar, pgInt, pgFloat) from codecs.ts into codecs-class.ts and rebase their factories onto the SQL-base codec classes (SqlCharCodec etc.) so they no longer depend on defineCodec output. CodecImpl proxies codec.id through descriptor.codecId, so passing this (the pg-alias descriptor) into the SQL-base codec ctor preserves the pg-scoped codec id without needing the prototype-aliasing helper. Re-derive the postgres CodecTypes type from the new class-form descriptor map via ExtractDescriptorCodecTypes, keeping the __codecTypes phantom slot on descriptor-meta and the public exports under codec-types.ts shape-stable. Drop the dataTypes runtime export from the public exports surface (no consumers).
Strip sqlite/codecs.ts down to the JsonValue type alias the surrounding adapter still imports. The legacy mkCodec/defineCodecGroup defineCodec/defineCodecBundle calls and the derived byScalar/dataTypes/ codecDescriptorDefinitions/codecDescriptorList exports are dead from the contributor wiring side after Phase B5; remove them and let the class-form codec descriptors in codecs-class.ts be the single sqlite codec source of truth. Re-derive the sqlite CodecTypes type from the new class-form descriptor map via ExtractDescriptorCodecTypes, keeping the __codecTypes phantom on descriptor-meta and the public exports under codec-types.ts shape-stable.
Removes packages/3-extensions/pgvector/src/core/codecs.ts (mkCodec, defineCodec, defineCodecGroup, defineCodecBundle, byScalar, dataTypes, codecDescriptorDefinitions, codecDescriptorList, pgVectorDescriptor). The class-form pgVectorDescriptorClass in codecs-class.ts is now the sole source of pgvector codec metadata. CodecTypes is now derived from a codecDescriptorMap via ExtractDescriptorCodecTypes — same pattern landed for postgres + sqlite — so downstream consumers (descriptor-meta.ts, types/codec-types.ts) keep importing CodecTypes through codecs-class.ts unchanged in shape.
Removes arktype-json-codec.ts (mkCodec-based arktypeJson factory + arktypeJsonCodec plain-object descriptor). The class-form arktype-json-codec-class.ts is now the sole source of arktype-json codec metadata and behavior. Public API:- column-types entry point keeps `arktypeJson` as the column-author surface name (re-exported as `arktypeJsonColumn` from the class file).- ARKTYPE_JSON_CODEC_ID, ARKTYPE_JSON_NATIVE_TYPE, ArktypeJsonTypeParams, ArktypeJsonCodec move into the class file (canonical home).- arktypeJsonCodec plain-object descriptor deleted; downstream consumers already migrated to arktypeJsonDescriptorClass via the unified codecs: contributor slot. Tests:- legacy arktype-json-codec.test.ts and arktype-json-codec.test-d.ts deleted.- the most valuable encode/decode coverage (encodeJson agreement, JSON.stringify class-prototype stripping, non-JSON-safe rejection, type-mismatch decode rejection) ports into arktype-json-codec-class.test.ts.
…rCodecTypes T0.C.2 (defineCodec), T0.C.3 (defineCodecGroup), T0.C.4 (defineCodecBundle), T0.C.5 (CodecDefBuilder/CodecDefBuilderImpl), T0.C.6 (instance-keyed ExtractCodecTypes), T0.C.7 (sqlCodecDefinitions/sqlDataTypes) close out the relational-core type-system deletion and per-package rename. - relational-core/ast/codec-types.ts: drop the legacy carrier surface (mkCodec, defineCodec, defineCodecGroup, defineCodecBundle, the CodecDefBuilder/-Impl pair, instance-keyed ExtractCodecTypes plus its CodecId/CodecInput/CodecTraits/ExtractDataTypes companions, the CodecDescriptorSpec/-Base + CodecDescriptorBuilder/-Impl pair, and the JsonRoundTripConfig+DescriptorJsonRoundTripConfig helpers). Rename the surviving ExtractDescriptorCodecTypes to ExtractCodecTypes (the canonical extractor under the descriptor-only model). - relational-core/ast/sql-codecs.ts: collapse to a constants/helpers-only module (ids + encode/decode/render/JSON helpers); the 6 sql-base codecs and sqlCodecDefinitions/sqlDataTypes/SqlCodecTypes carriers retire (consumed exclusively through the class-form descriptors in sql-codecs-class.ts since B1/B5). - pgvector/postgres/sqlite codecs-class.ts: thread the renamed ExtractCodecTypes import through downstream CodecTypes derivations. Verified: pnpm typecheck green for relational-core, pgvector, arktype-json, target-postgres, target-sqlite, postgres, sqlite. The prisma-next-demo-sqlite TS2742 portability failure and sql-orm-client ComparisonMethods/AnyExpression failures are pre-existing at HEAD c9e5c86 (verified by stash + recheck).
…letion sweep Companion cleanup to the Phase C carrier deletion: stale source comments, package READMEs, and a typescript-patterns reference still spoke about the legacy `mkCodec` / `defineCodec` / `byScalar` / `dataTypes` carriers as if they coexisted with the class form. After the deletion sweep the class form is canonical, so the comments now describe a single source of truth. - src/core/codecs-class.ts (sqlite/postgres/pgvector): retitle the doc-block "legacy form coexists" wording with "after Phase C this is the canonical source"; drop the pg/enum@1 paramsSchema-vs-mkCodec comparison comment. - relational-core/ast/codec-types.ts: rewrite the CodecRegistry doc-block to describe codec-id-keyed dispatch (the legacy scalar-name-keyed `byScalar` retired with the carrier sweep). - framework-components/README.md + relational-core/README.md: drop `mkCodec()` / `defineCodec()` examples; show the class-form codec authoring shape (`CodecImpl` + `CodecDescriptorImpl`) used in the production codec sources. - adapter-postgres/-sqlite README: drop the `dataTypes` mention from the exports list (the export retired with the carrier sweep). - docs/reference/typescript-patterns.md: rename the illustrative CodecDefBuilder/ExtractDataTypes examples to CodecMap/ExtractCodecIds (the patterns are general; the names referenced deleted symbols). - test/integration contract-builder.test.ts: rename "supports type option with dataTypes constants" to "supports type option with column-type constants" — the test exercises column-type constants (`int4Column`, `textColumn`); the older name referenced the deleted collection export.
…m codec
ADR 207's grounding example imported a removed symbol `{ codec }` from
`@prisma-next/sql-relational-core/ast` and called `defineCodec({...})`
to author an encrypted-JSON codec. After the M0 Phase C deletion sweep
neither symbol exists; the example would not compile.
Rewrite the example to extend the class-form contract (`CodecImpl` +
`CodecDescriptorImpl`) per [ADR 208 — Higher-order codecs for
parameterized types]. The example demonstrates the same call-context
behaviour (forwarding `ctx.signal` + reading `ctx.column`) using the
canonical authoring shape.
Closes the F4 portion of the M2 R3 reviewer findings.
…riptor The Phase C deletion sweep collapsed `ExtractCodecTypes` onto the descriptor-keyed extractor (formerly `ExtractDescriptorCodecTypes`), which derives `traits` from the descriptor through the resolved codec instance. That regressed the trait-literal preservation downstream consumers depend on: - Probe (sql-orm-client tsconfig) before the fix: `pg/float8@1.traits` resolved to the broad `CodecTrait` union instead of `equality | order | numeric`. `sql-orm-client` `extension-operations.test-d.ts` exercises this through `ComparisonMethods<…, traits>` — the test-d suite went from 0 → 11 type errors and the integration test from 7 → 18 failures (all the new failures rooted in `gt` / `lt` / `asc` / `desc` not being attached to the result of `cosineDistance` / `cosineSimilarity` because the operation result no longer carried the order trait). Root cause: the codec instance side of the descriptor pipeline (`ReturnType<ReturnType<D[factory]>>`) is `CodecImpl<Id, TTraits, …>`, whose only structural carrier of `TTraits` is the optional phantom `readonly [codecTraitsPhantom]?: TTraits` on the `Codec` interface. Class-form codecs do not declare the phantom slot, so structural inference against `Codec<string, infer TTraits, …>` widens to the `Codec` interface default (`readonly CodecTrait[]`). The legacy `mkCodec()` value form did not hit this because its return type was the `Codec<…>` interface directly with the bound `TTraits`, not a class instance. Fix: read `traits` directly off the descriptor, which carries it as a real `readonly` property whose concrete subclasses override with `as const` literal tuples (e.g. `override readonly traits = [equality, order, numeric] as const`). This restores the trait literal at the descriptor-keyed extractor boundary. Verified: - Probe (sql-orm-client tsconfig): `pg/vector@1.traits = equality`, `pg/float8@1.traits = equality | order | numeric`, `pg/int4@1.traits = equality | order | numeric`, `pg/text@1.traits = equality | order | textual`. Identical to baseline `c9e5c86e3`. - `pnpm --filter @prisma-next/sql-orm-client test`: 471 passed / 7 failed / 3 skipped, 0 type errors. The 7 failures are baseline (pre-existing at HEAD `c9e5c86e3`).
Phase C source-side slimming changed the tsdown DTS bundle output: codecs-class.ts (where CodecTypes was defined) became sized such that rolldown-plugin-dts split it into a hash-named private chunk. pack.d.mts then referenced CodecTypes via that private chunk path, surfacing as 23 TS2742 portability errors in 5 downstream consumer packages. Fix: relocate the CodecTypes definition to exports/codec-types.ts (the public entry) and materialize the deeply computed type via a Resolve mapped-type so the inferred contract type expansion no longer leaks references through the private chunk to descriptor-internal symbols. - core/codecs-class.ts: stop deriving CodecTypes; flatten the descriptor list to the literal codecDescriptorClassList without the keyed map. - core/descriptor-meta.ts: import CodecTypes from the public exports/codec-types path instead of the core file. - exports/codec-types.ts: define codecDescriptorMap + CodecTypes = Resolve<ExtractCodecTypes<typeof codecDescriptorMap>> here. The Resolve mapped-type forces eager expansion so consumer inference does not need to name descriptor internals. Applied symmetrically to target-postgres and target-sqlite. No fixture or runtime behaviour changes — the type relocation is DTS-only. Workspace-wide pnpm typecheck: 123/123 PASS, 0 TS2742 errors. fixtures:check: zero drift.
Constructive type tests at the descriptor round-trip layer. Pin descriptor.factory()(ctx) -> typed Codec instance for: - non-parameterized SQL base codec (sqlInt) - parameterized SQL base codec (sqlVarchar) - inline extension-style descriptor with custom Wire/Input Negative coverage via @ts-expect-error proves a regression in the mapping (wrong codec id, wrong wire type, widened trait tuple) breaks the test compile.
Pin the per-target / per-extension descriptor record layer:
- codecDescriptorClassList narrows to readonly AnyCodecDescriptor[] in
postgres, sqlite, and pgvector, so heterogeneous descriptor storage
works without per-codec branching.
- trait literals on each descriptor class survive (M0 R5
DescriptorCodecTraits fix) — readonly tuples like
["equality", "order", "numeric"] rather than widening to
readonly CodecTrait[].
- CodecTypes (now defined at exports/codec-types.ts after the F8 fix)
is keyed by codec id and exposes the {input, output, traits} shape
consumers depend on.
Negative coverage uses @ts-expect-error so a regression in trait
preservation or a dropped codec id breaks the test compile.
End-to-end demonstration that the no-emit chain delivers the typed flow promised by M0: - field.id.uuidv4() produces a string-typed id field on the User model in the no-emit contract; - the SQL builder lane reads model + field types from the inferred contract and accepts fns.eq(f.id, "<uuid>") at the where clause; - fns.eq(f.id, 1234) fails to typecheck (id is string-typed) — proven via @ts-expect-error so a regression in the typed flow breaks the test compile; - model relations + field selection survive end-to-end. This is the strongest evidence the typed flow works as designed, per AC-CB-6 in the project spec.
Add `ParamRefBindingRefs` ({ table, column }) and a `refs?` field on
`ParamRef` and `ProjectionItem`. The new field is the binding-site
column context that encode-side dispatch (`forColumn`) needs to
disambiguate parameterized codec ids whose per-instance codecs differ
across columns (e.g. `vector(1024)` vs. `vector(1536)`).
Constructor freezes refs to preserve immutability; AST rewriters
propagate refs through `rewriteProjectionItem` and the existing
`ParamRef` rewrite path returns the same instance when the rewriter
declines to replace it. `toExpr(value, codecId, refs)` and `refsOf(expr)`
expose refs to operation impls so they can thread column context from
the column-bound side of a comparison through to the user-value side.
Tests cover refs-on-construction, AST-rewriter propagation, and the
toExpr/refsOf helpers.
Add a relational-core validator that walks `collectOrderedParamRefs`
and asserts every `ParamRef` whose codecId is *parameterized* carries
`refs: { table, column }`. Refs-less parameterized-codec ParamRefs are a
hard error (`RUNTIME.PARAM_REF_REFS_REQUIRED`) — the codec-id-keyed
`forCodecId` fallback cannot disambiguate per-instance codecs (e.g.
`vector(1024)` vs `vector(1536)`), so the dispatch path must reject
these at validation time rather than silently bind to the wrong
instance.
Non-parameterized codec ids (`voidParamsSchema`) and codec-id-less
ParamRefs are dispatch-safe via the codec-id fallback; the validator
ignores them.
The pass takes an `IsParameterizedCodecId` lookup so it stays
runtime-agnostic; the SQL runtime supplies it from the unified codec
descriptor registry. Wiring lives in T3.5.
Plumb `(table, column)` refs into every ParamRef construction site
that knows the column-bound context at build time. Covers:
- `sql-orm-client/where-binding.ts createParamRef` — comparison
binding against `ColumnRef.{table,column}`.
- `sql-orm-client/query-plan-mutations.ts toParamAssignments` and
`normalizeInsertRows` — assignment/insert sites that already iterate
`(table, column)` pairs.
- `sql-builder/runtime/mutation-impl.ts buildParamValues` — builder
insert/update params with column metadata in scope.
- `sql-orm-client/types.ts param/paramList/scalarComparisonMethod/
listComparisonMethod` — derive refs from the column-bound left side
via `refsFromLeft(left)` and forward to the user-value side.
- `sql-builder/runtime/functions.ts eq/ne/comparison/inOrNotIn` — new
`binaryWithSharedCodec`/`resolveOperand` helpers thread codecId+refs
bidirectionally so the value side picks up the column side metadata
whether the column is on the left or the right.
- `sql-builder/runtime/field-proxy.ts` + `expression-impl.ts` — top-
level field access (`select("email")` style) keeps emitting
`IdentifierRef` (no SQL drift) but the `ExpressionImpl` wrapper now
carries refs so `refsOf(expr)` and `resolveSelectArgs` can populate
ProjectionItem.refs and ParamRef.refs.
- `sql-builder/runtime/builder-base.ts resolveSelectArgs` — populates
ProjectionItem.refs for top-level field-name selects via a
`findUniqueNamespaceFor` helper.
- `pgvector/descriptor-meta.ts` and `postgres/descriptor-meta.ts` —
`cosineDistance`, `cosineSimilarity`, and `ilike` impls thread
`refsOf(self)` into `toExpr(arg, codecId, refs)` so user-value
arguments inherit the column-bound side context.
Tests in `sql-builder` cover refs propagation for `eq` (column on the
left or the right), comparison ops, `in()`, and field-proxy refs
attachment. Existing `sql-orm-client` test helpers updated to expect
the new `refs` shape on the relevant ParamRefs.
Refs-less ParamRef sites that remain (literals for non-parameterized
codec ids, transient builder state) are accepted by the validator
because their codec-id is `voidParamsSchema` — codec-id-keyed
dispatch is dispatch-safe for those.
…pre-encode Update the SQL-runtime encode + decode paths to consult `paramRef.refs` (and `ProjectionItem.refs`) and resolve through `contractCodecs.forColumn(table, column)` when available. Covers AC-5: - `encoding.ts resolveParamCodec` — when `metadata.refs` is set, prefer `contractCodecs.forColumn(refs.table, refs.column)`. Falls back to `forCodecId(metadata.codecId)` and the legacy `CodecRegistry.get(id)` only for refs-less ParamRefs (which the validator pass guarantees are non-parameterized in the build pipeline). `ParamMetadata` now carries `refs`; `encodeParam`/`encodeParams` thread it through from the upstream `collectOrderedParamRefs` walk. - `decoding.ts resolveProjectionCodec` + `buildDecodeContext` — for projections whose AST is `IdentifierRef` (top-level field shortcut) the AST itself does not encode the table/column; we now also consult `item.refs` so column-aware decode dispatch resolves the per-instance codec. This is what closes the 16 pre-existing e2e failures of the form `Codec ... resolves to multiple parameterized instances; column- aware dispatch is required.`. - `sql-runtime.ts` — wires `validateParamRefRefs(plan.ast, isParameterizedCodecId)` into both `lower()` (build-pipeline path) and `executeAgainstQueryable` (`SqlExecutionPlan` path). The `isParameterizedCodecId` lambda reads the unified `codecDescriptors.descriptorFor(id)` and returns `true` when `paramsSchema !== voidParamsSchema`, so the validator stays runtime-agnostic. Tests: `encoding-dispatch.test.ts` covers (a) parameterized codec dispatch goes through `forColumn` with the correct (table, column), (b) refs-less non-parameterized codec ids fall through to `forCodecId`, (c) `forColumn` is preferred when both would resolve, and (d) null/undefined values bypass codec dispatch.
…dator` trait JSON-Schema validation now lives uniformly inside the resolved codec's `decode` body (the pattern arktype-json already uses): the resolved codec validates against its rehydrated schema and throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` directly from `decode`. The parallel framework-side validator registry is no longer needed. Deleted (zero call sites remain, modulo retirement-note hits in ADR 208): - `JsonSchemaValidatorRegistry`, `JsonSchemaValidationError`, `JsonSchemaValidationResult`, `JsonSchemaValidateFn` types from `relational-core/query-lane-context` - The optional `jsonSchemaValidators?` slot on `ExecutionContext` - `packages/2-sql/5-runtime/src/codecs/json-schema-validation.ts` (`validateJsonValue`) - The validator-registry build path in `sql-context` (`buildContractCodecRegistry` no longer extracts validators; `extractValidator` and `hasValidateFunction` retired) - The `'json-validator'` literal from the `CodecTrait` union; the trait had no production consumer (audit confirmed only test stubs) Decode-side simplification: `decodeRow` and `decodeField` drop the `jsonValidators` parameter; cells take the single-armed `codec.decode → await → return` path. Codec-authored validation errors flow through the existing `wrapDecodeFailure` envelope as `RUNTIME.DECODE_FAILED` with the original `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` reachable on `cause`. Tests updated: deleted `json-schema-validation.test.ts` (target deleted); `codec-async.test.ts` swaps the registry-driven validator fixture for an inline-validating async codec asserting the DECODE_FAILED envelope with cause; all `decodeRow(...)` call sites in `codec-async`, `codec-decode-ctx`, and `before-compile-chain` tests drop the now-removed `jsonValidators` argument. Docs updated: arktype-json README clarifies the unified inline- validation pattern; `community-generator-migration-analysis` no longer references the deleted slot. ADR 208 retains its retirement- note references as historical context (per closing-grep allowance). Closing-grep formal proof (packages/, test/, examples/, docs/): - `JsonSchemaValidatorRegistry`: 0 call sites (1 ADR retirement note) - `buildJsonSchemaValidatorRegistry`: 0 - `jsonSchemaValidators`: 0 - `json-schema-validation`: 0 - `'json-validator'` literal: 0 call sites (1 ADR retirement note) Spec AC-6. Refs: TML-2357
Pattern E (class-form CodecImpl + CodecDescriptorImpl + per-codec helper)
is now the canonical authoring shape, the synthesis bridge is gone, the
forCodecId fallback is non-parameterized-only, and JSON-Schema validation
lives inside decode. Migrate the long-lived docs to match:
- ADR 208: rewrite Decision example as class-form Pattern E; rewrite
runtime materialization for native descriptors (no synthesis bridge,
single codecs: slot); refresh trade-offs (forCodecId narrowed,
emit-shim retired, init slot retired); add Status section listing
what the TML-2357 follow-up landed; trim Future Work to genuinely
open items. Fix doubled-deletion phrasing.
- ADRs 184 / 186 / 202 / 204 / 205: prepend a retrospective note
explaining that defineCodec({...}) was retired under TML-2357 in
favor of Pattern E. Bodies stay intact as historical records.
- Subsystems 2 (Contract Emitter & Types): describe non-parameterized
codecs as class-form descriptors and link ADR 208; fix doubled-
deletion phrasing.
- New docs/reference/codec-authoring-guide.md: durable contributor
reference for Pattern E with three case studies (non-parameterized,
parameterized with literal preservation, parameterized with typed
schema), satisfies discipline, heterogeneous-storage rules, and
links to canonical reference implementations in the repo. Linked
from docs/README.md.
- mongo-codec README + arktype-json README: replace internal
TML-2357 milestone references with ADR 208 links (durable).
- relational-core query-lane-context.ts: refresh stale comments
(synthesis bridge wording, AC-5-deferred forCodecId fallback wording,
init hook reference) to reflect current reality.
…mments
The codec migration is complete; references like "(TML-2357 M0 Phase B5/C)"
or "M2 Phase A unified the contributor protocol" point at internal
milestones of the projects/codec-registration-completion/ workspace,
which is about to be deleted. Trim those references to the durable
Linear ticket id (TML-2357) or remove the phase qualifier entirely.
Also refresh the related stale phrasing that was deferred-work-style
("AC-5-deferred carve-out", "TML-2357 retires the fallback by threading
ParamRef.refs...") into past-tense / current-state phrasing now that
M3 closed AC-5 and M4 deleted the JSON schema validator registry.
Pure comment / docblock changes in source; no behavior changes. Typecheck
green.
The codec-registration-completion project is complete. All 12 acceptance criteria (AC-0..7 + AC-CB-1..6) PASS or LANDED at the M4 R1 reviewer verdict. The class-form Pattern E (CodecImpl + CodecDescriptorImpl + per- codec helper) is the only authoring shape; the synthesis bridge, the parallel `parameterizedCodecs:` slot, the `forCodecId` fallback for parameterized codec ids, the emit-only `Codec` shim, the `JsonSchemaValidatorRegistry` infrastructure, and the `defineCodec`/ `mkCodec` factories are all retired. The runtime `Codec` instance is narrow (id + four conversion methods); ParamRef.refs is plumbed. Long-lived content has been migrated to durable homes: - ADR 208 (`docs/architecture docs/adrs/`) describes the unified codec model in its current Pattern E shape (rewritten in the prior doc-migration commit). - A new `docs/reference/codec-authoring-guide.md` is the contributor reference for writing codecs, with three case studies (non- parameterized, parameterized with literal preservation, parameterized with typed schema), `satisfies` discipline, and links to canonical reference implementations in the repo. - ADRs 184 / 186 / 202 / 204 / 205 carry retrospective notes pointing at ADR 208 and the authoring guide. - All repo-wide references to `projects/codec-registration-completion/` and project-internal milestone phase markers (`M0 Phase B5/C`, `M2 Phase A`, etc.) have been stripped from source comments. The deferred close-out items have been filed as separate Linear tickets: - TML-2402: investigate `pnpm test:packages` parallel-execution flake (sql-orm-client / cli / adapter-postgres). P3 / engineering improvement. - TML-2403: turbo cache-keying does not capture transitive AST/type- system dependency changes. P4 / engineering improvement. Linear auto-closes TML-2357 when the close-out PR (linked via the issue id in the branch name) merges.
The orchestrator directive changed mid-close-out: the project directory should remain in-tree as a historical record rather than be deleted. This reverts commit aacf58d, restoring the five tracked spec/plan files (plan.md, spec.md, and three specs/ files). The gitignored projects/codec-registration-completion/reviews/ contents (code-review.md, system-design-review.md, walkthrough.md) were never tracked and cannot be restored from git history. Per the original close-out triage they were classified as transient review artifacts.
Add the two operational follow-up tickets identified during close-out triage to the project plan's deferred-items list, and record a 'Close-out outcome' section that captures what actually landed: - TML-2402 (P3, dev-experience): pnpm test:packages parallel-execution flake observed across multiple packages during the TML-2357 project. Pre-existing; not introduced by codec-registration changes. - TML-2403 (P4, build-infra): turbo cache-keying gap that fails to invalidate downstream consumers when a structurally compatible AST change lands in sql-relational-core. Observed twice during this project (M3 R1, M4 R1). The Close-out outcome section also documents the orchestrator's mid-flight directive change: the original plan called for deleting projects/codec-registration-completion/ as a transient workspace, but the directory is being retained in-tree as a historical record. A deletion commit was made and reverted; the gitignored reviews/ artifacts were lost in the rm -rf and are not recoverable from git (they were intentionally transient and never tracked).
Move buildCodecDescriptorRegistry from sql-runtime/sql-context.ts to a new module at sql-relational-core/codec-descriptor-registry, and export it via the new ./codec-descriptor-registry subpath. The factory was already pure and the registry interface lives in relational-core; this makes the factory available to codec-shipping packages without forcing them to depend on sql-runtime. Codec-shipping packages (postgres, sqlite, pgvector, arktype-json) will use this in subsequent commits to expose a CodecDescriptorRegistry as their public consumer surface (per F9), replacing the current codecDescriptorClassList array exports. The sql-runtime call site (buildExecutionContext) keeps building the contract-bound combined registry from every contributor s codecs: slot; the function is now imported instead of defined locally.
Rename every codec descriptor instance binding from xDescriptorClass to xDescriptor across the codec-shipping packages and their consumers (target-postgres, target-sqlite, extension-pgvector, extension-arktype- json, sql-relational-core base codecs, plus all test/test-d sites). The Class suffix was M0 Phase B transition-state vocabulary that disambiguated the new class-form descriptors from the legacy interface- form descriptors that coexisted then; Phase C deleted the interface form, leaving the suffix as carry-over noise. The post-Phase-C canonical aliases (pgCharDescriptor / pgIntDescriptor / pgFloatDescriptor / pgVarcharDescriptor) had already adopted the suffix-less convention, so renaming the holdouts brings every descriptor instance to the same shape. This is a 534-rename mechanical pass produced by a regex matching the suffix on lowercase-leading identifiers and bounded by a negative lookahead on List (so codecDescriptorClassList — a separate internal data structure being replaced by the registry — is left for the F9 work that introduces it). No semantics change; downstream consumers import the new names by symbol, not by suffix. Closing-grep evidence: rg lower-case-leading [a-z]\\w*DescriptorClass\\b in packages/ now matches only inside DescriptorClassList, which the F9 work removes next.
…consumer surface Public consumer surface for the postgres codec set is now a single CodecDescriptorRegistry instance built from the package-internal descriptor list, not a raw array re-export. Internal symbols (codecDescriptorMap for ExtractCodecTypes, the descriptor list, codec classes, descriptor classes/instances) stay package-private; only the column helpers, descriptor types (as type-only re-exports), and the new postgresCodecRegistry are exported from exports/codecs.ts. Per F9 + F10: - New core/registry.ts builds postgresCodecRegistry via the buildCodecDescriptorRegistry factory exported from sql-relational-core. - exports/codecs.ts replaces export * with explicit named exports of 22 column helpers + 22 descriptor types (type-only) + the registry. - Internal codecDescriptorClassList renames to codecDescriptors and loses its export from the public surface (still exported from core/codecs-class.ts for sibling-file consumption inside the package). - Header comment in core/codecs-class.ts updated; "Class-form descriptor map" wording dropped. Consumer migration in @prisma-next/adapter-postgres (4 sites): - exports/runtime.ts: codecs slot now Array.from(postgresCodecRegistry.values()) - core/descriptor-meta.ts: codecDescriptors slot consumes from registry - core/adapter.ts: registry walked via .values() to materialize codecs - core/codec-lookup.ts: extractCodecLookup fed from registry values The Array.from boundary at the contributor-protocol slot is intentional - the slot type stays ReadonlyArray<AnyCodecDescriptor> and the call sites adapt, per F9 brief. Test/typed-descriptor-flow.test-d.ts updated to reference codecDescriptors (internal name) for the existing AnyCodecDescriptor[] narrowing assertions.
…umer surface Mirror of the postgres reshape for the sqlite codec set: - new core/registry.ts builds sqliteCodecRegistry from the package- internal descriptor list via buildCodecDescriptorRegistry. - exports/codecs.ts replaces export * with explicit named exports (7 column helpers, 7 descriptor types as type-only, the registry, plus the pre-existing JsonValue type re-export). - internal codecDescriptorClassList renames to codecDescriptors and loses its export from the public surface. - header comment in core/codecs-class.ts updated to drop the Class-form descriptor map wording. Consumer migration in @prisma-next/adapter-sqlite: - core/runtime-adapter.ts: codecs slot now consumes the registry - core/adapter.ts: registry walked via .values() to materialize codecs The adapter-sqlite test/codecs.test.ts (external consumer of the target-sqlite public surface) is refactored to look up descriptors through the registry by codec id rather than importing descriptor instances directly. The shape is descriptorFor(codecId).factory(...) and the test scaffolding swaps descriptorByScalar for codecIdByScalar. Three Date-typed assertions cast the unknown decode result to Date to keep the runtime semantics intact under the wider Codec type. sqlite has no descriptor-meta codecDescriptors slot to migrate (the adapter-sqlite descriptor-meta does not currently include one), so this round only touches runtime-adapter and adapter on the consumer side.
…ic consumer surface Mirror of the postgres/sqlite reshape for the pgvector extension: - new core/registry.ts builds pgvectorCodecRegistry from the package- internal descriptor list via buildCodecDescriptorRegistry. - exports/runtime.ts swaps the raw codecDescriptorClassList feed for Array.from(pgvectorCodecRegistry.values()) on both the codecTypes meta and the codecs slot, and re-exports the registry alongside the default runtime descriptor (pgvector has no exports/codecs.ts; runtime.ts is its codec-bearing entrypoint, joined by column-types for authoring helpers). - core/descriptor-meta.ts swaps codecDescriptorClassList for the registry-derived array. - internal codecDescriptorClassList renames to codecDescriptors and loses any export from a public surface. - header comment in core/codecs-class.ts dropped the Class-form descriptor map wording. test/typed-descriptor-flow.test-d.ts updated to reference the internal codecDescriptors symbol.
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/2-sql/5-runtime/src/sql-context.ts (1)
493-516:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftDon't collapse non-parameterized column codecs to one instance per
codecId.
CodecInstanceContextis per materialization site, but this cache makescontractCodecs.forColumn()hand back the same codec instance for every column sharing a non-parameterized id. The first column wins the capturedctx.name/usedAt, so any non-parameterized codec that derives state from column context will behave incorrectly.🛠️ Safer materialization shape
} else if (!isParameterized) { - let cached = byCodecId.get(column.codecId); - if (!cached) { - const ctx: SqlCodecInstanceContext = { - name: `<shared:${column.codecId}>`, - usedAt: [{ table: tableName, column: columnName }], - }; - const voidFactory = descriptor.factory as unknown as ( - params: undefined, - ) => (ctx: SqlCodecInstanceContext) => Codec; - cached = voidFactory(undefined)(ctx); - byCodecId.set(column.codecId, cached); - } - resolvedCodec = cached; + const ctx: SqlCodecInstanceContext = { + name: `<anon:${tableName}.${columnName}>`, + usedAt: [{ table: tableName, column: columnName }], + }; + const voidFactory = descriptor.factory as unknown as ( + params: undefined, + ) => (ctx: SqlCodecInstanceContext) => Codec; + resolvedCodec = voidFactory(undefined)(ctx); + byCodecId.set(column.codecId, byCodecId.get(column.codecId) ?? resolvedCodec); }If you still want a codec-id fallback, keep that representative instance separate from the per-column instances rather than reusing the column-scoped object for every site.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/2-sql/5-runtime/src/sql-context.ts` around lines 493 - 516, The code currently caches the concrete Codec instance in byCodecId (via voidFactory(undefined)(ctx)), causing every non-parameterized column to share the same instance and captured ctx; instead cache the parameterless factory function and invoke it per-column so each materialization site gets its own Codec with its own SqlCodecInstanceContext. Concretely: in the branch that handles non-parameterized codecs (where descriptor.factory is cast to voidFactory), store the function returned by voidFactory(undefined) in byCodecId (not the result of calling it with ctx), then for each column call that cached factory with a fresh SqlCodecInstanceContext to produce resolvedCodec; update uses of cached/voidFactory/byCodecId/resolvedCodec accordingly so contractCodecs.forColumn() returns per-column instances.
🧹 Nitpick comments (7)
examples/prisma-next-demo/test/no-emit-typed-flow.test-d.ts (1)
20-22: ⚡ Quick winUse package-export imports in this example test
Line 20 and Line 21 import internals via relative paths. For
examples/**/test/**, this should validate the public API surface by importing from package exports (@prisma-next/...) instead.As per coding guidelines, “Cross-package and integration tests: Import from package exports (
@prisma-next/...) to verify the public API.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/prisma-next-demo/test/no-emit-typed-flow.test-d.ts` around lines 20 - 22, The test imports package internals via relative paths; update the imports to use the package-export public API instead: replace the relative imports that bring in the types/symbols "contract" and "sql" with their corresponding exported package paths so the test validates the public surface (import the "contract" type and the "sql" helper from the package exports for prisma/prisma-no-emit rather than from ../prisma/contract and ../src/prisma-no-emit/context).packages/2-sql/4-lanes/relational-core/test/utils.ts (1)
4-11: ⚡ Quick winAvoid
/exports/barrel imports in this test utility.Line 10 and Line 11 import through
../src/exports/*; switch these to direct module imports to keep dependency boundaries explicit.As per coding guidelines: “Avoid barrel files for exports; use direct file imports instead” and “Import from shared plane packages using package name and export identifier … not
/exports/paths.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/2-sql/4-lanes/relational-core/test/utils.ts` around lines 4 - 11, The test utility currently imports Adapter, Codec, CodecRegistry, LoweredStatement, SelectAst and ExecutionContext from barrel paths ('../src/exports/*'); change these to import the same symbols directly from their concrete modules (the specific source files that define Adapter, Codec, CodecRegistry, LoweredStatement, SelectAst and ExecutionContext) so you avoid `/exports/` barrel imports and keep dependency boundaries explicit; update the import statements to reference the original modules that declare those types and remove the barrel paths.packages/3-targets/3-targets/postgres/src/core/codecs.ts (1)
55-72: 💤 Low valueRedundant undefined check for
precisionparameter.The function parameter declares
precisionasreadonly precision: number(required), but line 60 checksif (precision === undefined). This check is unreachable given the type signature.If
precisioncan actually be undefined at runtime (e.g., from untyped callers), consider changing the type toreadonly precision?: number. Otherwise, remove the redundant check.♻️ Suggested fix if precision is guaranteed
export const pgNumericRenderOutputType = (typeParams: { readonly precision: number; readonly scale?: number; }): string | undefined => { const precision = typeParams.precision; - if (precision === undefined) return undefined; if ( typeof precision !== 'number' || !Number.isFinite(precision) || !Number.isInteger(precision) ) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts` around lines 55 - 72, The check for precision being undefined in pgNumericRenderOutputType is unreachable because typeParams declares readonly precision: number; remove the if (precision === undefined) return undefined; branch and any related early-return logic; alternatively, if precision can be missing at runtime, change the signature to readonly precision?: number and keep the check — reference the function name pgNumericRenderOutputType and the property typeParams.precision when making the change.packages/2-sql/2-authoring/contract-psl/test/fixtures.ts (1)
160-164: ⚡ Quick winHarden codec-id presence checks with
Object.hasOwn.Line 160 and Line 163 currently rely on plain index lookups, which can accidentally treat prototype keys (for example,
"constructor") as valid codec ids.Suggested patch
export const postgresCodecLookup: CodecLookup = { get: (id: string) => { - if (!targetTypesByCodecId[id]) return undefined; + if (!Object.hasOwn(targetTypesByCodecId, id)) return undefined; return { id } as ReturnType<CodecLookup['get']>; }, - targetTypesFor: (id: string) => targetTypesByCodecId[id], + targetTypesFor: (id: string) => + Object.hasOwn(targetTypesByCodecId, id) ? targetTypesByCodecId[id] : undefined, metaFor: () => undefined, renderOutputTypeFor: () => undefined, };As per coding guidelines "Always use
Object.hasOwn()instead ofhasOwnProperty()to check for own properties."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/2-sql/2-authoring/contract-psl/test/fixtures.ts` around lines 160 - 164, Replace plain property index checks on targetTypesByCodecId with Object.hasOwn to avoid treating prototype keys as valid codec ids: in the implementation returning ReturnType<CodecLookup['get']> change the existence check from if (!targetTypesByCodecId[id]) to if (!Object.hasOwn(targetTypesByCodecId, id)) and in the targetTypesFor method ensure it only returns targetTypesByCodecId[id] when Object.hasOwn(targetTypesByCodecId, id) (otherwise return undefined); leave metaFor as-is.packages/1-framework/1-core/framework-components/test/codec.types.test-d.ts (1)
249-249: 💤 Low valueUse
toExtendinstead of deprecatedtoMatchTypeOf.Per coding guidelines,
toMatchTypeOf()is deprecated in Vitest'sexpectTypeOfAPI. UsetoExtend<T>()instead.♻️ Proposed fix
- expectTypeOf<typeof reg>().toMatchTypeOf<Map<string, AnyCodecDescriptor>>(); + expectTypeOf<typeof reg>().toExtend<Map<string, AnyCodecDescriptor>>();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/1-core/framework-components/test/codec.types.test-d.ts` at line 249, Replace the deprecated expectTypeOf(...).toMatchTypeOf usage with the supported generic toExtend form: locate the assertion using reg (expectTypeOf<typeof reg>().toMatchTypeOf<Map<string, AnyCodecDescriptor>>()) and change it to use toExtend with the same generic type (expectTypeOf<typeof reg>().toExtend<Map<string, AnyCodecDescriptor>>()); ensure the generic parameter remains Map<string, AnyCodecDescriptor> and the assertion target remains typeof reg.packages/1-framework/1-core/framework-components/src/shared/column-spec.ts (1)
34-55: ⚡ Quick winThread the
TCodecIdgeneric throughColumnSpecto preserve codec ID literals in helper return types.
ColumnTypeDescriptoris generic inTCodecId, butColumnSpecand thecolumn()function drop this parameter. Codec ID literals widen tostringat the helper boundary, losing type precision that downstream code (e.g.,contract-dsl.ts) already extracts viaDescriptor['codecId']. Threading the generic would maintain literal fidelity through the helper surface and align with the field-state pattern used in contract authoring.♻️ Suggested type-surface fix
-export interface ColumnSpec<R, P extends Record<string, unknown> | undefined> - extends ColumnTypeDescriptor { +export interface ColumnSpec< + TCodecId extends string, + R, + P extends Record<string, unknown> | undefined, +> extends ColumnTypeDescriptor<TCodecId> { readonly codecFactory: (ctx: CodecInstanceContext) => R; readonly typeParams: P; } -export function column<R, P extends Record<string, unknown> | undefined>( +export function column< + TCodecId extends string, + R, + P extends Record<string, unknown> | undefined, +>( codecFactory: (ctx: CodecInstanceContext) => R, - codecId: string, + codecId: TCodecId, typeParams: P, nativeType: string, -): ColumnSpec<R, P> { +): ColumnSpec<TCodecId, R, P> { return { codecFactory, codecId, typeParams, nativeType, }; } export type ColumnHelperFor<D extends CodecDescriptor<any>> = ( ...args: any[] -) => ColumnSpec<unknown, ColumnHelperParams<D>>; +) => ColumnSpec<D['codecId'], unknown, ColumnHelperParams<D>>; export type ColumnHelperForStrict<D extends CodecDescriptor<any>> = ( ...args: any[] -) => ColumnSpec<ReturnType<ReturnType<D['factory']>>, ColumnHelperParams<D>>; +) => ColumnSpec< + D['codecId'], + ReturnType<ReturnType<D['factory']>>, + ColumnHelperParams<D> +>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/1-core/framework-components/src/shared/column-spec.ts` around lines 34 - 55, ColumnSpec currently drops the TCodecId generic from ColumnTypeDescriptor which widens codecId literals to string; update ColumnSpec to be generic in TCodecId and extend ColumnTypeDescriptor<TCodecId> (e.g. change the declaration to something like interface ColumnSpec<TCodecId extends string, R, P extends Record<string, unknown> | undefined> extends ColumnTypeDescriptor<TCodecId>), keep the existing codecFactory and typeParams members but use the P and R generics, and then propagate the TCodecId generic through the column() helper signature so helper return types preserve codec ID literals.packages/3-extensions/sql-orm-client/src/types.ts (1)
302-305: ⚡ Quick winUse
ifDefined()instead of inline conditional object spreads inparam().Line 302-Line 305 currently uses inline spread conditionals; this codepath should use the shared helper for consistency with repo conventions.
As per coding guidelines, "Use
ifDefined()from@prisma-next/utils/definedfor conditional object spreads instead of inline conditional spread patterns".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/sql-orm-client/src/types.ts` around lines 302 - 305, The inline conditional spreads in param() should be replaced with the shared helper ifDefined from `@prisma-next/utils/defined`: import ifDefined and pass ParamRef.of(value, ifDefined({ codecId }, { refs })) (or a single ifDefined call that combines the optional codecId and refs objects) instead of the current ...(codecId !== undefined ? { codecId } : {}), ...(refs !== undefined ? { refs } : {}); update the param() return to use ifDefined and remove the inline conditional spreads while keeping ParamRef.of and the same option shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture` docs/adrs/ADR 184 - Codec-owned value serialization.md:
- Line 59: The paragraph claims "the defineCodec() factory provides these
defaults" but defineCodec() is retired; update the text that describes
encodeJson/decodeJson to either mark the defineCodec() statement as historical
("historically, defineCodec() provided these defaults") or reword it to the
current authoring surface (e.g., explain that the codec class/descriptor default
implementations make encodeJson/decodeJson identity for JSON-safe types and only
Date/binary codecs override them); do the same for the duplicate instance of
this sentence elsewhere so the ADR is internally consistent.
In `@docs/reference/codec-authoring-guide.md`:
- Around line 262-269: Update the docs' references that point to old filenames
so readers are directed to the new class-style implementations: change mentions
of the pre-migration files (e.g. "sql-codecs.ts" and "codecs.ts") to the
migrated filenames "sql-codecs-class.ts" and "codecs-class.ts" in the paragraphs
that describe the aliasCodec helper, CodecDescriptorImpl<P>, and the alias
factory pattern (around the aliasCodec/CodecDescriptorImpl example); ensure any
other occurrences (including the similar text at lines ~320-331) are updated
consistently so the guide links to the current implementations.
In `@examples/prisma-next-demo/test/no-emit-typed-flow.test-d.ts`:
- Around line 27-32: The test title claims the User.id is a string but current
assertions only check presence; update the test "field.id.uuidv4() produces a
string-typed id field on User" to actually assert the scalar type (or rename the
test to reflect presence-only checks). Specifically, in the test referencing
UserStorageFields and IdField (from (typeof
contract.models)['User']['storage']['fields']), add a type-level assertion that
IdField's column (or the appropriate scalar property on IdField) is string-typed
(e.g., assert IdField['column'] equals string) using expectTypeOf, or change the
test name to remove the "string-typed" claim.
In `@packages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.ts`:
- Around line 67-76: lengthParamsSchema currently requires length but
LengthParams marks it optional and the sqlCharColumn/sqlVarcharColumn helpers
default params to {} which produces descriptors that validateTypeParams later
rejects; either relax lengthParamsSchema to make length optional (change to
'length?') so bare char/varchar is supported, or make LengthParams require
length and stop defaulting params to {} in the helper functions (sqlCharColumn,
sqlVarcharColumn and the other affected helpers mentioned) so callers must
provide length; update validateTypeParams usage accordingly to match the chosen
contract.
In `@packages/2-sql/4-lanes/relational-core/test/utils.ts`:
- Around line 39-41: profile.codecs() currently returns a new empty registry
every call (via emptyCodecRegistry()), causing registered codecs to be lost
between calls; fix by creating a single stable registry instance (e.g., const
codecRegistry = emptyCodecRegistry()) and have profile.codecs() return that same
codecRegistry so registrations persist; update the utils.ts factory that defines
profile.codecs() to initialize the registry once (module-scope or closure) and
return it on each call, leaving emptyCodecRegistry() usage but only invoked
once.
In `@packages/2-sql/5-runtime/src/codecs/encoding.ts`:
- Around line 56-62: When metadata.refs is present the resolution must use the
column path only and must not fall back to id-based resolution; in the block
that checks contractCodecs.forColumn(metadata.refs.table, metadata.refs.column)
(symbols: metadata.refs, contractCodecs, forColumn), change the logic so that if
byColumn is falsy you stop resolution immediately (e.g. return undefined or
throw a clear error) instead of continuing to
contractCodecs.forCodecId(metadata.codecId) or registry.get(metadata.codecId);
this ensures a missing column lookup fails fast rather than downgrading to
codec-id dispatch.
In `@packages/3-extensions/arktype-json/src/core/arktype-json-codec-class.ts`:
- Around line 144-154: Replace the blind cast in rehydrateSchema by adding a
type predicate (e.g. isArktypeSchemaLike) and use it to assert the return of
ark.schema(jsonIr) is an ArktypeSchemaLike before returning; if the predicate
fails throw the same runtimeError (preserving ARKTYPE_JSON_CODEC_ID and jsonIr)
so invalid shapes are rejected instead of being force-cast; keep the existing
try/catch and error message behavior around ark.schema and runtimeError.
In `@packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts`:
- Around line 75-79: The test uses an explicit any cast and a biome-ignore;
replace that with a TypeScript compile-time suppression: remove the "as any" and
the biome lint suppression, and instead place a single-line "@ts-expect-error —
intentionally passing non-callable to test runtime guard" immediately above the
arktypeJsonColumn(notASchema) call (leaving notASchema typed as an object), so
the runtime negative-path assertion remains but the test follows the repo rule
against any/biome ignores; target the arktypeJsonColumn call in
arktype-json-codec-class.test.ts when making this change.
In
`@packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts`:
- Around line 140-142: The type-test assertion for variable col uses the
deprecated matcher toMatchTypeOf; update the expectTypeOf(col) call to use
toExtend with the same generic target type ColumnSpec<ArktypeJsonCodecClass<{
name: string; price: number }>, ArktypeJsonTypeParams> so the assertion reads
expectTypeOf(col).toExtend<...>(), keeping the same generics (ColumnSpec,
ArktypeJsonCodecClass, ArktypeJsonTypeParams) and not changing the tested
variable name col.
In `@packages/3-extensions/sql-orm-client/src/types.ts`:
- Around line 292-294: refsFromLeft currently only handles kind === 'column-ref'
which drops refs for other AnyExpression shapes (so result.buildAst() loses refs
and breaks codec dispatch); update refsFromLeft (and the comparison factory
usage that passes result.buildAst()) to preserve/collect refs for any expression
by returning refs found on the expression (e.g., expression.refs if present) or
by recursively extracting column-ref nodes from
BinaryExpr/Unary/Identifier/Literal wrappers so column refs are not lost (keep
the return type { table: string; column: string } | undefined or adjust to
return refs array if needed and update callers accordingly); also replace the
inline conditional spreads in param() that do ...(codecId !== undefined ? {
codecId } : {}) and ...(refs !== undefined ? { refs } : {}) with the ifDefined()
helper from '@prisma-next/utils/defined' (add the import) to follow the
repository guideline.
In `@packages/3-targets/3-targets/postgres/src/core/codecs-class.ts`:
- Around line 110-126: The TypeScript type LengthParams marks length as optional
but lengthParamsSchema requires it, causing runtime validation failures when
helpers (e.g., pgBitColumn) pass {} as params; update lengthParamsSchema to make
length optional (change the schema key to 'length?' with the same constraint) so
the runtime arktype matches the LengthParams type and existing callers that
default params = {} continue to validate correctly.
In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts`:
- Around line 250-264: The decode paths (async decode and decodeJson) and the
encoder path (encode and encodeJson) should validate Date objects: after
constructing a Date in decode/decodeJson, check that date.getTime() is not NaN
and throw a TypeError (same contract message) for malformed/invalid ISO strings;
in encode/encodeJson ensure the provided Date is valid (check
isFinite(date.getTime()) or !isNaN) and throw TypeError if it is an Invalid Date
before calling toISOString; update the functions named encode, decode,
encodeJson, and decodeJson in codecs-class.ts accordingly.
---
Outside diff comments:
In `@packages/2-sql/5-runtime/src/sql-context.ts`:
- Around line 493-516: The code currently caches the concrete Codec instance in
byCodecId (via voidFactory(undefined)(ctx)), causing every non-parameterized
column to share the same instance and captured ctx; instead cache the
parameterless factory function and invoke it per-column so each materialization
site gets its own Codec with its own SqlCodecInstanceContext. Concretely: in the
branch that handles non-parameterized codecs (where descriptor.factory is cast
to voidFactory), store the function returned by voidFactory(undefined) in
byCodecId (not the result of calling it with ctx), then for each column call
that cached factory with a fresh SqlCodecInstanceContext to produce
resolvedCodec; update uses of cached/voidFactory/byCodecId/resolvedCodec
accordingly so contractCodecs.forColumn() returns per-column instances.
---
Nitpick comments:
In `@examples/prisma-next-demo/test/no-emit-typed-flow.test-d.ts`:
- Around line 20-22: The test imports package internals via relative paths;
update the imports to use the package-export public API instead: replace the
relative imports that bring in the types/symbols "contract" and "sql" with their
corresponding exported package paths so the test validates the public surface
(import the "contract" type and the "sql" helper from the package exports for
prisma/prisma-no-emit rather than from ../prisma/contract and
../src/prisma-no-emit/context).
In `@packages/1-framework/1-core/framework-components/src/shared/column-spec.ts`:
- Around line 34-55: ColumnSpec currently drops the TCodecId generic from
ColumnTypeDescriptor which widens codecId literals to string; update ColumnSpec
to be generic in TCodecId and extend ColumnTypeDescriptor<TCodecId> (e.g. change
the declaration to something like interface ColumnSpec<TCodecId extends string,
R, P extends Record<string, unknown> | undefined> extends
ColumnTypeDescriptor<TCodecId>), keep the existing codecFactory and typeParams
members but use the P and R generics, and then propagate the TCodecId generic
through the column() helper signature so helper return types preserve codec ID
literals.
In `@packages/1-framework/1-core/framework-components/test/codec.types.test-d.ts`:
- Line 249: Replace the deprecated expectTypeOf(...).toMatchTypeOf usage with
the supported generic toExtend form: locate the assertion using reg
(expectTypeOf<typeof reg>().toMatchTypeOf<Map<string, AnyCodecDescriptor>>())
and change it to use toExtend with the same generic type (expectTypeOf<typeof
reg>().toExtend<Map<string, AnyCodecDescriptor>>()); ensure the generic
parameter remains Map<string, AnyCodecDescriptor> and the assertion target
remains typeof reg.
In `@packages/2-sql/2-authoring/contract-psl/test/fixtures.ts`:
- Around line 160-164: Replace plain property index checks on
targetTypesByCodecId with Object.hasOwn to avoid treating prototype keys as
valid codec ids: in the implementation returning ReturnType<CodecLookup['get']>
change the existence check from if (!targetTypesByCodecId[id]) to if
(!Object.hasOwn(targetTypesByCodecId, id)) and in the targetTypesFor method
ensure it only returns targetTypesByCodecId[id] when
Object.hasOwn(targetTypesByCodecId, id) (otherwise return undefined); leave
metaFor as-is.
In `@packages/2-sql/4-lanes/relational-core/test/utils.ts`:
- Around line 4-11: The test utility currently imports Adapter, Codec,
CodecRegistry, LoweredStatement, SelectAst and ExecutionContext from barrel
paths ('../src/exports/*'); change these to import the same symbols directly
from their concrete modules (the specific source files that define Adapter,
Codec, CodecRegistry, LoweredStatement, SelectAst and ExecutionContext) so you
avoid `/exports/` barrel imports and keep dependency boundaries explicit; update
the import statements to reference the original modules that declare those types
and remove the barrel paths.
In `@packages/3-extensions/sql-orm-client/src/types.ts`:
- Around line 302-305: The inline conditional spreads in param() should be
replaced with the shared helper ifDefined from `@prisma-next/utils/defined`:
import ifDefined and pass ParamRef.of(value, ifDefined({ codecId }, { refs }))
(or a single ifDefined call that combines the optional codecId and refs objects)
instead of the current ...(codecId !== undefined ? { codecId } : {}), ...(refs
!== undefined ? { refs } : {}); update the param() return to use ifDefined and
remove the inline conditional spreads while keeping ParamRef.of and the same
option shape.
In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts`:
- Around line 55-72: The check for precision being undefined in
pgNumericRenderOutputType is unreachable because typeParams declares readonly
precision: number; remove the if (precision === undefined) return undefined;
branch and any related early-return logic; alternatively, if precision can be
missing at runtime, change the signature to readonly precision?: number and keep
the check — reference the function name pgNumericRenderOutputType and the
property typeParams.precision when making the change.
🪄 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: 69fcb105-9c82-4733-9268-a3018eb3c54d
⛔ Files ignored due to path filters (6)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/codec-registration-completion/plan.mdis excluded by!projects/**projects/codec-registration-completion/spec.mdis excluded by!projects/**projects/codec-registration-completion/specs/class-based-codec-design.spec.mdis excluded by!projects/**projects/codec-registration-completion/specs/factory-defined-codec-types.spec.mdis excluded by!projects/**projects/codec-registration-completion/specs/typed-codec-flow.spec.mdis excluded by!projects/**
📒 Files selected for processing (218)
docs/README.mddocs/Testing Guide.mddocs/architecture docs/adrs/ADR 152 - Execution Plane Descriptors and Instances.mddocs/architecture docs/adrs/ADR 184 - Codec-owned value serialization.mddocs/architecture docs/adrs/ADR 186 - Codec-dispatched type rendering.mddocs/architecture docs/adrs/ADR 202 - Codec trait system.mddocs/architecture docs/adrs/ADR 204 - Single-Path Async Codec Runtime.mddocs/architecture docs/adrs/ADR 205 - SQL cast emission is adapter policy.mddocs/architecture docs/adrs/ADR 207 - Codec call context per-query AbortSignal and column metadata.mddocs/architecture docs/adrs/ADR 208 - Higher-order codecs for parameterized types.mddocs/architecture docs/adrs/ADR 209 - Mongo result-shape as a structural plan field.mddocs/architecture docs/subsystems/2. Contract Emitter & Types.mddocs/reference/codec-authoring-guide.mddocs/reference/community-generator-migration-analysis.mddocs/reference/framework-gaps.mddocs/reference/typescript-patterns.mdexamples/prisma-next-demo/test/no-emit-typed-flow.test-d.tspackages/1-framework/0-foundation/contract/package.jsonpackages/1-framework/0-foundation/contract/src/exports/types.tspackages/1-framework/0-foundation/contract/src/standard-schema-utils.tspackages/1-framework/1-core/framework-components/README.mdpackages/1-framework/1-core/framework-components/src/control/control-stack.tspackages/1-framework/1-core/framework-components/src/exports/codec.tspackages/1-framework/1-core/framework-components/src/shared/codec-descriptor.tspackages/1-framework/1-core/framework-components/src/shared/codec-types.tspackages/1-framework/1-core/framework-components/src/shared/codec.tspackages/1-framework/1-core/framework-components/src/shared/column-spec.tspackages/1-framework/1-core/framework-components/src/shared/framework-components.tspackages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.tspackages/1-framework/1-core/framework-components/test/codec-types.types.test-d.tspackages/1-framework/1-core/framework-components/test/codec.test.tspackages/1-framework/1-core/framework-components/test/codec.types.test-d.tspackages/1-framework/1-core/framework-components/test/control-stack.test.tspackages/1-framework/2-authoring/contract/README.mdpackages/1-framework/2-authoring/contract/src/descriptors.tspackages/1-framework/2-authoring/contract/src/index.tspackages/1-framework/2-authoring/contract/test/descriptors.test.tspackages/1-framework/2-authoring/ids/package.jsonpackages/1-framework/2-authoring/ids/src/index.tspackages/1-framework/3-tooling/cli/src/control-api/contract-enrichment.tspackages/1-framework/3-tooling/cli/test/config-types.test.tspackages/1-framework/3-tooling/cli/test/control-api/contract-enrichment.test.tspackages/1-framework/3-tooling/emitter/src/domain-type-generation.tspackages/1-framework/3-tooling/emitter/test/domain-type-generation.test.tspackages/2-mongo-family/1-foundation/mongo-codec/README.mdpackages/2-mongo-family/1-foundation/mongo-codec/src/codec-registry.tspackages/2-mongo-family/1-foundation/mongo-codec/src/codecs.tspackages/2-mongo-family/1-foundation/mongo-codec/src/exports/index.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/1-foundation/mongo-codec/test/codecs.test-d.tspackages/2-mongo-family/1-foundation/mongo-codec/test/codecs.test.tspackages/2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.tspackages/2-mongo-family/2-authoring/contract-psl/test/derive-json-schema.test.tspackages/2-mongo-family/2-authoring/contract-psl/test/interpreter.polymorphism.test.tspackages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.tspackages/2-mongo-family/7-runtime/README.mdpackages/2-mongo-family/7-runtime/src/mongo-execution-stack.tspackages/2-mongo-family/7-runtime/test/codecs/decoding.test.tspackages/2-mongo-family/7-runtime/test/decode.integration.test.tspackages/2-mongo-family/7-runtime/test/mongo-execution-stack.test.tspackages/2-mongo-family/7-runtime/test/mongo-middleware.test.tspackages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.tspackages/2-sql/2-authoring/contract-psl/src/provider.tspackages/2-sql/2-authoring/contract-psl/test/fixtures.tspackages/2-sql/2-authoring/contract-psl/test/provider.test.tspackages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.tspackages/2-sql/2-authoring/contract-ts/src/contract-definition.tspackages/2-sql/2-authoring/contract-ts/src/contract-dsl.tspackages/2-sql/2-authoring/contract-ts/src/contract-lowering.tspackages/2-sql/2-authoring/contract-ts/test/config-types.test.tspackages/2-sql/2-authoring/contract-ts/test/contract-builder.contract-definition.test.tspackages/2-sql/2-authoring/contract-ts/test/contract-builder.value-objects.test.tspackages/2-sql/2-authoring/contract-ts/test/helpers/column-descriptor.tspackages/2-sql/3-tooling/emitter/test/emitter-hook.typeref-resolver.test.tspackages/2-sql/4-lanes/relational-core/README.mdpackages/2-sql/4-lanes/relational-core/package.jsonpackages/2-sql/4-lanes/relational-core/src/ast/codec-types.tspackages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.tspackages/2-sql/4-lanes/relational-core/src/ast/sql-codecs.tspackages/2-sql/4-lanes/relational-core/src/ast/types.tspackages/2-sql/4-lanes/relational-core/src/ast/validate-param-refs.tspackages/2-sql/4-lanes/relational-core/src/exports/ast.tspackages/2-sql/4-lanes/relational-core/src/expression.tspackages/2-sql/4-lanes/relational-core/src/query-lane-context.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/4-lanes/relational-core/test/ast/codec-factory.test.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-factory.types.test-d.tspackages/2-sql/4-lanes/relational-core/test/ast/codec-types.test.tspackages/2-sql/4-lanes/relational-core/test/ast/refs-propagation.test.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs-class.test.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs-class.types.test-d.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs.test.tspackages/2-sql/4-lanes/relational-core/test/ast/test-codec.tspackages/2-sql/4-lanes/relational-core/test/ast/test-helpers.tspackages/2-sql/4-lanes/relational-core/test/ast/validate-param-refs.test.tspackages/2-sql/4-lanes/relational-core/test/expression.test.tspackages/2-sql/4-lanes/relational-core/test/typed-codec-flow.test-d.tspackages/2-sql/4-lanes/relational-core/test/utils.tspackages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.tspackages/2-sql/4-lanes/sql-builder/src/runtime/expression-impl.tspackages/2-sql/4-lanes/sql-builder/src/runtime/field-proxy.tspackages/2-sql/4-lanes/sql-builder/src/runtime/functions.tspackages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.tspackages/2-sql/4-lanes/sql-builder/test/runtime/field-proxy.test.tspackages/2-sql/4-lanes/sql-builder/test/runtime/functions.test.tspackages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/2-sql/5-runtime/src/codecs/json-schema-validation.tspackages/2-sql/5-runtime/src/sql-context.tspackages/2-sql/5-runtime/src/sql-runtime.tspackages/2-sql/5-runtime/test/before-compile-chain.test.tspackages/2-sql/5-runtime/test/codec-async.test.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/contract-codec-registry.test.tspackages/2-sql/5-runtime/test/encoding-dispatch.test.tspackages/2-sql/5-runtime/test/execution-stack.test.tspackages/2-sql/5-runtime/test/json-schema-validation.test.tspackages/2-sql/5-runtime/test/mutation-default-generators.test.tspackages/2-sql/5-runtime/test/parameterized-types.test.tspackages/2-sql/5-runtime/test/seeded-secret-codec.tspackages/2-sql/5-runtime/test/sql-context.test.tspackages/2-sql/5-runtime/test/sql-runtime-abort.test.tspackages/2-sql/5-runtime/test/sql-runtime.test.tspackages/2-sql/5-runtime/test/test-codec.tspackages/2-sql/5-runtime/test/utils.tspackages/3-extensions/arktype-json/README.mdpackages/3-extensions/arktype-json/src/core/arktype-json-codec-class.tspackages/3-extensions/arktype-json/src/core/arktype-json-codec.tspackages/3-extensions/arktype-json/src/core/pack-meta.tspackages/3-extensions/arktype-json/src/exports/codecs.tspackages/3-extensions/arktype-json/src/exports/column-types.tspackages/3-extensions/arktype-json/src/exports/control.tspackages/3-extensions/arktype-json/src/exports/runtime.tspackages/3-extensions/arktype-json/test/arktype-json-codec-class.test.tspackages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.tspackages/3-extensions/arktype-json/test/arktype-json-codec.test-d.tspackages/3-extensions/arktype-json/test/arktype-json-codec.test.tspackages/3-extensions/arktype-json/test/extension-descriptors.test.tspackages/3-extensions/pgvector/src/core/codecs-class.tspackages/3-extensions/pgvector/src/core/codecs.tspackages/3-extensions/pgvector/src/core/descriptor-meta.tspackages/3-extensions/pgvector/src/exports/column-types.tspackages/3-extensions/pgvector/src/exports/runtime.tspackages/3-extensions/pgvector/src/types/codec-types.tspackages/3-extensions/pgvector/test/codec-render-output-type.test.tspackages/3-extensions/pgvector/test/codecs-class.types.test-d.tspackages/3-extensions/pgvector/test/codecs.test.tspackages/3-extensions/pgvector/test/operations.test.tspackages/3-extensions/pgvector/test/typed-descriptor-flow.test-d.tspackages/3-extensions/sql-orm-client/src/query-plan-mutations.tspackages/3-extensions/sql-orm-client/src/types.tspackages/3-extensions/sql-orm-client/src/where-binding.tspackages/3-extensions/sql-orm-client/test/codec-async.types.test-d.tspackages/3-extensions/sql-orm-client/test/filters.test.tspackages/3-extensions/sql-orm-client/test/integration/codec-async.test.tspackages/3-extensions/sql-orm-client/test/integration/create.test.tspackages/3-extensions/sql-orm-client/test/model-accessor.test.tspackages/3-extensions/sql-orm-client/test/query-plan-mutations.test.tspackages/3-extensions/sql-orm-client/test/repository.test.tspackages/3-extensions/sql-orm-client/test/rich-filters-and-where.test.tspackages/3-extensions/sql-orm-client/test/test-codec.tspackages/3-mongo-target/1-mongo-target/src/exports/runtime.tspackages/3-mongo-target/2-mongo-adapter/src/core/codecs.tspackages/3-mongo-target/2-mongo-adapter/src/exports/control.tspackages/3-mongo-target/2-mongo-adapter/src/exports/runtime.tspackages/3-mongo-target/2-mongo-adapter/test/codecs.test.tspackages/3-mongo-target/2-mongo-adapter/test/lowering.test.tspackages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.tspackages/3-mongo-target/2-mongo-adapter/test/mongo-adapter.test.tspackages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.tspackages/3-mongo-target/2-mongo-adapter/test/resolve-value.test.tspackages/3-targets/3-targets/postgres/package.jsonpackages/3-targets/3-targets/postgres/src/core/codecs-class.tspackages/3-targets/3-targets/postgres/src/core/codecs.tspackages/3-targets/3-targets/postgres/src/core/descriptor-meta.tspackages/3-targets/3-targets/postgres/src/exports/codec-types.tspackages/3-targets/3-targets/postgres/src/exports/codecs.tspackages/3-targets/3-targets/postgres/src/exports/runtime.tspackages/3-targets/3-targets/postgres/test/codec-render-output-type.test.tspackages/3-targets/3-targets/postgres/test/codecs-class.test.tspackages/3-targets/3-targets/postgres/test/codecs-class.types.test-d.tspackages/3-targets/3-targets/postgres/test/codecs.test.tspackages/3-targets/3-targets/postgres/test/typed-descriptor-flow.test-d.tspackages/3-targets/3-targets/sqlite/src/core/codecs-class.tspackages/3-targets/3-targets/sqlite/src/core/codecs.tspackages/3-targets/3-targets/sqlite/src/core/descriptor-meta.tspackages/3-targets/3-targets/sqlite/src/core/runtime-target.tspackages/3-targets/3-targets/sqlite/src/exports/codec-types.tspackages/3-targets/3-targets/sqlite/src/exports/codecs.tspackages/3-targets/3-targets/sqlite/test/codecs-class.types.test-d.tspackages/3-targets/3-targets/sqlite/test/typed-descriptor-flow.test-d.tspackages/3-targets/6-adapters/postgres/README.mdpackages/3-targets/6-adapters/postgres/src/core/adapter.tspackages/3-targets/6-adapters/postgres/src/core/codec-lookup.tspackages/3-targets/6-adapters/postgres/src/core/descriptor-meta.tspackages/3-targets/6-adapters/postgres/src/core/sql-renderer.tspackages/3-targets/6-adapters/postgres/src/exports/column-types.tspackages/3-targets/6-adapters/postgres/src/exports/runtime.tspackages/3-targets/6-adapters/postgres/test/adapter.test.tspackages/3-targets/6-adapters/postgres/test/descriptor-parity.test.tspackages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.tspackages/3-targets/6-adapters/postgres/test/test-codec.tspackages/3-targets/6-adapters/sqlite/README.mdpackages/3-targets/6-adapters/sqlite/src/core/adapter.tspackages/3-targets/6-adapters/sqlite/src/core/runtime-adapter.tspackages/3-targets/6-adapters/sqlite/src/exports/codec-types.tspackages/3-targets/6-adapters/sqlite/test/codecs.test.tstest/integration/test/authoring/parity/ts-psl-parity.real-packs.test.tstest/integration/test/contract-builder.test.tstest/integration/test/cross-package/cross-family-codec.test.tstest/integration/test/cross-package/test-codec.tstest/integration/test/mongo/migration-psl-authoring.test.tstest/integration/test/pgvector.test.tstest/utils/README.mdtest/utils/src/column-descriptors.ts
💤 Files with no reviewable changes (11)
- packages/2-mongo-family/7-runtime/test/decode.integration.test.ts
- packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.ts
- packages/2-sql/4-lanes/relational-core/test/ast/codec-types.test.ts
- packages/3-extensions/pgvector/src/core/codecs.ts
- packages/2-sql/5-runtime/src/codecs/json-schema-validation.ts
- packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts
- packages/3-extensions/arktype-json/test/arktype-json-codec.test-d.ts
- packages/1-framework/2-authoring/contract/src/descriptors.ts
- packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.ts
- packages/2-sql/5-runtime/test/json-schema-validation.test.ts
- packages/3-extensions/arktype-json/test/arktype-json-codec.test.ts
| The consumer reads `"2024-01-15T00:00:00.000Z"`, looks up `pg/timestamptz@1`, calls `decodeJson(...)`, gets a `Date` object. | ||
|
|
||
| Every codec has `encodeJson` and `decodeJson`. For JSON-safe types (strings, numbers, booleans, null), they are identity functions — the `codec()` factory provides these defaults. Only codecs for types that JSON can't represent (`Date`, binary data, etc.) override them. | ||
| Every codec has `encodeJson` and `decodeJson`. For JSON-safe types (strings, numbers, booleans, null), they are identity functions — the `defineCodec()` factory provides these defaults. Only codecs for types that JSON can't represent (`Date`, binary data, etc.) override them. |
There was a problem hiding this comment.
Rephrase the identity-default note in current terms.
The retrospective note already says defineCodec() is retired, but these paragraphs still describe its defaults in the present tense. Please restate this in terms of the current class/descriptor authoring surface, or mark it explicitly as historical, so the ADR does not read as internally contradictory.
As per coding guidelines, "**/*.{md,mdx}: Keep docs current (READMEs, rules, links)`."
Also applies to: 136-136
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/architecture` docs/adrs/ADR 184 - Codec-owned value serialization.md at
line 59, The paragraph claims "the defineCodec() factory provides these
defaults" but defineCodec() is retired; update the text that describes
encodeJson/decodeJson to either mark the defineCodec() statement as historical
("historically, defineCodec() provided these defaults") or reword it to the
current authoring surface (e.g., explain that the codec class/descriptor default
implementations make encodeJson/decodeJson identity for JSON-safe types and only
Date/binary codecs override them); do the same for the duplicate instance of
this sentence elsewhere so the ADR is internally consistent.
| The Postgres adapter ships a small file-local `aliasCodec` helper that | ||
| derives codec instances via | ||
| `Object.create(Object.getPrototypeOf(baseCodec))` + `Object.assign` | ||
| + `Object.defineProperty(_, 'id', ...)`; aliases are authored as | ||
| `class extends CodecDescriptorImpl<P>` declarations whose factory wraps | ||
| the base's factory. See | ||
| `packages/3-targets/3-targets/postgres/src/core/codecs.ts` for the | ||
| canonical pattern. |
There was a problem hiding this comment.
Update the reference implementation paths.
These sections still point readers to pre-migration filenames like sql-codecs.ts and codecs.ts, but the class-form implementations in this PR live under sql-codecs-class.ts / codecs-class.ts. As written, the new guide sends contributors to stale or removed files right after introducing the replacement pattern.
As per coding guidelines, "Keep docs current (READMEs, rules, links)."
Also applies to: 320-331
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reference/codec-authoring-guide.md` around lines 262 - 269, Update the
docs' references that point to old filenames so readers are directed to the new
class-style implementations: change mentions of the pre-migration files (e.g.
"sql-codecs.ts" and "codecs.ts") to the migrated filenames "sql-codecs-class.ts"
and "codecs-class.ts" in the paragraphs that describe the aliasCodec helper,
CodecDescriptorImpl<P>, and the alias factory pattern (around the
aliasCodec/CodecDescriptorImpl example); ensure any other occurrences (including
the similar text at lines ~320-331) are updated consistently so the guide links
to the current implementations.
| test('field.id.uuidv4() produces a string-typed id field on User', () => { | ||
| type UserStorageFields = (typeof contract.models)['User']['storage']['fields']; | ||
| expectTypeOf<UserStorageFields>().toHaveProperty('id'); | ||
| type IdField = UserStorageFields['id']; | ||
| expectTypeOf<IdField>().toHaveProperty('column'); | ||
| }); |
There was a problem hiding this comment.
Test claim and assertion are misaligned
Line 27 says this verifies a string-typed id, but Line 29–Line 31 only verify property presence (id, column). Please add a concrete type assertion for the id scalar type (or rename the test to match current assertions).
As per coding guidelines, “Avoid tautological tests that only restate fixture input - tests must verify behavior, not mirror object shape passed by the test itself.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/prisma-next-demo/test/no-emit-typed-flow.test-d.ts` around lines 27
- 32, The test title claims the User.id is a string but current assertions only
check presence; update the test "field.id.uuidv4() produces a string-typed id
field on User" to actually assert the scalar type (or rename the test to reflect
presence-only checks). Specifically, in the test referencing UserStorageFields
and IdField (from (typeof contract.models)['User']['storage']['fields']), add a
type-level assertion that IdField's column (or the appropriate scalar property
on IdField) is string-typed (e.g., assert IdField['column'] equals string) using
expectTypeOf, or change the test name to remove the "string-typed" claim.
| if (!byId.has(codecDescriptor.codecId)) { | ||
| try { | ||
| const representative = codecDescriptor.factory(undefined as never)({ | ||
| name: `<lookup:${codecDescriptor.codecId}>`, | ||
| } as Parameters<ReturnType<typeof codecDescriptor.factory>>[0]); | ||
| byId.set(codecDescriptor.codecId, representative); | ||
| } catch { | ||
| // Parameterized factory needs real params; leave `byId.get()` | ||
| // returning `undefined` for this codec id. | ||
| } |
There was a problem hiding this comment.
Don’t treat every factory failure as “parameterized”.
The broad catch {} makes any descriptor construction error look like an expected “needs real params” case. That means a registered codec can disappear from byId, and downstream codecLookup.get() consumers in this file then misclassify it as unregistered. Please narrow this to the expected missing-params path, or track descriptor presence separately instead of using factory success as the registration signal.
| const lengthParamsSchema = arktype({ | ||
| length: 'number.integer > 0', | ||
| }); | ||
|
|
||
| const precisionParamsSchema = arktype({ | ||
| 'precision?': 'number.integer >= 0 & number.integer <= 6', | ||
| }); | ||
|
|
||
| type LengthParams = { readonly length?: number }; | ||
| type PrecisionParams = { readonly precision?: number }; |
There was a problem hiding this comment.
Don't default char/varchar params to {} while the schema requires length.
lengthParamsSchema makes length mandatory, but LengthParams marks it optional and both helpers default to {}. That means sqlCharColumn() / sqlVarcharColumn() can successfully build a contract shape that this same descriptor later rejects in validateTypeParams().
🐛 Suggested fix
-type LengthParams = { readonly length?: number };
+type LengthParams = { readonly length: number };
-export const sqlCharColumn = (params: LengthParams = {}) =>
+export const sqlCharColumn = (params: LengthParams) =>
column(sqlCharDescriptorClass.factory(params), sqlCharDescriptorClass.codecId, params, 'char');
-export const sqlVarcharColumn = (params: LengthParams = {}) =>
+export const sqlVarcharColumn = (params: LengthParams) =>
column(
sqlVarcharDescriptorClass.factory(params),
sqlVarcharDescriptorClass.codecId,
params,
'varchar',
);If bare char / varchar is intentionally supported, then the schema should be relaxed to match instead of rejecting the helper output later.
Also applies to: 245-246, 290-296
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.ts` around
lines 67 - 76, lengthParamsSchema currently requires length but LengthParams
marks it optional and the sqlCharColumn/sqlVarcharColumn helpers default params
to {} which produces descriptors that validateTypeParams later rejects; either
relax lengthParamsSchema to make length optional (change to 'length?') so bare
char/varchar is supported, or make LengthParams require length and stop
defaulting params to {} in the helper functions (sqlCharColumn, sqlVarcharColumn
and the other affected helpers mentioned) so callers must provide length; update
validateTypeParams usage accordingly to match the chosen contract.
| it('rejects non-callable schema lookalikes at the call site', () => { | ||
| const notASchema = { foo: 'bar' }; | ||
| // biome-ignore lint/suspicious/noExplicitAny: deliberately malformed input for the call-site guard | ||
| expect(() => arktypeJsonColumn(notASchema as any)).toThrow(/callable arktype Type/); | ||
| }); |
There was a problem hiding this comment.
Avoid any and the Biome suppression in this negative-path test.
This runtime-guard test can use @ts-expect-error instead; the current as any plus biome-ignore violates the repo’s TypeScript/test rules for intentional invalid inputs.
♻️ Suggested cleanup
it('rejects non-callable schema lookalikes at the call site', () => {
const notASchema = { foo: 'bar' };
- // biome-ignore lint/suspicious/noExplicitAny: deliberately malformed input for the call-site guard
- expect(() => arktypeJsonColumn(notASchema as any)).toThrow(/callable arktype Type/);
+ // `@ts-expect-error` deliberate invalid input for the runtime guard
+ expect(() => arktypeJsonColumn(notASchema)).toThrow(/callable arktype Type/);
});As per coding guidelines, "Never use any type", "Never suppress biome lints", and "Use @ts-expect-error with a short reason in tests instead of blanket @ts-ignore."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts`
around lines 75 - 79, The test uses an explicit any cast and a biome-ignore;
replace that with a TypeScript compile-time suppression: remove the "as any" and
the biome lint suppression, and instead place a single-line "@ts-expect-error —
intentionally passing non-callable to test runtime guard" immediately above the
arktypeJsonColumn(notASchema) call (leaving notASchema typed as an object), so
the runtime negative-path assertion remains but the test follows the repo rule
against any/biome ignores; target the arktypeJsonColumn call in
arktype-json-codec-class.test.ts when making this change.
| expectTypeOf(col).toMatchTypeOf< | ||
| ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams> | ||
| >(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify no deprecated matcher usage remains in declaration type tests.
rg -n --glob '**/*.test-d.ts' '\.toMatchTypeOf\s*<'Repository: prisma/prisma-next
Length of output: 492
🏁 Script executed:
cat -n packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts | sed -n '135,145p'Repository: prisma/prisma-next
Length of output: 540
🏁 Script executed:
rg -n 'toMatchTypeOf' packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.tsRepository: prisma/prisma-next
Length of output: 101
Replace deprecated toMatchTypeOf matcher in this .test-d.ts assertion.
Line 140 uses toMatchTypeOf, which this repo disallows for type tests; use toExtend instead.
Proposed change
- expectTypeOf(col).toMatchTypeOf<
+ expectTypeOf(col).toExtend<
ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams>
>();📝 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.
| expectTypeOf(col).toMatchTypeOf< | |
| ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams> | |
| >(); | |
| expectTypeOf(col).toExtend< | |
| ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams> | |
| >(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts`
around lines 140 - 142, The type-test assertion for variable col uses the
deprecated matcher toMatchTypeOf; update the expectTypeOf(col) call to use
toExtend with the same generic target type ColumnSpec<ArktypeJsonCodecClass<{
name: string; price: number }>, ArktypeJsonTypeParams> so the assertion reads
expectTypeOf(col).toExtend<...>(), keeping the same generics (ColumnSpec,
ArktypeJsonCodecClass, ArktypeJsonTypeParams) and not changing the tested
variable name col.
| function refsFromLeft(left: AnyExpression): { table: string; column: string } | undefined { | ||
| return left.kind === 'column-ref' ? { table: left.table, column: left.column } : undefined; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate comparison factory usage =="
rg -n -C4 "COMPARISON_METHODS_META|scalarComparisonMethod|listComparisonMethod|\\.create\\(" packages/3-extensions/sql-orm-client/src
echo
echo "== Locate method binding call-sites that pass left expressions =="
rg -n -C6 "COMPARISON_METHODS_META\\[|create\\(left|create\\(.*left|buildAst\\(" packages/3-extensions/sql-orm-client/src
echo
echo "== Check whether expression-level refs are plumbed before AST conversion =="
rg -n -C6 "\\.refs\\b|buildAst\\(" packages/3-extensions/sql-orm-client/src packages/2-sql/4-lanes/sql-builder/srcRepository: prisma/prisma-next
Length of output: 44753
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Type definitions and param function =="
sed -n '296,320p' packages/3-extensions/sql-orm-client/src/types.ts
echo
echo "== Check AnyExpression type definition =="
rg -n "type AnyExpression|interface AnyExpression" packages/3-extensions/sql-orm-client/src -A 5
echo
echo "== Check MethodFactory type =="
rg -n "type MethodFactory" packages/3-extensions/sql-orm-client/src -A 3
echo
echo "== Full context around line 184 in model-accessor.ts =="
sed -n '165,190p' packages/3-extensions/sql-orm-client/src/model-accessor.tsRepository: prisma/prisma-next
Length of output: 7595
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check AnyExpression definition in sql-relational-core =="
find packages/2-sql -name "*.ts" -type f | xargs rg -l "export.*type AnyExpression|export.*AnyExpression =" | head -1 | xargs cat | head -100
echo
echo "== Check what buildAst() returns (AstExpression type) =="
rg -n "type AstExpression|interface AstExpression" packages/2-sql -A 3 | head -30
echo
echo "== Check Expression interface and its buildAst method =="
rg -n "interface Expression|type Expression" packages/3-extensions/sql-orm-client/src -B 2 -A 5 | grep -A 8 "buildAst"Repository: prisma/prisma-next
Length of output: 3308
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find AnyExpression definition directly =="
rg -n "^export type AnyExpression|^type AnyExpression" packages/2-sql -A 1
echo
echo "== Check discriminated union structure =="
rg -n "kind:" packages/2-sql/2-core/ast/src -C 2 | head -80
echo
echo "== Look at ColumnRef and IdentifierRef definitions =="
rg -n "^export class ColumnRef|^export class IdentifierRef" packages/2-sql -A 10 | head -60
echo
echo "== Check Expression interface buildAst signature =="
rg -n "buildAst.*:" packages/3-extensions/sql-orm-client/src -B 2 -A 1 | grep -B 3 -A 1 "buildAst"Repository: prisma/prisma-next
Length of output: 402
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Get full AnyExpression definition =="
sed -n '1674,1700p' packages/2-sql/4-lanes/relational-core/src/ast/types.ts
echo
echo "== Check ColumnRef structure =="
rg -n "^export class ColumnRef" packages/2-sql -A 15 | head -40
echo
echo "== Check what properties discriminate AnyExpression types =="
rg -n "readonly kind" packages/2-sql/4-lanes/relational-core/src/ast -B 2 | head -60
echo
echo "== Verify line 184 passes AST (not Expression) to factory =="
sed -n '180,190p' packages/3-extensions/sql-orm-client/src/model-accessor.tsRepository: prisma/prisma-next
Length of output: 6709
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check if ifDefined is imported in types.ts =="
head -30 packages/3-extensions/sql-orm-client/src/types.ts
echo
echo "== Check ifDefined utility =="
rg -n "export.*ifDefined" packages -A 5
echo
echo "== Look for example usage of ifDefined =="
rg -n "ifDefined" packages/3-extensions/sql-orm-client/src -B 2 -A 2 | head -40
echo
echo "== Check if any other files in this package use inline conditional spreads like param() =="
rg -n "\.\.\.\(" packages/3-extensions/sql-orm-client/src/types.ts | grep -E "undefined.*\?" | head -5Repository: prisma/prisma-next
Length of output: 1922
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Show param() and paramList() functions completely =="
sed -n '296,316p' packages/3-extensions/sql-orm-client/src/types.ts
echo
echo "== Check if ifDefined is imported or used anywhere in this file =="
grep -n "ifDefined\|from.*utils/defined" packages/3-extensions/sql-orm-client/src/types.ts
echo
echo "== Verify the actual Expression type and buildAst return =="
rg -n "class Expression|buildAst" packages/2-sql/4-lanes/relational-core/src/expression.ts | head -20
echo
echo "== Confirm line 180 actually passes resultAst (AnyExpression) to factory =="
sed -n '176,186p' packages/3-extensions/sql-orm-client/src/model-accessor.tsRepository: prisma/prisma-next
Length of output: 856
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check where buildAst() is called on Expression result =="
sed -n '165,190p' packages/3-extensions/sql-orm-client/src/model-accessor.ts
echo
echo "== Verify that resultAst is indeed passed to meta.create =="
sed -n '180,185p' packages/3-extensions/sql-orm-client/src/model-accessor.ts
echo
echo "== Confirm refsFromLeft logic - only handles column-ref =="
sed -n '292,294p' packages/3-extensions/sql-orm-client/src/types.ts
echo
echo "== Check if result is always guaranteed to be a ColumnRef or simple expression =="
rg -n "result\\.buildAst\\(\\)" packages/3-extensions/sql-orm-client/src/model-accessor.ts -B 5 -A 2
echo
echo "== Check what types of expressions can be returned from impl() =="
rg -n "Expression<ScopeField>" packages/3-extensions/sql-orm-client/src/model-accessor.ts -B 3 | head -40Repository: prisma/prisma-next
Length of output: 3260
Expression results lose refs when compared, and param() uses inline spreads instead of ifDefined() helper.
Line 292 only extracts refs from column-ref kind. When line 184 passes resultAst = result.buildAst() to the comparison factory, that AST can be any AnyExpression type (BinaryExpr, IdentifierRef, LiteralExpr, etc.), not just ColumnRef. Any non-column-ref expression loses refs, causing parameterized codec dispatch to fail and column-aware codec selection to be bypassed.
Additionally, lines 302–305 use inline conditional object spreads instead of the ifDefined() helper:
...(codecId !== undefined ? { codecId } : {}),
...(refs !== undefined ? { refs } : {}),Use ifDefined() from @prisma-next/utils/defined to follow the repository guideline for conditional object spreads in TypeScript files.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-extensions/sql-orm-client/src/types.ts` around lines 292 - 294,
refsFromLeft currently only handles kind === 'column-ref' which drops refs for
other AnyExpression shapes (so result.buildAst() loses refs and breaks codec
dispatch); update refsFromLeft (and the comparison factory usage that passes
result.buildAst()) to preserve/collect refs for any expression by returning refs
found on the expression (e.g., expression.refs if present) or by recursively
extracting column-ref nodes from BinaryExpr/Unary/Identifier/Literal wrappers so
column refs are not lost (keep the return type { table: string; column: string }
| undefined or adjust to return refs array if needed and update callers
accordingly); also replace the inline conditional spreads in param() that do
...(codecId !== undefined ? { codecId } : {}) and ...(refs !== undefined ? {
refs } : {}) with the ifDefined() helper from '@prisma-next/utils/defined' (add
the import) to follow the repository guideline.
| const lengthParamsSchema = arktype({ | ||
| length: 'number.integer > 0', | ||
| }); | ||
|
|
||
| const numericParamsSchema = arktype({ | ||
| precision: 'number.integer > 0 & number.integer <= 1000', | ||
| 'scale?': 'number.integer >= 0', | ||
| }); | ||
|
|
||
| const precisionParamsSchema = arktype({ | ||
| 'precision?': 'number.integer >= 0 & number.integer <= 6', | ||
| }); | ||
|
|
||
| type LengthParams = { readonly length?: number }; | ||
| type PrecisionParams = { readonly precision?: number }; | ||
| type NumericParams = { readonly precision: number; readonly scale?: number }; | ||
| type EnumParams = { readonly values?: readonly unknown[] }; |
There was a problem hiding this comment.
Type vs schema mismatch: LengthParams.length is optional but schema requires it.
LengthParams declares length?: number (optional), but lengthParamsSchema requires length: 'number.integer > 0'. This inconsistency means:
- The TypeScript type allows calling
pgBitColumn()with no params - But runtime validation via
paramsSchemawould reject{}becauselengthis required
Looking at usage (lines 748, 794), the column helpers default to params = {}, which would fail schema validation if params are validated at materialization time.
Either make length required in the type, or use 'length?': 'number.integer > 0' in the schema.
🔧 Proposed fix — align schema with optional length
const lengthParamsSchema = arktype({
- length: 'number.integer > 0',
+ 'length?': 'number.integer > 0',
});📝 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 lengthParamsSchema = arktype({ | |
| length: 'number.integer > 0', | |
| }); | |
| const numericParamsSchema = arktype({ | |
| precision: 'number.integer > 0 & number.integer <= 1000', | |
| 'scale?': 'number.integer >= 0', | |
| }); | |
| const precisionParamsSchema = arktype({ | |
| 'precision?': 'number.integer >= 0 & number.integer <= 6', | |
| }); | |
| type LengthParams = { readonly length?: number }; | |
| type PrecisionParams = { readonly precision?: number }; | |
| type NumericParams = { readonly precision: number; readonly scale?: number }; | |
| type EnumParams = { readonly values?: readonly unknown[] }; | |
| const lengthParamsSchema = arktype({ | |
| 'length?': 'number.integer > 0', | |
| }); | |
| const numericParamsSchema = arktype({ | |
| precision: 'number.integer > 0 & number.integer <= 1000', | |
| 'scale?': 'number.integer >= 0', | |
| }); | |
| const precisionParamsSchema = arktype({ | |
| 'precision?': 'number.integer >= 0 & number.integer <= 6', | |
| }); | |
| type LengthParams = { readonly length?: number }; | |
| type PrecisionParams = { readonly precision?: number }; | |
| type NumericParams = { readonly precision: number; readonly scale?: number }; | |
| type EnumParams = { readonly values?: readonly unknown[] }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-targets/3-targets/postgres/src/core/codecs-class.ts` around lines
110 - 126, The TypeScript type LengthParams marks length as optional but
lengthParamsSchema requires it, causing runtime validation failures when helpers
(e.g., pgBitColumn) pass {} as params; update lengthParamsSchema to make length
optional (change the schema key to 'length?' with the same constraint) so the
runtime arktype matches the LengthParams type and existing callers that default
params = {} continue to validate correctly.
| async encode(value: Date, _ctx: CodecCallContext): Promise<string> { | ||
| return value.toISOString(); | ||
| } | ||
| async decode(wire: string, _ctx: CodecCallContext): Promise<Date> { | ||
| return new Date(wire); | ||
| } | ||
| encodeJson(value: Date): JsonValue { | ||
| return value.toISOString(); | ||
| } | ||
| decodeJson(json: JsonValue): Date { | ||
| if (typeof json !== 'string') { | ||
| throw new TypeError('sqlite/datetime@1 contract value must be an ISO-8601 string'); | ||
| } | ||
| return new Date(json); | ||
| } |
There was a problem hiding this comment.
Reject malformed datetime strings instead of returning Invalid Date.
Both decode paths currently accept any string that new Date(...) can parse structurally, including values that become Invalid Date. That lets bad storage values or contract JSON escape the codec boundary and fail later in less obvious places.
🐛 Suggested validation
async decode(wire: string, _ctx: CodecCallContext): Promise<Date> {
- return new Date(wire);
+ const date = new Date(wire);
+ if (Number.isNaN(date.getTime())) {
+ throw new TypeError('sqlite/datetime@1 wire value must be a valid ISO-8601 string');
+ }
+ return date;
}
encodeJson(value: Date): JsonValue {
return value.toISOString();
}
decodeJson(json: JsonValue): Date {
if (typeof json !== 'string') {
throw new TypeError('sqlite/datetime@1 contract value must be an ISO-8601 string');
}
- return new Date(json);
+ const date = new Date(json);
+ if (Number.isNaN(date.getTime())) {
+ throw new TypeError('sqlite/datetime@1 contract value must be a valid ISO-8601 string');
+ }
+ return date;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts` around lines
250 - 264, The decode paths (async decode and decodeJson) and the encoder path
(encode and encodeJson) should validate Date objects: after constructing a Date
in decode/decodeJson, check that date.getTime() is not NaN and throw a TypeError
(same contract message) for malformed/invalid ISO strings; in encode/encodeJson
ensure the provided Date is valid (check isFinite(date.getTime()) or !isNaN) and
throw TypeError if it is an Invalid Date before calling toISOString; update the
functions named encode, decode, encodeJson, and decodeJson in codecs-class.ts
accordingly.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (5)
packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts (1)
140-142:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse the approved matcher for
.test-d.tstype compatibility assertions.Line 140 uses deprecated
toMatchTypeOf; switch totoExtendfor this subtype-style check.♻️ Proposed fix
- expectTypeOf(col).toMatchTypeOf< + expectTypeOf(col).toExtend< ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams> >();#!/bin/bash # Verify deprecated matcher usage in .test-d.ts files. rg -n --glob '**/*.test-d.ts' '\.toMatchTypeOf\s*<'As per coding guidelines, "
**/*.test-d.ts: Do not use the deprecatedtoMatchTypeOf()matcher in Vitest'sexpectTypeOfAPI. UsetoExtend()instead."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts` around lines 140 - 142, Replace the deprecated matcher usage: locate the expectTypeOf assertion that references ColumnSpec<ArktypeJsonCodecClass<{ name: string; price: number }>, ArktypeJsonTypeParams> (the expectTypeOf(col) line) and change the matcher from toMatchTypeOf<...>() to toExtend<...>() so the subtype-style compatibility check uses the approved Vitest matcher for .test-d.ts files.packages/3-extensions/arktype-json/src/core/arktype-json-codec-class.ts (1)
144-147:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReplace the double-cast in schema rehydration with a predicate-based narrow.
Line 146 force-casts
ark.schema(jsonIr)withas unknown as ArktypeSchemaLike, which bypasses static safety and can mask shape drift.♻️ Proposed fix
function rehydrateSchema(jsonIr: object): ArktypeSchemaLike { try { - return ark.schema(jsonIr) as unknown as ArktypeSchemaLike; + const schema = ark.schema(jsonIr); + if (!isArktypeSchemaLike(schema)) { + throw new Error('Rehydrated schema is not callable with expression metadata'); + } + return schema; } catch (error) { throw runtimeError( 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED', `Failed to rehydrate arktype schema from contract IR: ${error instanceof Error ? error.message : String(error)}`, { codecId: ARKTYPE_JSON_CODEC_ID, jsonIr }, ); } }In current Arktype docs/types, what does `ark.schema(jsonIr)` return, and is the return value guaranteed to be callable with an `expression: string` property?As per coding guidelines, "
packages/**/*.{ts,tsx}: No blind casts in production code:as unknown as Xis forbidden outside tests. Prefer type predicates or fix the type surface."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/arktype-json/src/core/arktype-json-codec-class.ts` around lines 144 - 147, The rehydrateSchema function silently force-casts ark.schema(jsonIr) to ArktypeSchemaLike with "as unknown as", which violates the no-blind-casts rule; replace the double-cast with a runtime type-narrow: call ark.schema(jsonIr) into a local const (no cast), then implement and use a type predicate/is-check (for example isArktypeSchemaLike) that verifies the returned value is callable and has the expected shape (e.g., a function and the "expression" property of type string or other required members), throw a clear error if the check fails, and only then return the value typed as ArktypeSchemaLike; reference rehydrateSchema and ark.schema when locating code to change.packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts (1)
75-79:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace
as any+ lint suppression with@ts-expect-errorin the guard test.Lines 77-78 currently bypass both typing and lint policy; this negative-path test can express intent without
anyorbiome-ignore.♻️ Proposed fix
it('rejects non-callable schema lookalikes at the call site', () => { const notASchema = { foo: 'bar' }; - // biome-ignore lint/suspicious/noExplicitAny: deliberately malformed input for the call-site guard - expect(() => arktypeJsonColumn(notASchema as any)).toThrow(/callable arktype Type/); + // `@ts-expect-error` -- intentionally passing non-callable value to test runtime guard + expect(() => arktypeJsonColumn(notASchema)).toThrow(/callable arktype Type/); });#!/bin/bash # Verify explicit any and biome suppressions in this test file. rg -n 'biome-ignore|as any|@ts-expect-error' packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts -C2As per coding guidelines, "Never use
anytype", "Never suppress biome lints", and "Use@ts-expect-errorwith a short reason in tests."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts` around lines 75 - 79, The test currently silences type + lint rules by using "as any" and a "biome-ignore" comment; replace that with a TypeScript compile-time suppression using `@ts-expect-error` plus a short reason above the offending call to arktypeJsonColumn so the test still asserts the runtime guard without using any/biome ignores. Locate the failing test in arktype-json-codec-class.test.ts where arktypeJsonColumn(notASchema as any) is called and remove the "as any" cast and the biome-ignore, adding a single-line "@ts-expect-error TS-EXPLAIN: deliberate malformed input to exercise call-site guard" immediately above the call.packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts (1)
245-258:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject invalid
Datevalues at the codec boundary.
new Date(wire)/new Date(json)can produce anInvalid Date, andtoISOString()will also fail if callers hand this codec an invalidDate. Validating here keeps malformed storage values and contract JSON from escaping as poisonedDateinstances.🐛 Minimal validation
export class SqliteDatetimeCodec extends CodecImpl< @@ > { async encode(value: Date, _ctx: CodecCallContext): Promise<string> { + if (Number.isNaN(value.getTime())) { + throw new TypeError('sqlite/datetime@1 input value must be a valid Date'); + } return value.toISOString(); } async decode(wire: string, _ctx: CodecCallContext): Promise<Date> { - return new Date(wire); + const date = new Date(wire); + if (Number.isNaN(date.getTime())) { + throw new TypeError('sqlite/datetime@1 wire value must be a valid ISO-8601 string'); + } + return date; } encodeJson(value: Date): JsonValue { + if (Number.isNaN(value.getTime())) { + throw new TypeError('sqlite/datetime@1 contract value must be a valid Date'); + } return value.toISOString(); } decodeJson(json: JsonValue): Date { if (typeof json !== 'string') { throw new TypeError('sqlite/datetime@1 contract value must be an ISO-8601 string'); } - return new Date(json); + const date = new Date(json); + if (Number.isNaN(date.getTime())) { + throw new TypeError('sqlite/datetime@1 contract value must be a valid ISO-8601 string'); + } + return date; } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts` around lines 245 - 258, The codec must reject invalid Date instances and non-strings: in encode and encodeJson (functions encode(value: Date, _ctx: CodecCallContext) and encodeJson(value: Date)) check that value is a Date (instanceof Date) and that Number.isFinite(value.getTime())/!Number.isNaN(value.getTime()) before calling toISOString(), and throw a TypeError if not; in decode and decodeJson (decode(wire: string, _ctx: CodecCallContext) and decodeJson(json: JsonValue)) ensure the input is a string, construct the Date, then validate the produced Date with Number.isFinite(date.getTime())/!Number.isNaN(...) and throw a TypeError for invalid dates instead of returning them.packages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.ts (1)
67-76:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
sqlCharColumn()/sqlVarcharColumn()still expose an impossible params shape.
lengthParamsSchemarequireslength, butLengthParamsmakes it optional and both helpers default to{}. That lets callers build column specs that this same descriptor later rejects during params validation. Either requirelengthend-to-end, or relax the schema if barechar/varcharis intentionally supported.🐛 Minimal fix
-type LengthParams = { readonly length?: number }; +type LengthParams = { readonly length: number }; -export const sqlCharColumn = (params: LengthParams = {}) => +export const sqlCharColumn = (params: LengthParams) => column(sqlCharDescriptor.factory(params), sqlCharDescriptor.codecId, params, 'char'); -export const sqlVarcharColumn = (params: LengthParams = {}) => +export const sqlVarcharColumn = (params: LengthParams) => column(sqlVarcharDescriptor.factory(params), sqlVarcharDescriptor.codecId, params, 'varchar');Also applies to: 245-246, 290-291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.ts` around lines 67 - 76, The params shape is inconsistent: lengthParamsSchema currently requires length but the TypeScript type LengthParams and the sqlCharColumn/sqlVarcharColumn helpers treat length as optional and default to {}. Fix by making the contract consistent — either (A) relax lengthParamsSchema to allow an optional length (e.g., make the length rule accept undefined or mark it optional) so it matches LengthParams and the default {} behavior, or (B) make LengthParams require length and update sqlCharColumn/sqlVarcharColumn to require callers to supply length (and remove the default {}). Locate and update lengthParamsSchema, the LengthParams type, and the sqlCharColumn/sqlVarcharColumn call sites to implement the chosen option so params validation no longer rejects valid inputs.
🧹 Nitpick comments (4)
packages/3-targets/6-adapters/postgres/src/core/adapter.ts (1)
44-60: ⚡ Quick winDon’t leave the
factory(undefined)invariant implicit.This block now depends on every descriptor in
postgresCodecRegistrybeing safely materializable without params. That may be true for current builtins, but it is only enforced by comments right now. Please move this through a small helper owned next topostgresCodecRegistryor add a fail-fast assertion here so a future param-sensitive descriptor does not silently instantiate the wrong runtime codec.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-targets/6-adapters/postgres/src/core/adapter.ts` around lines 44 - 60, The code silently calls factory(undefined) for each descriptor from postgresCodecRegistry when constructing codecRegistry, which assumes every descriptor is paramless; add a fail-fast check or helper next to postgresCodecRegistry to enforce/verify that a descriptor is materializable without params before calling factory(undefined). Concretely, introduce a small predicate/helper (e.g., isParamlessDescriptor) or add an assertion in the codecRegistry initializer that inspects the descriptor shape and throws a clear error if params are required, and then only call (descriptor.factory)(undefined) (or use the helper) to produce the codec instance; reference codecRegistry, postgresCodecRegistry, CodecInstanceContext and the current factory(undefined) usage when making the change.packages/3-targets/6-adapters/sqlite/src/core/adapter.ts (1)
55-70: 🏗️ Heavy liftDon't materialize every descriptor with
factory(undefined).
sqliteCodecRegistrynow includes parameterized SQL descriptors, but this loop hard-casts everyfactoryto acceptunknownand instantiates it withundefined. The adapter registry therefore only works for descriptors whose runtime instances happen to ignore params; the first param-sensitive codec added here will be mis-materialized. Please keep this surface descriptor-based, or restrict this fallback registry to descriptors whose params are trulyvoid.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-targets/6-adapters/sqlite/src/core/adapter.ts` around lines 55 - 70, The current codecRegistry eagerly materializes every descriptor by calling descriptor.factory(undefined), which mis-instantiates parameterized descriptors from sqliteCodecRegistry; change this to preserve descriptor-based entries and only instantiate codecs when they are truly parameterless: in the codecRegistry initialization (and related logic around codecRegistry, sqliteCodecRegistry, CodecInstanceContext and factory), avoid calling factory(undefined) for all descriptors—instead detect parameterized descriptors (e.g., via a param-type flag or factory arity) and either keep the original descriptor in the registry or only call factory() for descriptors known to accept no params; ensure the fallback registry only materializes codecs whose params are void so parameterized codecs remain descriptor-based.packages/3-targets/3-targets/postgres/test/codecs.test.ts (1)
75-82: ⚡ Quick winDon't bypass parameterized descriptor factories here.
descriptor.factory(undefined as never)sidesteps the public params contract forcharacter,numeric,timestamp,enum, and the other parameterized descriptors in this table. The suite still checks encode/decode behavior, but it no longer proves those codecs can be constructed through their supported API shape. A smallparamsByScalarmap, or separate helpers for parameterized vs non-parameterized codecs, would keep these tests aligned with the descriptor-first surface.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-targets/3-targets/postgres/test/codecs.test.ts` around lines 75 - 82, The helper codecForScalar currently calls descriptor.factory(undefined as never) which bypasses the public params contract for parameterized descriptors; change codecForScalar to supply real params for parameterized descriptors (e.g. via a small paramsByScalar map or conditional helpers) and call descriptor.factory(params) for those scalars, falling back to descriptor.factory() or descriptor.factory(undefined) only for true non-parameterized descriptors so that descriptorByScalar/descriptor.factory and SYNTH_CTX are exercised via the intended public API shape.packages/3-targets/3-targets/sqlite/src/exports/codec-types.ts (1)
11-45: ⚡ Quick winKeep
src/exports/codec-types.tsas a thin re-export surface.
codecDescriptorMapandResolvemake this export file a second source of truth for SQLite codec membership. That raises the odds ofCodecTypesdrifting from the core registry/list when the next codec is added or removed. Please move the map/type derivation into../core/...and re-export the final alias from here so the public entrypoint stays aligned with the package’s single descriptor source.♻️ Suggested shape
-import type { ExtractCodecTypes } from '@prisma-next/sql-relational-core/ast'; -import { - sqlCharDescriptor, - sqlFloatDescriptor, - sqlIntDescriptor, - sqlVarcharDescriptor, -} from '@prisma-next/sql-relational-core/ast'; import type { JsonValue } from '../core/codecs'; -import { - sqliteBigintDescriptor, - sqliteBlobDescriptor, - sqliteDatetimeDescriptor, - sqliteIntegerDescriptor, - sqliteJsonDescriptor, - sqliteRealDescriptor, - sqliteTextDescriptor, -} from '../core/codecs-class'; - -const codecDescriptorMap = { - char: sqlCharDescriptor, - varchar: sqlVarcharDescriptor, - int: sqlIntDescriptor, - float: sqlFloatDescriptor, - text: sqliteTextDescriptor, - integer: sqliteIntegerDescriptor, - real: sqliteRealDescriptor, - blob: sqliteBlobDescriptor, - datetime: sqliteDatetimeDescriptor, - json: sqliteJsonDescriptor, - bigint: sqliteBigintDescriptor, -} as const; - -type Resolve<T> = { readonly [K in keyof T]: { readonly [P in keyof T[K]]: T[K][P] } }; - -export type CodecTypes = Resolve<ExtractCodecTypes<typeof codecDescriptorMap>>; +export type { CodecTypes } from '../core/codec-types'; export type { JsonValue };As per coding guidelines,
packages/**/src/exports/{adapter,codec-types,types}.tsre-export files must re-export from core without adding additional logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/2-sql/4-lanes/relational-core/src/codec-descriptor-registry.ts`:
- Around line 31-39: The registry currently overwrites duplicate codecId entries
causing inconsistent state; before calling byId.set(descriptor.codecId, widened)
in the loop over allDescriptors (where widened is cast to AnyDescriptor), check
if byId.has(descriptor.codecId) and throw a clear error (including the
conflicting codecId and descriptor identity) to fail-fast on duplicates; only
proceed to add to byTargetType when the codecId is not already present (so byId
and byTargetType remain consistent and descriptorFor(id) will match target-type
lists).
In `@packages/3-extensions/pgvector/src/core/codecs-class.ts`:
- Around line 82-118: The PgVectorCodec produced by factory() must retain
params.length (the declared dimension) and validate input across all ingress
paths: modify factory() to store the expected length on the PgVectorCodec
instance, add a single reused validator function (used by encode, decode, and
decodeJson) that checks the value is an array of numbers and that its length
equals the stored dimension, and update encodeJson/decodeJson/decode/encode to
call that validator and throw descriptive errors for wrong-dimension or
non-numeric entries; ensure contractCodecs.forColumn(table, column) returns a
codec that enforces the declared vector length rather than ignoring
params.length.
In `@packages/3-targets/3-targets/postgres/src/exports/codec-types.ts`:
- Around line 19-86: The file defines codecDescriptorMap, Resolve, and
CodecTypes inline which violates the export-only rule; move the
codecDescriptorMap, Resolve type helper, and the ExtractCodecTypes processing
into a new or existing module under src/core (e.g., export the computed type
there) and then change src/exports/codec-types.ts to only re-export the public
type (export type { CodecTypes } from '../core/...') so the exports file
contains no computation or logic and only exposes the type symbol CodecTypes.
In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts`:
- Around line 204-211: The encodeJson and decodeJson methods currently use
Node-only Buffer; replace that with a runtime-neutral base64 utility so src/core
stays platform-agnostic: implement or import cross-platform helpers (e.g.,
uint8ArrayToBase64 and base64ToUint8Array) that use Web APIs (atob/btoa or
TextEncoder/TextDecoder) and fall back to Buffer only when necessary, then
update encodeJson(value: Uint8Array) to return uint8ArrayToBase64(value) and
decodeJson(json: JsonValue) to validate string and return
base64ToUint8Array(json); keep the function names encodeJson and decodeJson
unchanged.
---
Duplicate comments:
In `@packages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.ts`:
- Around line 67-76: The params shape is inconsistent: lengthParamsSchema
currently requires length but the TypeScript type LengthParams and the
sqlCharColumn/sqlVarcharColumn helpers treat length as optional and default to
{}. Fix by making the contract consistent — either (A) relax lengthParamsSchema
to allow an optional length (e.g., make the length rule accept undefined or mark
it optional) so it matches LengthParams and the default {} behavior, or (B) make
LengthParams require length and update sqlCharColumn/sqlVarcharColumn to require
callers to supply length (and remove the default {}). Locate and update
lengthParamsSchema, the LengthParams type, and the
sqlCharColumn/sqlVarcharColumn call sites to implement the chosen option so
params validation no longer rejects valid inputs.
In `@packages/3-extensions/arktype-json/src/core/arktype-json-codec-class.ts`:
- Around line 144-147: The rehydrateSchema function silently force-casts
ark.schema(jsonIr) to ArktypeSchemaLike with "as unknown as", which violates the
no-blind-casts rule; replace the double-cast with a runtime type-narrow: call
ark.schema(jsonIr) into a local const (no cast), then implement and use a type
predicate/is-check (for example isArktypeSchemaLike) that verifies the returned
value is callable and has the expected shape (e.g., a function and the
"expression" property of type string or other required members), throw a clear
error if the check fails, and only then return the value typed as
ArktypeSchemaLike; reference rehydrateSchema and ark.schema when locating code
to change.
In `@packages/3-extensions/arktype-json/test/arktype-json-codec-class.test.ts`:
- Around line 75-79: The test currently silences type + lint rules by using "as
any" and a "biome-ignore" comment; replace that with a TypeScript compile-time
suppression using `@ts-expect-error` plus a short reason above the offending call
to arktypeJsonColumn so the test still asserts the runtime guard without using
any/biome ignores. Locate the failing test in arktype-json-codec-class.test.ts
where arktypeJsonColumn(notASchema as any) is called and remove the "as any"
cast and the biome-ignore, adding a single-line "@ts-expect-error TS-EXPLAIN:
deliberate malformed input to exercise call-site guard" immediately above the
call.
In
`@packages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.ts`:
- Around line 140-142: Replace the deprecated matcher usage: locate the
expectTypeOf assertion that references ColumnSpec<ArktypeJsonCodecClass<{ name:
string; price: number }>, ArktypeJsonTypeParams> (the expectTypeOf(col) line)
and change the matcher from toMatchTypeOf<...>() to toExtend<...>() so the
subtype-style compatibility check uses the approved Vitest matcher for
.test-d.ts files.
In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts`:
- Around line 245-258: The codec must reject invalid Date instances and
non-strings: in encode and encodeJson (functions encode(value: Date, _ctx:
CodecCallContext) and encodeJson(value: Date)) check that value is a Date
(instanceof Date) and that
Number.isFinite(value.getTime())/!Number.isNaN(value.getTime()) before calling
toISOString(), and throw a TypeError if not; in decode and decodeJson
(decode(wire: string, _ctx: CodecCallContext) and decodeJson(json: JsonValue))
ensure the input is a string, construct the Date, then validate the produced
Date with Number.isFinite(date.getTime())/!Number.isNaN(...) and throw a
TypeError for invalid dates instead of returning them.
---
Nitpick comments:
In `@packages/3-targets/3-targets/postgres/test/codecs.test.ts`:
- Around line 75-82: The helper codecForScalar currently calls
descriptor.factory(undefined as never) which bypasses the public params contract
for parameterized descriptors; change codecForScalar to supply real params for
parameterized descriptors (e.g. via a small paramsByScalar map or conditional
helpers) and call descriptor.factory(params) for those scalars, falling back to
descriptor.factory() or descriptor.factory(undefined) only for true
non-parameterized descriptors so that descriptorByScalar/descriptor.factory and
SYNTH_CTX are exercised via the intended public API shape.
In `@packages/3-targets/6-adapters/postgres/src/core/adapter.ts`:
- Around line 44-60: The code silently calls factory(undefined) for each
descriptor from postgresCodecRegistry when constructing codecRegistry, which
assumes every descriptor is paramless; add a fail-fast check or helper next to
postgresCodecRegistry to enforce/verify that a descriptor is materializable
without params before calling factory(undefined). Concretely, introduce a small
predicate/helper (e.g., isParamlessDescriptor) or add an assertion in the
codecRegistry initializer that inspects the descriptor shape and throws a clear
error if params are required, and then only call (descriptor.factory)(undefined)
(or use the helper) to produce the codec instance; reference codecRegistry,
postgresCodecRegistry, CodecInstanceContext and the current factory(undefined)
usage when making the change.
In `@packages/3-targets/6-adapters/sqlite/src/core/adapter.ts`:
- Around line 55-70: The current codecRegistry eagerly materializes every
descriptor by calling descriptor.factory(undefined), which mis-instantiates
parameterized descriptors from sqliteCodecRegistry; change this to preserve
descriptor-based entries and only instantiate codecs when they are truly
parameterless: in the codecRegistry initialization (and related logic around
codecRegistry, sqliteCodecRegistry, CodecInstanceContext and factory), avoid
calling factory(undefined) for all descriptors—instead detect parameterized
descriptors (e.g., via a param-type flag or factory arity) and either keep the
original descriptor in the registry or only call factory() for descriptors known
to accept no params; ensure the fallback registry only materializes codecs whose
params are void so parameterized codecs remain descriptor-based.
🪄 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: 395e4d68-7371-4142-b775-7022bfe445bb
📒 Files selected for processing (46)
packages/2-sql/4-lanes/relational-core/package.jsonpackages/2-sql/4-lanes/relational-core/src/ast/sql-codecs-class.tspackages/2-sql/4-lanes/relational-core/src/codec-descriptor-registry.tspackages/2-sql/4-lanes/relational-core/src/exports/codec-descriptor-registry.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs-class.test.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs-class.types.test-d.tspackages/2-sql/4-lanes/relational-core/test/ast/sql-codecs.test.tspackages/2-sql/4-lanes/relational-core/test/typed-codec-flow.test-d.tspackages/2-sql/4-lanes/relational-core/tsdown.config.tspackages/2-sql/5-runtime/src/sql-context.tspackages/3-extensions/arktype-json/src/core/arktype-json-codec-class.tspackages/3-extensions/arktype-json/src/core/pack-meta.tspackages/3-extensions/arktype-json/src/exports/codecs.tspackages/3-extensions/arktype-json/test/arktype-json-codec-class.test.tspackages/3-extensions/arktype-json/test/arktype-json-codec-class.types.test-d.tspackages/3-extensions/arktype-json/test/extension-descriptors.test.tspackages/3-extensions/pgvector/src/core/codecs-class.tspackages/3-extensions/pgvector/src/core/descriptor-meta.tspackages/3-extensions/pgvector/src/core/registry.tspackages/3-extensions/pgvector/src/exports/runtime.tspackages/3-extensions/pgvector/test/codec-render-output-type.test.tspackages/3-extensions/pgvector/test/codecs-class.types.test-d.tspackages/3-extensions/pgvector/test/codecs.test.tspackages/3-extensions/pgvector/test/typed-descriptor-flow.test-d.tspackages/3-targets/3-targets/postgres/src/core/codecs-class.tspackages/3-targets/3-targets/postgres/src/core/registry.tspackages/3-targets/3-targets/postgres/src/exports/codec-types.tspackages/3-targets/3-targets/postgres/src/exports/codecs.tspackages/3-targets/3-targets/postgres/test/codec-render-output-type.test.tspackages/3-targets/3-targets/postgres/test/codecs-class.test.tspackages/3-targets/3-targets/postgres/test/codecs-class.types.test-d.tspackages/3-targets/3-targets/postgres/test/codecs.test.tspackages/3-targets/3-targets/postgres/test/typed-descriptor-flow.test-d.tspackages/3-targets/3-targets/sqlite/src/core/codecs-class.tspackages/3-targets/3-targets/sqlite/src/core/registry.tspackages/3-targets/3-targets/sqlite/src/exports/codec-types.tspackages/3-targets/3-targets/sqlite/src/exports/codecs.tspackages/3-targets/3-targets/sqlite/test/codecs-class.types.test-d.tspackages/3-targets/3-targets/sqlite/test/typed-descriptor-flow.test-d.tspackages/3-targets/6-adapters/postgres/src/core/adapter.tspackages/3-targets/6-adapters/postgres/src/core/codec-lookup.tspackages/3-targets/6-adapters/postgres/src/core/descriptor-meta.tspackages/3-targets/6-adapters/postgres/src/exports/runtime.tspackages/3-targets/6-adapters/sqlite/src/core/adapter.tspackages/3-targets/6-adapters/sqlite/src/core/runtime-adapter.tspackages/3-targets/6-adapters/sqlite/test/codecs.test.ts
✅ Files skipped from review due to trivial changes (9)
- packages/2-sql/4-lanes/relational-core/package.json
- packages/2-sql/4-lanes/relational-core/src/exports/codec-descriptor-registry.ts
- packages/3-targets/3-targets/postgres/src/core/registry.ts
- packages/3-extensions/pgvector/src/core/registry.ts
- packages/3-targets/3-targets/sqlite/src/core/registry.ts
- packages/2-sql/4-lanes/relational-core/tsdown.config.ts
- packages/3-extensions/arktype-json/src/exports/codecs.ts
- packages/3-extensions/arktype-json/src/core/pack-meta.ts
- packages/3-targets/3-targets/sqlite/src/exports/codecs.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/3-targets/3-targets/postgres/src/exports/codecs.ts
- packages/3-targets/3-targets/postgres/test/codecs-class.types.test-d.ts
- packages/2-sql/5-runtime/src/sql-context.ts
- packages/3-targets/3-targets/postgres/src/core/codecs-class.ts
| for (const descriptor of allDescriptors) { | ||
| const widened = descriptor as unknown as AnyDescriptor; | ||
| byId.set(descriptor.codecId, widened); | ||
| for (const targetType of descriptor.targetTypes) { | ||
| const list = byTargetType.get(targetType); | ||
| if (list) { | ||
| list.push(widened); | ||
| } else { | ||
| byTargetType.set(targetType, [widened]); |
There was a problem hiding this comment.
Fail fast on duplicate codecIds.
byId.set() silently overwrites the first descriptor, but byTargetType still retains both entries. That leaves the registry internally inconsistent: descriptorFor(id) returns the last descriptor while target-type iteration still sees every duplicate. Since this builder merges contributions from multiple packages, duplicate ids should throw during construction.
🐛 Minimal guard
for (const descriptor of allDescriptors) {
const widened = descriptor as unknown as AnyDescriptor;
+ if (byId.has(descriptor.codecId)) {
+ throw new Error(`Duplicate codec descriptor id: ${descriptor.codecId}`);
+ }
byId.set(descriptor.codecId, widened);📝 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.
| for (const descriptor of allDescriptors) { | |
| const widened = descriptor as unknown as AnyDescriptor; | |
| byId.set(descriptor.codecId, widened); | |
| for (const targetType of descriptor.targetTypes) { | |
| const list = byTargetType.get(targetType); | |
| if (list) { | |
| list.push(widened); | |
| } else { | |
| byTargetType.set(targetType, [widened]); | |
| for (const descriptor of allDescriptors) { | |
| const widened = descriptor as unknown as AnyDescriptor; | |
| if (byId.has(descriptor.codecId)) { | |
| throw new Error(`Duplicate codec descriptor id: ${descriptor.codecId}`); | |
| } | |
| byId.set(descriptor.codecId, widened); | |
| for (const targetType of descriptor.targetTypes) { | |
| const list = byTargetType.get(targetType); | |
| if (list) { | |
| list.push(widened); | |
| } else { | |
| byTargetType.set(targetType, [widened]); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/2-sql/4-lanes/relational-core/src/codec-descriptor-registry.ts`
around lines 31 - 39, The registry currently overwrites duplicate codecId
entries causing inconsistent state; before calling byId.set(descriptor.codecId,
widened) in the loop over allDescriptors (where widened is cast to
AnyDescriptor), check if byId.has(descriptor.codecId) and throw a clear error
(including the conflicting codecId and descriptor identity) to fail-fast on
duplicates; only proceed to add to byTargetType when the codecId is not already
present (so byId and byTargetType remain consistent and descriptorFor(id) will
match target-type lists).
| async encode(value: number[], _ctx: CodecCallContext): Promise<string> { | ||
| if (!Array.isArray(value)) { | ||
| throw new Error('Vector value must be an array of numbers'); | ||
| } | ||
| if (!value.every((v) => typeof v === 'number')) { | ||
| throw new Error('Vector value must contain only numbers'); | ||
| } | ||
| return `[${value.join(',')}]`; | ||
| } | ||
|
|
||
| async decode(wire: string, _ctx: CodecCallContext): Promise<number[]> { | ||
| if (typeof wire !== 'string') { | ||
| throw new Error('Vector wire value must be a string'); | ||
| } | ||
| if (!wire.startsWith('[') || !wire.endsWith(']')) { | ||
| throw new Error(`Invalid vector format: expected "[...]", got "${wire}"`); | ||
| } | ||
| const content = wire.slice(1, -1).trim(); | ||
| if (content === '') { | ||
| return []; | ||
| } | ||
| return content.split(',').map((v) => { | ||
| const num = Number.parseFloat(v.trim()); | ||
| if (Number.isNaN(num)) { | ||
| throw new Error(`Invalid vector value: "${v}" is not a number`); | ||
| } | ||
| return num; | ||
| }); | ||
| } | ||
|
|
||
| encodeJson(value: number[]): JsonValue { | ||
| return value; | ||
| } | ||
|
|
||
| decodeJson(json: JsonValue): number[] { | ||
| return json as number[]; | ||
| } |
There was a problem hiding this comment.
Carry length into the codec and validate every ingress path.
factory() currently discards params.length, so contractCodecs.forColumn(table, column) still produces a codec that behaves the same for vector(3) and vector(1536). Combined with decodeJson() blindly casting JsonValue to number[], wrong-dimension vectors and malformed JSON can slip through until much later instead of failing at the codec boundary. Thread the dimension into PgVectorCodec and reuse one validator from encode, decode, and decodeJson.
Suggested direction
export class PgVectorCodec extends CodecImpl<
typeof VECTOR_CODEC_ID,
readonly ['equality'],
string,
number[]
> {
+ constructor(
+ descriptor: PgVectorDescriptor,
+ private readonly length: number,
+ ) {
+ super(descriptor);
+ }
+
+ private assertVector(value: JsonValue | number[]): asserts value is number[] {
+ if (!Array.isArray(value)) {
+ throw new Error('Vector value must be an array of numbers');
+ }
+ if (!value.every((v) => typeof v === 'number')) {
+ throw new Error('Vector value must contain only numbers');
+ }
+ if (value.length !== this.length) {
+ throw new Error(`Vector length mismatch: expected ${this.length}, got ${value.length}`);
+ }
+ }
+
async encode(value: number[], _ctx: CodecCallContext): Promise<string> {
- if (!Array.isArray(value)) {
- throw new Error('Vector value must be an array of numbers');
- }
- if (!value.every((v) => typeof v === 'number')) {
- throw new Error('Vector value must contain only numbers');
- }
+ this.assertVector(value);
return `[${value.join(',')}]`;
}
async decode(wire: string, _ctx: CodecCallContext): Promise<number[]> {
if (typeof wire !== 'string') {
throw new Error('Vector wire value must be a string');
@@
- return content.split(',').map((v) => {
+ const value = content.split(',').map((v) => {
const num = Number.parseFloat(v.trim());
if (Number.isNaN(num)) {
throw new Error(`Invalid vector value: "${v}" is not a number`);
}
return num;
});
+ this.assertVector(value);
+ return value;
}
@@
decodeJson(json: JsonValue): number[] {
- return json as number[];
+ this.assertVector(json);
+ return json;
}
}
@@
override factory(_params: VectorParams): (ctx: CodecInstanceContext) => PgVectorCodec {
- return () => new PgVectorCodec(this);
+ return () => new PgVectorCodec(this, _params.length);
}
}Also applies to: 130-131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-extensions/pgvector/src/core/codecs-class.ts` around lines 82 -
118, The PgVectorCodec produced by factory() must retain params.length (the
declared dimension) and validate input across all ingress paths: modify
factory() to store the expected length on the PgVectorCodec instance, add a
single reused validator function (used by encode, decode, and decodeJson) that
checks the value is an array of numbers and that its length equals the stored
dimension, and update encodeJson/decodeJson/decode/encode to call that validator
and throw descriptive errors for wrong-dimension or non-numeric entries; ensure
contractCodecs.forColumn(table, column) returns a codec that enforces the
declared vector length rather than ignoring params.length.
| import type { ExtractCodecTypes } from '@prisma-next/sql-relational-core/ast'; | ||
| import { | ||
| sqlCharDescriptor, | ||
| sqlFloatDescriptor, | ||
| sqlIntDescriptor, | ||
| sqlTextDescriptor, | ||
| sqlTimestampDescriptor, | ||
| sqlVarcharDescriptor, | ||
| } from '@prisma-next/sql-relational-core/ast'; | ||
| import { | ||
| pgBitDescriptor, | ||
| pgBoolDescriptor, | ||
| pgCharDescriptor, | ||
| pgEnumDescriptor, | ||
| pgFloat4Descriptor, | ||
| pgFloat8Descriptor, | ||
| pgFloatDescriptor, | ||
| pgInt2Descriptor, | ||
| pgInt4Descriptor, | ||
| pgInt8Descriptor, | ||
| pgIntDescriptor, | ||
| pgIntervalDescriptor, | ||
| pgJsonbDescriptor, | ||
| pgJsonDescriptor, | ||
| pgNumericDescriptor, | ||
| pgTextDescriptor, | ||
| pgTimeDescriptor, | ||
| pgTimestampDescriptor, | ||
| pgTimestamptzDescriptor, | ||
| pgTimetzDescriptor, | ||
| pgVarbitDescriptor, | ||
| pgVarcharDescriptor, | ||
| } from '../core/codecs-class'; | ||
|
|
||
| const codecDescriptorMap = { | ||
| char: sqlCharDescriptor, | ||
| varchar: sqlVarcharDescriptor, | ||
| int: sqlIntDescriptor, | ||
| float: sqlFloatDescriptor, | ||
| 'sql-text': sqlTextDescriptor, | ||
| 'sql-timestamp': sqlTimestampDescriptor, | ||
| text: pgTextDescriptor, | ||
| character: pgCharDescriptor, | ||
| 'character varying': pgVarcharDescriptor, | ||
| integer: pgIntDescriptor, | ||
| 'double precision': pgFloatDescriptor, | ||
| int4: pgInt4Descriptor, | ||
| int2: pgInt2Descriptor, | ||
| int8: pgInt8Descriptor, | ||
| float4: pgFloat4Descriptor, | ||
| float8: pgFloat8Descriptor, | ||
| numeric: pgNumericDescriptor, | ||
| timestamp: pgTimestampDescriptor, | ||
| timestamptz: pgTimestamptzDescriptor, | ||
| time: pgTimeDescriptor, | ||
| timetz: pgTimetzDescriptor, | ||
| bool: pgBoolDescriptor, | ||
| bit: pgBitDescriptor, | ||
| 'bit varying': pgVarbitDescriptor, | ||
| interval: pgIntervalDescriptor, | ||
| enum: pgEnumDescriptor, | ||
| json: pgJsonDescriptor, | ||
| jsonb: pgJsonbDescriptor, | ||
| } as const; | ||
|
|
||
| type Resolve<T> = { readonly [K in keyof T]: { readonly [P in keyof T[K]]: T[K][P] } }; | ||
|
|
||
| export type CodecTypes = CoreCodecTypes; | ||
| export type CodecTypes = Resolve<ExtractCodecTypes<typeof codecDescriptorMap>>; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Keep src/exports/codec-types.ts as a pure re-export surface.
This entrypoint now computes codecDescriptorMap, Resolve, and CodecTypes inline. Please move that implementation under src/core/** and have this file only re-export the public type so the export surface stays declarative.
As per coding guidelines, packages/**/src/exports/{adapter,codec-types,types}.ts: re-export files in src/exports/ must re-export from core (../core/...) without adding additional logic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-targets/3-targets/postgres/src/exports/codec-types.ts` around
lines 19 - 86, The file defines codecDescriptorMap, Resolve, and CodecTypes
inline which violates the export-only rule; move the codecDescriptorMap, Resolve
type helper, and the ExtractCodecTypes processing into a new or existing module
under src/core (e.g., export the computed type there) and then change
src/exports/codec-types.ts to only re-export the public type (export type {
CodecTypes } from '../core/...') so the exports file contains no computation or
logic and only exposes the type symbol CodecTypes.
| encodeJson(value: Uint8Array): JsonValue { | ||
| return Buffer.from(value).toString('base64'); | ||
| } | ||
| decodeJson(json: JsonValue): Uint8Array { | ||
| if (typeof json !== 'string') { | ||
| throw new TypeError('sqlite/blob@1 contract value must be a base64 string'); | ||
| } | ||
| return new Uint8Array(Buffer.from(json, 'base64')); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the file
find . -path "*sqlite/src/core/codecs-class.ts" -type f 2>/dev/null | head -5Repository: prisma/prisma-next
Length of output: 125
🏁 Script executed:
# Check directory structure around the file
find . -path "*3-targets/sqlite/src*" -type f -name "*.ts" 2>/dev/null | head -20Repository: prisma/prisma-next
Length of output: 1560
🏁 Script executed:
# Search for base64 utilities in the codebase
rg "base64|toBase64|fromBase64" --type ts -l | head -20Repository: prisma/prisma-next
Length of output: 176
🏁 Script executed:
# Read the codecs-class.ts file
cat -n ./packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts | head -250Repository: prisma/prisma-next
Length of output: 10865
🏁 Script executed:
# Check if Buffer is imported or used as a global
rg "Buffer|import.*Buffer" ./packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts -A 2 -B 2Repository: prisma/prisma-next
Length of output: 639
🏁 Script executed:
# Search for base64 utilities in shared packages
find ./packages -path "*/shared*" -o -path "*/utils*" | xargs rg "base64" --type ts 2>/dev/null | head -30Repository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Check what utilities exist in `@prisma-next/utils`
ls -la ./packages/framework/utils/src/ 2>/dev/null || ls -la ./packages/*/utils* 2>/dev/null | head -30Repository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Check the full imports section of the file
head -60 ./packages/3-targets/3-targets/sqlite/src/core/codecs-class.tsRepository: prisma/prisma-next
Length of output: 2327
🏁 Script executed:
# Check if Buffer is imported anywhere
grep -n "import.*Buffer\|from.*Buffer" ./packages/3-targets/3-targets/sqlite/src/core/codecs-class.tsRepository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# See how Postgres handles base64 encoding in its codecs
cat ./packages/3-targets/3-targets/postgres/src/core/codecs-class.ts | grep -A 10 -B 5 "base64\|Buffer"Repository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Check if there are any shared utilities for base64 encoding
find ./packages -name "*.ts" | xargs rg "base64.*encode|fromBase64|toBase64" --type ts -l | head -10Repository: prisma/prisma-next
Length of output: 117
🏁 Script executed:
# Check how postgres handles blob/bytea codecs
find ./packages/3-targets/3-targets/postgres -name "*.ts" -type f | xargs grep -l "bytea\|blob\|base64" | head -5Repository: prisma/prisma-next
Length of output: 211
🏁 Script executed:
# Read the postgres codecs-class.ts to see the blob/bytea implementation
cat ./packages/3-targets/3-targets/postgres/src/core/codecs-class.ts | grep -A 20 "bytea\|Bytea" | head -50Repository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Search all files for base64 implementations
rg "toString\('base64'\)|from\(.*'base64'\)" --type ts -B 3 -A 3 | head -50Repository: prisma/prisma-next
Length of output: 2104
🏁 Script executed:
# Check if Buffer needs to be imported or if it's used as global
rg "^import.*Buffer|^import.*from.*buffer" ./packages/3-targets/3-targets/sqlite --type tsRepository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Check the file structure to understand the layer separation
ls -la ./packages/3-targets/3-targets/sqlite/src/Repository: prisma/prisma-next
Length of output: 280
🏁 Script executed:
# Look for any exports files in the sqlite target
find ./packages/3-targets/3-targets/sqlite/src -name "exports" -o -name "*.exports.ts" | head -10Repository: prisma/prisma-next
Length of output: 112
🏁 Script executed:
# Check the guidelines for how blob encoding should be handled in src/core
rg "src/core" ./packages/3-targets/3-targets/sqlite/src --type ts -l | head -5Repository: prisma/prisma-next
Length of output: 254
🏁 Script executed:
# See if there's a runtime-specific exports path for sqlite codecs
find ./packages/3-targets/3-targets/sqlite -name "*.ts" | xargs grep -l "encodeJson.*Uint8Array" | head -10Repository: prisma/prisma-next
Length of output: 125
🏁 Script executed:
# Check what's in the exports directory
ls -la ./packages/3-targets/3-targets/sqlite/src/exports/Repository: prisma/prisma-next
Length of output: 1229
🏁 Script executed:
# Read the exports files to see how runtime-specific code is handled
cat ./packages/3-targets/3-targets/sqlite/src/exports/*.ts | head -100Repository: prisma/prisma-next
Length of output: 3487
🏁 Script executed:
# Check the package.json to understand this package's context
cat ./packages/3-targets/3-targets/sqlite/package.json | head -50Repository: prisma/prisma-next
Length of output: 1844
🏁 Script executed:
# Look at how other targets handle this - check PostgreSQL exports
ls -la ./packages/3-targets/3-targets/postgres/src/exports/ 2>/dev/null || echo "No exports dir in postgres"Repository: prisma/prisma-next
Length of output: 1846
🏁 Script executed:
# Check the runtime.ts export file to understand the plane separation
cat ./packages/3-targets/3-targets/sqlite/src/exports/runtime.tsRepository: prisma/prisma-next
Length of output: 187
🏁 Script executed:
# Check if there's any actual plane separation or if src/core imports from runtime
cat ./packages/3-targets/3-targets/sqlite/src/core/codecs.tsRepository: prisma/prisma-next
Length of output: 430
🏁 Script executed:
# Look at how the codecs are registered or used
cat ./packages/3-targets/3-targets/sqlite/src/core/registry.ts 2>/dev/null || echo "No registry file"Repository: prisma/prisma-next
Length of output: 746
🏁 Script executed:
# Check the actual directory structure to confirm Buffer usage location
ls -la ./packages/3-targets/3-targets/sqlite/src/core/ | grep codecRepository: prisma/prisma-next
Length of output: 251
🏁 Script executed:
# Verify if codecs-class.ts is in core and check what imports it has
head -70 ./packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts | grep "^import\|^export"Repository: prisma/prisma-next
Length of output: 199
🏁 Script executed:
# Check if there's a shared utility module for encoding/decoding
find ./packages -path "*/framework*" -o -path "*/utils*" -name "*.ts" | xargs grep -l "TextEncoder\|btoa\|atob" | head -10Repository: prisma/prisma-next
Length of output: 631
🏁 Script executed:
# Check how Postgres handles bytea/binary data if it does
rg "Bytea|bytea" ./packages/3-targets/3-targets/postgres/src/core/ -A 5 -B 5 | head -80Repository: prisma/prisma-next
Length of output: 2268
🏁 Script executed:
# Look for cross-platform encoding utilities in utils package
find ./packages -name "utils" -type d | xargs ls -la 2>/dev/null | head -20Repository: prisma/prisma-next
Length of output: 1065
🏁 Script executed:
# Check what's in the framework utils for encoding
ls -la ./packages/framework/utils/src/ 2>/dev/null || find ./packages -path "*utils/src*" -type d -name "src" | head -3 | xargs ls -laRepository: prisma/prisma-next
Length of output: 671
🏁 Script executed:
# Check what's exported from utils package
ls -la ./packages/1-framework/0-foundation/utils/src/exports/Repository: prisma/prisma-next
Length of output: 605
🏁 Script executed:
# Look for any encoding utilities
rg "TextEncoder|TextDecoder|base64|Buffer" ./packages/1-framework/0-foundation/utils/src -lRepository: prisma/prisma-next
Length of output: 44
🏁 Script executed:
# Check if there's a utilities file for encoding/base64
find ./packages -name "*.ts" | xargs grep -l "TextEncoder" | head -10Repository: prisma/prisma-next
Length of output: 117
🏁 Script executed:
# Now check the dependency constraints - does src/core allow runtime imports?
cat ./packages/3-targets/3-targets/sqlite/package.json | grep -A 20 "dependencies"Repository: prisma/prisma-next
Length of output: 925
🏁 Script executed:
# Verify the actual scope of the issue - check if this is the only use of Buffer in src/core
rg "Buffer" ./packages/3-targets/3-targets/sqlite/src/core/ --type tsRepository: prisma/prisma-next
Length of output: 401
🏁 Script executed:
# Check if TextEncoder/TextDecoder are used elsewhere as an alternative
rg "TextEncoder|TextDecoder" ./packages -l --type ts | head -10Repository: prisma/prisma-next
Length of output: 117
🏁 Script executed:
# Verify if this is truly in the shared plane (src/core) not runtime plane
cat ./packages/3-targets/3-targets/sqlite/src/exports/codecs.tsRepository: prisma/prisma-next
Length of output: 578
🏁 Script executed:
# Final verification: confirm that Buffer is a Node-only global and there's no cross-platform fallback
node -e "console.log(typeof Buffer)"Repository: prisma/prisma-next
Length of output: 71
🏁 Script executed:
# Check if any shared packages provide base64 encoding utilities
find ./packages/1-framework -name "*.ts" | xargs grep -l "base64\|toBase64\|fromBase64" | head -5Repository: prisma/prisma-next
Length of output: 44
Avoid Node-only Buffer usage in src/core.
The base64 conversion in encodeJson() and decodeJson() methods makes this shared-plane codec depend on Node.js globals. This violates the guideline that src/core/** must remain runtime-neutral with no runtime-specific dependencies. The codec will fail in non-Node runtimes (browser, Deno, Bun, edge) unless a polyfill is injected. Use a cross-platform encoding utility (e.g., TextEncoder/TextDecoder from Web APIs, or a shared codec helper) instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/3-targets/3-targets/sqlite/src/core/codecs-class.ts` around lines
204 - 211, The encodeJson and decodeJson methods currently use Node-only Buffer;
replace that with a runtime-neutral base64 utility so src/core stays
platform-agnostic: implement or import cross-platform helpers (e.g.,
uint8ArrayToBase64 and base64ToUint8Array) that use Web APIs (atob/btoa or
TextEncoder/TextDecoder) and fall back to Buffer only when necessary, then
update encodeJson(value: Uint8Array) to return uint8ArrayToBase64(value) and
decodeJson(json: JsonValue) to validate string and return
base64ToUint8Array(json); keep the function names encodeJson and decodeJson
unchanged.
Completes the registration-side migration that the parent codec-registry-unification project (merged via ADR 208) deliberately deferred. (TML-2357)
Spec & plan
projects/codec-registration-completion/spec.md— eight ACs (AC-0..AC-7), three pinning cases.projects/codec-registration-completion/plan.md— five milestones with validation gates and risks.projects/codec-registration-completion/specs/class-based-codec-design.spec.md— six implementation-level ACs (AC-CB-1..6) covering the Pattern E class hierarchy.Milestones
a210fa1c5Codecinstance + descriptor-keyed metadata reads1be7564c4; reverified through M4aliasCodec/arktypeJsonEmitCodecdeletionParamRef.refsplumbing + encode-sideforColumn+forCodecIdretirement3f0ec224aJsonSchemaValidatorRegistry; retire'json-validator'traite055c9455What landed
interface CodecDescriptor<P>+abstract class CodecDescriptorImpl<P>, paired withinterface Codec<Id, Traits, Wire, Input>+abstract class CodecImpl<...>. Per-codec column helpers (e.g.vectorColumn(N)) directly invokedescriptor.factory(params)to preserve method-level generics;satisfies ColumnHelperFor<D>ties helpers to their descriptor without polymorphism.AnyCodecDescriptor(alias forCodecDescriptor<any>) is the canonical heterogeneous-storage type.mkCodec,defineCodec,defineCodecGroup,defineCodecBundle,CodecDefBuilder*,synthesizeNonParameterizedDescriptor, instance-keyedExtractCodecTypes,byScalarmaps,dataTypesexports,sqlCodecDefinitions,codecDescriptorDefinitions,pgVectorRepresentativeCodecplaceholder,parameterizedCodecs:slot,CodecParamsDescriptor,arktypeJsonEmitCodec,aliasCodec,aliasDescriptorfunction form, the'json-validator'trait,JsonSchemaValidatorRegistryinfrastructure. Closing-grep zero call-sites across all 19 retired symbols.ParamRef+ProjectionItemcarryrefs?: { table; column };validateParamRefRefsbuilder-pipeline pass enforces refs for parameterized codec ids; encode/decode dispatch consultsmetadata.refsfirst viacontractCodecs.forColumn(table, column).decodebody (arktype-json's inline pattern from TML-2229). Decode-error envelope equivalence verified viacodec-async.test.ts:408-456.ColumnTypeDescriptorrelocated from@prisma-next/contract-authoring(layer 2) to@prisma-next/framework-components(layer 1) so codec base types live with the framework primitives.fae3bd688):CodecTypesexposed at the publicexports/codec-types.tsentry point with aResolve<T>materializer to break tsdown's chunk-private path reference and restore consumer-side typecheck.Notable side effect: 16 pre-existing e2e failures resolved by M3
Before M3, the e2e suite ran 75/91 with 16 failures of the form
Codec '...' resolves to multiple parameterized instances; column-aware dispatch is required.— top-level field shortcuts (select('vectorCol')) emittedIdentifierRefAST that didn't carry(table, column)context, so decode-sideresolveProjectionCodecfell back toforCodecIdand threw. M3'sProjectionItem.refsextension + decode-side parity indecoding.tsclosed the path. e2e is now 91/91.Validation gates at HEAD
2d4eb3ba3pnpm typecheck --forcepnpm lint:depspnpm fixtures:checkorigin/main)pnpm buildpnpm test:e2epnpm test:packagesLinear follow-ups filed during close-out
pnpm test:packagesparallel-execution flake (adapter-postgres/cli/sql-orm-client; passes cleanly in isolation). P3.pnpm build --force). P4.(table, column)lookup keys on the AST. The current shape's validator pass is a smell; instance-on-Plan would retireforColumn/forCodecIdfrom the runtime dispatch surface entirely. Architectural successor to this work. P3.Out of scope
forColumnplumbing).Codec— type name stays; only the field set narrows.CodecCallContext(ADR 207).pgEnumCodecplaceholder factory audit (already clean at HEAD; documented in ADR 208 § Future work).Note on project artifacts
Per the user's mid-flight directive at close-out,
projects/codec-registration-completion/is preserved in-tree (the standard transient-directory deletion was reverted at5b0113a5a). The directory's review artifacts underreviews/are gitignored and don't appear in the diff; they live in the working tree only as historical context.Summary by CodeRabbit
Documentation
New Features
Refactor