@prisma-next/extension-arktype-json 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.mts","names":[],"sources":["../src/core/pack-meta.ts"],"sourcesContent":[],"mappings":";;;;;cAsBM;;;;;;;;;yCA+BI,wCAAA,CAAA;;;;;;;;;;;;;;;;;;;;cAOG,4BAA4B;0BACf"}
package/dist/pack.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { t as arktypeJsonPackMeta } from "./pack-meta-DycetSQB.mjs";
2
+
3
+ export { arktypeJsonPackMeta, arktypeJsonPackMeta as default };
@@ -0,0 +1,8 @@
1
+ import { SqlRuntimeExtensionDescriptor } from "@prisma-next/sql-runtime";
2
+
3
+ //#region src/exports/runtime.d.ts
4
+
5
+ declare const arktypeJsonRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'>;
6
+ //#endregion
7
+ export { arktypeJsonRuntimeDescriptor, arktypeJsonRuntimeDescriptor as default };
8
+ //# sourceMappingURL=runtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/exports/runtime.ts"],"sourcesContent":[],"mappings":";;;;cA0Ba,8BAA8B"}
@@ -0,0 +1,42 @@
1
+ import { i as arktypeJsonCodec } from "./arktype-json-codec-BbiBmtTK.mjs";
2
+ import { t as arktypeJsonPackMeta } from "./pack-meta-DycetSQB.mjs";
3
+ import { createCodecRegistry } from "@prisma-next/sql-relational-core/ast";
4
+
5
+ //#region src/exports/runtime.ts
6
+ /**
7
+ * Runtime-plane extension descriptor for arktype-json.
8
+ *
9
+ * Registers `arktypeJsonCodec` (the parameterized codec descriptor)
10
+ * through the SQL runtime's `parameterizedCodecs:` slot. Per Phase B of
11
+ * codec-registry-unification, the legacy `codecs:` slot returns an empty
12
+ * registry — the unified descriptor map subsumes the codec-id-keyed
13
+ * metadata reads that the legacy slot used to back, and the runtime
14
+ * dispatch (`forColumn`) materializes the per-instance codec from the
15
+ * descriptor's factory.
16
+ *
17
+ * Lives at the runtime-plane entrypoint so `src/core/**` stays free of
18
+ * runtime-plane imports (per `.cursor/rules/multi-plane-entrypoints.mdc`).
19
+ */
20
+ function createArktypeJsonCodecRegistry() {
21
+ return createCodecRegistry();
22
+ }
23
+ const arktypeJsonRuntimeDescriptor = {
24
+ kind: "extension",
25
+ id: arktypeJsonPackMeta.id,
26
+ version: arktypeJsonPackMeta.version,
27
+ familyId: "sql",
28
+ targetId: "postgres",
29
+ codecs: createArktypeJsonCodecRegistry,
30
+ parameterizedCodecs: () => [arktypeJsonCodec],
31
+ create() {
32
+ return {
33
+ familyId: "sql",
34
+ targetId: "postgres"
35
+ };
36
+ }
37
+ };
38
+ var runtime_default = arktypeJsonRuntimeDescriptor;
39
+
40
+ //#endregion
41
+ export { arktypeJsonRuntimeDescriptor, runtime_default as default };
42
+ //# sourceMappingURL=runtime.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.mjs","names":["arktypeJsonRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'>"],"sources":["../src/exports/runtime.ts"],"sourcesContent":["/**\n * Runtime-plane extension descriptor for arktype-json.\n *\n * Registers `arktypeJsonCodec` (the parameterized codec descriptor)\n * through the SQL runtime's `parameterizedCodecs:` slot. Per Phase B of\n * codec-registry-unification, the legacy `codecs:` slot returns an empty\n * registry — the unified descriptor map subsumes the codec-id-keyed\n * metadata reads that the legacy slot used to back, and the runtime\n * dispatch (`forColumn`) materializes the per-instance codec from the\n * descriptor's factory.\n *\n * Lives at the runtime-plane entrypoint so `src/core/**` stays free of\n * runtime-plane imports (per `.cursor/rules/multi-plane-entrypoints.mdc`).\n */\n\nimport { createCodecRegistry } from '@prisma-next/sql-relational-core/ast';\nimport type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';\nimport { arktypeJsonCodec } from '../core/arktype-json-codec';\nimport { arktypeJsonPackMeta } from '../core/pack-meta';\n\nfunction createArktypeJsonCodecRegistry() {\n // arktype-json ships only the parameterized descriptor; the legacy\n // `codecs:` slot has nothing to register.\n return createCodecRegistry();\n}\n\nexport const arktypeJsonRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {\n kind: 'extension' as const,\n id: arktypeJsonPackMeta.id,\n version: arktypeJsonPackMeta.version,\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: createArktypeJsonCodecRegistry,\n parameterizedCodecs: () => [arktypeJsonCodec],\n create() {\n return {\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n };\n },\n};\n\nexport default arktypeJsonRuntimeDescriptor;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,SAAS,iCAAiC;AAGxC,QAAO,qBAAqB;;AAG9B,MAAaA,+BAA0E;CACrF,MAAM;CACN,IAAI,oBAAoB;CACxB,SAAS,oBAAoB;CAC7B,UAAU;CACV,UAAU;CACV,QAAQ;CACR,2BAA2B,CAAC,iBAAiB;CAC7C,SAAS;AACP,SAAO;GACL,UAAU;GACV,UAAU;GACX;;CAEJ;AAED,sBAAe"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@prisma-next/extension-arktype-json",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "scripts": {
7
+ "build": "tsdown",
8
+ "test": "vitest run",
9
+ "test:coverage": "vitest run --coverage",
10
+ "typecheck": "tsc --project tsconfig.json --noEmit",
11
+ "lint": "biome check . --error-on-warnings",
12
+ "lint:fix": "biome check --write .",
13
+ "lint:fix:unsafe": "biome check --write --unsafe .",
14
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
15
+ },
16
+ "dependencies": {
17
+ "@prisma-next/contract": "workspace:*",
18
+ "@prisma-next/contract-authoring": "workspace:*",
19
+ "@prisma-next/family-sql": "workspace:*",
20
+ "@prisma-next/framework-components": "workspace:*",
21
+ "@prisma-next/sql-relational-core": "workspace:*",
22
+ "@prisma-next/sql-runtime": "workspace:*",
23
+ "arktype": "^2.1.29"
24
+ },
25
+ "devDependencies": {
26
+ "@prisma-next/sql-contract": "workspace:*",
27
+ "@prisma-next/test-utils": "workspace:*",
28
+ "@prisma-next/tsconfig": "workspace:*",
29
+ "@prisma-next/tsdown": "workspace:*",
30
+ "tsdown": "catalog:",
31
+ "typescript": "catalog:",
32
+ "vitest": "catalog:"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src"
37
+ ],
38
+ "exports": {
39
+ "./codec-types": "./dist/codec-types.mjs",
40
+ "./codecs": "./dist/codecs.mjs",
41
+ "./column-types": "./dist/column-types.mjs",
42
+ "./control": "./dist/control.mjs",
43
+ "./pack": "./dist/pack.mjs",
44
+ "./runtime": "./dist/runtime.mjs",
45
+ "./package.json": "./package.json"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/prisma/prisma-next.git",
50
+ "directory": "packages/3-extensions/arktype-json"
51
+ }
52
+ }
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Single source of truth for the arktype-json `arktype/json@1` codec.
3
+ *
4
+ * Ships the per-library JSON-with-schema column factory (`arktypeJson`) and
5
+ * the framework-registration descriptor (`arktypeJsonCodec`). The two
6
+ * surfaces share one serialize/rehydrate pipeline keyed on arktype's
7
+ * internal IR.
8
+ *
9
+ * **Serialization** (column-author site, eager):
10
+ *
11
+ * - `expression`: `schema.expression` — arktype's TypeScript-source-like
12
+ * rendering used by the emit-path `renderOutputType` to produce the
13
+ * column's TS type in `contract.d.ts`.
14
+ * - `jsonIr`: `schema.json` — arktype's internal IR. Lossless; the
15
+ * rehydration source consumed by `ark.schema(jsonIr)` at runtime.
16
+ *
17
+ * The pair is sufficient: `expression` round-trips with the rehydrated
18
+ * schema (`ark.schema(jsonIr).expression === expression`) so the emit-path
19
+ * output is stable across serialize/rehydrate.
20
+ *
21
+ * **Rehydration** (runtime, on factory invocation): `ark.schema(typeParams.jsonIr)`
22
+ * returns a callable `Type`-like with `~standard`. The returned codec's
23
+ * `decode` body validates wire payloads through the rehydrated schema and
24
+ * throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` on rejection — no separate
25
+ * validator-registry consultation.
26
+ *
27
+ * See the codec-registry-unification spec § Case J (JSON-with-schema).
28
+ */
29
+
30
+ import type { JsonValue } from '@prisma-next/contract/types';
31
+ import type { ColumnTypeDescriptor } from '@prisma-next/contract-authoring';
32
+ import type {
33
+ Codec,
34
+ CodecDescriptor,
35
+ CodecInstanceContext,
36
+ } from '@prisma-next/framework-components/codec';
37
+ import { runtimeError } from '@prisma-next/framework-components/runtime';
38
+ import { codec } from '@prisma-next/sql-relational-core/ast';
39
+ import { ArkErrors, ark, type Type, type } from 'arktype';
40
+
41
+ // ── Constants ────────────────────────────────────────────────────────────
42
+
43
+ /** Codec id for arktype-backed JSON columns. Library-bound, not target-bound. */
44
+ export const ARKTYPE_JSON_CODEC_ID = 'arktype/json@1' as const;
45
+
46
+ /** Native storage type backing the codec. JSONB on Postgres; binary, indexable. */
47
+ export const ARKTYPE_JSON_NATIVE_TYPE = 'jsonb' as const;
48
+
49
+ // ── typeParams shape ─────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Eagerly serialized typeParams for the arktype-json column. Carried in
53
+ * the contract IR; the runtime descriptor's factory rehydrates `jsonIr`
54
+ * and the emitter consumes `expression`.
55
+ */
56
+ export type ArktypeJsonTypeParams = {
57
+ /**
58
+ * Arktype's TypeScript-source-like rendering of the schema. Read by
59
+ * `renderOutputType` to emit the column's TS type into `contract.d.ts`.
60
+ * Stable across the serialize/rehydrate cycle: the rehydrated schema's
61
+ * `expression` matches the source schema's.
62
+ */
63
+ readonly expression: string;
64
+ /**
65
+ * Arktype's internal IR for the schema. Lossless; the rehydration
66
+ * source. Schema-shape — `ark.schema(jsonIr)` reconstructs a callable
67
+ * `Type`-like structurally identical to the original `type(definition)`
68
+ * output.
69
+ */
70
+ readonly jsonIr: object;
71
+ };
72
+
73
+ // ── Curried higher-order codec factory ───────────────────────────────────
74
+
75
+ /**
76
+ * Codec instance returned by `arktypeJson(schema)(ctx)` and by
77
+ * `arktypeJsonCodec.factory(typeParams)(ctx)`. The `TInferred` slot
78
+ * carries the arktype schema's inferred output type.
79
+ */
80
+ export type ArktypeJsonCodec<TInferred> = Codec<
81
+ typeof ARKTYPE_JSON_CODEC_ID,
82
+ readonly ['equality'],
83
+ string,
84
+ TInferred
85
+ >;
86
+
87
+ /**
88
+ * Structural narrow of arktype's `Type` — the surface our codec depends
89
+ * on: a callable validator that returns `inferOut | ArkErrors`, plus the
90
+ * `expression` string for emit-path rendering.
91
+ *
92
+ * Avoids depending on the precise generics of arktype's `Type<t, $>` so
93
+ * schemas built in any scope (the default `Ark` from `type(...)` AND the
94
+ * minimal scope from `ark.schema(...)`) satisfy the same contract.
95
+ */
96
+ type ArktypeSchemaLike = ((value: unknown) => unknown) & {
97
+ readonly expression: string;
98
+ };
99
+
100
+ /**
101
+ * Type predicate for `ArktypeSchemaLike`. Lets the column-author
102
+ * factory narrow `unknown` schemas to the structural shape the codec
103
+ * depends on after the explicit field guards run, so the descriptor
104
+ * builder doesn't fall back to a `as unknown as` cast.
105
+ */
106
+ function isArktypeSchemaLike(value: unknown): value is ArktypeSchemaLike {
107
+ if (typeof value !== 'function') return false;
108
+ const expression = (value as { readonly expression?: unknown }).expression;
109
+ return typeof expression === 'string';
110
+ }
111
+
112
+ /**
113
+ * Build the curried factory for a rehydrated arktype schema. The factory's
114
+ * returned codec carries the schema in its closure; `decode` validates
115
+ * wire payloads via `schema(parsed)`, throwing
116
+ * `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` on rejection.
117
+ *
118
+ * Encode is `JSON.stringify` — the schema validates the input shape only
119
+ * at the read boundary (decode), matching the JSON-validator philosophy:
120
+ * the payload may have been written by any source (this writer, a
121
+ * previous version of the schema, a manual SQL `INSERT`); validate when
122
+ * reading, not when writing.
123
+ *
124
+ * Author bodies are sync; main's `codec({...})` factory promise-lifts
125
+ * `encode`/`decode` into the framework-required `Promise<…>` boundary
126
+ * shape (per ADR 204).
127
+ */
128
+ function arktypeJsonCodecForSchema<TInferred>(
129
+ schema: ArktypeSchemaLike,
130
+ ): (ctx: CodecInstanceContext) => ArktypeJsonCodec<TInferred> {
131
+ // Shared schema check used by both `decode` (wire → JS) and
132
+ // `decodeJson` (JsonValue → JS). Either entry point must reject
133
+ // payloads that don't match the schema; without the shared validator,
134
+ // any caller that hands parsed JSON straight to the codec would bypass
135
+ // schema enforcement and return unchecked data.
136
+ function validateSchema(value: unknown): TInferred {
137
+ const result = schema(value);
138
+ if (result instanceof ArkErrors) {
139
+ throw runtimeError(
140
+ 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
141
+ `arktype-json schema validation failed (decode): ${result.summary}`,
142
+ { codecId: ARKTYPE_JSON_CODEC_ID, issues: result.summary },
143
+ );
144
+ }
145
+ // arktype's call-result is `inferOut | ArkErrors`; the ArkErrors
146
+ // branch is excluded above. The cast threads the caller-supplied
147
+ // generic onto the structurally-typed validation output.
148
+ return result as TInferred;
149
+ }
150
+
151
+ // Derive both `encode` (wire string) and `encodeJson` (JsonValue)
152
+ // outputs from the same `JSON.stringify` → `JSON.parse` round-trip,
153
+ // then validate the normalized payload through the schema. Without
154
+ // this normalization, a non-JSON-safe runtime value (e.g. a class
155
+ // instance, a function field on a narrowed type) could slip through
156
+ // `encodeJson` unchanged while `encode` silently dropped or
157
+ // transformed it — producing wire payloads the codec's own decode
158
+ // path would later reject. The serialize/parse round-trip also
159
+ // produces the JSON-safe shape required by the contract IR's
160
+ // `JsonValue` surface, so `encodeJson` no longer needs a blind cast.
161
+ function serializeToJsonSafe(value: TInferred): { wire: string; json: JsonValue } {
162
+ // `JSON.stringify` returns `string | undefined` — `undefined`
163
+ // happens when the input is `undefined` itself or contains only
164
+ // unserializable values (functions, symbols). Reject explicitly so
165
+ // the caller sees the schema-failure code rather than a downstream
166
+ // `JSON.parse(undefined)` SyntaxError.
167
+ const wire: string | undefined = JSON.stringify(value);
168
+ if (typeof wire !== 'string') {
169
+ throw runtimeError(
170
+ 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
171
+ `arktype-json value is not representable as JSON (codecId: ${ARKTYPE_JSON_CODEC_ID})`,
172
+ { codecId: ARKTYPE_JSON_CODEC_ID },
173
+ );
174
+ }
175
+ const json = JSON.parse(wire) as JsonValue;
176
+ // Validate the normalized payload — the round-trip strips
177
+ // class-prototype shape and arktype-narrowed fields, and the
178
+ // schema must still accept the result. Run validation and discard
179
+ // its return value (we keep `json` as the JsonValue, not the
180
+ // schema's `inferOut` which already matches `TInferred`).
181
+ validateSchema(json);
182
+ return { wire, json };
183
+ }
184
+
185
+ return (_ctx) =>
186
+ codec<typeof ARKTYPE_JSON_CODEC_ID, readonly ['equality'], string, TInferred>({
187
+ typeId: ARKTYPE_JSON_CODEC_ID,
188
+ targetTypes: [ARKTYPE_JSON_NATIVE_TYPE],
189
+ traits: ['equality'] as const,
190
+ encode: (value: TInferred): string => serializeToJsonSafe(value).wire,
191
+ decode: (wire: string): TInferred => validateSchema(JSON.parse(wire)),
192
+ encodeJson: (value: TInferred): JsonValue => serializeToJsonSafe(value).json,
193
+ decodeJson: (json: JsonValue) => validateSchema(json),
194
+ }) as ArktypeJsonCodec<TInferred>;
195
+ }
196
+
197
+ // ── Column-author surface ────────────────────────────────────────────────
198
+
199
+ /**
200
+ * Curried column-author factory for arktype-validated JSON columns.
201
+ *
202
+ * Usage:
203
+ *
204
+ * ```ts
205
+ * import { type } from 'arktype';
206
+ * import { arktypeJson } from '@prisma-next/extension-arktype-json/column-types';
207
+ *
208
+ * const ProductSchema = type({ name: 'string', price: 'number' });
209
+ *
210
+ * const Product = {
211
+ * columns: {
212
+ * id: textCodec,
213
+ * settings: arktypeJson(ProductSchema),
214
+ * // ^? ColumnTypeDescriptor with type :: (ctx) => Codec<…, { name: string; price: number }>
215
+ * },
216
+ * };
217
+ * ```
218
+ *
219
+ * The schema's inferred output flows through `S['infer']` so the no-emit
220
+ * `FieldOutputType` resolver produces the precise TS type at the column
221
+ * site. Eager serialization at this call site captures `expression` (for
222
+ * the emit-path renderer) and `jsonIr` (for runtime rehydration).
223
+ *
224
+ * @throws {Error} if the schema doesn't expose `expression` and `json`
225
+ * fields (i.e. is not an arktype `Type`). The factory validates the
226
+ * schema shape at the call site so configuration errors surface during
227
+ * contract authoring, not at runtime.
228
+ */
229
+ export function arktypeJson<S extends Type<unknown>>(
230
+ schema: S,
231
+ ): ColumnTypeDescriptor & {
232
+ readonly codecId: typeof ARKTYPE_JSON_CODEC_ID;
233
+ readonly nativeType: typeof ARKTYPE_JSON_NATIVE_TYPE;
234
+ readonly typeParams: ArktypeJsonTypeParams;
235
+ readonly type: (ctx: CodecInstanceContext) => ArktypeJsonCodec<S['infer']>;
236
+ } {
237
+ // Reject non-callable / non-arktype-shaped lookalikes before any
238
+ // property reads. An object shaped like `{ expression, json }` would
239
+ // otherwise pass the field checks and only explode on the first
240
+ // `decode`/`decodeJson` call, defeating the early authoring-time
241
+ // guard this factory provides. The `isArktypeSchemaLike` predicate
242
+ // narrows `schema` so the descriptor builder hands the typed shape
243
+ // straight to the curried factory — no `as unknown as` cast.
244
+ if (!isArktypeSchemaLike(schema)) {
245
+ throw new Error(
246
+ typeof schema !== 'function'
247
+ ? 'arktypeJson(schema) expects a callable arktype Type.'
248
+ : 'arktypeJson(schema) expects an arktype Type (missing `expression: string`).',
249
+ );
250
+ }
251
+ const jsonIr: unknown = (schema as { readonly json?: unknown }).json;
252
+ if (jsonIr === null || typeof jsonIr !== 'object') {
253
+ throw new Error('arktypeJson(schema) expects an arktype Type (missing `json` IR).');
254
+ }
255
+ return {
256
+ codecId: ARKTYPE_JSON_CODEC_ID,
257
+ nativeType: ARKTYPE_JSON_NATIVE_TYPE,
258
+ typeParams: { expression: schema.expression, jsonIr },
259
+ type: arktypeJsonCodecForSchema<S['infer']>(schema),
260
+ } as const;
261
+ }
262
+
263
+ // ── Framework-registration descriptor ────────────────────────────────────
264
+
265
+ /**
266
+ * Standard Schema validator for the descriptor's typeParams. Asserts the
267
+ * shape `{ expression: string; jsonIr: object }` at the contract IR
268
+ * boundary; deeper IR-shape validation happens implicitly when
269
+ * `ark.schema(jsonIr)` reparses (corrupt IR throws there).
270
+ *
271
+ * Eats its own dog food: the validator is itself an arktype schema.
272
+ */
273
+ const arktypeJsonParamsSchema = type({
274
+ expression: 'string',
275
+ jsonIr: 'object',
276
+ });
277
+
278
+ /**
279
+ * Rehydrate an arktype schema from the serialized IR. Throws a clean
280
+ * error if the IR is corrupt — the "corruption-of-contract.json" case.
281
+ */
282
+ function rehydrateSchema(jsonIr: object): ArktypeSchemaLike {
283
+ try {
284
+ return ark.schema(jsonIr) as unknown as ArktypeSchemaLike;
285
+ } catch (error) {
286
+ throw runtimeError(
287
+ 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
288
+ `Failed to rehydrate arktype schema from contract IR: ${error instanceof Error ? error.message : String(error)}`,
289
+ { codecId: ARKTYPE_JSON_CODEC_ID, jsonIr },
290
+ );
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Render the emit-path TS type for an arktype-json column. Reads the
296
+ * eagerly-extracted `expression` directly — the round-trip stability
297
+ * guarantee (rehydrated schema's `expression` matches the source's)
298
+ * means the rendered output is consistent across serialize/rehydrate.
299
+ */
300
+ function renderArktypeJsonOutputType(params: ArktypeJsonTypeParams): string {
301
+ const expression = params.expression.trim();
302
+ return expression.length > 0 ? expression : 'unknown';
303
+ }
304
+
305
+ /**
306
+ * Build a permissive `renderOutputType` that accepts the framework's
307
+ * generic typeParams shape and dispatches to the type-narrow renderer
308
+ * once the input is structurally an `ArktypeJsonTypeParams`.
309
+ */
310
+ function renderArktypeJsonOutputTypeFromUnknownParams(
311
+ typeParams: Record<string, unknown>,
312
+ ): string | undefined {
313
+ const expression = typeParams['expression'];
314
+ const jsonIr = typeParams['jsonIr'];
315
+ if (typeof expression !== 'string' || jsonIr === null || typeof jsonIr !== 'object') {
316
+ return undefined;
317
+ }
318
+ return renderArktypeJsonOutputType({ expression, jsonIr });
319
+ }
320
+
321
+ /**
322
+ * Emit-only `Codec` instance for `arktype/json@1`. Threaded through the
323
+ * pack-meta's `codecInstances` array so the emitter's `CodecLookup` can
324
+ * find a `renderOutputType` for the codec id (the emitter consults the
325
+ * codec-id-keyed `CodecLookup` at the framework boundary; the unified
326
+ * descriptor's `renderOutputType` is the long-term home for the renderer
327
+ * but the emit-path glue still routes through `CodecLookup`).
328
+ *
329
+ * All conversion methods are sentinels that throw if invoked — runtime
330
+ * materialization always goes through `arktypeJsonCodec.factory`'s
331
+ * curried `(params) => (ctx) => Codec`, never through this instance.
332
+ * `encodeJson`/`decodeJson` throw alongside `encode`/`decode` so a
333
+ * mistaken contract-load that resolved to this stub fails fast at the
334
+ * JSON boundary instead of silently returning unvalidated payloads. A
335
+ * future cleanup could route the emit path through the descriptor map
336
+ * directly and retire this shim.
337
+ */
338
+ const ARKTYPE_JSON_RUNTIME_DISPATCH_ERROR =
339
+ 'arktype-json codec instances must be materialized via the descriptor factory; this is an emit-only stub';
340
+
341
+ export const arktypeJsonEmitCodec: Codec<
342
+ typeof ARKTYPE_JSON_CODEC_ID,
343
+ readonly ['equality'],
344
+ string,
345
+ unknown
346
+ > = {
347
+ id: ARKTYPE_JSON_CODEC_ID,
348
+ targetTypes: [ARKTYPE_JSON_NATIVE_TYPE],
349
+ traits: ['equality'] as const,
350
+ encode: () => Promise.reject(new Error(ARKTYPE_JSON_RUNTIME_DISPATCH_ERROR)),
351
+ decode: () => Promise.reject(new Error(ARKTYPE_JSON_RUNTIME_DISPATCH_ERROR)),
352
+ encodeJson: () => {
353
+ throw new Error(ARKTYPE_JSON_RUNTIME_DISPATCH_ERROR);
354
+ },
355
+ decodeJson: () => {
356
+ throw new Error(ARKTYPE_JSON_RUNTIME_DISPATCH_ERROR);
357
+ },
358
+ renderOutputType: renderArktypeJsonOutputTypeFromUnknownParams,
359
+ };
360
+
361
+ /**
362
+ * Framework-registration descriptor for the arktype-json codec. Registered
363
+ * through the SQL runtime's `parameterizedCodecs:` slot. `sql-runtime`'s
364
+ * `initializeTypeHelpers` (and per-column walk in
365
+ * `buildContractCodecRegistry`) calls `arktypeJsonCodec.factory(typeParams)
366
+ * (ctx)` once per `storage.types` instance (or once per inline-typeParams
367
+ * column) to materialize the resolved codec carrying the rehydrated
368
+ * schema.
369
+ *
370
+ * Per Phase B of codec-registry-unification, `descriptorFor('arktype/json@1')`
371
+ * returns this descriptor and its `traits`/`targetTypes` are the codec-id-
372
+ * keyed source of truth — no parallel placeholder on the legacy `codecs:`
373
+ * slot is needed (the runtime descriptor ships `codecs: () => createCodecRegistry()`
374
+ * — empty).
375
+ */
376
+ export const arktypeJsonCodec: CodecDescriptor<ArktypeJsonTypeParams> = {
377
+ codecId: ARKTYPE_JSON_CODEC_ID,
378
+ traits: ['equality'] as const,
379
+ targetTypes: [ARKTYPE_JSON_NATIVE_TYPE] as const,
380
+ paramsSchema: arktypeJsonParamsSchema,
381
+ renderOutputType: renderArktypeJsonOutputType,
382
+ factory: (params) => {
383
+ const schema = rehydrateSchema(params.jsonIr);
384
+ /* c8 ignore start — defensive parity check; not exercised by typical contracts */
385
+ // The rehydrated schema's `expression` should match the serialized
386
+ // one; diverging means contract.json was hand-edited out from under
387
+ // the emit-path renderer. Surface as a soft warning at materialization
388
+ // time so the caller knows their emit output may not match the
389
+ // runtime schema. The runtime keeps using the schema rehydrated from
390
+ // `jsonIr` — that's the lossless source — so the worst case is an
391
+ // emit-vs-runtime divergence at a single column, not a runtime
392
+ // failure.
393
+ const rehydratedExpression = (schema as { readonly expression?: unknown }).expression;
394
+ if (typeof rehydratedExpression === 'string' && rehydratedExpression !== params.expression) {
395
+ console.warn(
396
+ `[arktype-json] typeParams.expression (${params.expression}) does not match rehydrated schema expression (${rehydratedExpression}); contract.json may be stale relative to the runtime schema.`,
397
+ );
398
+ }
399
+ /* c8 ignore stop */
400
+ return arktypeJsonCodecForSchema<unknown>(schema);
401
+ },
402
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * arktype-json pack metadata.
3
+ *
4
+ * The pack metadata is the framework-composition entry point: control-
5
+ * stack assembly reads `types.codecTypes.import` to thread the type-side
6
+ * imports into emitted `contract.d.ts`, and `types.storage` declares the
7
+ * codec id's storage backing (`jsonb` on Postgres).
8
+ *
9
+ * Per Phase B of codec-registry-unification, runtime materialization
10
+ * flows through the unified descriptor map (`arktypeJsonCodec`
11
+ * parameterized descriptor), not through the legacy runtime codec
12
+ * lookup. This metadata still carries an emit-only `Codec` instance
13
+ * (`arktypeJsonEmitCodec`) under `codecInstances` so the framework
14
+ * emitter's codec-id-keyed `CodecLookup` can resolve `renderOutputType`
15
+ * at emit time — that shim retires when the emit path consults the
16
+ * descriptor map directly (TML-2357). Control-stack consumers read
17
+ * codec metadata from `descriptorFor('arktype/json@1')`.
18
+ */
19
+
20
+ import type { CodecTypes } from '../types/codec-types';
21
+ import { ARKTYPE_JSON_CODEC_ID, arktypeJsonEmitCodec } from './arktype-json-codec';
22
+
23
+ const arktypeJsonPackMetaBase = {
24
+ kind: 'extension',
25
+ id: 'arktype-json',
26
+ familyId: 'sql',
27
+ targetId: 'postgres',
28
+ version: '0.0.1',
29
+ capabilities: {},
30
+ types: {
31
+ codecTypes: {
32
+ // The emitter's `CodecLookup` is the codec-id-keyed source of
33
+ // truth for `renderOutputType` at the framework emit-path
34
+ // boundary. We thread an emit-only `Codec` instance carrying the
35
+ // `renderOutputType` here so the lookup resolves; runtime
36
+ // materialization goes through the unified descriptor's
37
+ // `factory: (P) => (CodecInstanceContext) => Codec`, never through this shim.
38
+ codecInstances: [arktypeJsonEmitCodec],
39
+ import: {
40
+ package: '@prisma-next/extension-arktype-json/codec-types',
41
+ named: 'CodecTypes',
42
+ alias: 'ArktypeJsonTypes',
43
+ },
44
+ },
45
+ storage: [
46
+ {
47
+ typeId: ARKTYPE_JSON_CODEC_ID,
48
+ familyId: 'sql' as const,
49
+ targetId: 'postgres' as const,
50
+ nativeType: 'jsonb',
51
+ },
52
+ ],
53
+ },
54
+ } as const;
55
+
56
+ /**
57
+ * Public pack metadata. The phantom `__codecTypes` field threads the
58
+ * codec-types map's literal type into the pack ref so contract-builder
59
+ * generics can pick it up; it is never accessed at runtime.
60
+ */
61
+ export const arktypeJsonPackMeta: typeof arktypeJsonPackMetaBase & {
62
+ readonly __codecTypes?: CodecTypes;
63
+ } = arktypeJsonPackMetaBase;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Codec type definitions for the arktype-json extension.
3
+ *
4
+ * Re-export from types module for public API. The pack-meta references
5
+ * this entrypoint via `import: { package:
6
+ * '@prisma-next/extension-arktype-json/codec-types' }` so the emitter
7
+ * threads `CodecTypes` into `contract.d.ts`.
8
+ */
9
+
10
+ export type { CodecTypes } from '../types/codec-types';
@@ -0,0 +1,6 @@
1
+ export type { ArktypeJsonCodec, ArktypeJsonTypeParams } from '../core/arktype-json-codec';
2
+ export {
3
+ ARKTYPE_JSON_CODEC_ID,
4
+ ARKTYPE_JSON_NATIVE_TYPE,
5
+ arktypeJsonCodec,
6
+ } from '../core/arktype-json-codec';
@@ -0,0 +1,2 @@
1
+ export type { ArktypeJsonCodec, ArktypeJsonTypeParams } from '../core/arktype-json-codec';
2
+ export { arktypeJson } from '../core/arktype-json-codec';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Control-plane extension descriptor for arktype-json.
3
+ *
4
+ * Composes pack metadata and the control-plane hooks into the migration-
5
+ * plane shape the framework's control stack consumes. Lives at the
6
+ * control-plane entrypoint so `src/core/**` stays free of migration-plane
7
+ * imports (per `.cursor/rules/multi-plane-entrypoints.mdc`).
8
+ *
9
+ * Unlike pgvector, arktype-json has no database extension to install
10
+ * (`jsonb` is a built-in Postgres type), no `databaseDependencies`, no
11
+ * query operations, and the only control-plane hook is the identity
12
+ * `expandNativeType` (jsonb is dimension-free; the schema in typeParams
13
+ * affects runtime validation only, never DDL).
14
+ */
15
+
16
+ import type {
17
+ CodecControlHooks,
18
+ SqlControlExtensionDescriptor,
19
+ } from '@prisma-next/family-sql/control';
20
+ import { ARKTYPE_JSON_CODEC_ID } from '../core/arktype-json-codec';
21
+ import { arktypeJsonPackMeta } from '../core/pack-meta';
22
+
23
+ const arktypeJsonControlPlaneHooks: CodecControlHooks = {
24
+ expandNativeType: ({ nativeType }) => nativeType,
25
+ };
26
+
27
+ export const arktypeJsonExtensionDescriptor: SqlControlExtensionDescriptor<'postgres'> = {
28
+ ...arktypeJsonPackMeta,
29
+ types: {
30
+ ...arktypeJsonPackMeta.types,
31
+ codecTypes: {
32
+ ...arktypeJsonPackMeta.types.codecTypes,
33
+ controlPlaneHooks: {
34
+ [ARKTYPE_JSON_CODEC_ID]: arktypeJsonControlPlaneHooks,
35
+ },
36
+ },
37
+ },
38
+ create: () => ({
39
+ familyId: 'sql' as const,
40
+ targetId: 'postgres' as const,
41
+ }),
42
+ };
43
+
44
+ export default arktypeJsonExtensionDescriptor;
@@ -0,0 +1 @@
1
+ export { arktypeJsonPackMeta as default, arktypeJsonPackMeta } from '../core/pack-meta';