@prisma-next/sql-contract-ts 0.3.0-dev.131 → 0.3.0-dev.133
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.
- package/README.md +2 -2
- package/dist/contract-builder.d.mts +4 -5
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +33 -144
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +6 -6
- package/schemas/data-contract-sql-v1.json +8 -21
- package/src/contract-builder.ts +47 -82
- package/src/contract.ts +55 -132
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ This package was created in Phase 1 and refactored in Phase 2. It now composes t
|
|
|
33
33
|
## Architecture
|
|
34
34
|
|
|
35
35
|
- **Composes generic core**: Uses `@prisma-next/contract-authoring` for generic builder state management (`TableBuilder`, `ModelBuilder`, `ContractBuilder` base class)
|
|
36
|
-
- **SQL-specific types**: Provides SQL-specific contract types (`SqlContract`, `SqlStorage`, `
|
|
36
|
+
- **SQL-specific types**: Provides SQL-specific contract types (`SqlContract`, `SqlStorage`, `SqlModelStorage`) from `@prisma-next/sql-contract/types`
|
|
37
37
|
- **SQL-specific build()**: Implements SQL-specific `build()` method in `SqlContractBuilder` that constructs `SqlContract` instances with SQL-specific structure (uniques, indexes, foreignKeys arrays)
|
|
38
38
|
|
|
39
39
|
```mermaid
|
|
@@ -152,7 +152,7 @@ export default defineConfig({
|
|
|
152
152
|
- **`@prisma-next/contract-authoring`** - Target-agnostic builder core (builder state types, builder classes, type helpers)
|
|
153
153
|
- **`@prisma-next/contract`** - Core contract types (`ContractBase`)
|
|
154
154
|
- **`@prisma-next/core-control-plane`** - Contract config types used by `typescriptContract`
|
|
155
|
-
- **`@prisma-next/sql-contract`** - SQL contract types (`SqlContract`, `SqlStorage`, `
|
|
155
|
+
- **`@prisma-next/sql-contract`** - SQL contract types (`SqlContract`, `SqlStorage`, `SqlModelStorage`)
|
|
156
156
|
- **`arktype`** - Runtime validation
|
|
157
157
|
- **`ts-toolbelt`** - Type utilities
|
|
158
158
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { BuildModels,
|
|
2
|
-
import { ContractWithTypeMaps, Index, ReferentialAction, SqlContract,
|
|
1
|
+
import { BuildModels, BuildStorageColumn, ColumnBuilderState, ContractBuilder, ContractBuilderState, ExtractColumns, ExtractPrimaryKey, ForeignKeyDefaultsState, ModelBuilder, ModelBuilderState, RelationDefinition, TableBuilder, TableBuilderState } from "@prisma-next/contract-authoring";
|
|
2
|
+
import { ContractWithTypeMaps, Index, ReferentialAction, SqlContract, StorageTypeInstance, TypeMaps } from "@prisma-next/sql-contract/types";
|
|
3
3
|
import { ExtensionPackRef, TargetPackRef } from "@prisma-next/contract/framework-components";
|
|
4
4
|
|
|
5
5
|
//#region src/contract-builder.d.ts
|
|
6
6
|
|
|
7
|
-
type ContractBuilderMappings = SqlMappings;
|
|
8
7
|
type ExtractCodecTypesFromPack<P> = P extends {
|
|
9
8
|
__codecTypes?: infer C;
|
|
10
9
|
} ? C extends Record<string, {
|
|
@@ -59,7 +58,7 @@ declare class SqlContractBuilder<CodecTypes extends Record<string, {
|
|
|
59
58
|
* - `uniques`: defaults to `[]` (empty array)
|
|
60
59
|
* - `indexes`: defaults to `[]` (empty array)
|
|
61
60
|
* - `foreignKeys`: defaults to `[]` (empty array)
|
|
62
|
-
* - `relations`: defaults to `{}` (empty object)
|
|
61
|
+
* - model `relations`: defaults to `{}` (empty object) when not populated by the builder
|
|
63
62
|
* - `nativeType`: required field set from column type descriptor when columns are defined
|
|
64
63
|
*
|
|
65
64
|
* The contract builder is the **only** place where normalization should occur.
|
|
@@ -70,7 +69,7 @@ declare class SqlContractBuilder<CodecTypes extends Record<string, {
|
|
|
70
69
|
*
|
|
71
70
|
* @returns A normalized SqlContract with all required fields present
|
|
72
71
|
*/
|
|
73
|
-
build(): Target extends string ? ContractWithTypeMaps<SqlContract<BuildStorage<Tables, Types>, BuildModels<Models
|
|
72
|
+
build(): Target extends string ? ContractWithTypeMaps<SqlContract<BuildStorage<Tables, Types>, BuildModels<Models>> & {
|
|
74
73
|
readonly schemaVersion: '1';
|
|
75
74
|
readonly target: Target;
|
|
76
75
|
readonly targetFamily: 'sql';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-builder.d.mts","names":[],"sources":["../src/contract-builder.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"contract-builder.d.mts","names":[],"sources":["../src/contract-builder.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -1,126 +1,7 @@
|
|
|
1
1
|
import { ContractBuilder, ModelBuilder, TableBuilder, createTable } from "@prisma-next/contract-authoring";
|
|
2
2
|
import { applyFkDefaults } from "@prisma-next/sql-contract/types";
|
|
3
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
|
-
import "@prisma-next/sql-contract/validate";
|
|
5
|
-
import { ColumnDefaultSchema, ForeignKeySchema, IndexSchema } from "@prisma-next/sql-contract/validators";
|
|
6
|
-
import { type } from "arktype";
|
|
7
4
|
|
|
8
|
-
//#region src/contract.ts
|
|
9
|
-
/**
|
|
10
|
-
* Structural validation schema for SqlContract using Arktype.
|
|
11
|
-
* This validates the shape and types of the contract structure.
|
|
12
|
-
*/
|
|
13
|
-
const StorageColumnSchema = type({
|
|
14
|
-
nativeType: "string",
|
|
15
|
-
codecId: "string",
|
|
16
|
-
nullable: "boolean",
|
|
17
|
-
"typeParams?": "Record<string, unknown>",
|
|
18
|
-
"typeRef?": "string",
|
|
19
|
-
"default?": ColumnDefaultSchema
|
|
20
|
-
});
|
|
21
|
-
const StorageTypeInstanceSchema = type.declare().type({
|
|
22
|
-
codecId: "string",
|
|
23
|
-
nativeType: "string",
|
|
24
|
-
typeParams: "Record<string, unknown>"
|
|
25
|
-
});
|
|
26
|
-
const PrimaryKeySchema = type.declare().type({
|
|
27
|
-
columns: type.string.array().readonly(),
|
|
28
|
-
"name?": "string"
|
|
29
|
-
});
|
|
30
|
-
const UniqueConstraintSchema = type.declare().type({
|
|
31
|
-
columns: type.string.array().readonly(),
|
|
32
|
-
"name?": "string"
|
|
33
|
-
});
|
|
34
|
-
const StorageTableSchema = type({
|
|
35
|
-
columns: type({ "[string]": StorageColumnSchema }),
|
|
36
|
-
"primaryKey?": PrimaryKeySchema,
|
|
37
|
-
uniques: UniqueConstraintSchema.array().readonly(),
|
|
38
|
-
indexes: IndexSchema.array().readonly(),
|
|
39
|
-
foreignKeys: ForeignKeySchema.array().readonly()
|
|
40
|
-
});
|
|
41
|
-
const StorageSchema = type({
|
|
42
|
-
tables: type({ "[string]": StorageTableSchema }),
|
|
43
|
-
"types?": type({ "[string]": StorageTypeInstanceSchema })
|
|
44
|
-
});
|
|
45
|
-
const ModelFieldSchema = type.declare().type({ column: "string" });
|
|
46
|
-
const ModelStorageSchema = type.declare().type({ table: "string" });
|
|
47
|
-
const ModelSchema = type.declare().type({
|
|
48
|
-
storage: ModelStorageSchema,
|
|
49
|
-
fields: type({ "[string]": ModelFieldSchema }),
|
|
50
|
-
relations: type({ "[string]": "unknown" }),
|
|
51
|
-
"owner?": "string"
|
|
52
|
-
});
|
|
53
|
-
const ExecutionMutationDefaultValueSchema = type({
|
|
54
|
-
kind: "'generator'",
|
|
55
|
-
id: type("string").narrow((value, ctx) => {
|
|
56
|
-
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value) ? true : ctx.mustBe("a flat generator id");
|
|
57
|
-
}),
|
|
58
|
-
"params?": "Record<string, unknown>"
|
|
59
|
-
});
|
|
60
|
-
const ExecutionSchema = type({ mutations: { defaults: type({
|
|
61
|
-
ref: {
|
|
62
|
-
table: "string",
|
|
63
|
-
column: "string"
|
|
64
|
-
},
|
|
65
|
-
"onCreate?": ExecutionMutationDefaultValueSchema,
|
|
66
|
-
"onUpdate?": ExecutionMutationDefaultValueSchema
|
|
67
|
-
}).array().readonly() } });
|
|
68
|
-
/**
|
|
69
|
-
* Complete SqlContract schema for structural validation.
|
|
70
|
-
* This validates the entire contract structure at once.
|
|
71
|
-
*/
|
|
72
|
-
const SqlContractSchema = type({
|
|
73
|
-
"schemaVersion?": "'1'",
|
|
74
|
-
target: "string",
|
|
75
|
-
targetFamily: "'sql'",
|
|
76
|
-
storageHash: "string",
|
|
77
|
-
"executionHash?": "string",
|
|
78
|
-
"profileHash?": "string",
|
|
79
|
-
"capabilities?": "Record<string, Record<string, boolean>>",
|
|
80
|
-
"extensionPacks?": "Record<string, unknown>",
|
|
81
|
-
"meta?": "Record<string, unknown>",
|
|
82
|
-
"sources?": "Record<string, unknown>",
|
|
83
|
-
"roots?": "Record<string, string>",
|
|
84
|
-
models: type({ "[string]": ModelSchema }),
|
|
85
|
-
storage: StorageSchema,
|
|
86
|
-
"execution?": ExecutionSchema
|
|
87
|
-
});
|
|
88
|
-
/**
|
|
89
|
-
* Computes mapping dictionaries from models and storage structures.
|
|
90
|
-
* Assumes valid input - validation happens separately in validateContractLogic().
|
|
91
|
-
*
|
|
92
|
-
* @param models - Models object from contract
|
|
93
|
-
* @param storage - Storage object from contract
|
|
94
|
-
* @param existingMappings - Existing mappings from contract input (optional)
|
|
95
|
-
* @returns Computed mappings dictionary
|
|
96
|
-
*/
|
|
97
|
-
function computeMappings(models, _storage, existingMappings) {
|
|
98
|
-
const modelToTable = {};
|
|
99
|
-
const tableToModel = {};
|
|
100
|
-
const fieldToColumn = {};
|
|
101
|
-
const columnToField = {};
|
|
102
|
-
for (const [modelName, model] of Object.entries(models)) {
|
|
103
|
-
const tableName = model.storage.table;
|
|
104
|
-
modelToTable[modelName] = tableName;
|
|
105
|
-
tableToModel[tableName] = modelName;
|
|
106
|
-
const modelFieldToColumn = {};
|
|
107
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
108
|
-
const columnName = field.column;
|
|
109
|
-
modelFieldToColumn[fieldName] = columnName;
|
|
110
|
-
if (!columnToField[tableName]) columnToField[tableName] = {};
|
|
111
|
-
columnToField[tableName][columnName] = fieldName;
|
|
112
|
-
}
|
|
113
|
-
fieldToColumn[modelName] = modelFieldToColumn;
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
modelToTable: existingMappings?.modelToTable ?? modelToTable,
|
|
117
|
-
tableToModel: existingMappings?.tableToModel ?? tableToModel,
|
|
118
|
-
fieldToColumn: existingMappings?.fieldToColumn ?? fieldToColumn,
|
|
119
|
-
columnToField: existingMappings?.columnToField ?? columnToField
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
//#endregion
|
|
124
5
|
//#region src/contract-builder.ts
|
|
125
6
|
function isPlainObject(value) {
|
|
126
7
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -168,7 +49,7 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
|
|
|
168
49
|
* - `uniques`: defaults to `[]` (empty array)
|
|
169
50
|
* - `indexes`: defaults to `[]` (empty array)
|
|
170
51
|
* - `foreignKeys`: defaults to `[]` (empty array)
|
|
171
|
-
* - `relations`: defaults to `{}` (empty object)
|
|
52
|
+
* - model `relations`: defaults to `{}` (empty object) when not populated by the builder
|
|
172
53
|
* - `nativeType`: required field set from column type descriptor when columns are defined
|
|
173
54
|
*
|
|
174
55
|
* The contract builder is the **only** place where normalization should occur.
|
|
@@ -251,35 +132,44 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
|
|
|
251
132
|
const modelState = this.state.models[modelName];
|
|
252
133
|
if (!modelState) continue;
|
|
253
134
|
const modelStateTyped = modelState;
|
|
254
|
-
const
|
|
135
|
+
const tableName = modelStateTyped.table;
|
|
136
|
+
const tableState = this.state.tables[tableName];
|
|
137
|
+
const tableColumns = tableState ? tableState.columns : {};
|
|
138
|
+
const storageFields = {};
|
|
139
|
+
const domainFields = {};
|
|
255
140
|
for (const fieldName in modelStateTyped.fields) {
|
|
256
141
|
const columnName = modelStateTyped.fields[fieldName];
|
|
257
|
-
if (columnName)
|
|
142
|
+
if (!columnName) continue;
|
|
143
|
+
storageFields[fieldName] = { column: columnName };
|
|
144
|
+
const column = tableColumns[columnName];
|
|
145
|
+
if (column) domainFields[fieldName] = {
|
|
146
|
+
codecId: column.type,
|
|
147
|
+
nullable: column.nullable ?? false
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const modelRelations = {};
|
|
151
|
+
if (modelStateTyped.relations) for (const relName in modelStateTyped.relations) {
|
|
152
|
+
const rel = modelStateTyped.relations[relName];
|
|
153
|
+
if (!rel) continue;
|
|
154
|
+
modelRelations[relName] = {
|
|
155
|
+
to: rel.to,
|
|
156
|
+
cardinality: rel.cardinality,
|
|
157
|
+
on: {
|
|
158
|
+
localFields: rel.on.parentCols,
|
|
159
|
+
targetFields: rel.on.childCols
|
|
160
|
+
}
|
|
161
|
+
};
|
|
258
162
|
}
|
|
259
163
|
modelsPartial[modelName] = {
|
|
260
|
-
storage: {
|
|
261
|
-
|
|
262
|
-
|
|
164
|
+
storage: {
|
|
165
|
+
table: tableName,
|
|
166
|
+
fields: storageFields
|
|
167
|
+
},
|
|
168
|
+
fields: domainFields,
|
|
169
|
+
relations: modelRelations
|
|
263
170
|
};
|
|
264
171
|
}
|
|
265
|
-
const relationsPartial = {};
|
|
266
|
-
for (const modelName in this.state.models) {
|
|
267
|
-
const modelState = this.state.models[modelName];
|
|
268
|
-
if (!modelState) continue;
|
|
269
|
-
const modelStateTyped = modelState;
|
|
270
|
-
const tableName = modelStateTyped.table;
|
|
271
|
-
if (!tableName) continue;
|
|
272
|
-
if (modelStateTyped.relations && Object.keys(modelStateTyped.relations).length > 0) {
|
|
273
|
-
if (!relationsPartial[tableName]) relationsPartial[tableName] = {};
|
|
274
|
-
const tableRelations = relationsPartial[tableName];
|
|
275
|
-
if (tableRelations) for (const relationName in modelStateTyped.relations) {
|
|
276
|
-
const relation = modelStateTyped.relations[relationName];
|
|
277
|
-
if (relation) tableRelations[relationName] = relation;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
172
|
const models = modelsPartial;
|
|
282
|
-
const mappings = computeMappings(models, storage);
|
|
283
173
|
const extensionNamespaces = this.state.extensionNamespaces ?? [];
|
|
284
174
|
const extensionPacks = { ...this.state.extensionPacks || {} };
|
|
285
175
|
for (const namespace of extensionNamespaces) if (!Object.hasOwn(extensionPacks, namespace)) extensionPacks[namespace] = {};
|
|
@@ -289,9 +179,8 @@ var SqlContractBuilder = class SqlContractBuilder extends ContractBuilder {
|
|
|
289
179
|
targetFamily: "sql",
|
|
290
180
|
storageHash: this.state.storageHash || "sha256:ts-builder-placeholder",
|
|
291
181
|
models,
|
|
292
|
-
|
|
182
|
+
roots: {},
|
|
293
183
|
storage,
|
|
294
|
-
mappings,
|
|
295
184
|
...execution ? { execution } : {},
|
|
296
185
|
extensionPacks,
|
|
297
186
|
capabilities: this.state.capabilities || {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-builder.mjs","names":["modelToTable: Record<string, string>","tableToModel: Record<string, string>","fieldToColumn: Record<string, Record<string, string>>","columnToField: Record<string, Record<string, string>>","modelFieldToColumn: Record<string, string>","executionDefaults: ExecutionMutationDefault[]","storage: BuildStorage<Tables, Types>","modelsPartial: Partial<BuildModels<Models>>","fields: Partial<Record<string, ModelField>>","relationsPartial: Partial<Record<string, Record<string, RelationDefinition>>>","extensionPacks: Record<string, unknown>"],"sources":["../src/contract.ts","../src/contract-builder.ts"],"sourcesContent":["import type {\n ModelDefinition,\n ModelField,\n ModelStorage,\n PrimaryKey,\n SqlContract,\n SqlMappings,\n SqlStorage,\n StorageTypeInstance,\n UniqueConstraint,\n} from '@prisma-next/sql-contract/types';\nimport { decodeContractDefaults } from '@prisma-next/sql-contract/validate';\nimport {\n ColumnDefaultSchema,\n ForeignKeySchema,\n IndexSchema,\n} from '@prisma-next/sql-contract/validators';\nimport { type } from 'arktype';\nimport type { O } from 'ts-toolbelt';\n\n/**\n * Structural validation schema for SqlContract using Arktype.\n * This validates the shape and types of the contract structure.\n */\n\nconst StorageColumnSchema = type({\n nativeType: 'string',\n codecId: 'string',\n nullable: 'boolean',\n 'typeParams?': 'Record<string, unknown>',\n 'typeRef?': 'string',\n 'default?': ColumnDefaultSchema,\n});\n\nconst StorageTypeInstanceSchema = type.declare<StorageTypeInstance>().type({\n codecId: 'string',\n nativeType: 'string',\n typeParams: 'Record<string, unknown>',\n});\n\nconst PrimaryKeySchema = type.declare<PrimaryKey>().type({\n columns: type.string.array().readonly(),\n 'name?': 'string',\n});\n\nconst UniqueConstraintSchema = type.declare<UniqueConstraint>().type({\n columns: type.string.array().readonly(),\n 'name?': 'string',\n});\n\nconst StorageTableSchema = type({\n columns: type({ '[string]': StorageColumnSchema }),\n 'primaryKey?': PrimaryKeySchema,\n uniques: UniqueConstraintSchema.array().readonly(),\n indexes: IndexSchema.array().readonly(),\n foreignKeys: ForeignKeySchema.array().readonly(),\n});\n\nconst StorageSchema = type({\n tables: type({ '[string]': StorageTableSchema }),\n 'types?': type({ '[string]': StorageTypeInstanceSchema }),\n});\n\nconst ModelFieldSchema = type.declare<ModelField>().type({\n column: 'string',\n});\n\nconst ModelStorageSchema = type.declare<ModelStorage>().type({\n table: 'string',\n});\n\nconst ModelSchema = type.declare<ModelDefinition>().type({\n storage: ModelStorageSchema,\n fields: type({ '[string]': ModelFieldSchema }),\n relations: type({ '[string]': 'unknown' }),\n 'owner?': 'string',\n});\n\nconst GeneratorIdSchema = type('string').narrow((value, ctx) => {\n return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value) ? true : ctx.mustBe('a flat generator id');\n});\n\nconst ExecutionMutationDefaultValueSchema = type({\n kind: \"'generator'\",\n id: GeneratorIdSchema,\n 'params?': 'Record<string, unknown>',\n});\n\nconst ExecutionMutationDefaultSchema = type({\n ref: {\n table: 'string',\n column: 'string',\n },\n 'onCreate?': ExecutionMutationDefaultValueSchema,\n 'onUpdate?': ExecutionMutationDefaultValueSchema,\n});\n\nconst ExecutionSchema = type({\n mutations: {\n defaults: ExecutionMutationDefaultSchema.array().readonly(),\n },\n});\n\n/**\n * Complete SqlContract schema for structural validation.\n * This validates the entire contract structure at once.\n */\nconst SqlContractSchema = type({\n 'schemaVersion?': \"'1'\",\n target: 'string',\n targetFamily: \"'sql'\",\n storageHash: 'string',\n 'executionHash?': 'string',\n 'profileHash?': 'string',\n 'capabilities?': 'Record<string, Record<string, boolean>>',\n 'extensionPacks?': 'Record<string, unknown>',\n 'meta?': 'Record<string, unknown>',\n 'sources?': 'Record<string, unknown>',\n 'roots?': 'Record<string, string>',\n models: type({ '[string]': ModelSchema }),\n storage: StorageSchema,\n 'execution?': ExecutionSchema,\n});\n\n/**\n * Validates the structural shape of a SqlContract using Arktype.\n *\n * **Responsibility: Validation Only**\n * This function validates that the contract has the correct structure and types.\n * It does NOT normalize the contract - normalization must happen in the contract builder.\n *\n * The contract passed to this function must already be normalized (all required fields present).\n * If normalization is needed, it should be done by the contract builder before calling this function.\n *\n * This ensures all required fields are present and have the correct types.\n *\n * @param value - The contract value to validate (typically from a JSON import)\n * @returns The validated contract if structure is valid\n * @throws Error if the contract structure is invalid\n */\nfunction validateContractStructure<T extends SqlContract<SqlStorage>>(\n value: unknown,\n): O.Overwrite<T, { targetFamily: 'sql' }> {\n // Check targetFamily first to provide a clear error message for unsupported target families\n const rawValue = value as { targetFamily?: string };\n if (rawValue.targetFamily !== undefined && rawValue.targetFamily !== 'sql') {\n /* c8 ignore next */\n throw new Error(`Unsupported target family: ${rawValue.targetFamily}`);\n }\n\n const contractResult = SqlContractSchema(value);\n\n if (contractResult instanceof type.errors) {\n const messages = contractResult.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Contract structural validation failed: ${messages}`);\n }\n\n // After validation, contractResult matches the schema and preserves the input structure\n // TypeScript needs an assertion here due to exactOptionalPropertyTypes differences\n // between Arktype's inferred type and the generic T, but runtime-wise they're compatible\n return contractResult as O.Overwrite<T, { targetFamily: 'sql' }>;\n}\n\n/**\n * Computes mapping dictionaries from models and storage structures.\n * Assumes valid input - validation happens separately in validateContractLogic().\n *\n * @param models - Models object from contract\n * @param storage - Storage object from contract\n * @param existingMappings - Existing mappings from contract input (optional)\n * @returns Computed mappings dictionary\n */\nexport function computeMappings(\n models: Record<string, ModelDefinition>,\n _storage: SqlStorage,\n existingMappings?: Partial<SqlMappings>,\n): SqlMappings {\n const modelToTable: Record<string, string> = {};\n const tableToModel: Record<string, string> = {};\n const fieldToColumn: Record<string, Record<string, string>> = {};\n const columnToField: Record<string, Record<string, string>> = {};\n\n for (const [modelName, model] of Object.entries(models)) {\n const tableName = model.storage.table;\n modelToTable[modelName] = tableName;\n tableToModel[tableName] = modelName;\n\n const modelFieldToColumn: Record<string, string> = {};\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const columnName = field.column;\n modelFieldToColumn[fieldName] = columnName;\n\n if (!columnToField[tableName]) {\n columnToField[tableName] = {};\n }\n columnToField[tableName][columnName] = fieldName;\n }\n fieldToColumn[modelName] = modelFieldToColumn;\n }\n\n return {\n modelToTable: existingMappings?.modelToTable ?? modelToTable,\n tableToModel: existingMappings?.tableToModel ?? tableToModel,\n fieldToColumn: existingMappings?.fieldToColumn ?? fieldToColumn,\n columnToField: existingMappings?.columnToField ?? columnToField,\n };\n}\n\n/**\n * Validates logical consistency of a **structurally validated** SqlContract.\n * This checks that references (e.g., foreign keys, primary keys, uniques) point to storage objects that already exist.\n * Structural validation is expected to have already completed before this helper runs.\n *\n * Rule: keep this focused on structural consistency only; capability/feature\n * gating (e.g., defaults.*) belongs in migration/runtime verification, not here.\n *\n * @param structurallyValidatedContract - The contract whose structure has already been validated\n * @throws Error if logical validation fails\n */\nfunction validateContractLogic(structurallyValidatedContract: SqlContract<SqlStorage>): void {\n const { storage, models } = structurallyValidatedContract;\n const tableNames = new Set(Object.keys(storage.tables));\n\n // Validate storage.types if present\n if (storage.types) {\n for (const [typeName, typeInstance] of Object.entries(storage.types)) {\n // Validate typeParams is not an array (arrays are objects in JS but not valid here)\n if (Array.isArray(typeInstance.typeParams)) {\n throw new Error(\n `Type instance \"${typeName}\" has invalid typeParams: must be a plain object, not an array`,\n );\n }\n }\n }\n\n // Validate columns in all tables\n for (const [tableName, table] of Object.entries(storage.tables)) {\n for (const [columnName, column] of Object.entries(table.columns)) {\n // Validate typeParams and typeRef are mutually exclusive\n if (column.typeParams !== undefined && column.typeRef !== undefined) {\n throw new Error(\n `Column \"${columnName}\" in table \"${tableName}\" has both typeParams and typeRef; these are mutually exclusive`,\n );\n }\n\n // Validate typeParams is not an array (arrays are objects in JS but not valid here)\n if (column.typeParams !== undefined && Array.isArray(column.typeParams)) {\n throw new Error(\n `Column \"${columnName}\" in table \"${tableName}\" has invalid typeParams: must be a plain object, not an array`,\n );\n }\n\n // Validate NOT NULL columns do not have literal null defaults\n if (!column.nullable && column.default?.kind === 'literal' && column.default.value === null) {\n throw new Error(\n `Table \"${tableName}\" column \"${columnName}\" is NOT NULL but has a literal null default`,\n );\n }\n\n // Validate typeRef points to an existing storage.types key and matches codecId/nativeType\n if (column.typeRef !== undefined) {\n const referencedType = storage.types?.[column.typeRef];\n if (!referencedType) {\n throw new Error(\n `Column \"${columnName}\" in table \"${tableName}\" references non-existent type instance \"${column.typeRef}\" (not found in storage.types)`,\n );\n }\n\n if (column.codecId !== referencedType.codecId) {\n throw new Error(\n `Column \"${columnName}\" in table \"${tableName}\" has codecId \"${column.codecId}\" but references type instance \"${column.typeRef}\" with codecId \"${referencedType.codecId}\"`,\n );\n }\n\n if (column.nativeType !== referencedType.nativeType) {\n throw new Error(\n `Column \"${columnName}\" in table \"${tableName}\" has nativeType \"${column.nativeType}\" but references type instance \"${column.typeRef}\" with nativeType \"${referencedType.nativeType}\"`,\n );\n }\n }\n }\n }\n\n // Validate models\n for (const [modelName, modelUnknown] of Object.entries(models)) {\n // normalizeContract() ensures models have both domain (DomainModel) and SQL-specific (ModelDefinition) properties\n const model = modelUnknown as unknown as ModelDefinition;\n // Validate model has storage.table\n if (!model.storage?.table) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" is missing storage.table`);\n }\n\n const tableName = model.storage.table;\n\n // Validate model's table exists in storage\n if (!tableNames.has(tableName)) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n const table = storage.tables[tableName];\n if (!table) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n // Validate model's table has a primary key\n if (!table.primaryKey) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" table \"${tableName}\" is missing a primary key`);\n }\n\n const columnNames = new Set(Object.keys(table.columns));\n\n // Validate model fields\n if (!model.fields) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" is missing fields`);\n }\n\n for (const [fieldName, fieldUnknown] of Object.entries(model.fields)) {\n const field = fieldUnknown as { column: string };\n // Validate field has column property\n if (!field.column) {\n /* c8 ignore next */\n throw new Error(`Model \"${modelName}\" field \"${fieldName}\" is missing column property`);\n }\n\n // Validate field's column exists in the model's backing table\n if (!columnNames.has(field.column)) {\n /* c8 ignore next */\n throw new Error(\n `Model \"${modelName}\" field \"${fieldName}\" references non-existent column \"${field.column}\" in table \"${tableName}\"`,\n );\n }\n }\n\n // Validate model relations have corresponding foreign keys\n if (model.relations) {\n for (const [relationName, relation] of Object.entries(model.relations)) {\n // For now, we'll do basic validation. Full FK validation can be added later\n // This would require checking that the relation's on.parentCols/childCols match FKs\n if (\n typeof relation === 'object' &&\n relation !== null &&\n 'on' in relation &&\n 'to' in relation\n ) {\n const on = relation.on as { parentCols?: string[]; childCols?: string[] };\n const cardinality = (relation as { cardinality?: string }).cardinality;\n if (on.parentCols && on.childCols) {\n // For 1:N relations, the foreign key is on the child table\n // For N:1 relations, the foreign key is on the parent table (this table)\n // For now, we'll skip validation for 1:N relations as the FK is on the child table\n // and we'll validate it when we process the child model\n if (cardinality === '1:N') {\n // Foreign key is on the child table, skip validation here\n // It will be validated when we process the child model\n continue;\n }\n\n // For N:1 relations, check that there's a foreign key matching this relation\n const hasMatchingFk = table.foreignKeys?.some((fk) => {\n return (\n fk.columns.length === on.childCols?.length &&\n fk.columns.every((col, i) => col === on.childCols?.[i]) &&\n fk.references.table &&\n fk.references.columns.length === on.parentCols?.length &&\n fk.references.columns.every((col, i) => col === on.parentCols?.[i])\n );\n });\n\n if (!hasMatchingFk) {\n /* c8 ignore next */\n throw new Error(\n `Model \"${modelName}\" relation \"${relationName}\" does not have a corresponding foreign key in table \"${tableName}\"`,\n );\n }\n }\n }\n }\n }\n }\n\n for (const [tableName, table] of Object.entries(storage.tables)) {\n const columnNames = new Set(Object.keys(table.columns));\n\n // Validate primaryKey references existing columns\n if (table.primaryKey) {\n for (const colName of table.primaryKey.columns) {\n if (!columnNames.has(colName)) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" primaryKey references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n // Validate unique constraints reference existing columns\n for (const unique of table.uniques) {\n for (const colName of unique.columns) {\n if (!columnNames.has(colName)) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" unique constraint references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n // Validate indexes reference existing columns\n for (const index of table.indexes) {\n for (const colName of index.columns) {\n if (!columnNames.has(colName)) {\n /* c8 ignore next */\n throw new Error(`Table \"${tableName}\" index references non-existent column \"${colName}\"`);\n }\n }\n }\n\n // Validate foreignKeys reference existing tables and columns\n for (const fk of table.foreignKeys) {\n // Validate FK columns exist in the referencing table\n for (const colName of fk.columns) {\n if (!columnNames.has(colName)) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\"`,\n );\n }\n }\n\n // Validate referenced table exists\n if (!tableNames.has(fk.references.table)) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n }\n\n // Validate referenced columns exist in the referenced table\n const referencedTable = storage.tables[fk.references.table];\n if (!referencedTable) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n }\n const referencedColumnNames = new Set(Object.keys(referencedTable.columns));\n\n for (const colName of fk.references.columns) {\n if (!referencedColumnNames.has(colName)) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\" in table \"${fk.references.table}\"`,\n );\n }\n }\n\n if (fk.columns.length !== fk.references.columns.length) {\n /* c8 ignore next */\n throw new Error(\n `Table \"${tableName}\" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`,\n );\n }\n }\n }\n}\n\nimport { normalizeContract } from '@prisma-next/sql-contract/validate';\nexport { normalizeContract };\n\n/**\n * Validates that a JSON import conforms to the SqlContract structure\n * and returns a fully typed SqlContract.\n *\n * This function is specifically for validating JSON imports (e.g., from contract.json).\n * Contracts created via the builder API (defineContract) are already valid and should\n * not be passed to this function - use them directly without validation.\n *\n * Performs both structural validation (using Arktype) and logical validation\n * (ensuring all references are valid).\n *\n *\n * The type parameter `TContract` must be a fully-typed contract type (e.g., from `contract.d.ts`),\n * NOT a generic `SqlContract<SqlStorage>`.\n *\n * **Correct:**\n * ```typescript\n * import type { Contract } from './contract.d';\n * const contract = validateContract<Contract>(contractJson);\n * ```\n *\n * **Incorrect:**\n * ```typescript\n * import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';\n * const contract = validateContract<SqlContract<SqlStorage>>(contractJson);\n * // ❌ Types will be inferred as 'unknown' - this won't work!\n * ```\n *\n * The type parameter provides the specific table structure, column types, and model definitions.\n * This function validates the runtime structure matches the type, but does not infer types\n * from JSON (as JSON imports lose literal type information).\n *\n * @param value - The contract value to validate (must be from a JSON import, not a builder)\n * @returns A validated contract matching the TContract type\n * @throws Error if the contract structure or logic is invalid\n */\nexport function validateContract<TContract extends SqlContract<SqlStorage>>(\n value: unknown,\n): TContract {\n // Normalize contract first (add defaults for missing fields)\n const normalized = normalizeContract(value);\n\n const structurallyValid = validateContractStructure<SqlContract<SqlStorage>>(normalized);\n\n const contractForValidation = structurallyValid as SqlContract<SqlStorage>;\n\n // Validate contract logic (contracts must already have fully qualified type IDs)\n validateContractLogic(contractForValidation);\n\n // Extract existing mappings (optional - will be computed if missing)\n const existingMappings = (contractForValidation as { mappings?: Partial<SqlMappings> }).mappings;\n\n // Compute mappings from models and storage\n const mappings = computeMappings(\n contractForValidation.models as Record<string, ModelDefinition>,\n contractForValidation.storage,\n existingMappings,\n );\n\n // Add default values for optional metadata fields if missing\n const contractWithMappings = {\n ...structurallyValid,\n models: contractForValidation.models,\n relations: contractForValidation.relations,\n storage: contractForValidation.storage,\n mappings,\n };\n\n // Type assertion: The caller provides the strict type via TContract.\n // We validate the structure matches, but the precise types come from contract.d.ts\n return decodeContractDefaults(contractWithMappings) as TContract;\n}\n","import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/contract/framework-components';\nimport type {\n ColumnDefault,\n ColumnDefaultLiteralInputValue,\n ColumnDefaultLiteralValue,\n ExecutionMutationDefault,\n ExecutionMutationDefaultValue,\n TaggedRaw,\n} from '@prisma-next/contract/types';\nimport type {\n ColumnBuilderState,\n ColumnTypeDescriptor,\n ContractBuilderState,\n ForeignKeyDefaultsState,\n ModelBuilderState,\n RelationDefinition,\n TableBuilderState,\n} from '@prisma-next/contract-authoring';\nimport {\n type BuildModels,\n type BuildRelations,\n type BuildStorageColumn,\n ContractBuilder,\n createTable,\n type ExtractColumns,\n type ExtractPrimaryKey,\n ModelBuilder,\n type Mutable,\n TableBuilder,\n} from '@prisma-next/contract-authoring';\nimport {\n applyFkDefaults,\n type ContractWithTypeMaps,\n type Index,\n type ModelDefinition,\n type ModelField,\n type ReferentialAction,\n type SqlContract,\n type SqlMappings,\n type SqlStorage,\n type StorageTypeInstance,\n type TypeMaps,\n} from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { computeMappings } from './contract';\n\ntype ColumnDefaultForCodec<\n CodecTypes extends Record<string, { output: unknown }>,\n CodecId extends string,\n> =\n | {\n readonly kind: 'literal';\n readonly value: CodecId extends keyof CodecTypes ? CodecTypes[CodecId]['output'] : unknown;\n }\n | { readonly kind: 'function'; readonly expression: string };\n\ntype SqlNullableColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = {\n readonly type: Descriptor;\n readonly nullable: true;\n readonly typeParams?: Record<string, unknown>;\n readonly default?: ColumnDefaultForCodec<CodecTypes, Descriptor['codecId']>;\n};\n\ntype SqlNonNullableColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = {\n readonly type: Descriptor;\n readonly nullable?: false;\n readonly typeParams?: Record<string, unknown>;\n readonly default?: ColumnDefaultForCodec<CodecTypes, Descriptor['codecId']>;\n};\n\ntype SqlGeneratedColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = Omit<SqlNonNullableColumnOptions<Descriptor, CodecTypes>, 'default' | 'nullable'> & {\n readonly nullable?: false;\n readonly generated: ExecutionMutationDefaultValue;\n};\n\ntype SqlColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> =\n | SqlNullableColumnOptions<Descriptor, CodecTypes>\n | SqlNonNullableColumnOptions<Descriptor, CodecTypes>;\n\nexport interface SqlTableBuilder<\n Name extends string,\n CodecTypes extends Record<string, { output: unknown }>,\n Columns extends Record<string, ColumnBuilderState<string, boolean, string>> = Record<\n never,\n ColumnBuilderState<string, boolean, string>\n >,\n PrimaryKey extends readonly string[] | undefined = undefined,\n> extends Omit<TableBuilder<Name, Columns, PrimaryKey>, 'column' | 'generated'> {\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlNullableColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, true, Descriptor['codecId']>>,\n PrimaryKey\n >;\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlNonNullableColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, false, Descriptor['codecId']>>,\n PrimaryKey\n >;\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, boolean, Descriptor['codecId']>>,\n PrimaryKey\n >;\n generated<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlGeneratedColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, false, Descriptor['codecId']>>,\n PrimaryKey\n >;\n}\n\ntype ContractBuilderMappings = SqlMappings;\n\ntype ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }\n ? C extends Record<string, { output: unknown }>\n ? C\n : Record<string, never>\n : Record<string, never>;\n\ntype UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (\n k: infer I,\n) => void\n ? I\n : never;\n\ntype MergeExtensionCodecTypes<Packs extends Record<string, unknown>> = UnionToIntersection<\n {\n [K in keyof Packs]: ExtractCodecTypesFromPack<Packs[K]>;\n }[keyof Packs]\n>;\n\ntype BuildStorageTable<\n _TableName extends string,\n Columns extends Record<string, ColumnBuilderState<string, boolean, string>>,\n PK extends readonly string[] | undefined,\n> = {\n readonly columns: {\n readonly [K in keyof Columns]: Columns[K] extends ColumnBuilderState<\n string,\n infer Null,\n infer TType\n >\n ? BuildStorageColumn<Null & boolean, TType>\n : never;\n };\n readonly uniques: ReadonlyArray<{ readonly columns: readonly string[]; readonly name?: string }>;\n readonly indexes: ReadonlyArray<Index>;\n readonly foreignKeys: ReadonlyArray<{\n readonly columns: readonly string[];\n readonly references: { readonly table: string; readonly columns: readonly string[] };\n readonly name?: string;\n readonly onDelete?: ReferentialAction;\n readonly onUpdate?: ReferentialAction;\n readonly constraint: boolean;\n readonly index: boolean;\n }>;\n} & (PK extends readonly string[]\n ? { readonly primaryKey: { readonly columns: PK; readonly name?: string } }\n : Record<string, never>);\n\ntype BuildStorage<\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n >,\n Types extends Record<string, StorageTypeInstance>,\n> = {\n readonly tables: {\n readonly [K in keyof Tables]: BuildStorageTable<\n K & string,\n ExtractColumns<Tables[K]>,\n ExtractPrimaryKey<Tables[K]>\n >;\n };\n readonly types: Types;\n};\n\ntype BuildStorageTables<\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n >,\n> = {\n readonly [K in keyof Tables]: BuildStorageTable<\n K & string,\n ExtractColumns<Tables[K]>,\n ExtractPrimaryKey<Tables[K]>\n >;\n};\n\nexport interface ColumnBuilder<Name extends string, Nullable extends boolean, Type extends string> {\n nullable<Value extends boolean>(value?: Value): ColumnBuilder<Name, Value, Type>;\n type<Id extends string>(id: Id): ColumnBuilder<Name, Nullable, Id>;\n build(): ColumnBuilderState<Name, Nullable, Type>;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isJsonValue(value: unknown): value is ColumnDefaultLiteralValue {\n if (value === null) return true;\n const valueType = typeof value;\n if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') return true;\n if (Array.isArray(value)) {\n return value.every((item) => isJsonValue(item));\n }\n if (isPlainObject(value)) {\n return Object.values(value).every((item) => isJsonValue(item));\n }\n return false;\n}\n\nfunction encodeDefaultLiteralValue(\n value: ColumnDefaultLiteralInputValue,\n): ColumnDefaultLiteralValue {\n if (typeof value === 'bigint') {\n return { $type: 'bigint', value: value.toString() };\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (isJsonValue(value)) {\n if (isPlainObject(value) && '$type' in value) {\n return { $type: 'raw', value } satisfies TaggedRaw;\n }\n return value;\n }\n throw new Error(\n 'Unsupported column default literal value: expected JSON-safe value, bigint, or Date.',\n );\n}\n\nfunction encodeColumnDefault(defaultInput: ColumnDefault): ColumnDefault {\n if (defaultInput.kind === 'function') {\n return { kind: 'function', expression: defaultInput.expression };\n }\n return { kind: 'literal', value: encodeDefaultLiteralValue(defaultInput.value) };\n}\n\nclass SqlContractBuilder<\n CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,\n Target extends string | undefined = undefined,\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n > = Record<never, never>,\n Models extends Record<\n string,\n ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>\n > = Record<never, never>,\n Types extends Record<string, StorageTypeInstance> = Record<never, never>,\n StorageHash extends string | undefined = undefined,\n ExtensionPacks extends Record<string, unknown> | undefined = undefined,\n Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,\n> extends ContractBuilder<Target, Tables, Models, StorageHash, ExtensionPacks, Capabilities> {\n protected declare readonly state: ContractBuilderState<\n Target,\n Tables,\n Models,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > & {\n readonly storageTypes?: Types;\n };\n /**\n * This method is responsible for normalizing the contract IR by setting default values\n * for all required fields:\n * - `nullable`: defaults to `false` if not provided\n * - `uniques`: defaults to `[]` (empty array)\n * - `indexes`: defaults to `[]` (empty array)\n * - `foreignKeys`: defaults to `[]` (empty array)\n * - `relations`: defaults to `{}` (empty object) for both model-level and contract-level\n * - `nativeType`: required field set from column type descriptor when columns are defined\n *\n * The contract builder is the **only** place where normalization should occur.\n * Validators, parsers, and emitters should assume the contract is already normalized.\n *\n * **Required**: Use column type descriptors (e.g., `int4Column`, `textColumn`) when defining columns.\n * This ensures `nativeType` is set correctly at build time.\n *\n * @returns A normalized SqlContract with all required fields present\n */\n build(): Target extends string\n ? ContractWithTypeMaps<\n SqlContract<\n BuildStorage<Tables, Types>,\n BuildModels<Models>,\n BuildRelations<Models>,\n ContractBuilderMappings\n > & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly storageHash: StorageHash extends string ? StorageHash : string;\n } & (ExtensionPacks extends Record<string, unknown>\n ? { readonly extensionPacks: ExtensionPacks }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>),\n TypeMaps<CodecTypes, Record<string, never>>\n >\n : never {\n type BuiltContract = Target extends string\n ? ContractWithTypeMaps<\n SqlContract<\n BuildStorage<Tables, Types>,\n BuildModels<Models>,\n BuildRelations<Models>,\n ContractBuilderMappings\n > & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly storageHash: StorageHash extends string ? StorageHash : string;\n } & (ExtensionPacks extends Record<string, unknown>\n ? { readonly extensionPacks: ExtensionPacks }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>),\n TypeMaps<CodecTypes, Record<string, never>>\n >\n : never;\n if (!this.state.target) {\n throw new Error('target is required. Call .target() before .build()');\n }\n\n const target = this.state.target as Target & string;\n\n const storageTables = {} as Partial<Mutable<BuildStorageTables<Tables>>>;\n const executionDefaults: ExecutionMutationDefault[] = [];\n\n for (const tableName of Object.keys(this.state.tables) as Array<keyof Tables & string>) {\n const tableState = this.state.tables[tableName];\n if (!tableState) continue;\n\n type TableKey = typeof tableName;\n type ColumnDefs = ExtractColumns<Tables[TableKey]>;\n type PrimaryKey = ExtractPrimaryKey<Tables[TableKey]>;\n\n const columns = {} as Partial<{\n [K in keyof ColumnDefs]: BuildStorageColumn<\n ColumnDefs[K]['nullable'] & boolean,\n ColumnDefs[K]['type']\n >;\n }>;\n\n for (const columnName in tableState.columns) {\n const columnState = tableState.columns[columnName];\n if (!columnState) continue;\n const codecId = columnState.type;\n const nativeType = columnState.nativeType;\n const typeRef = columnState.typeRef;\n\n const encodedDefault =\n columnState.default !== undefined\n ? encodeColumnDefault(columnState.default as ColumnDefault)\n : undefined;\n\n columns[columnName as keyof ColumnDefs] = {\n nativeType,\n codecId,\n nullable: (columnState.nullable ?? false) as ColumnDefs[keyof ColumnDefs]['nullable'] &\n boolean,\n ...ifDefined('typeParams', columnState.typeParams),\n ...ifDefined('default', encodedDefault),\n ...ifDefined('typeRef', typeRef),\n } as BuildStorageColumn<\n ColumnDefs[keyof ColumnDefs]['nullable'] & boolean,\n ColumnDefs[keyof ColumnDefs]['type']\n >;\n\n if ('executionDefault' in columnState && columnState.executionDefault) {\n executionDefaults.push({\n ref: { table: tableName, column: columnName },\n onCreate: columnState.executionDefault,\n });\n }\n }\n\n // Build uniques from table state\n const uniques = (tableState.uniques ?? []).map((u) => ({\n columns: u.columns,\n ...(u.name ? { name: u.name } : {}),\n }));\n\n // Build indexes from table state\n const indexes = (tableState.indexes ?? []).map((i) => ({\n columns: i.columns,\n ...(i.name ? { name: i.name } : {}),\n ...(i.using ? { using: i.using } : {}),\n ...(i.config ? { config: i.config } : {}),\n }));\n\n // Build foreign keys from table state, materializing defaults\n const foreignKeys = (tableState.foreignKeys ?? []).map((fk) => ({\n columns: fk.columns,\n references: fk.references,\n ...applyFkDefaults(fk, this.state.foreignKeyDefaults),\n ...(fk.name ? { name: fk.name } : {}),\n ...(fk.onDelete !== undefined ? { onDelete: fk.onDelete } : {}),\n ...(fk.onUpdate !== undefined ? { onUpdate: fk.onUpdate } : {}),\n }));\n\n const table = {\n columns: columns as {\n [K in keyof ColumnDefs]: BuildStorageColumn<\n ColumnDefs[K]['nullable'] & boolean,\n ColumnDefs[K]['type']\n >;\n },\n uniques,\n indexes,\n foreignKeys,\n ...(tableState.primaryKey\n ? {\n primaryKey: {\n columns: tableState.primaryKey,\n ...(tableState.primaryKeyName ? { name: tableState.primaryKeyName } : {}),\n },\n }\n : {}),\n } as unknown as BuildStorageTable<TableKey & string, ColumnDefs, PrimaryKey>;\n\n (storageTables as Mutable<BuildStorageTables<Tables>>)[tableName] = table;\n }\n\n const storageTypes = (this.state.storageTypes ?? {}) as Types;\n const storage: BuildStorage<Tables, Types> = {\n tables: storageTables as BuildStorageTables<Tables>,\n types: storageTypes,\n };\n\n const execution =\n executionDefaults.length > 0\n ? {\n mutations: {\n defaults: executionDefaults.sort((a, b) => {\n const tableCompare = a.ref.table.localeCompare(b.ref.table);\n if (tableCompare !== 0) {\n return tableCompare;\n }\n return a.ref.column.localeCompare(b.ref.column);\n }),\n },\n }\n : undefined;\n\n // Build models - construct as partial first, then assert full type\n const modelsPartial: Partial<BuildModels<Models>> = {};\n\n // Iterate over models - TypeScript will see keys as string, but type assertion preserves literals\n for (const modelName in this.state.models) {\n const modelState = this.state.models[modelName];\n if (!modelState) continue;\n\n const modelStateTyped = modelState as unknown as {\n name: string;\n table: string;\n fields: Record<string, string>;\n };\n\n // Build fields object\n const fields: Partial<Record<string, ModelField>> = {};\n\n // Iterate over fields\n for (const fieldName in modelStateTyped.fields) {\n const columnName = modelStateTyped.fields[fieldName];\n if (columnName) {\n fields[fieldName] = {\n column: columnName,\n };\n }\n }\n\n // Assign to models - type assertion preserves literal keys\n (modelsPartial as unknown as Record<string, ModelDefinition>)[modelName] = {\n storage: {\n table: modelStateTyped.table,\n },\n fields: fields as Record<string, ModelField>,\n relations: {},\n };\n }\n\n // Build relations object - organized by table name\n const relationsPartial: Partial<Record<string, Record<string, RelationDefinition>>> = {};\n\n // Iterate over models to collect relations\n for (const modelName in this.state.models) {\n const modelState = this.state.models[modelName];\n if (!modelState) continue;\n\n const modelStateTyped = modelState as unknown as {\n name: string;\n table: string;\n fields: Record<string, string>;\n relations: Record<string, RelationDefinition>;\n };\n\n const tableName = modelStateTyped.table;\n if (!tableName) continue;\n\n // Only initialize relations object for this table if it has relations\n if (modelStateTyped.relations && Object.keys(modelStateTyped.relations).length > 0) {\n if (!relationsPartial[tableName]) {\n relationsPartial[tableName] = {};\n }\n\n // Add relations from this model to the table's relations\n const tableRelations = relationsPartial[tableName];\n if (tableRelations) {\n for (const relationName in modelStateTyped.relations) {\n const relation = modelStateTyped.relations[relationName];\n if (relation) {\n tableRelations[relationName] = relation;\n }\n }\n }\n }\n }\n\n const models = modelsPartial as unknown as BuildModels<Models>;\n\n const baseMappings = computeMappings(\n models as unknown as Record<string, ModelDefinition>,\n storage as SqlStorage,\n );\n\n const mappings = baseMappings as ContractBuilderMappings;\n\n const extensionNamespaces = this.state.extensionNamespaces ?? [];\n const extensionPacks: Record<string, unknown> = { ...(this.state.extensionPacks || {}) };\n for (const namespace of extensionNamespaces) {\n if (!Object.hasOwn(extensionPacks, namespace)) {\n extensionPacks[namespace] = {};\n }\n }\n\n // Construct contract with explicit type that matches the generic parameters\n // This ensures TypeScript infers literal types from the generics, not runtime values\n // Always include relations, even if empty (normalized to empty object)\n const contract = {\n schemaVersion: '1' as const,\n target,\n targetFamily: 'sql' as const,\n storageHash: this.state.storageHash || 'sha256:ts-builder-placeholder',\n models,\n relations: relationsPartial,\n storage,\n mappings,\n ...(execution ? { execution } : {}),\n extensionPacks,\n capabilities: this.state.capabilities || {},\n meta: {},\n sources: {},\n } as unknown as BuiltContract;\n\n return contract as unknown as ReturnType<\n SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >['build']\n >;\n }\n\n override target<\n T extends string,\n TPack extends TargetPackRef<string, T> = TargetPackRef<string, T>,\n >(\n packRef: TPack & TargetPackRef<string, T>,\n ): SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n target: packRef.targetId,\n }) as SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >;\n }\n\n extensionPacks<const Packs extends Record<string, ExtensionPackRef<'sql', string>>>(\n packs: Packs,\n ): SqlContractBuilder<\n CodecTypes & MergeExtensionCodecTypes<Packs>,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n if (!this.state.target) {\n throw new Error('extensionPacks() requires target() to be called first');\n }\n\n const namespaces = new Set(this.state.extensionNamespaces ?? []);\n\n for (const packRef of Object.values(packs) as ExtensionPackRef<'sql', string>[]) {\n if (!packRef) continue;\n\n if (packRef.kind !== 'extension') {\n throw new Error(\n `extensionPacks() only accepts extension pack refs. Received kind \"${packRef.kind}\".`,\n );\n }\n\n if (packRef.familyId !== 'sql') {\n throw new Error(\n `extension pack \"${packRef.id}\" targets family \"${packRef.familyId}\" but this builder targets \"sql\".`,\n );\n }\n\n if (packRef.targetId && packRef.targetId !== this.state.target) {\n throw new Error(\n `extension pack \"${packRef.id}\" targets \"${packRef.targetId}\" but builder target is \"${this.state.target}\".`,\n );\n }\n\n namespaces.add(packRef.id);\n }\n\n return new SqlContractBuilder<\n CodecTypes & MergeExtensionCodecTypes<Packs>,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n extensionNamespaces: [...namespaces],\n });\n }\n\n override capabilities<C extends Record<string, Record<string, boolean>>>(\n capabilities: C,\n ): SqlContractBuilder<CodecTypes, Target, Tables, Models, Types, StorageHash, ExtensionPacks, C> {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n C\n >({\n ...this.state,\n capabilities,\n });\n }\n\n override storageHash<H extends string>(\n hash: H,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n H,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n H,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n storageHash: hash,\n });\n }\n\n override table<\n TableName extends string,\n T extends TableBuilder<\n TableName,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >,\n >(\n name: TableName,\n callback: (t: TableBuilder<TableName>) => T | undefined,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n const tableBuilder = createTable(name);\n const result = callback(\n tableBuilder as unknown as SqlTableBuilder<\n TableName,\n CodecTypes\n > as unknown as TableBuilder<TableName>,\n );\n const finalBuilder = result instanceof TableBuilder ? result : tableBuilder;\n const tableState = finalBuilder.build();\n\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n tables: { ...this.state.tables, [name]: tableState } as Tables &\n Record<TableName, ReturnType<T['build']>>,\n });\n }\n\n override model<\n ModelName extends string,\n TableName extends string,\n M extends ModelBuilder<\n ModelName,\n TableName,\n Record<string, string>,\n Record<string, RelationDefinition>\n >,\n >(\n name: ModelName,\n table: TableName,\n callback: (\n m: ModelBuilder<ModelName, TableName, Record<never, never>, Record<never, never>>,\n ) => M | undefined,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n const modelBuilder = new ModelBuilder<ModelName, TableName>(name, table);\n const result = callback(modelBuilder);\n const finalBuilder = result instanceof ModelBuilder ? result : modelBuilder;\n const modelState = finalBuilder.build();\n\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n models: { ...this.state.models, [name]: modelState } as Models &\n Record<ModelName, ReturnType<M['build']>>,\n });\n }\n\n override foreignKeyDefaults(\n config: ForeignKeyDefaultsState,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n foreignKeyDefaults: config,\n });\n }\n\n storageType<Name extends string, Type extends StorageTypeInstance>(\n name: Name,\n typeInstance: Type,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types & Record<Name, Type>,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types & Record<Name, Type>,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n storageTypes: {\n ...(this.state.storageTypes ?? {}),\n [name]: typeInstance,\n },\n });\n }\n}\n\nexport function defineContract<\n CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,\n>(): SqlContractBuilder<CodecTypes> {\n return new SqlContractBuilder<CodecTypes>();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"contract-builder.mjs","names":["executionDefaults: ExecutionMutationDefault[]","storage: BuildStorage<Tables, Types>","modelsPartial: Partial<BuildModels<Models>>","storageFields: Record<string, { readonly column: string }>","domainFields: Record<string, Record<string, unknown>>","modelRelations: Record<string, Record<string, unknown>>","extensionPacks: Record<string, unknown>"],"sources":["../src/contract-builder.ts"],"sourcesContent":["import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/contract/framework-components';\nimport type {\n ColumnDefault,\n ColumnDefaultLiteralInputValue,\n ColumnDefaultLiteralValue,\n ExecutionMutationDefault,\n ExecutionMutationDefaultValue,\n TaggedRaw,\n} from '@prisma-next/contract/types';\nimport type {\n ColumnBuilderState,\n ColumnTypeDescriptor,\n ContractBuilderState,\n ForeignKeyDefaultsState,\n ModelBuilderState,\n RelationDefinition,\n TableBuilderState,\n} from '@prisma-next/contract-authoring';\nimport {\n type BuildModels,\n type BuildStorageColumn,\n ContractBuilder,\n createTable,\n type ExtractColumns,\n type ExtractPrimaryKey,\n ModelBuilder,\n type Mutable,\n TableBuilder,\n} from '@prisma-next/contract-authoring';\nimport {\n applyFkDefaults,\n type ContractWithTypeMaps,\n type Index,\n type ReferentialAction,\n type SqlContract,\n type StorageTypeInstance,\n type TypeMaps,\n} from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\ntype ColumnDefaultForCodec<\n CodecTypes extends Record<string, { output: unknown }>,\n CodecId extends string,\n> =\n | {\n readonly kind: 'literal';\n readonly value: CodecId extends keyof CodecTypes ? CodecTypes[CodecId]['output'] : unknown;\n }\n | { readonly kind: 'function'; readonly expression: string };\n\ntype SqlNullableColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = {\n readonly type: Descriptor;\n readonly nullable: true;\n readonly typeParams?: Record<string, unknown>;\n readonly default?: ColumnDefaultForCodec<CodecTypes, Descriptor['codecId']>;\n};\n\ntype SqlNonNullableColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = {\n readonly type: Descriptor;\n readonly nullable?: false;\n readonly typeParams?: Record<string, unknown>;\n readonly default?: ColumnDefaultForCodec<CodecTypes, Descriptor['codecId']>;\n};\n\ntype SqlGeneratedColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> = Omit<SqlNonNullableColumnOptions<Descriptor, CodecTypes>, 'default' | 'nullable'> & {\n readonly nullable?: false;\n readonly generated: ExecutionMutationDefaultValue;\n};\n\ntype SqlColumnOptions<\n Descriptor extends ColumnTypeDescriptor,\n CodecTypes extends Record<string, { output: unknown }>,\n> =\n | SqlNullableColumnOptions<Descriptor, CodecTypes>\n | SqlNonNullableColumnOptions<Descriptor, CodecTypes>;\n\nexport interface SqlTableBuilder<\n Name extends string,\n CodecTypes extends Record<string, { output: unknown }>,\n Columns extends Record<string, ColumnBuilderState<string, boolean, string>> = Record<\n never,\n ColumnBuilderState<string, boolean, string>\n >,\n PrimaryKey extends readonly string[] | undefined = undefined,\n> extends Omit<TableBuilder<Name, Columns, PrimaryKey>, 'column' | 'generated'> {\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlNullableColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, true, Descriptor['codecId']>>,\n PrimaryKey\n >;\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlNonNullableColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, false, Descriptor['codecId']>>,\n PrimaryKey\n >;\n column<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, boolean, Descriptor['codecId']>>,\n PrimaryKey\n >;\n generated<ColName extends string, Descriptor extends ColumnTypeDescriptor>(\n name: ColName,\n options: SqlGeneratedColumnOptions<Descriptor, CodecTypes>,\n ): TableBuilder<\n Name,\n Columns & Record<ColName, ColumnBuilderState<ColName, false, Descriptor['codecId']>>,\n PrimaryKey\n >;\n}\n\ntype ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }\n ? C extends Record<string, { output: unknown }>\n ? C\n : Record<string, never>\n : Record<string, never>;\n\ntype UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (\n k: infer I,\n) => void\n ? I\n : never;\n\ntype MergeExtensionCodecTypes<Packs extends Record<string, unknown>> = UnionToIntersection<\n {\n [K in keyof Packs]: ExtractCodecTypesFromPack<Packs[K]>;\n }[keyof Packs]\n>;\n\ntype BuildStorageTable<\n _TableName extends string,\n Columns extends Record<string, ColumnBuilderState<string, boolean, string>>,\n PK extends readonly string[] | undefined,\n> = {\n readonly columns: {\n readonly [K in keyof Columns]: Columns[K] extends ColumnBuilderState<\n string,\n infer Null,\n infer TType\n >\n ? BuildStorageColumn<Null & boolean, TType>\n : never;\n };\n readonly uniques: ReadonlyArray<{ readonly columns: readonly string[]; readonly name?: string }>;\n readonly indexes: ReadonlyArray<Index>;\n readonly foreignKeys: ReadonlyArray<{\n readonly columns: readonly string[];\n readonly references: { readonly table: string; readonly columns: readonly string[] };\n readonly name?: string;\n readonly onDelete?: ReferentialAction;\n readonly onUpdate?: ReferentialAction;\n readonly constraint: boolean;\n readonly index: boolean;\n }>;\n} & (PK extends readonly string[]\n ? { readonly primaryKey: { readonly columns: PK; readonly name?: string } }\n : Record<string, never>);\n\ntype BuildStorage<\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n >,\n Types extends Record<string, StorageTypeInstance>,\n> = {\n readonly tables: {\n readonly [K in keyof Tables]: BuildStorageTable<\n K & string,\n ExtractColumns<Tables[K]>,\n ExtractPrimaryKey<Tables[K]>\n >;\n };\n readonly types: Types;\n};\n\ntype BuildStorageTables<\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n >,\n> = {\n readonly [K in keyof Tables]: BuildStorageTable<\n K & string,\n ExtractColumns<Tables[K]>,\n ExtractPrimaryKey<Tables[K]>\n >;\n};\n\nexport interface ColumnBuilder<Name extends string, Nullable extends boolean, Type extends string> {\n nullable<Value extends boolean>(value?: Value): ColumnBuilder<Name, Value, Type>;\n type<Id extends string>(id: Id): ColumnBuilder<Name, Nullable, Id>;\n build(): ColumnBuilderState<Name, Nullable, Type>;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isJsonValue(value: unknown): value is ColumnDefaultLiteralValue {\n if (value === null) return true;\n const valueType = typeof value;\n if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') return true;\n if (Array.isArray(value)) {\n return value.every((item) => isJsonValue(item));\n }\n if (isPlainObject(value)) {\n return Object.values(value).every((item) => isJsonValue(item));\n }\n return false;\n}\n\nfunction encodeDefaultLiteralValue(\n value: ColumnDefaultLiteralInputValue,\n): ColumnDefaultLiteralValue {\n if (typeof value === 'bigint') {\n return { $type: 'bigint', value: value.toString() };\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (isJsonValue(value)) {\n if (isPlainObject(value) && '$type' in value) {\n return { $type: 'raw', value } satisfies TaggedRaw;\n }\n return value;\n }\n throw new Error(\n 'Unsupported column default literal value: expected JSON-safe value, bigint, or Date.',\n );\n}\n\nfunction encodeColumnDefault(defaultInput: ColumnDefault): ColumnDefault {\n if (defaultInput.kind === 'function') {\n return { kind: 'function', expression: defaultInput.expression };\n }\n return { kind: 'literal', value: encodeDefaultLiteralValue(defaultInput.value) };\n}\n\nclass SqlContractBuilder<\n CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,\n Target extends string | undefined = undefined,\n Tables extends Record<\n string,\n TableBuilderState<\n string,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >\n > = Record<never, never>,\n Models extends Record<\n string,\n ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>\n > = Record<never, never>,\n Types extends Record<string, StorageTypeInstance> = Record<never, never>,\n StorageHash extends string | undefined = undefined,\n ExtensionPacks extends Record<string, unknown> | undefined = undefined,\n Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,\n> extends ContractBuilder<Target, Tables, Models, StorageHash, ExtensionPacks, Capabilities> {\n protected declare readonly state: ContractBuilderState<\n Target,\n Tables,\n Models,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > & {\n readonly storageTypes?: Types;\n };\n /**\n * This method is responsible for normalizing the contract IR by setting default values\n * for all required fields:\n * - `nullable`: defaults to `false` if not provided\n * - `uniques`: defaults to `[]` (empty array)\n * - `indexes`: defaults to `[]` (empty array)\n * - `foreignKeys`: defaults to `[]` (empty array)\n * - model `relations`: defaults to `{}` (empty object) when not populated by the builder\n * - `nativeType`: required field set from column type descriptor when columns are defined\n *\n * The contract builder is the **only** place where normalization should occur.\n * Validators, parsers, and emitters should assume the contract is already normalized.\n *\n * **Required**: Use column type descriptors (e.g., `int4Column`, `textColumn`) when defining columns.\n * This ensures `nativeType` is set correctly at build time.\n *\n * @returns A normalized SqlContract with all required fields present\n */\n build(): Target extends string\n ? ContractWithTypeMaps<\n SqlContract<BuildStorage<Tables, Types>, BuildModels<Models>> & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly storageHash: StorageHash extends string ? StorageHash : string;\n } & (ExtensionPacks extends Record<string, unknown>\n ? { readonly extensionPacks: ExtensionPacks }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>),\n TypeMaps<CodecTypes, Record<string, never>>\n >\n : never {\n type BuiltContract = Target extends string\n ? ContractWithTypeMaps<\n SqlContract<BuildStorage<Tables, Types>, BuildModels<Models>> & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly storageHash: StorageHash extends string ? StorageHash : string;\n } & (ExtensionPacks extends Record<string, unknown>\n ? { readonly extensionPacks: ExtensionPacks }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>),\n TypeMaps<CodecTypes, Record<string, never>>\n >\n : never;\n if (!this.state.target) {\n throw new Error('target is required. Call .target() before .build()');\n }\n\n const target = this.state.target as Target & string;\n\n const storageTables = {} as Partial<Mutable<BuildStorageTables<Tables>>>;\n const executionDefaults: ExecutionMutationDefault[] = [];\n\n for (const tableName of Object.keys(this.state.tables) as Array<keyof Tables & string>) {\n const tableState = this.state.tables[tableName];\n if (!tableState) continue;\n\n type TableKey = typeof tableName;\n type ColumnDefs = ExtractColumns<Tables[TableKey]>;\n type PrimaryKey = ExtractPrimaryKey<Tables[TableKey]>;\n\n const columns = {} as Partial<{\n [K in keyof ColumnDefs]: BuildStorageColumn<\n ColumnDefs[K]['nullable'] & boolean,\n ColumnDefs[K]['type']\n >;\n }>;\n\n for (const columnName in tableState.columns) {\n const columnState = tableState.columns[columnName];\n if (!columnState) continue;\n const codecId = columnState.type;\n const nativeType = columnState.nativeType;\n const typeRef = columnState.typeRef;\n\n const encodedDefault =\n columnState.default !== undefined\n ? encodeColumnDefault(columnState.default as ColumnDefault)\n : undefined;\n\n columns[columnName as keyof ColumnDefs] = {\n nativeType,\n codecId,\n nullable: (columnState.nullable ?? false) as ColumnDefs[keyof ColumnDefs]['nullable'] &\n boolean,\n ...ifDefined('typeParams', columnState.typeParams),\n ...ifDefined('default', encodedDefault),\n ...ifDefined('typeRef', typeRef),\n } as BuildStorageColumn<\n ColumnDefs[keyof ColumnDefs]['nullable'] & boolean,\n ColumnDefs[keyof ColumnDefs]['type']\n >;\n\n if ('executionDefault' in columnState && columnState.executionDefault) {\n executionDefaults.push({\n ref: { table: tableName, column: columnName },\n onCreate: columnState.executionDefault,\n });\n }\n }\n\n // Build uniques from table state\n const uniques = (tableState.uniques ?? []).map((u) => ({\n columns: u.columns,\n ...(u.name ? { name: u.name } : {}),\n }));\n\n // Build indexes from table state\n const indexes = (tableState.indexes ?? []).map((i) => ({\n columns: i.columns,\n ...(i.name ? { name: i.name } : {}),\n ...(i.using ? { using: i.using } : {}),\n ...(i.config ? { config: i.config } : {}),\n }));\n\n // Build foreign keys from table state, materializing defaults\n const foreignKeys = (tableState.foreignKeys ?? []).map((fk) => ({\n columns: fk.columns,\n references: fk.references,\n ...applyFkDefaults(fk, this.state.foreignKeyDefaults),\n ...(fk.name ? { name: fk.name } : {}),\n ...(fk.onDelete !== undefined ? { onDelete: fk.onDelete } : {}),\n ...(fk.onUpdate !== undefined ? { onUpdate: fk.onUpdate } : {}),\n }));\n\n const table = {\n columns: columns as {\n [K in keyof ColumnDefs]: BuildStorageColumn<\n ColumnDefs[K]['nullable'] & boolean,\n ColumnDefs[K]['type']\n >;\n },\n uniques,\n indexes,\n foreignKeys,\n ...(tableState.primaryKey\n ? {\n primaryKey: {\n columns: tableState.primaryKey,\n ...(tableState.primaryKeyName ? { name: tableState.primaryKeyName } : {}),\n },\n }\n : {}),\n } as unknown as BuildStorageTable<TableKey & string, ColumnDefs, PrimaryKey>;\n\n (storageTables as Mutable<BuildStorageTables<Tables>>)[tableName] = table;\n }\n\n const storageTypes = (this.state.storageTypes ?? {}) as Types;\n const storage: BuildStorage<Tables, Types> = {\n tables: storageTables as BuildStorageTables<Tables>,\n types: storageTypes,\n };\n\n const execution =\n executionDefaults.length > 0\n ? {\n mutations: {\n defaults: executionDefaults.sort((a, b) => {\n const tableCompare = a.ref.table.localeCompare(b.ref.table);\n if (tableCompare !== 0) {\n return tableCompare;\n }\n return a.ref.column.localeCompare(b.ref.column);\n }),\n },\n }\n : undefined;\n\n // Build models - construct as partial first, then assert full type\n const modelsPartial: Partial<BuildModels<Models>> = {};\n\n for (const modelName in this.state.models) {\n const modelState = this.state.models[modelName];\n if (!modelState) continue;\n\n const modelStateTyped = modelState as unknown as {\n name: string;\n table: string;\n fields: Record<string, string>;\n relations: Record<string, RelationDefinition>;\n };\n\n const tableName = modelStateTyped.table;\n const tableState = this.state.tables[tableName as keyof Tables];\n const tableColumns = tableState\n ? (\n tableState as unknown as {\n columns: Record<string, { type: string; nullable?: boolean }>;\n }\n ).columns\n : {};\n\n const storageFields: Record<string, { readonly column: string }> = {};\n const domainFields: Record<string, Record<string, unknown>> = {};\n\n for (const fieldName in modelStateTyped.fields) {\n const columnName = modelStateTyped.fields[fieldName];\n if (!columnName) continue;\n\n storageFields[fieldName] = { column: columnName };\n\n const column = tableColumns[columnName];\n if (column) {\n domainFields[fieldName] = {\n codecId: column.type,\n nullable: column.nullable ?? false,\n };\n }\n }\n\n const modelRelations: Record<string, Record<string, unknown>> = {};\n if (modelStateTyped.relations) {\n for (const relName in modelStateTyped.relations) {\n const rel = modelStateTyped.relations[relName];\n if (!rel) continue;\n modelRelations[relName] = {\n to: rel.to,\n cardinality: rel.cardinality,\n on: {\n localFields: rel.on.parentCols,\n targetFields: rel.on.childCols,\n },\n };\n }\n }\n\n (modelsPartial as unknown as Record<string, Record<string, unknown>>)[modelName] = {\n storage: {\n table: tableName,\n fields: storageFields,\n },\n fields: domainFields,\n relations: modelRelations,\n };\n }\n\n const models = modelsPartial as unknown as BuildModels<Models>;\n\n const extensionNamespaces = this.state.extensionNamespaces ?? [];\n const extensionPacks: Record<string, unknown> = { ...(this.state.extensionPacks || {}) };\n for (const namespace of extensionNamespaces) {\n if (!Object.hasOwn(extensionPacks, namespace)) {\n extensionPacks[namespace] = {};\n }\n }\n\n const contract = {\n schemaVersion: '1' as const,\n target,\n targetFamily: 'sql' as const,\n storageHash: this.state.storageHash || 'sha256:ts-builder-placeholder',\n models,\n roots: {},\n storage,\n ...(execution ? { execution } : {}),\n extensionPacks,\n capabilities: this.state.capabilities || {},\n meta: {},\n sources: {},\n } as unknown as BuiltContract;\n\n return contract as unknown as ReturnType<\n SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >['build']\n >;\n }\n\n override target<\n T extends string,\n TPack extends TargetPackRef<string, T> = TargetPackRef<string, T>,\n >(\n packRef: TPack & TargetPackRef<string, T>,\n ): SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n target: packRef.targetId,\n }) as SqlContractBuilder<\n ExtractCodecTypesFromPack<TPack> extends Record<string, never>\n ? CodecTypes\n : ExtractCodecTypesFromPack<TPack>,\n T,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >;\n }\n\n extensionPacks<const Packs extends Record<string, ExtensionPackRef<'sql', string>>>(\n packs: Packs,\n ): SqlContractBuilder<\n CodecTypes & MergeExtensionCodecTypes<Packs>,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n if (!this.state.target) {\n throw new Error('extensionPacks() requires target() to be called first');\n }\n\n const namespaces = new Set(this.state.extensionNamespaces ?? []);\n\n for (const packRef of Object.values(packs) as ExtensionPackRef<'sql', string>[]) {\n if (!packRef) continue;\n\n if (packRef.kind !== 'extension') {\n throw new Error(\n `extensionPacks() only accepts extension pack refs. Received kind \"${packRef.kind}\".`,\n );\n }\n\n if (packRef.familyId !== 'sql') {\n throw new Error(\n `extension pack \"${packRef.id}\" targets family \"${packRef.familyId}\" but this builder targets \"sql\".`,\n );\n }\n\n if (packRef.targetId && packRef.targetId !== this.state.target) {\n throw new Error(\n `extension pack \"${packRef.id}\" targets \"${packRef.targetId}\" but builder target is \"${this.state.target}\".`,\n );\n }\n\n namespaces.add(packRef.id);\n }\n\n return new SqlContractBuilder<\n CodecTypes & MergeExtensionCodecTypes<Packs>,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n extensionNamespaces: [...namespaces],\n });\n }\n\n override capabilities<C extends Record<string, Record<string, boolean>>>(\n capabilities: C,\n ): SqlContractBuilder<CodecTypes, Target, Tables, Models, Types, StorageHash, ExtensionPacks, C> {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n C\n >({\n ...this.state,\n capabilities,\n });\n }\n\n override storageHash<H extends string>(\n hash: H,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n H,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n H,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n storageHash: hash,\n });\n }\n\n override table<\n TableName extends string,\n T extends TableBuilder<\n TableName,\n Record<string, ColumnBuilderState<string, boolean, string>>,\n readonly string[] | undefined\n >,\n >(\n name: TableName,\n callback: (t: TableBuilder<TableName>) => T | undefined,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n const tableBuilder = createTable(name);\n const result = callback(\n tableBuilder as unknown as SqlTableBuilder<\n TableName,\n CodecTypes\n > as unknown as TableBuilder<TableName>,\n );\n const finalBuilder = result instanceof TableBuilder ? result : tableBuilder;\n const tableState = finalBuilder.build();\n\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables & Record<TableName, ReturnType<T['build']>>,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n tables: { ...this.state.tables, [name]: tableState } as Tables &\n Record<TableName, ReturnType<T['build']>>,\n });\n }\n\n override model<\n ModelName extends string,\n TableName extends string,\n M extends ModelBuilder<\n ModelName,\n TableName,\n Record<string, string>,\n Record<string, RelationDefinition>\n >,\n >(\n name: ModelName,\n table: TableName,\n callback: (\n m: ModelBuilder<ModelName, TableName, Record<never, never>, Record<never, never>>,\n ) => M | undefined,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n const modelBuilder = new ModelBuilder<ModelName, TableName>(name, table);\n const result = callback(modelBuilder);\n const finalBuilder = result instanceof ModelBuilder ? result : modelBuilder;\n const modelState = finalBuilder.build();\n\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n models: { ...this.state.models, [name]: modelState } as Models &\n Record<ModelName, ReturnType<M['build']>>,\n });\n }\n\n override foreignKeyDefaults(\n config: ForeignKeyDefaultsState,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n foreignKeyDefaults: config,\n });\n }\n\n storageType<Name extends string, Type extends StorageTypeInstance>(\n name: Name,\n typeInstance: Type,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types & Record<Name, Type>,\n StorageHash,\n ExtensionPacks,\n Capabilities\n > {\n return new SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models,\n Types & Record<Name, Type>,\n StorageHash,\n ExtensionPacks,\n Capabilities\n >({\n ...this.state,\n storageTypes: {\n ...(this.state.storageTypes ?? {}),\n [name]: typeInstance,\n },\n });\n }\n}\n\nexport function defineContract<\n CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,\n>(): SqlContractBuilder<CodecTypes> {\n return new SqlContractBuilder<CodecTypes>();\n}\n"],"mappings":";;;;;AA2NA,SAAS,cAAc,OAAkD;AACvE,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;CACxD,MAAM,QAAQ,OAAO,eAAe,MAAM;AAC1C,QAAO,UAAU,OAAO,aAAa,UAAU;;AAGjD,SAAS,YAAY,OAAoD;AACvE,KAAI,UAAU,KAAM,QAAO;CAC3B,MAAM,YAAY,OAAO;AACzB,KAAI,cAAc,YAAY,cAAc,YAAY,cAAc,UAAW,QAAO;AACxF,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,OAAO,SAAS,YAAY,KAAK,CAAC;AAEjD,KAAI,cAAc,MAAM,CACtB,QAAO,OAAO,OAAO,MAAM,CAAC,OAAO,SAAS,YAAY,KAAK,CAAC;AAEhE,QAAO;;AAGT,SAAS,0BACP,OAC2B;AAC3B,KAAI,OAAO,UAAU,SACnB,QAAO;EAAE,OAAO;EAAU,OAAO,MAAM,UAAU;EAAE;AAErD,KAAI,iBAAiB,KACnB,QAAO,MAAM,aAAa;AAE5B,KAAI,YAAY,MAAM,EAAE;AACtB,MAAI,cAAc,MAAM,IAAI,WAAW,MACrC,QAAO;GAAE,OAAO;GAAO;GAAO;AAEhC,SAAO;;AAET,OAAM,IAAI,MACR,uFACD;;AAGH,SAAS,oBAAoB,cAA4C;AACvE,KAAI,aAAa,SAAS,WACxB,QAAO;EAAE,MAAM;EAAY,YAAY,aAAa;EAAY;AAElE,QAAO;EAAE,MAAM;EAAW,OAAO,0BAA0B,aAAa,MAAM;EAAE;;AAGlF,IAAM,qBAAN,MAAM,2BAmBI,gBAAmF;;;;;;;;;;;;;;;;;;;CA6B3F,QAeU;AAiBR,MAAI,CAAC,KAAK,MAAM,OACd,OAAM,IAAI,MAAM,qDAAqD;EAGvE,MAAM,SAAS,KAAK,MAAM;EAE1B,MAAM,gBAAgB,EAAE;EACxB,MAAMA,oBAAgD,EAAE;AAExD,OAAK,MAAM,aAAa,OAAO,KAAK,KAAK,MAAM,OAAO,EAAkC;GACtF,MAAM,aAAa,KAAK,MAAM,OAAO;AACrC,OAAI,CAAC,WAAY;GAMjB,MAAM,UAAU,EAAE;AAOlB,QAAK,MAAM,cAAc,WAAW,SAAS;IAC3C,MAAM,cAAc,WAAW,QAAQ;AACvC,QAAI,CAAC,YAAa;IAClB,MAAM,UAAU,YAAY;IAC5B,MAAM,aAAa,YAAY;IAC/B,MAAM,UAAU,YAAY;IAE5B,MAAM,iBACJ,YAAY,YAAY,SACpB,oBAAoB,YAAY,QAAyB,GACzD;AAEN,YAAQ,cAAkC;KACxC;KACA;KACA,UAAW,YAAY,YAAY;KAEnC,GAAG,UAAU,cAAc,YAAY,WAAW;KAClD,GAAG,UAAU,WAAW,eAAe;KACvC,GAAG,UAAU,WAAW,QAAQ;KACjC;AAKD,QAAI,sBAAsB,eAAe,YAAY,iBACnD,mBAAkB,KAAK;KACrB,KAAK;MAAE,OAAO;MAAW,QAAQ;MAAY;KAC7C,UAAU,YAAY;KACvB,CAAC;;AAgDN,GAAC,cAAsD,aApBzC;IACH;IAMT,UA9Be,WAAW,WAAW,EAAE,EAAE,KAAK,OAAO;KACrD,SAAS,EAAE;KACX,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;KACnC,EAAE;IA4BD,UAzBe,WAAW,WAAW,EAAE,EAAE,KAAK,OAAO;KACrD,SAAS,EAAE;KACX,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;KAClC,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE;KACrC,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,EAAE;KACzC,EAAE;IAqBD,cAlBmB,WAAW,eAAe,EAAE,EAAE,KAAK,QAAQ;KAC9D,SAAS,GAAG;KACZ,YAAY,GAAG;KACf,GAAG,gBAAgB,IAAI,KAAK,MAAM,mBAAmB;KACrD,GAAI,GAAG,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,EAAE;KACpC,GAAI,GAAG,aAAa,SAAY,EAAE,UAAU,GAAG,UAAU,GAAG,EAAE;KAC9D,GAAI,GAAG,aAAa,SAAY,EAAE,UAAU,GAAG,UAAU,GAAG,EAAE;KAC/D,EAAE;IAYD,GAAI,WAAW,aACX,EACE,YAAY;KACV,SAAS,WAAW;KACpB,GAAI,WAAW,iBAAiB,EAAE,MAAM,WAAW,gBAAgB,GAAG,EAAE;KACzE,EACF,GACD,EAAE;IACP;;EAMH,MAAMC,UAAuC;GAC3C,QAAQ;GACR,OAHoB,KAAK,MAAM,gBAAgB,EAAE;GAIlD;EAED,MAAM,YACJ,kBAAkB,SAAS,IACvB,EACE,WAAW,EACT,UAAU,kBAAkB,MAAM,GAAG,MAAM;GACzC,MAAM,eAAe,EAAE,IAAI,MAAM,cAAc,EAAE,IAAI,MAAM;AAC3D,OAAI,iBAAiB,EACnB,QAAO;AAET,UAAO,EAAE,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO;IAC/C,EACH,EACF,GACD;EAGN,MAAMC,gBAA8C,EAAE;AAEtD,OAAK,MAAM,aAAa,KAAK,MAAM,QAAQ;GACzC,MAAM,aAAa,KAAK,MAAM,OAAO;AACrC,OAAI,CAAC,WAAY;GAEjB,MAAM,kBAAkB;GAOxB,MAAM,YAAY,gBAAgB;GAClC,MAAM,aAAa,KAAK,MAAM,OAAO;GACrC,MAAM,eAAe,aAEf,WAGA,UACF,EAAE;GAEN,MAAMC,gBAA6D,EAAE;GACrE,MAAMC,eAAwD,EAAE;AAEhE,QAAK,MAAM,aAAa,gBAAgB,QAAQ;IAC9C,MAAM,aAAa,gBAAgB,OAAO;AAC1C,QAAI,CAAC,WAAY;AAEjB,kBAAc,aAAa,EAAE,QAAQ,YAAY;IAEjD,MAAM,SAAS,aAAa;AAC5B,QAAI,OACF,cAAa,aAAa;KACxB,SAAS,OAAO;KAChB,UAAU,OAAO,YAAY;KAC9B;;GAIL,MAAMC,iBAA0D,EAAE;AAClE,OAAI,gBAAgB,UAClB,MAAK,MAAM,WAAW,gBAAgB,WAAW;IAC/C,MAAM,MAAM,gBAAgB,UAAU;AACtC,QAAI,CAAC,IAAK;AACV,mBAAe,WAAW;KACxB,IAAI,IAAI;KACR,aAAa,IAAI;KACjB,IAAI;MACF,aAAa,IAAI,GAAG;MACpB,cAAc,IAAI,GAAG;MACtB;KACF;;AAIL,GAAC,cAAqE,aAAa;IACjF,SAAS;KACP,OAAO;KACP,QAAQ;KACT;IACD,QAAQ;IACR,WAAW;IACZ;;EAGH,MAAM,SAAS;EAEf,MAAM,sBAAsB,KAAK,MAAM,uBAAuB,EAAE;EAChE,MAAMC,iBAA0C,EAAE,GAAI,KAAK,MAAM,kBAAkB,EAAE,EAAG;AACxF,OAAK,MAAM,aAAa,oBACtB,KAAI,CAAC,OAAO,OAAO,gBAAgB,UAAU,CAC3C,gBAAe,aAAa,EAAE;AAmBlC,SAfiB;GACf,eAAe;GACf;GACA,cAAc;GACd,aAAa,KAAK,MAAM,eAAe;GACvC;GACA,OAAO,EAAE;GACT;GACA,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;GAClC;GACA,cAAc,KAAK,MAAM,gBAAgB,EAAE;GAC3C,MAAM,EAAE;GACR,SAAS,EAAE;GACZ;;CAgBH,AAAS,OAIP,SAYA;AACA,SAAO,IAAI,mBAWT;GACA,GAAG,KAAK;GACR,QAAQ,QAAQ;GACjB,CAAC;;CAcJ,eACE,OAUA;AACA,MAAI,CAAC,KAAK,MAAM,OACd,OAAM,IAAI,MAAM,wDAAwD;EAG1E,MAAM,aAAa,IAAI,IAAI,KAAK,MAAM,uBAAuB,EAAE,CAAC;AAEhE,OAAK,MAAM,WAAW,OAAO,OAAO,MAAM,EAAuC;AAC/E,OAAI,CAAC,QAAS;AAEd,OAAI,QAAQ,SAAS,YACnB,OAAM,IAAI,MACR,qEAAqE,QAAQ,KAAK,IACnF;AAGH,OAAI,QAAQ,aAAa,MACvB,OAAM,IAAI,MACR,mBAAmB,QAAQ,GAAG,oBAAoB,QAAQ,SAAS,mCACpE;AAGH,OAAI,QAAQ,YAAY,QAAQ,aAAa,KAAK,MAAM,OACtD,OAAM,IAAI,MACR,mBAAmB,QAAQ,GAAG,aAAa,QAAQ,SAAS,2BAA2B,KAAK,MAAM,OAAO,IAC1G;AAGH,cAAW,IAAI,QAAQ,GAAG;;AAG5B,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,qBAAqB,CAAC,GAAG,WAAW;GACrC,CAAC;;CAGJ,AAAS,aACP,cAC+F;AAC/F,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR;GACD,CAAC;;CAGJ,AAAS,YACP,MAUA;AACA,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,aAAa;GACd,CAAC;;CAGJ,AAAS,MAQP,MACA,UAUA;EACA,MAAM,eAAe,YAAY,KAAK;EACtC,MAAM,SAAS,SACb,aAID;EAED,MAAM,cADe,kBAAkB,eAAe,SAAS,cAC/B,OAAO;AAEvC,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,QAAQ;IAAE,GAAG,KAAK,MAAM;KAAS,OAAO;IAAY;GAErD,CAAC;;CAGJ,AAAS,MAUP,MACA,OACA,UAYA;EACA,MAAM,eAAe,IAAI,aAAmC,MAAM,MAAM;EACxE,MAAM,SAAS,SAAS,aAAa;EAErC,MAAM,cADe,kBAAkB,eAAe,SAAS,cAC/B,OAAO;AAEvC,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,QAAQ;IAAE,GAAG,KAAK,MAAM;KAAS,OAAO;IAAY;GAErD,CAAC;;CAGJ,AAAS,mBACP,QAUA;AACA,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,oBAAoB;GACrB,CAAC;;CAGJ,YACE,MACA,cAUA;AACA,SAAO,IAAI,mBAST;GACA,GAAG,KAAK;GACR,cAAc;IACZ,GAAI,KAAK,MAAM,gBAAgB,EAAE;KAChC,OAAO;IACT;GACF,CAAC;;;AAIN,SAAgB,iBAEoB;AAClC,QAAO,IAAI,oBAAgC"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-ts",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.133",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"arktype": "^2.1.25",
|
|
9
9
|
"ts-toolbelt": "^9.6.0",
|
|
10
|
-
"@prisma-next/contract": "0.3.0-dev.
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/sql-contract": "0.3.0-dev.
|
|
14
|
-
"@prisma-next/utils": "0.3.0-dev.
|
|
10
|
+
"@prisma-next/contract": "0.3.0-dev.133",
|
|
11
|
+
"@prisma-next/contract-authoring": "0.3.0-dev.133",
|
|
12
|
+
"@prisma-next/config": "0.3.0-dev.133",
|
|
13
|
+
"@prisma-next/sql-contract": "0.3.0-dev.133",
|
|
14
|
+
"@prisma-next/utils": "0.3.0-dev.133"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/pg": "8.16.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
-
"
|
|
52
|
+
"extensionPacks": {
|
|
53
53
|
"type": "object",
|
|
54
54
|
"description": "Extension packs and their configuration",
|
|
55
55
|
"additionalProperties": true
|
|
@@ -498,7 +498,7 @@
|
|
|
498
498
|
},
|
|
499
499
|
"fields": {
|
|
500
500
|
"type": "object",
|
|
501
|
-
"description": "
|
|
501
|
+
"description": "Domain field metadata (codecId, nullable)",
|
|
502
502
|
"additionalProperties": {
|
|
503
503
|
"$ref": "#/$defs/ModelField"
|
|
504
504
|
}
|
|
@@ -519,13 +519,9 @@
|
|
|
519
519
|
},
|
|
520
520
|
"ModelField": {
|
|
521
521
|
"type": "object",
|
|
522
|
-
"description": "Domain field definition
|
|
522
|
+
"description": "Domain field definition (codec and nullability; column mapping lives in model.storage.fields)",
|
|
523
523
|
"additionalProperties": false,
|
|
524
524
|
"properties": {
|
|
525
|
-
"column": {
|
|
526
|
-
"type": "string",
|
|
527
|
-
"description": "Column name in the model's backing table (old format; new format uses model.storage.fields)"
|
|
528
|
-
},
|
|
529
525
|
"codecId": {
|
|
530
526
|
"type": "string",
|
|
531
527
|
"description": "Codec identifier for the field"
|
|
@@ -534,7 +530,8 @@
|
|
|
534
530
|
"type": "boolean",
|
|
535
531
|
"description": "Whether the field allows NULL values"
|
|
536
532
|
}
|
|
537
|
-
}
|
|
533
|
+
},
|
|
534
|
+
"required": ["codecId", "nullable"]
|
|
538
535
|
},
|
|
539
536
|
"ModelStorageField": {
|
|
540
537
|
"type": "object",
|
|
@@ -558,7 +555,7 @@
|
|
|
558
555
|
},
|
|
559
556
|
"ModelRelation": {
|
|
560
557
|
"type": "object",
|
|
561
|
-
"description": "Model relation definition (domain format with localFields/targetFields
|
|
558
|
+
"description": "Model relation definition (domain format with localFields/targetFields)",
|
|
562
559
|
"properties": {
|
|
563
560
|
"to": {
|
|
564
561
|
"type": "string",
|
|
@@ -578,24 +575,14 @@
|
|
|
578
575
|
"type": "object",
|
|
579
576
|
"description": "Relation field mappings",
|
|
580
577
|
"properties": {
|
|
581
|
-
"parentCols": {
|
|
582
|
-
"type": "array",
|
|
583
|
-
"description": "Parent table columns (storage format)",
|
|
584
|
-
"items": { "type": "string" }
|
|
585
|
-
},
|
|
586
|
-
"childCols": {
|
|
587
|
-
"type": "array",
|
|
588
|
-
"description": "Child table columns (storage format)",
|
|
589
|
-
"items": { "type": "string" }
|
|
590
|
-
},
|
|
591
578
|
"localFields": {
|
|
592
579
|
"type": "array",
|
|
593
|
-
"description": "Local model fields
|
|
580
|
+
"description": "Local model fields",
|
|
594
581
|
"items": { "type": "string" }
|
|
595
582
|
},
|
|
596
583
|
"targetFields": {
|
|
597
584
|
"type": "array",
|
|
598
|
-
"description": "Target model fields
|
|
585
|
+
"description": "Target model fields",
|
|
599
586
|
"items": { "type": "string" }
|
|
600
587
|
}
|
|
601
588
|
}
|
package/src/contract-builder.ts
CHANGED
|
@@ -18,7 +18,6 @@ import type {
|
|
|
18
18
|
} from '@prisma-next/contract-authoring';
|
|
19
19
|
import {
|
|
20
20
|
type BuildModels,
|
|
21
|
-
type BuildRelations,
|
|
22
21
|
type BuildStorageColumn,
|
|
23
22
|
ContractBuilder,
|
|
24
23
|
createTable,
|
|
@@ -32,17 +31,12 @@ import {
|
|
|
32
31
|
applyFkDefaults,
|
|
33
32
|
type ContractWithTypeMaps,
|
|
34
33
|
type Index,
|
|
35
|
-
type ModelDefinition,
|
|
36
|
-
type ModelField,
|
|
37
34
|
type ReferentialAction,
|
|
38
35
|
type SqlContract,
|
|
39
|
-
type SqlMappings,
|
|
40
|
-
type SqlStorage,
|
|
41
36
|
type StorageTypeInstance,
|
|
42
37
|
type TypeMaps,
|
|
43
38
|
} from '@prisma-next/sql-contract/types';
|
|
44
39
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
45
|
-
import { computeMappings } from './contract';
|
|
46
40
|
|
|
47
41
|
type ColumnDefaultForCodec<
|
|
48
42
|
CodecTypes extends Record<string, { output: unknown }>,
|
|
@@ -132,8 +126,6 @@ export interface SqlTableBuilder<
|
|
|
132
126
|
>;
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
type ContractBuilderMappings = SqlMappings;
|
|
136
|
-
|
|
137
129
|
type ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }
|
|
138
130
|
? C extends Record<string, { output: unknown }>
|
|
139
131
|
? C
|
|
@@ -308,7 +300,7 @@ class SqlContractBuilder<
|
|
|
308
300
|
* - `uniques`: defaults to `[]` (empty array)
|
|
309
301
|
* - `indexes`: defaults to `[]` (empty array)
|
|
310
302
|
* - `foreignKeys`: defaults to `[]` (empty array)
|
|
311
|
-
* - `relations`: defaults to `{}` (empty object)
|
|
303
|
+
* - model `relations`: defaults to `{}` (empty object) when not populated by the builder
|
|
312
304
|
* - `nativeType`: required field set from column type descriptor when columns are defined
|
|
313
305
|
*
|
|
314
306
|
* The contract builder is the **only** place where normalization should occur.
|
|
@@ -321,12 +313,7 @@ class SqlContractBuilder<
|
|
|
321
313
|
*/
|
|
322
314
|
build(): Target extends string
|
|
323
315
|
? ContractWithTypeMaps<
|
|
324
|
-
SqlContract<
|
|
325
|
-
BuildStorage<Tables, Types>,
|
|
326
|
-
BuildModels<Models>,
|
|
327
|
-
BuildRelations<Models>,
|
|
328
|
-
ContractBuilderMappings
|
|
329
|
-
> & {
|
|
316
|
+
SqlContract<BuildStorage<Tables, Types>, BuildModels<Models>> & {
|
|
330
317
|
readonly schemaVersion: '1';
|
|
331
318
|
readonly target: Target;
|
|
332
319
|
readonly targetFamily: 'sql';
|
|
@@ -342,12 +329,7 @@ class SqlContractBuilder<
|
|
|
342
329
|
: never {
|
|
343
330
|
type BuiltContract = Target extends string
|
|
344
331
|
? ContractWithTypeMaps<
|
|
345
|
-
SqlContract<
|
|
346
|
-
BuildStorage<Tables, Types>,
|
|
347
|
-
BuildModels<Models>,
|
|
348
|
-
BuildRelations<Models>,
|
|
349
|
-
ContractBuilderMappings
|
|
350
|
-
> & {
|
|
332
|
+
SqlContract<BuildStorage<Tables, Types>, BuildModels<Models>> & {
|
|
351
333
|
readonly schemaVersion: '1';
|
|
352
334
|
readonly target: Target;
|
|
353
335
|
readonly targetFamily: 'sql';
|
|
@@ -489,7 +471,6 @@ class SqlContractBuilder<
|
|
|
489
471
|
// Build models - construct as partial first, then assert full type
|
|
490
472
|
const modelsPartial: Partial<BuildModels<Models>> = {};
|
|
491
473
|
|
|
492
|
-
// Iterate over models - TypeScript will see keys as string, but type assertion preserves literals
|
|
493
474
|
for (const modelName in this.state.models) {
|
|
494
475
|
const modelState = this.state.models[modelName];
|
|
495
476
|
if (!modelState) continue;
|
|
@@ -498,77 +479,65 @@ class SqlContractBuilder<
|
|
|
498
479
|
name: string;
|
|
499
480
|
table: string;
|
|
500
481
|
fields: Record<string, string>;
|
|
482
|
+
relations: Record<string, RelationDefinition>;
|
|
501
483
|
};
|
|
502
484
|
|
|
503
|
-
|
|
504
|
-
const
|
|
485
|
+
const tableName = modelStateTyped.table;
|
|
486
|
+
const tableState = this.state.tables[tableName as keyof Tables];
|
|
487
|
+
const tableColumns = tableState
|
|
488
|
+
? (
|
|
489
|
+
tableState as unknown as {
|
|
490
|
+
columns: Record<string, { type: string; nullable?: boolean }>;
|
|
491
|
+
}
|
|
492
|
+
).columns
|
|
493
|
+
: {};
|
|
494
|
+
|
|
495
|
+
const storageFields: Record<string, { readonly column: string }> = {};
|
|
496
|
+
const domainFields: Record<string, Record<string, unknown>> = {};
|
|
505
497
|
|
|
506
|
-
// Iterate over fields
|
|
507
498
|
for (const fieldName in modelStateTyped.fields) {
|
|
508
499
|
const columnName = modelStateTyped.fields[fieldName];
|
|
509
|
-
if (columnName)
|
|
510
|
-
|
|
511
|
-
|
|
500
|
+
if (!columnName) continue;
|
|
501
|
+
|
|
502
|
+
storageFields[fieldName] = { column: columnName };
|
|
503
|
+
|
|
504
|
+
const column = tableColumns[columnName];
|
|
505
|
+
if (column) {
|
|
506
|
+
domainFields[fieldName] = {
|
|
507
|
+
codecId: column.type,
|
|
508
|
+
nullable: column.nullable ?? false,
|
|
512
509
|
};
|
|
513
510
|
}
|
|
514
511
|
}
|
|
515
512
|
|
|
516
|
-
|
|
517
|
-
(
|
|
513
|
+
const modelRelations: Record<string, Record<string, unknown>> = {};
|
|
514
|
+
if (modelStateTyped.relations) {
|
|
515
|
+
for (const relName in modelStateTyped.relations) {
|
|
516
|
+
const rel = modelStateTyped.relations[relName];
|
|
517
|
+
if (!rel) continue;
|
|
518
|
+
modelRelations[relName] = {
|
|
519
|
+
to: rel.to,
|
|
520
|
+
cardinality: rel.cardinality,
|
|
521
|
+
on: {
|
|
522
|
+
localFields: rel.on.parentCols,
|
|
523
|
+
targetFields: rel.on.childCols,
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
(modelsPartial as unknown as Record<string, Record<string, unknown>>)[modelName] = {
|
|
518
530
|
storage: {
|
|
519
|
-
table:
|
|
531
|
+
table: tableName,
|
|
532
|
+
fields: storageFields,
|
|
520
533
|
},
|
|
521
|
-
fields:
|
|
522
|
-
relations:
|
|
534
|
+
fields: domainFields,
|
|
535
|
+
relations: modelRelations,
|
|
523
536
|
};
|
|
524
537
|
}
|
|
525
538
|
|
|
526
|
-
// Build relations object - organized by table name
|
|
527
|
-
const relationsPartial: Partial<Record<string, Record<string, RelationDefinition>>> = {};
|
|
528
|
-
|
|
529
|
-
// Iterate over models to collect relations
|
|
530
|
-
for (const modelName in this.state.models) {
|
|
531
|
-
const modelState = this.state.models[modelName];
|
|
532
|
-
if (!modelState) continue;
|
|
533
|
-
|
|
534
|
-
const modelStateTyped = modelState as unknown as {
|
|
535
|
-
name: string;
|
|
536
|
-
table: string;
|
|
537
|
-
fields: Record<string, string>;
|
|
538
|
-
relations: Record<string, RelationDefinition>;
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
const tableName = modelStateTyped.table;
|
|
542
|
-
if (!tableName) continue;
|
|
543
|
-
|
|
544
|
-
// Only initialize relations object for this table if it has relations
|
|
545
|
-
if (modelStateTyped.relations && Object.keys(modelStateTyped.relations).length > 0) {
|
|
546
|
-
if (!relationsPartial[tableName]) {
|
|
547
|
-
relationsPartial[tableName] = {};
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Add relations from this model to the table's relations
|
|
551
|
-
const tableRelations = relationsPartial[tableName];
|
|
552
|
-
if (tableRelations) {
|
|
553
|
-
for (const relationName in modelStateTyped.relations) {
|
|
554
|
-
const relation = modelStateTyped.relations[relationName];
|
|
555
|
-
if (relation) {
|
|
556
|
-
tableRelations[relationName] = relation;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
539
|
const models = modelsPartial as unknown as BuildModels<Models>;
|
|
564
540
|
|
|
565
|
-
const baseMappings = computeMappings(
|
|
566
|
-
models as unknown as Record<string, ModelDefinition>,
|
|
567
|
-
storage as SqlStorage,
|
|
568
|
-
);
|
|
569
|
-
|
|
570
|
-
const mappings = baseMappings as ContractBuilderMappings;
|
|
571
|
-
|
|
572
541
|
const extensionNamespaces = this.state.extensionNamespaces ?? [];
|
|
573
542
|
const extensionPacks: Record<string, unknown> = { ...(this.state.extensionPacks || {}) };
|
|
574
543
|
for (const namespace of extensionNamespaces) {
|
|
@@ -577,18 +546,14 @@ class SqlContractBuilder<
|
|
|
577
546
|
}
|
|
578
547
|
}
|
|
579
548
|
|
|
580
|
-
// Construct contract with explicit type that matches the generic parameters
|
|
581
|
-
// This ensures TypeScript infers literal types from the generics, not runtime values
|
|
582
|
-
// Always include relations, even if empty (normalized to empty object)
|
|
583
549
|
const contract = {
|
|
584
550
|
schemaVersion: '1' as const,
|
|
585
551
|
target,
|
|
586
552
|
targetFamily: 'sql' as const,
|
|
587
553
|
storageHash: this.state.storageHash || 'sha256:ts-builder-placeholder',
|
|
588
554
|
models,
|
|
589
|
-
|
|
555
|
+
roots: {},
|
|
590
556
|
storage,
|
|
591
|
-
mappings,
|
|
592
557
|
...(execution ? { execution } : {}),
|
|
593
558
|
extensionPacks,
|
|
594
559
|
capabilities: this.state.capabilities || {},
|
package/src/contract.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ModelDefinition,
|
|
3
|
-
ModelField,
|
|
4
|
-
ModelStorage,
|
|
5
2
|
PrimaryKey,
|
|
6
3
|
SqlContract,
|
|
7
|
-
SqlMappings,
|
|
8
4
|
SqlStorage,
|
|
9
5
|
StorageTypeInstance,
|
|
10
6
|
UniqueConstraint,
|
|
@@ -61,15 +57,21 @@ const StorageSchema = type({
|
|
|
61
57
|
'types?': type({ '[string]': StorageTypeInstanceSchema }),
|
|
62
58
|
});
|
|
63
59
|
|
|
64
|
-
const ModelFieldSchema = type
|
|
60
|
+
const ModelFieldSchema = type({
|
|
61
|
+
'nullable?': 'boolean',
|
|
62
|
+
'codecId?': 'string',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const ModelStorageFieldSchema = type({
|
|
65
66
|
column: 'string',
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
const ModelStorageSchema = type
|
|
69
|
+
const ModelStorageSchema = type({
|
|
69
70
|
table: 'string',
|
|
71
|
+
'fields?': type({ '[string]': ModelStorageFieldSchema }),
|
|
70
72
|
});
|
|
71
73
|
|
|
72
|
-
const ModelSchema = type
|
|
74
|
+
const ModelSchema = type({
|
|
73
75
|
storage: ModelStorageSchema,
|
|
74
76
|
fields: type({ '[string]': ModelFieldSchema }),
|
|
75
77
|
relations: type({ '[string]': 'unknown' }),
|
|
@@ -161,51 +163,6 @@ function validateContractStructure<T extends SqlContract<SqlStorage>>(
|
|
|
161
163
|
return contractResult as O.Overwrite<T, { targetFamily: 'sql' }>;
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
/**
|
|
165
|
-
* Computes mapping dictionaries from models and storage structures.
|
|
166
|
-
* Assumes valid input - validation happens separately in validateContractLogic().
|
|
167
|
-
*
|
|
168
|
-
* @param models - Models object from contract
|
|
169
|
-
* @param storage - Storage object from contract
|
|
170
|
-
* @param existingMappings - Existing mappings from contract input (optional)
|
|
171
|
-
* @returns Computed mappings dictionary
|
|
172
|
-
*/
|
|
173
|
-
export function computeMappings(
|
|
174
|
-
models: Record<string, ModelDefinition>,
|
|
175
|
-
_storage: SqlStorage,
|
|
176
|
-
existingMappings?: Partial<SqlMappings>,
|
|
177
|
-
): SqlMappings {
|
|
178
|
-
const modelToTable: Record<string, string> = {};
|
|
179
|
-
const tableToModel: Record<string, string> = {};
|
|
180
|
-
const fieldToColumn: Record<string, Record<string, string>> = {};
|
|
181
|
-
const columnToField: Record<string, Record<string, string>> = {};
|
|
182
|
-
|
|
183
|
-
for (const [modelName, model] of Object.entries(models)) {
|
|
184
|
-
const tableName = model.storage.table;
|
|
185
|
-
modelToTable[modelName] = tableName;
|
|
186
|
-
tableToModel[tableName] = modelName;
|
|
187
|
-
|
|
188
|
-
const modelFieldToColumn: Record<string, string> = {};
|
|
189
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
190
|
-
const columnName = field.column;
|
|
191
|
-
modelFieldToColumn[fieldName] = columnName;
|
|
192
|
-
|
|
193
|
-
if (!columnToField[tableName]) {
|
|
194
|
-
columnToField[tableName] = {};
|
|
195
|
-
}
|
|
196
|
-
columnToField[tableName][columnName] = fieldName;
|
|
197
|
-
}
|
|
198
|
-
fieldToColumn[modelName] = modelFieldToColumn;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
modelToTable: existingMappings?.modelToTable ?? modelToTable,
|
|
203
|
-
tableToModel: existingMappings?.tableToModel ?? tableToModel,
|
|
204
|
-
fieldToColumn: existingMappings?.fieldToColumn ?? fieldToColumn,
|
|
205
|
-
columnToField: existingMappings?.columnToField ?? columnToField,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
166
|
/**
|
|
210
167
|
* Validates logical consistency of a **structurally validated** SqlContract.
|
|
211
168
|
* This checks that references (e.g., foreign keys, primary keys, uniques) point to storage objects that already exist.
|
|
@@ -281,11 +238,12 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
|
|
|
281
238
|
}
|
|
282
239
|
}
|
|
283
240
|
|
|
284
|
-
// Validate models
|
|
285
241
|
for (const [modelName, modelUnknown] of Object.entries(models)) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
242
|
+
const model = modelUnknown as {
|
|
243
|
+
storage?: { table?: string; fields?: Record<string, { column?: string }> };
|
|
244
|
+
fields?: Record<string, unknown>;
|
|
245
|
+
relations?: Record<string, unknown>;
|
|
246
|
+
};
|
|
289
247
|
if (!model.storage?.table) {
|
|
290
248
|
/* c8 ignore next */
|
|
291
249
|
throw new Error(`Model "${modelName}" is missing storage.table`);
|
|
@@ -293,7 +251,6 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
|
|
|
293
251
|
|
|
294
252
|
const tableName = model.storage.table;
|
|
295
253
|
|
|
296
|
-
// Validate model's table exists in storage
|
|
297
254
|
if (!tableNames.has(tableName)) {
|
|
298
255
|
/* c8 ignore next */
|
|
299
256
|
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
@@ -305,7 +262,6 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
|
|
|
305
262
|
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
306
263
|
}
|
|
307
264
|
|
|
308
|
-
// Validate model's table has a primary key
|
|
309
265
|
if (!table.primaryKey) {
|
|
310
266
|
/* c8 ignore next */
|
|
311
267
|
throw new Error(`Model "${modelName}" table "${tableName}" is missing a primary key`);
|
|
@@ -313,70 +269,59 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
|
|
|
313
269
|
|
|
314
270
|
const columnNames = new Set(Object.keys(table.columns));
|
|
315
271
|
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const field = fieldUnknown as { column: string };
|
|
324
|
-
// Validate field has column property
|
|
325
|
-
if (!field.column) {
|
|
326
|
-
/* c8 ignore next */
|
|
327
|
-
throw new Error(`Model "${modelName}" field "${fieldName}" is missing column property`);
|
|
328
|
-
}
|
|
272
|
+
const storageFields = model.storage.fields;
|
|
273
|
+
if (storageFields) {
|
|
274
|
+
for (const [fieldName, storageField] of Object.entries(storageFields)) {
|
|
275
|
+
if (!storageField.column) {
|
|
276
|
+
/* c8 ignore next */
|
|
277
|
+
throw new Error(`Model "${modelName}" field "${fieldName}" is missing column property`);
|
|
278
|
+
}
|
|
329
279
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
280
|
+
if (!columnNames.has(storageField.column)) {
|
|
281
|
+
/* c8 ignore next */
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Model "${modelName}" field "${fieldName}" references non-existent column "${storageField.column}" in table "${tableName}"`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
336
286
|
}
|
|
337
287
|
}
|
|
338
288
|
|
|
339
|
-
// Validate model relations have corresponding foreign keys
|
|
340
289
|
if (model.relations) {
|
|
341
290
|
for (const [relationName, relation] of Object.entries(model.relations)) {
|
|
342
|
-
// For now, we'll do basic validation. Full FK validation can be added later
|
|
343
|
-
// This would require checking that the relation's on.parentCols/childCols match FKs
|
|
344
291
|
if (
|
|
345
292
|
typeof relation === 'object' &&
|
|
346
293
|
relation !== null &&
|
|
347
294
|
'on' in relation &&
|
|
348
295
|
'to' in relation
|
|
349
296
|
) {
|
|
350
|
-
const on = relation.on as {
|
|
297
|
+
const on = relation.on as { localFields?: string[]; targetFields?: string[] };
|
|
351
298
|
const cardinality = (relation as { cardinality?: string }).cardinality;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
);
|
|
379
|
-
}
|
|
299
|
+
const localFields = on.localFields;
|
|
300
|
+
const targetFields = on.targetFields;
|
|
301
|
+
if (!localFields || !targetFields) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Model "${modelName}" relation "${relationName}" uses unsupported relation format (expected localFields/targetFields)`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (cardinality === '1:N') {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const hasMatchingFk = table.foreignKeys?.some((fk) => {
|
|
311
|
+
return (
|
|
312
|
+
fk.columns.length === localFields.length &&
|
|
313
|
+
fk.columns.every((col, i) => col === localFields[i]) &&
|
|
314
|
+
fk.references.table &&
|
|
315
|
+
fk.references.columns.length === targetFields.length &&
|
|
316
|
+
fk.references.columns.every((col, i) => col === targetFields[i])
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (!hasMatchingFk) {
|
|
321
|
+
/* c8 ignore next */
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Model "${modelName}" relation "${relationName}" does not have a corresponding foreign key in table "${tableName}"`,
|
|
324
|
+
);
|
|
380
325
|
}
|
|
381
326
|
}
|
|
382
327
|
}
|
|
@@ -518,29 +463,7 @@ export function validateContract<TContract extends SqlContract<SqlStorage>>(
|
|
|
518
463
|
|
|
519
464
|
const contractForValidation = structurallyValid as SqlContract<SqlStorage>;
|
|
520
465
|
|
|
521
|
-
// Validate contract logic (contracts must already have fully qualified type IDs)
|
|
522
466
|
validateContractLogic(contractForValidation);
|
|
523
467
|
|
|
524
|
-
|
|
525
|
-
const existingMappings = (contractForValidation as { mappings?: Partial<SqlMappings> }).mappings;
|
|
526
|
-
|
|
527
|
-
// Compute mappings from models and storage
|
|
528
|
-
const mappings = computeMappings(
|
|
529
|
-
contractForValidation.models as Record<string, ModelDefinition>,
|
|
530
|
-
contractForValidation.storage,
|
|
531
|
-
existingMappings,
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
// Add default values for optional metadata fields if missing
|
|
535
|
-
const contractWithMappings = {
|
|
536
|
-
...structurallyValid,
|
|
537
|
-
models: contractForValidation.models,
|
|
538
|
-
relations: contractForValidation.relations,
|
|
539
|
-
storage: contractForValidation.storage,
|
|
540
|
-
mappings,
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
// Type assertion: The caller provides the strict type via TContract.
|
|
544
|
-
// We validate the structure matches, but the precise types come from contract.d.ts
|
|
545
|
-
return decodeContractDefaults(contractWithMappings) as TContract;
|
|
468
|
+
return decodeContractDefaults(contractForValidation) as TContract;
|
|
546
469
|
}
|