Skip to content

Commit 14060ca

Browse files
wmaddencursoragent
andauthored
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..c4d81ad`): 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..0e3aafc` 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>&nbsp;<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>&nbsp;</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>
1 parent c48ad71 commit 14060ca

253 files changed

Lines changed: 11626 additions & 7406 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/rules/jsdoc-line-width.mdc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
description: Wrap JSDoc / block-comment prose at the same line width as the biome formatter (currently 100). Markdown is exempt — see the markdown-no-artificial-line-wraps skill.
3+
alwaysApply: false
4+
---
5+
6+
# JSDoc / block-comment prose — wrap at the biome line width
7+
8+
When adding or editing **prose** in `/** ... */` or long `//` documentation:
9+
10+
1. **Wrap at biome's `lineWidth`** — Wrap prose at the line width configured in `biome.jsonc` (`formatter.lineWidth`, currently **100**). Source of truth: read `biome.jsonc` rather than hard-coding the number; if it changes, this rule changes with it.
11+
12+
2. **No fixed-column wraps below the biome width** — Do not break mid-sentence to stay under ~72 or ~80 characters. Editors soft-wrap; arbitrary narrow wraps make diffs noisy and conflict-prone.
13+
14+
3. **Bullet lists and tagged blocks**
15+
- **Bullets** (`*` / `-`): keep each bullet's prose on one line up to the biome width before wrapping; wrap continuation lines aligned with the bullet text.
16+
- **Tagged blocks** (`@param`, `@returns`, `@example`): follow normal JSDoc conventions; the same line-width rule applies to the sentence text.
17+
18+
4. **Orphaned blocks** — A doc comment must document the declaration that immediately follows it (after optional blank lines only before `import` / file headers). Do not leave a standalone `/** ... */` between imports and the next export with no attached symbol; merge into the relevant declaration's doc or delete redundancy.
19+
20+
5. **Markdown is exempt** — Do not apply this rule to `.md` files (READMEs, ADRs, PR bodies, rulecards, and any other Markdown). Markdown prose stays on one continuous line per paragraph; see `.agents/skills/markdown-no-artificial-line-wraps/SKILL.md`.
21+
22+
6. **Rationale** — Wrapping at the same width that the formatter uses keeps doc-comment prose visually consistent with surrounding code, avoids both "one giant line" and "narrow 1990s terminal" extremes, and produces stable diffs because the wrap width is anchored to the formatter rather than picked per-author.
23+
24+
Related: `.agents/skills/markdown-no-artificial-line-wraps/SKILL.md` (Markdown files).

.cursor/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Thresholds are defined in `.cursor/rules-footprint.config.json`.
7575

7676
## TypeScript & Typing
7777
- `.cursor/rules/typescript-patterns.mdc` — TS patterns index (short)
78+
- `.agents/rules/jsdoc-no-artificial-line-wraps.mdc` — JSDoc prose: no manual ~80-column wraps; avoid orphaned doc blocks
7879
- `.cursor/rules/generic-parameters.mdc` — Generic parameter defaults
7980
- `.cursor/rules/interface-factory-pattern.mdc` — Interface-based design + factories
8081
- `.cursor/rules/type-predicates.mdc` — Replace blind casts with type predicates

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"cSpell.words": ["codegen", "Lowerer", "pgvector"],
2+
"cSpell.words": ["arktype", "codegen", "Lowerer", "pgvector"],
33
"editor.defaultFormatter": "biomejs.biome",
44
"typescript.tsdk": "node_modules/typescript/lib",
55
"[typescript]": {

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This directory contains the primary documentation for the repository.
2323
- [Glossary](./glossary.md) — user-facing terminology (source of truth for naming)
2424
- [Commands](./commands/README.md) — command docs and entry points
2525
- [Reference docs](./reference/) — conventions and patterns used across the codebase
26+
- [Codec authoring guide](./reference/codec-authoring-guide.md) — class-based codecs (`CodecImpl`, `CodecDescriptorImpl`) and column helpers
2627
- [CLI Style Guide](./CLI%20Style%20Guide.md) — CLI UX conventions
2728

2829
## Working with AI agents

docs/Testing Guide.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ function createStubAdapter(): Adapter<SelectAst, SqlContract<SqlStorage>, Lowere
297297
target: 'postgres',
298298
targetFamily: 'sql',
299299
capabilities: {},
300-
codecs: createCodecRegistry(),
300+
codecs: emptyCodecRegistry(),
301301
},
302302
lower: () => ({ sql: '', params: [] }),
303303
};
@@ -330,10 +330,7 @@ Low-level helper tests (for IR builders, planners, CLI glue, etc.) should prove
330330

