| feat(codec-registration-completion): unify codec registration (TML-2357) (#417) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> > **Status: ready for review.** All milestones complete; all 14 acceptance criteria PASS or LANDED at HEAD; validation gates green workspace-wide. Post-implementation surface cleanup (F9 + F10) and post-feedback hygiene round (F11–F39) landed on top. Completes the registration-side migration that the parent codec-registry-unification project (merged via [ADR 208](docs/architecture%20docs/adrs/ADR%20208%20-%20Higher-order%20codecs%20for%20parameterized%20types.md)) deliberately deferred. (TML-2357) ## Spec & plan - [`projects/codec-registration-completion/spec.md`](projects/codec-registration-completion/spec.md) — eight ACs (AC-0..AC-7), three pinning cases. - [`projects/codec-registration-completion/plan.md`](projects/codec-registration-completion/plan.md) — five milestones with validation gates and risks. - [`projects/codec-registration-completion/specs/class-based-codec-design.spec.md`](projects/codec-registration-completion/specs/class-based-codec-design.spec.md) — six implementation-level ACs (AC-CB-1..6) covering the abstract-class hierarchy. ## Milestones | # | Goal | Spec ACs | Status | |---|---|---|---| | **M0** | Class-based codec migration + per-codec helpers + Strength 3 deletion sweep | AC-0, AC-1, AC-CB-1..6 | **SATISFIED** at `a210fa1c5` | | **M1** | Narrow runtime `Codec` instance + descriptor-keyed metadata reads | AC-3 | **LANDED** at `1be7564c4`; reverified through M4 | | **M2** | Native descriptor migration + bridge / `aliasCodec` / `arktypeJsonEmitCodec` deletion | AC-2, AC-4 | **ABSORBED into M0** (Phase B + Phase C) | | **M3** | `ParamRef.refs` plumbing + encode-side `forColumn` + `forCodecId` retirement | AC-5 | **SATISFIED** at `3f0ec224a` | | **M4** | Delete `JsonSchemaValidatorRegistry`; retire `'json-validator'` trait | AC-6 | **SATISFIED** at `e055c9455` | | **F9 + F10** | Public-API surface cleanup: registry-only public surface; drop transition vocabulary | hygiene | **SATISFIED** at `c4d81ad1c` | | **F11–F39** | Post-feedback hygiene round: 27 findings closed; F38 rejected; SQL `CodecRegistry` consolidated into `CodecDescriptorRegistry` | hygiene + correctness | **SATISFIED** at `0e3aafc0b` | | **AC-7** | Validation gates green | AC-7 | **PASS** at every milestone close + every hygiene-round close | ## What landed - **Class-based codec hierarchy.** `interface CodecDescriptor<P>` + `abstract class CodecDescriptorImpl<P>`, paired with `interface Codec<Id, Traits, Wire, Input>` + `abstract class CodecImpl<...>`. Per-codec column helpers (e.g. `vectorColumn(N)`) directly invoke `descriptor.factory(params)` to preserve method-level generics; `satisfies ColumnHelperFor<D>` ties helpers to their descriptor without polymorphism. `AnyCodecDescriptor` (alias for `CodecDescriptor<any>`) is the canonical heterogeneous-storage type. - **Strength 3 forcing-function deletion.** Every legacy carrier deleted: `mkCodec`, `defineCodec`, `defineCodecGroup`, `defineCodecBundle`, `CodecDefBuilder*`, `synthesizeNonParameterizedDescriptor`, instance-keyed `ExtractCodecTypes`, `byScalar` maps, `dataTypes` exports, `sqlCodecDefinitions`, `codecDescriptorDefinitions`, `pgVectorRepresentativeCodec` placeholder, `parameterizedCodecs:` slot, `CodecParamsDescriptor`, `arktypeJsonEmitCodec`, `aliasCodec`, `aliasDescriptor` function form, the `'json-validator'` trait, `JsonSchemaValidatorRegistry` infrastructure, `arktypeParamsSchema` helper, the SQL-family `CodecRegistry` interface (consolidated into `CodecDescriptorRegistry`). Closing-grep zero call-sites. - **`ParamRef` + `ProjectionItem` carry `refs?: { table; column }`**; `validateParamRefRefs` builder-pipeline pass enforces refs for parameterized codec ids; encode/decode dispatch consults `metadata.refs` first via `contractCodecs.forColumn(table, column)`. - **JSON validation lives in the resolved codec's `decode` body** (arktype-json's inline pattern from TML-2229). Decode-error envelope equivalence verified via `codec-async.test.ts:408-456`. - **`ColumnTypeDescriptor` relocated** from `@prisma-next/contract-authoring` (layer 2) to `@prisma-next/framework-components` (layer 1) so codec base types live with the framework primitives. - **F8 portability fix** (`fae3bd688`): `CodecTypes` exposed at the public `exports/codec-types.ts` entry point with a `Resolve<T>` materializer to break tsdown's chunk-private path reference and restore consumer-side typecheck. - **F9 + F10 surface cleanup** (`3d57f9ecd..c4d81ad1c`): each codec-shipping package now exposes only column helpers + a `<package>CodecRegistry` instance + `type` re-exports of descriptor types. Internal `codecDescriptorMap` / `codecDescriptorClassList` no longer surface through public exports. Transition vocabulary (`Class` suffixes, `class-form` / `class-based` in comments, `codecs-class.ts` filenames) scrubbed. ## Post-feedback hygiene round (F11–F39) 19 commits between `c4d81ad1c..0e3aafc0b` close 27 findings surfaced from orchestrator-principal review and 30 GitHub PR review threads. Highlights: - **F11** (`f725db1f8`) — rename `ast/sql-codecs-class.ts` → `ast/sql-codecs.ts`, split helpers into `ast/sql-codec-helpers.ts`. - **F12** (`7d56a39b4`) — move parameterization predicate onto `CodecDescriptorImpl.isParameterized` getter; retire the standalone `IsParameterizedCodecId` callback type. - **F13 + F14 + F20** (`cfa078a23`) — align arktype params schemas with `TParams` optionality (`'length?': '...'`); delete `arktypeParamsSchema` helper; consumer sites direct-assign with `: StandardSchemaV1<TParams>`. The `arktype` runtime dep stays in `@prisma-next/contract` because `validate-contract.ts` consumes it directly for structural validation — that's by design, not a follow-up. - **F15** (`1051f24e8`) — replace `unique symbol` trait phantom with a string-key phantom property (`__codecTraits`) to avoid Node bundling failure modes. - **F16 + F17** (`083fff350`, `a278bd034`) — rewrite codec-authoring-guide alias section + fix self-referential JSDoc helper-source reference. - **F18** (`7ee52fae0`) — assert leaf scalar type in `no-emit-typed-flow.test-d.ts` to make AC-CB-6's literal claim explicit. - **F19 + F31 + F32 + F33** (`81c55248f`, `bc4e8d716`) — harden `extractCodecLookup`: lift inline imports, tighten `id` to non-optional `string`, refine the silent catch so non-parameterized codecs throw immediately while parameterized factories that tolerate empty params still materialise representatives. - **F21** (`f1be14d55`) — `createStubAdapter` returns a stable codec registry instance (no per-call rebuild). - **F22 + F26 + F29** (`20f99bffd`) — dispatch correctness: `refsFromLeft` walks via `collectColumnRefs` to preserve refs for wrapping expressions; encode-side `forColumn` fall-through is structurally safe (F19 + `ambiguousCodecIds` rejection + `byCodecId` column-correct materialisation); pgvector `length` threaded into `PgVectorCodec` constructor with `assertVector` validating the dimension at every ingress. - **F23 + F24 + F25** (`48ed1d135`) — type predicate `isArktypeSchemaLike` replaces blind cast in `rehydrateSchema`; `@ts-expect-error` replaces `as any` + biome-ignore in tests; `toExtend` replaces deprecated `toMatchTypeOf` matcher. - **F27** (`21b4cbca7`, `0e3aafc0b`) — retire SQL-family `CodecRegistry.register()` mutation surface in favour of `buildCodecRegistry(descriptors)` builder; phase-2 deletes the `CodecRegistry` interface entirely. Single registry surface in the SQL family is now `CodecDescriptorRegistry`. - **F28** (`9881a7efe`) — `buildCodecDescriptorRegistry` throws on duplicate `codecId`. - **F30** (`7a3faf20f`) — `codecDescriptorMap` relocated to `core/codec-type-map.ts`; `Resolve<T>` materialisation kept at the `exports/` boundary per F8. - **F34 + F35 + F36** (`4178072fa`) — postgres render hygiene: scale validation in `pgNumericRenderOutputType`; ISO 8601 regex for timestamps; string validation for enum values. - **F37** (`182d10c88`) — `SqliteDatetimeCodec` rejects `Invalid Date` in decode/decodeJson. - **F38** — **rejected.** Default identity codecs on `CodecImpl` would constrain its type signature and obscure where real conversion work happens. Convention stays: explicit identity overrides at codec-author site. - **F39** (`80ba4fd60`) — `enumParamsSchema` and `EnumParams` tightened to `readonly string[]`. Closure-mechanism note for F22: the implementer chose a structural argument over fail-fast on `forColumn` miss — F19's refinement makes `extractCodecLookup` skip parameterized descriptors that don't tolerate empty params; `buildContractCodecRegistry`'s `ambiguousCodecIds` set throws `RUNTIME.TYPE_PARAMS_INVALID` on multi-instance ids; for the non-ambiguous parameterized case `byCodecId` stores the column-correct per-instance codec. Reviewer cross-checked all three legs and accepted. ## 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')`) emitted `IdentifierRef` AST that didn't carry `(table, column)` context, so decode-side `resolveProjectionCodec` fell back to `forCodecId` and threw. M3's `ProjectionItem.refs` extension + decode-side parity in `decoding.ts` closed the path. **e2e is now 91/91.** ## Validation gates at HEAD `0e3aafc0b` | Gate | Result | |---|---| | `pnpm typecheck` | PASS — 123/123 | | `pnpm lint:deps` | PASS — 727 modules / 1444 deps / 0 violations | | `pnpm fixtures:check` | PASS — zero drift (demo emit byte-identical against `origin/main`) | | `pnpm build` | PASS — 62/62 | | `pnpm test:e2e` | **PASS — 91/91** (16 pre-existing failures resolved by M3) | | `pnpm test:packages` | PASS in scope (residual: pre-existing 7 sql-orm-client pgvector wire-format + TML-2402 parallel-flake) | ## Review artifacts `projects/codec-registration-completion/reviews/` carries three review artifacts produced by the reviewer subagent during orchestration. After user feedback that the initial structure didn't match the canonical skill output, all three were rewritten to conform to `/drive-pr-local-review` (flat F-numbered findings + AC verification table) and `/drive-pr-walkthrough` (intent-first semantic narrative). Final reviewer verdict at HEAD `0e3aafc0b`: **SATISFIED** with 12 PASS / 2 WEAK on the AC scoreboard. The two WEAKs are AC-7 (acknowledged baseline test failures in sql-orm-client pgvector wire format + TML-2402 parallel flake) and AC-CB-5 (a single internal `descriptor as unknown as AnyDescriptor` cast inside `buildCodecDescriptorRegistry` — purely a registry-internal heterogeneous-storage erasure, not a public surface concern). ## Linear follow-ups filed during close-out - **[TML-2402](https://linear.app/prisma-company/issue/TML-2402)** — `pnpm test:packages` parallel-execution flake (`adapter-postgres` / `cli` / `sql-orm-client`; passes cleanly in isolation). P3. - **[TML-2403](https://linear.app/prisma-company/issue/TML-2403)** — Turbo cache-keying gap on transitive AST/type-system changes (worked around with `pnpm build --force`). P4. - **[TML-2405](https://linear.app/prisma-company/issue/TML-2405)** — Codec dispatch follow-up: reference codec instances on the lowered Plan instead of carrying `(table, column)` lookup keys on the AST. The current shape's validator pass is a smell; instance-on-Plan would retire `forColumn`/`forCodecId` from the runtime dispatch surface entirely. Architectural successor to this work. P3. ## Out of scope - **Mongo codec registration migration** — folded into [TML-2324](https://linear.app/prisma-company/issue/TML-2324) (Mongo runtime `forColumn` plumbing). - **Renaming `Codec`** — type name stays; only the field set narrows. - **Reshaping the async codec runtime** ([ADR 204](docs/architecture%20docs/adrs/ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md)) or `CodecCallContext` ([ADR 207](docs/architecture%20docs/adrs/ADR%20207%20-%20Codec%20call%20context%20per-query%20AbortSignal%20and%20column%20metadata.md)). - **`pgEnumCodec` placeholder factory audit** (already clean at HEAD; documented in ADR 208 § Future work). - **Retiring `CodecLookup.get(id)` and `ProjectionItem.refs` / `ParamRef.refs` lookup-key carriers from the AST** — TML-2405. ## 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 at `5b0113a5a`). The directory's review artifacts under `reviews/` are gitignored and don't appear in the diff; they live in the working tree only as historical context. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-a00fe249-d674-4cb5-8939-5b9d17b36650"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-a00fe249-d674-4cb5-8939-5b9d17b36650"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Unified descriptor-driven codec system and class-based codec authoring; per-codec column helpers. * Codecs now receive per-call context (AbortSignal + column provenance). * **Documentation** * Added codec authoring reference and multiple ADR updates clarifying descriptor and async model. * **Improvements** * Stronger parameter validation and improved output-type rendering for parameterized codecs. * Query builder/runtime now propagate column refs into expressions and parameter encoding. * **Tests** * Expanded type and runtime tests covering descriptor-driven flows and helpers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> | 4 个月前 |