331331
### File Organization
332332

333-
**Unit tests:** `src/**/*.test.ts` (alongside source code)
334-
**Integration tests:** `test/**/*.integration.test.ts` or `src/**/*.integration.test.ts`
335-
**Type tests:** `src/**/*.test-d.ts` (type-level tests using `expectTypeOf`)
336-
**E2E tests:** `test/e2e/framework/test/**/*.test.ts`
333+
**Unit tests:** `src/**/*.test.ts` (alongside source code) **Integration tests:** `test/**/*.integration.test.ts` or `src/**/*.integration.test.ts` **Type tests:** `src/**/*.test-d.ts` (type-level tests using `expectTypeOf`) **E2E tests:** `test/e2e/framework/test/**/*.test.ts`
337334

338335
### Test File Structure
339336

@@ -969,9 +966,7 @@ it('reads user', async () => {
969966

970967
### 6. Use Appropriate Test Level
971968

972-
**Unit test:** Test a single function in isolation
973-
**Integration test:** Test multiple components working together
974-
**E2E test:** Test complete execution path to database and back
969+
**Unit test:** Test a single function in isolation **Integration test:** Test multiple components working together **E2E test:** Test complete execution path to database and back
975970

976971
**When in doubt:** Start with a unit test. If you need to test interactions, create an integration test. If you need to test the complete flow, create an E2E test.
977972

docs/architecture docs/adrs/ADR 152 - Execution Plane Descriptors and Instances.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,8 @@ The SQL family extends base execution-plane descriptors with `SqlStaticContribut
164164

165165
```ts
166166
interface SqlStaticContributions {
167-
codecs(): CodecRegistry
167+
codecs(): ReadonlyArray<CodecDescriptor>
168168
operationSignatures(): ReadonlyArray<SqlOperationSignature>
169-
parameterizedCodecs(): ReadonlyArray<RuntimeParameterizedCodecDescriptor>
170169
}
171170
```
172171

docs/architecture docs/adrs/ADR 180 - Dot-path field accessor.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR 180 — Dot-path field accessor
22

3-
> **Implementation update (Mongo query builder unification).** The consolidated `FieldAccessor` shipped in `@prisma-next/mongo-query-builder` replaced the earlier `FieldProxy` and `FilterProxy` types — filter and update operators now hang off a single accessor, used by both read callbacks (`match`, `addFields`, `project`, `group`) and write callbacks (`updateMany`, `findOneAndUpdate`, etc.). Type-safe dot-path validation for the callable form `f("address.city")` was implemented in [TML-2281](https://linear.app/prisma-company/issue/TML-2281): a second generic `N extends NestedDocShape` threads the contract's model + value-object structure through the pipeline, paths are constrained by `ValidPaths<N>`, the resolved leaf's codec drives the returned `Expression`, and non-leaf paths surface a reduced `ObjectExpression` operator surface. Additive pipeline stages preserve `N`; replacement stages (`project`, `group`, `replaceRoot`, …) reset it, disabling the callable form downstream. For paths that are intentionally outside the typed model (canonically, migration authoring where a backfill writes a field that is not yet in the contract), `f.rawPath("path")` is the sanctioned escape hatch — it returns a `LeafExpression<DocField>` with the verbatim path and the full leaf operator surface. The method is named `rawPath` rather than `raw` so the escape hatch does not shadow a legitimate top-level `raw` field on a user model (the callable fallback `f("raw")` is disabled downstream of replacement stages, which would otherwise leave such a field inaccessible).
3+
> **Implementation update (Mongo query builder unification).** The consolidated `FieldAccessor` shipped in `@prisma-next/mongo-query-builder` replaced the earlier `FieldProxy` and `FilterProxy` types — filter and update operators now hang off a single accessor, used by both read callbacks (`match`, `addFields`, `project`, `group`) and write callbacks (`updateMany`, `findOneAndUpdate`, etc.). Type-safe dot-path validation for the callable form `f("address.city")` added a second generic `N extends NestedDocShape` that threads the contract's model + value-object structure through the pipeline, constrains paths by `ValidPaths<N>`, drives the returned `Expression` from the resolved leaf's codec, and surfaces a reduced `ObjectExpression` operator surface for non-leaf paths. Additive pipeline stages preserve `N`; replacement stages (`project`, `group`, `replaceRoot`, …) reset it, disabling the callable form downstream. For paths that are intentionally outside the typed model (canonically, migration authoring where a backfill writes a field that is not yet in the contract), `f.rawPath("path")` is the sanctioned escape hatch — it returns a `LeafExpression<DocField>` with the verbatim path and the full leaf operator surface. The method is named `rawPath` rather than `raw` so the escape hatch does not shadow a legitimate top-level `raw` field on a user model (the callable fallback `f("raw")` is disabled downstream of replacement stages, which would otherwise leave such a field inaccessible).
44

55
## At a glance
66

docs/architecture docs/adrs/ADR 184 - Codec-owned value serialization.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# ADR 184 — Codec-owned value serialization
22

3+
> **Retrospective note.** This ADR's examples use the `defineCodec({...})` factory. That factory was the canonical codec-author surface at the time; it was later retired in favor of class-based authoring: concrete codecs extend `CodecImpl`, descriptors extend `CodecDescriptorImpl`, and per-codec column helpers tie helpers to descriptors with `satisfies`. The ADR's *decision* — that codecs own both wire and JSON-safe representations through `encode` / `decode` + `encodeJson` / `decodeJson` — is unchanged; only the authoring shape has moved on. See [ADR 208 — Higher-order codecs for parameterized types](ADR%20208%20-%20Higher-order%20codecs%20for%20parameterized%20types.md) and the [Codec authoring guide](../../reference/codec-authoring-guide.md) for the current shape.
4+
35
## At a glance
46

57
A column with `codecId: "pg/timestamptz@1"` has a default value of `new Date('2024-01-15')` — a JavaScript `Date`. This value has to survive a round-trip through `contract.json`, but `Date` has no JSON representation. The codec handles it:
68

79
```ts
8-
const pgTimestamptzCodec = codec({
10+
const pgTimestamptzCodec = defineCodec({
911
typeId: 'pg/timestamptz@1',
1012
targetTypes: ['timestamptz'],
1113
traits: ['equality', 'order'],
@@ -42,7 +44,7 @@ The resulting contract JSON is plain — no tags, no wrappers:
4244

4345
The consumer reads `"2024-01-15T00:00:00.000Z"`, looks up `pg/timestamptz@1`, calls `decodeJson(...)`, gets a `Date` object.
4446

45-
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.
47+
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.
4648

4749
The same typed value crosses other boundaries too. The migration planner renders it into DDL (`DEFAULT '2024-01-15T00:00:00.000Z'`). The PSL printer renders it into schema source (`@default("2024-01-15T00:00:00.000Z")`). Migration operations carry it in `ops.json`. These are the same problem for different media, but they live at different layers:
4850

@@ -119,7 +121,7 @@ Both SQL and Mongo families define structurally identical codec interfaces (`Cod
119121

120122
`encodeJson` and `decodeJson` are required on the `Codec` interface, not optional. Any type that can appear in the contract may need a literal value serialized for it (column defaults, discriminator values, type parameters, migration temporary defaults). Making the methods required eliminates null checks at every dispatch site.
121123

122-
For JSON-safe types (strings, numbers, booleans, null), the methods are identity functions. The `codec()` factory provides these defaults when not explicitly supplied, so codecs for JSON-safe types need no additional boilerplate.
124+
For JSON-safe types (strings, numbers, booleans, null), the methods are identity functions. The `defineCodec()` factory provides these defaults when not explicitly supplied, so codecs for JSON-safe types need no additional boilerplate.
123125

124126
### Contract loading integrates decoding
125127

docs/architecture docs/adrs/ADR 186 - Codec-dispatched type rendering.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ADR 186 — Codec-dispatched type rendering
22

3+
> **Retrospective note.** This ADR introduced the `renderOutputType` slot on the codec record (and shows `defineCodec({...})` examples). Both the codec authoring shape and the home of `renderOutputType` have since moved on: [ADR 208](ADR%20208%20-%20Higher-order%20codecs%20for%20parameterized%20types.md) relocated `renderOutputType` to the unified `CodecDescriptor`, and the `defineCodec({...})` factory was retired in favor of class-based descriptors (`CodecDescriptorImpl`) and codecs (`CodecImpl`). The decision this ADR records — that the codec is the dispatch authority for type rendering — is unchanged; only the slot's home and the authoring syntax have moved. See [Codec authoring guide](../../reference/codec-authoring-guide.md).
4+
35
## At a glance
46

57
A `vector(1536)` column should produce `Vector<1536>` as its output type. A `jsonb(schema)` column should produce `{ name: string }`. Today, resolving a field's output type requires dispatching through `CodecTypes[codecId]['output']` or `parameterizedOutput` — a hoop that varies depending on whether the codec is parameterized. After this change, every field's output type is resolved once and stamped into a dedicated map in `contract.d.ts`:
@@ -124,7 +126,7 @@ Non-parameterized codecs don't need to implement it. The common case is zero cod
124126
Here's what the JSONB codec looks like with `renderOutputType`:
125127

126128
```ts
127-
const pgJsonbCodec = codec({
129+
const pgJsonbCodec = defineCodec({
128130
typeId: 'pg/jsonb@1',
129131
targetTypes: ['jsonb'],
130132
encode: (value): string => JSON.stringify(value),
@@ -210,7 +212,7 @@ Rejected because phantom types are gross, and mixing type-only metadata into the
210212

211213
### Make `renderOutputType` required with a default
212214

213-
Like `encodeJson`/`decodeJson` on [ADR 184](ADR%20184%20-%20Codec-owned%20value%20serialization.md), make it required with a default provided by the `codec()` factory.
215+
Like `encodeJson`/`decodeJson` on [ADR 184](ADR%20184%20-%20Codec-owned%20value%20serialization.md), make it required with a default provided by the `defineCodec()` factory.
214216

215217
Deferred. Most codecs don't parameterize their output type — the default (`CodecTypes[codecId]['output']`) handles them. Optional with a well-defined fallback is cleaner for now.
216218

docs/architecture docs/adrs/ADR 202 - Codec trait system.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ADR 202 — Codec trait system
22

3+
> **Retrospective note.** This ADR's examples show codec definitions via `defineCodec({...})`. That factory was retired in favor of class-based descriptors (`CodecDescriptorImpl`); traits are now declared as `override readonly traits = [...] as const` on the descriptor class. The `'json-validator'` trait this ADR introduced was also retired — JSON-Schema validation now lives uniformly inside the resolved codec's `decode` body rather than behind a parallel `JsonSchemaValidatorRegistry`. The trait system itself, the operator-gating semantics, and the canonical traits set are all unchanged. See [ADR 208](ADR%20208%20-%20Higher-order%20codecs%20for%20parameterized%20types.md) and the [Codec authoring guide](../../reference/codec-authoring-guide.md).
4+
35
## Context
46

57
Data types in the system are identified by codec IDs (e.g., `pg/int4@1`, `pg/text@1`, `pg/vector@1`). Query surfaces need to know which operators and functions are valid for a given data type — for example, ordering a `jsonb` column with `lt` or applying `sum` to a `text` column are not meaningful SQL. Today there is no generic mechanism to express these semantic constraints.
@@ -63,7 +65,7 @@ Traits are declared at codec registration time. Core SQL codecs and adapter code
6365

6466
```ts
6567
// sql-codecs.ts
66-
const sqlIntCodec = codec({
68+
const sqlIntCodec = defineCodec({
6769
typeId: SQL_INT_CODEC_ID,
6870
targetTypes: ['int'],
6971
traits: ['equality', 'order', 'numeric'],
@@ -74,23 +76,23 @@ const sqlIntCodec = codec({
7476

7577
```ts
7678
// postgres adapter codecs
77-
const pgTextCodec = codec({
79+
const pgTextCodec = defineCodec({
7880
typeId: 'pg/text@1',
7981
targetTypes: ['text'],
8082
traits: ['equality', 'order', 'textual'],
8183
encode: (value) => value,
8284
decode: (wire) => wire,
8385
});
8486

85-
const pgBoolCodec = codec({
87+
const pgBoolCodec = defineCodec({
8688
typeId: 'pg/bool@1',
8789
targetTypes: ['bool'],
8890
traits: ['equality', 'boolean'],
8991
encode: (value) => value,
9092
decode: (wire) => wire,
9193
});
9294

93-
const pgJsonbCodec = codec({
95+
const pgJsonbCodec = defineCodec({
9496
typeId: 'pg/jsonb@1',
9597
targetTypes: ['jsonb'],
9698
traits: ['equality'], // equality only; not order-comparable
@@ -103,7 +105,7 @@ Extension codecs declare traits the same way:
103105

104106
```ts
105107
// pgvector codec — vectors have equality but are not order-comparable or numeric
106-
const pgVectorCodec = codec({
108+
const pgVectorCodec = defineCodec({
107109
typeId: 'pg/vector@1',
108110
targetTypes: ['vector'],
109111
traits: ['equality'],

0 commit comments

Comments
 (0)