@prisma-next/sql-contract-ts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @prisma-next/sql-contract-ts
2
+
3
+ **Status:** Phase 2 - SQL-specific contract authoring surface composed with generic core
4
+
5
+ This package contains the SQL-specific TypeScript contract authoring surface for Prisma Next.
6
+
7
+ ## Package Classification
8
+
9
+ - **Domain**: sql
10
+ - **Layer**: authoring
11
+ - **Plane**: migration
12
+
13
+ **Note**: SQL authoring may depend on SQL targets layer (e.g., `@prisma-next/sql-contract-types`) within the same domain.
14
+
15
+ ## Overview
16
+
17
+ This package is part of the SQL family namespace (`packages/sql/authoring/sql-contract-ts`) and provides:
18
+ - SQL contract builder (`defineContract`) - TypeScript builder for creating SQL contracts programmatically
19
+ - SQL contract validation (`validateContract`) - Structural and logical validation for SQL contracts
20
+ - SQL contract JSON schema - JSON schema for validating contract structure
21
+
22
+ ## Responsibilities
23
+
24
+ - **SQL Contract Builder**: Provides the `defineContract()` builder API for creating SQL contracts programmatically with type safety
25
+ - **SQL Contract Validation**: Implements SQL-specific contract validation (`validateContractStructure`, `validateContractLogic`, `validateContract`) and normalization
26
+ - **SQL Contract JSON Schema**: Provides JSON schema for validating contract structure in IDEs and tooling
27
+ - **Composition Layer**: Composes the target-agnostic builder core from `@prisma-next/contract-authoring` with SQL-specific types and validation logic
28
+
29
+ ## Package Status
30
+
31
+ This package was created in Phase 1 and refactored in Phase 2. It now composes the target-agnostic builder core from `@prisma-next/contract-authoring` with SQL-specific types and validation logic.
32
+
33
+ ## Architecture
34
+
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`, `SqlMappings`) from `@prisma-next/sql-target`
37
+ - **SQL-specific validation**: Implements SQL-specific contract validation (`validateContractStructure`, `validateContractLogic`, `validateContract`) and normalization (`normalizeContract`)
38
+ - **SQL-specific build()**: Implements SQL-specific `build()` method in `SqlContractBuilder` that constructs `SqlContract` instances with SQL-specific structure (uniques, indexes, foreignKeys arrays)
39
+
40
+ ## Architecture
41
+
42
+ This package is part of the package layering architecture:
43
+ - **Location**: `packages/sql/authoring/sql-contract-ts` (SQL family namespace)
44
+ - **Ring**: SQL family namespace (can import from core, authoring, targets, and other SQL family packages)
45
+ - **Dependencies**: `@prisma-next/contract`, `@prisma-next/sql-target`, `arktype`, `ts-toolbelt`
46
+
47
+ ## Exports
48
+
49
+ - `./contract-builder` - Contract builder API (`defineContract`, `ColumnBuilder`)
50
+ - `./contract` - Contract validation (`validateContract`, `computeMappings`)
51
+ - `./schema-sql` - SQL contract JSON schema (`data-contract-sql-v1.json`)
52
+
53
+ ## Usage
54
+
55
+ ### Building Contracts
56
+
57
+ ```typescript
58
+ import { defineContract } from '@prisma-next/sql-contract-ts/contract-builder';
59
+ import type { CodecTypes } from '@prisma-next/adapter-postgres/codec-types';
60
+
61
+ const contract = defineContract<CodecTypes>()
62
+ .target('postgres')
63
+ .table('user', (t) =>
64
+ t
65
+ .column('id', { type: 'pg/int4@1', nullable: false })
66
+ .column('email', { type: 'pg/text@1', nullable: false })
67
+ .primaryKey(['id']),
68
+ )
69
+ .model('User', 'user', (m) => m.field('id', 'id').field('email', 'email'))
70
+ .build();
71
+ ```
72
+
73
+ ### Validating Contracts
74
+
75
+ ```typescript
76
+ import { validateContract } from '@prisma-next/sql-contract-ts/contract';
77
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-target';
78
+ import type { Contract } from './contract.d';
79
+
80
+ // From JSON import
81
+ const contract = validateContract<Contract>(contractJson);
82
+
83
+ // Or with generic type (less type-safe)
84
+ const contract = validateContract<SqlContract<SqlStorage>>(contractJson);
85
+ ```
86
+
87
+ ## Dependencies
88
+
89
+ - **`@prisma-next/contract-authoring`** - Target-agnostic builder core (builder state types, builder classes, type helpers)
90
+ - **`@prisma-next/contract`** - Core contract types (`ContractBase`)
91
+ - **`@prisma-next/sql-contract`** - SQL contract types (`SqlContract`, `SqlStorage`, `SqlMappings`)
92
+ - **`arktype`** - Runtime validation
93
+ - **`ts-toolbelt`** - Type utilities
94
+
95
+ ## Testing
96
+
97
+ Integration tests that depend on both `sql-contract-ts` and `sql-query` are located in `@prisma-next/integration-tests` to avoid cyclic dependencies.
98
+
99
+ ## Migration Notes
100
+
101
+ - **Backward Compatibility**: `@prisma-next/sql-query` re-exports contract authoring functions for backward compatibility (will be removed in Slice 7)
102
+ - **Import Path**: New code should import directly from `@prisma-next/sql-contract-ts`
103
+ - **Phase 2 Complete**: The target-agnostic core has been extracted to `@prisma-next/contract-authoring`. This package composes the generic core with SQL-specific types.
104
+
105
+ ## See Also
106
+
107
+ - `@prisma-next/contract-authoring` - Target-agnostic builder core that this package composes
108
+
@@ -0,0 +1,307 @@
1
+ // src/contract.ts
2
+ import { type } from "arktype";
3
+ var StorageColumnSchema = type.declare().type({
4
+ type: "string",
5
+ nullable: "boolean"
6
+ });
7
+ var PrimaryKeySchema = type.declare().type({
8
+ columns: type.string.array().readonly(),
9
+ "name?": "string"
10
+ });
11
+ var UniqueConstraintSchema = type.declare().type({
12
+ columns: type.string.array().readonly(),
13
+ "name?": "string"
14
+ });
15
+ var IndexSchema = type.declare().type({
16
+ columns: type.string.array().readonly(),
17
+ "name?": "string"
18
+ });
19
+ var ForeignKeyReferencesSchema = type.declare().type({
20
+ table: "string",
21
+ columns: type.string.array().readonly()
22
+ });
23
+ var ForeignKeySchema = type.declare().type({
24
+ columns: type.string.array().readonly(),
25
+ references: ForeignKeyReferencesSchema,
26
+ "name?": "string"
27
+ });
28
+ var StorageTableSchema = type.declare().type({
29
+ columns: type({ "[string]": StorageColumnSchema }),
30
+ "primaryKey?": PrimaryKeySchema,
31
+ uniques: UniqueConstraintSchema.array().readonly(),
32
+ indexes: IndexSchema.array().readonly(),
33
+ foreignKeys: ForeignKeySchema.array().readonly()
34
+ });
35
+ var StorageSchema = type.declare().type({
36
+ tables: type({ "[string]": StorageTableSchema })
37
+ });
38
+ var ModelFieldSchema = type.declare().type({
39
+ column: "string"
40
+ });
41
+ var ModelStorageSchema = type.declare().type({
42
+ table: "string"
43
+ });
44
+ var ModelSchema = type.declare().type({
45
+ storage: ModelStorageSchema,
46
+ fields: type({ "[string]": ModelFieldSchema }),
47
+ relations: type({ "[string]": "unknown" })
48
+ });
49
+ var SqlContractSchema = type({
50
+ "schemaVersion?": "'1'",
51
+ target: "string",
52
+ targetFamily: "'sql'",
53
+ coreHash: "string",
54
+ "profileHash?": "string",
55
+ "capabilities?": "Record<string, Record<string, boolean>>",
56
+ "extensions?": "Record<string, unknown>",
57
+ "meta?": "Record<string, unknown>",
58
+ "sources?": "Record<string, unknown>",
59
+ models: type({ "[string]": ModelSchema }),
60
+ storage: StorageSchema
61
+ });
62
+ function validateContractStructure(value) {
63
+ const rawValue = value;
64
+ if (rawValue.targetFamily !== void 0 && rawValue.targetFamily !== "sql") {
65
+ throw new Error(`Unsupported target family: ${rawValue.targetFamily}`);
66
+ }
67
+ const contractResult = SqlContractSchema(value);
68
+ if (contractResult instanceof type.errors) {
69
+ const messages = contractResult.map((p) => p.message).join("; ");
70
+ throw new Error(`Contract structural validation failed: ${messages}`);
71
+ }
72
+ return contractResult;
73
+ }
74
+ function computeMappings(models, _storage, existingMappings) {
75
+ const modelToTable = {};
76
+ const tableToModel = {};
77
+ const fieldToColumn = {};
78
+ const columnToField = {};
79
+ for (const [modelName, model] of Object.entries(models)) {
80
+ const tableName = model.storage.table;
81
+ modelToTable[modelName] = tableName;
82
+ tableToModel[tableName] = modelName;
83
+ const modelFieldToColumn = {};
84
+ for (const [fieldName, field] of Object.entries(model.fields)) {
85
+ const columnName = field.column;
86
+ modelFieldToColumn[fieldName] = columnName;
87
+ if (!columnToField[tableName]) {
88
+ columnToField[tableName] = {};
89
+ }
90
+ columnToField[tableName][columnName] = fieldName;
91
+ }
92
+ fieldToColumn[modelName] = modelFieldToColumn;
93
+ }
94
+ return {
95
+ modelToTable: existingMappings?.modelToTable ?? modelToTable,
96
+ tableToModel: existingMappings?.tableToModel ?? tableToModel,
97
+ fieldToColumn: existingMappings?.fieldToColumn ?? fieldToColumn,
98
+ columnToField: existingMappings?.columnToField ?? columnToField,
99
+ codecTypes: existingMappings?.codecTypes ?? {},
100
+ operationTypes: existingMappings?.operationTypes ?? {}
101
+ };
102
+ }
103
+ function validateContractLogic(structurallyValidatedContract) {
104
+ const { storage, models } = structurallyValidatedContract;
105
+ const tableNames = new Set(Object.keys(storage.tables));
106
+ for (const [modelName, modelUnknown] of Object.entries(models)) {
107
+ const model = modelUnknown;
108
+ if (!model.storage?.table) {
109
+ throw new Error(`Model "${modelName}" is missing storage.table`);
110
+ }
111
+ const tableName = model.storage.table;
112
+ if (!tableNames.has(tableName)) {
113
+ throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
114
+ }
115
+ const table = storage.tables[tableName];
116
+ if (!table) {
117
+ throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
118
+ }
119
+ if (!table.primaryKey) {
120
+ throw new Error(`Model "${modelName}" table "${tableName}" is missing a primary key`);
121
+ }
122
+ const columnNames = new Set(Object.keys(table.columns));
123
+ if (!model.fields) {
124
+ throw new Error(`Model "${modelName}" is missing fields`);
125
+ }
126
+ for (const [fieldName, fieldUnknown] of Object.entries(model.fields)) {
127
+ const field = fieldUnknown;
128
+ if (!field.column) {
129
+ throw new Error(`Model "${modelName}" field "${fieldName}" is missing column property`);
130
+ }
131
+ if (!columnNames.has(field.column)) {
132
+ throw new Error(
133
+ `Model "${modelName}" field "${fieldName}" references non-existent column "${field.column}" in table "${tableName}"`
134
+ );
135
+ }
136
+ }
137
+ if (model.relations) {
138
+ for (const [relationName, relation] of Object.entries(model.relations)) {
139
+ if (typeof relation === "object" && relation !== null && "on" in relation && "to" in relation) {
140
+ const on = relation.on;
141
+ const cardinality = relation.cardinality;
142
+ if (on.parentCols && on.childCols) {
143
+ if (cardinality === "1:N") {
144
+ continue;
145
+ }
146
+ const hasMatchingFk = table.foreignKeys?.some((fk) => {
147
+ return fk.columns.length === on.childCols?.length && fk.columns.every((col, i) => col === on.childCols?.[i]) && fk.references.table && fk.references.columns.length === on.parentCols?.length && fk.references.columns.every((col, i) => col === on.parentCols?.[i]);
148
+ });
149
+ if (!hasMatchingFk) {
150
+ throw new Error(
151
+ `Model "${modelName}" relation "${relationName}" does not have a corresponding foreign key in table "${tableName}"`
152
+ );
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ for (const [tableName, table] of Object.entries(storage.tables)) {
160
+ const columnNames = new Set(Object.keys(table.columns));
161
+ if (table.primaryKey) {
162
+ for (const colName of table.primaryKey.columns) {
163
+ if (!columnNames.has(colName)) {
164
+ throw new Error(
165
+ `Table "${tableName}" primaryKey references non-existent column "${colName}"`
166
+ );
167
+ }
168
+ }
169
+ }
170
+ for (const unique of table.uniques) {
171
+ for (const colName of unique.columns) {
172
+ if (!columnNames.has(colName)) {
173
+ throw new Error(
174
+ `Table "${tableName}" unique constraint references non-existent column "${colName}"`
175
+ );
176
+ }
177
+ }
178
+ }
179
+ for (const index of table.indexes) {
180
+ for (const colName of index.columns) {
181
+ if (!columnNames.has(colName)) {
182
+ throw new Error(`Table "${tableName}" index references non-existent column "${colName}"`);
183
+ }
184
+ }
185
+ }
186
+ for (const fk of table.foreignKeys) {
187
+ for (const colName of fk.columns) {
188
+ if (!columnNames.has(colName)) {
189
+ throw new Error(
190
+ `Table "${tableName}" foreignKey references non-existent column "${colName}"`
191
+ );
192
+ }
193
+ }
194
+ if (!tableNames.has(fk.references.table)) {
195
+ throw new Error(
196
+ `Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`
197
+ );
198
+ }
199
+ const referencedTable = storage.tables[fk.references.table];
200
+ if (!referencedTable) {
201
+ throw new Error(
202
+ `Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`
203
+ );
204
+ }
205
+ const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
206
+ for (const colName of fk.references.columns) {
207
+ if (!referencedColumnNames.has(colName)) {
208
+ throw new Error(
209
+ `Table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.references.table}"`
210
+ );
211
+ }
212
+ }
213
+ if (fk.columns.length !== fk.references.columns.length) {
214
+ throw new Error(
215
+ `Table "${tableName}" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`
216
+ );
217
+ }
218
+ }
219
+ }
220
+ }
221
+ function normalizeContract(contract) {
222
+ const contractObj = contract;
223
+ let normalizedStorage = contractObj["storage"];
224
+ if (normalizedStorage && typeof normalizedStorage === "object" && normalizedStorage !== null) {
225
+ const storage = normalizedStorage;
226
+ const tables = storage["tables"];
227
+ if (tables) {
228
+ const normalizedTables = {};
229
+ for (const [tableName, table] of Object.entries(tables)) {
230
+ const tableObj = table;
231
+ const columns = tableObj["columns"];
232
+ if (columns) {
233
+ const normalizedColumns = {};
234
+ for (const [columnName, column] of Object.entries(columns)) {
235
+ const columnObj = column;
236
+ normalizedColumns[columnName] = {
237
+ ...columnObj,
238
+ nullable: columnObj["nullable"] ?? false
239
+ };
240
+ }
241
+ normalizedTables[tableName] = {
242
+ ...tableObj,
243
+ columns: normalizedColumns,
244
+ uniques: tableObj["uniques"] ?? [],
245
+ indexes: tableObj["indexes"] ?? [],
246
+ foreignKeys: tableObj["foreignKeys"] ?? []
247
+ };
248
+ } else {
249
+ normalizedTables[tableName] = tableObj;
250
+ }
251
+ }
252
+ normalizedStorage = {
253
+ ...storage,
254
+ tables: normalizedTables
255
+ };
256
+ }
257
+ }
258
+ let normalizedModels = contractObj["models"];
259
+ if (normalizedModels && typeof normalizedModels === "object" && normalizedModels !== null) {
260
+ const models = normalizedModels;
261
+ const normalizedModelsObj = {};
262
+ for (const [modelName, model] of Object.entries(models)) {
263
+ const modelObj = model;
264
+ normalizedModelsObj[modelName] = {
265
+ ...modelObj,
266
+ relations: modelObj["relations"] ?? {}
267
+ };
268
+ }
269
+ normalizedModels = normalizedModelsObj;
270
+ }
271
+ return {
272
+ ...contractObj,
273
+ models: normalizedModels,
274
+ relations: contractObj["relations"] ?? {},
275
+ storage: normalizedStorage,
276
+ extensions: contractObj["extensions"] ?? {},
277
+ capabilities: contractObj["capabilities"] ?? {},
278
+ meta: contractObj["meta"] ?? {},
279
+ sources: contractObj["sources"] ?? {}
280
+ };
281
+ }
282
+ function validateContract(value) {
283
+ const normalized = normalizeContract(value);
284
+ const structurallyValid = validateContractStructure(normalized);
285
+ const contractForValidation = structurallyValid;
286
+ validateContractLogic(contractForValidation);
287
+ const existingMappings = contractForValidation.mappings;
288
+ const mappings = computeMappings(
289
+ contractForValidation.models,
290
+ contractForValidation.storage,
291
+ existingMappings
292
+ );
293
+ const contractWithMappings = {
294
+ ...structurallyValid,
295
+ models: contractForValidation.models,
296
+ relations: contractForValidation.relations,
297
+ storage: contractForValidation.storage,
298
+ mappings
299
+ };
300
+ return contractWithMappings;
301
+ }
302
+
303
+ export {
304
+ computeMappings,
305
+ validateContract
306
+ };
307
+ //# sourceMappingURL=chunk-BJOMVCU6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/contract.ts"],"sourcesContent":["import type {\n ForeignKey,\n ForeignKeyReferences,\n Index,\n ModelDefinition,\n ModelField,\n ModelStorage,\n PrimaryKey,\n SqlContract,\n SqlMappings,\n SqlStorage,\n StorageColumn,\n StorageTable,\n UniqueConstraint,\n} from '@prisma-next/sql-contract/types';\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 */\nconst StorageColumnSchema = type.declare<StorageColumn>().type({\n type: 'string',\n nullable: 'boolean',\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 IndexSchema = type.declare<Index>().type({\n columns: type.string.array().readonly(),\n 'name?': 'string',\n});\n\nconst ForeignKeyReferencesSchema = type.declare<ForeignKeyReferences>().type({\n table: 'string',\n columns: type.string.array().readonly(),\n});\n\nconst ForeignKeySchema = type.declare<ForeignKey>().type({\n columns: type.string.array().readonly(),\n references: ForeignKeyReferencesSchema,\n 'name?': 'string',\n});\n\nconst StorageTableSchema = type.declare<StorageTable>().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.declare<SqlStorage>().type({\n tables: type({ '[string]': StorageTableSchema }),\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});\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 coreHash: 'string',\n 'profileHash?': 'string',\n 'capabilities?': 'Record<string, Record<string, boolean>>',\n 'extensions?': 'Record<string, unknown>',\n 'meta?': 'Record<string, unknown>',\n 'sources?': 'Record<string, unknown>',\n models: type({ '[string]': ModelSchema }),\n storage: StorageSchema,\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 // Preserve existing mappings if provided, otherwise use computed ones\n return {\n modelToTable: existingMappings?.modelToTable ?? modelToTable,\n tableToModel: existingMappings?.tableToModel ?? tableToModel,\n fieldToColumn: existingMappings?.fieldToColumn ?? fieldToColumn,\n columnToField: existingMappings?.columnToField ?? columnToField,\n codecTypes: existingMappings?.codecTypes ?? {},\n operationTypes: existingMappings?.operationTypes ?? {},\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 * @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 models\n for (const [modelName, modelUnknown] of Object.entries(models)) {\n const model = modelUnknown 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\nexport function normalizeContract(contract: unknown): SqlContract<SqlStorage> {\n const contractObj = contract as Record<string, unknown>;\n\n // Only normalize if storage exists (validation will catch if it's missing)\n let normalizedStorage = contractObj['storage'];\n if (normalizedStorage && typeof normalizedStorage === 'object' && normalizedStorage !== null) {\n const storage = normalizedStorage as Record<string, unknown>;\n const tables = storage['tables'] as Record<string, unknown> | undefined;\n\n if (tables) {\n // Normalize storage tables\n const normalizedTables: Record<string, unknown> = {};\n for (const [tableName, table] of Object.entries(tables)) {\n const tableObj = table as Record<string, unknown>;\n const columns = tableObj['columns'] as Record<string, unknown> | undefined;\n\n if (columns) {\n // Normalize columns: add nullable: false if missing\n const normalizedColumns: Record<string, unknown> = {};\n for (const [columnName, column] of Object.entries(columns)) {\n const columnObj = column as Record<string, unknown>;\n normalizedColumns[columnName] = {\n ...columnObj,\n nullable: columnObj['nullable'] ?? false,\n };\n }\n\n // Normalize table arrays: add empty arrays if missing\n normalizedTables[tableName] = {\n ...tableObj,\n columns: normalizedColumns,\n uniques: tableObj['uniques'] ?? [],\n indexes: tableObj['indexes'] ?? [],\n foreignKeys: tableObj['foreignKeys'] ?? [],\n };\n } else {\n normalizedTables[tableName] = tableObj;\n }\n }\n\n normalizedStorage = {\n ...storage,\n tables: normalizedTables,\n };\n }\n }\n\n // Only normalize if models exists (validation will catch if it's missing)\n let normalizedModels = contractObj['models'];\n if (normalizedModels && typeof normalizedModels === 'object' && normalizedModels !== null) {\n const models = normalizedModels as Record<string, unknown>;\n const normalizedModelsObj: Record<string, unknown> = {};\n for (const [modelName, model] of Object.entries(models)) {\n const modelObj = model as Record<string, unknown>;\n normalizedModelsObj[modelName] = {\n ...modelObj,\n relations: modelObj['relations'] ?? {},\n };\n }\n normalizedModels = normalizedModelsObj;\n }\n\n // Normalize top-level fields: add empty objects if missing\n return {\n ...contractObj,\n models: normalizedModels,\n relations: contractObj['relations'] ?? {},\n storage: normalizedStorage,\n extensions: contractObj['extensions'] ?? {},\n capabilities: contractObj['capabilities'] ?? {},\n meta: contractObj['meta'] ?? {},\n sources: contractObj['sources'] ?? {},\n } as SqlContract<SqlStorage>;\n}\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 contractWithMappings as TContract;\n}\n"],"mappings":";AAeA,SAAS,YAAY;AAOrB,IAAM,sBAAsB,KAAK,QAAuB,EAAE,KAAK;AAAA,EAC7D,MAAM;AAAA,EACN,UAAU;AACZ,CAAC;AAED,IAAM,mBAAmB,KAAK,QAAoB,EAAE,KAAK;AAAA,EACvD,SAAS,KAAK,OAAO,MAAM,EAAE,SAAS;AAAA,EACtC,SAAS;AACX,CAAC;AAED,IAAM,yBAAyB,KAAK,QAA0B,EAAE,KAAK;AAAA,EACnE,SAAS,KAAK,OAAO,MAAM,EAAE,SAAS;AAAA,EACtC,SAAS;AACX,CAAC;AAED,IAAM,cAAc,KAAK,QAAe,EAAE,KAAK;AAAA,EAC7C,SAAS,KAAK,OAAO,MAAM,EAAE,SAAS;AAAA,EACtC,SAAS;AACX,CAAC;AAED,IAAM,6BAA6B,KAAK,QAA8B,EAAE,KAAK;AAAA,EAC3E,OAAO;AAAA,EACP,SAAS,KAAK,OAAO,MAAM,EAAE,SAAS;AACxC,CAAC;AAED,IAAM,mBAAmB,KAAK,QAAoB,EAAE,KAAK;AAAA,EACvD,SAAS,KAAK,OAAO,MAAM,EAAE,SAAS;AAAA,EACtC,YAAY;AAAA,EACZ,SAAS;AACX,CAAC;AAED,IAAM,qBAAqB,KAAK,QAAsB,EAAE,KAAK;AAAA,EAC3D,SAAS,KAAK,EAAE,YAAY,oBAAoB,CAAC;AAAA,EACjD,eAAe;AAAA,EACf,SAAS,uBAAuB,MAAM,EAAE,SAAS;AAAA,EACjD,SAAS,YAAY,MAAM,EAAE,SAAS;AAAA,EACtC,aAAa,iBAAiB,MAAM,EAAE,SAAS;AACjD,CAAC;AAED,IAAM,gBAAgB,KAAK,QAAoB,EAAE,KAAK;AAAA,EACpD,QAAQ,KAAK,EAAE,YAAY,mBAAmB,CAAC;AACjD,CAAC;AAED,IAAM,mBAAmB,KAAK,QAAoB,EAAE,KAAK;AAAA,EACvD,QAAQ;AACV,CAAC;AAED,IAAM,qBAAqB,KAAK,QAAsB,EAAE,KAAK;AAAA,EAC3D,OAAO;AACT,CAAC;AAED,IAAM,cAAc,KAAK,QAAyB,EAAE,KAAK;AAAA,EACvD,SAAS;AAAA,EACT,QAAQ,KAAK,EAAE,YAAY,iBAAiB,CAAC;AAAA,EAC7C,WAAW,KAAK,EAAE,YAAY,UAAU,CAAC;AAC3C,CAAC;AAMD,IAAM,oBAAoB,KAAK;AAAA,EAC7B,kBAAkB;AAAA,EAClB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,QAAQ,KAAK,EAAE,YAAY,YAAY,CAAC;AAAA,EACxC,SAAS;AACX,CAAC;AAkBD,SAAS,0BACP,OACyC;AAEzC,QAAM,WAAW;AACjB,MAAI,SAAS,iBAAiB,UAAa,SAAS,iBAAiB,OAAO;AAE1E,UAAM,IAAI,MAAM,8BAA8B,SAAS,YAAY,EAAE;AAAA,EACvE;AAEA,QAAM,iBAAiB,kBAAkB,KAAK;AAE9C,MAAI,0BAA0B,KAAK,QAAQ;AACzC,UAAM,WAAW,eAAe,IAAI,CAAC,MAA2B,EAAE,OAAO,EAAE,KAAK,IAAI;AACpF,UAAM,IAAI,MAAM,0CAA0C,QAAQ,EAAE;AAAA,EACtE;AAKA,SAAO;AACT;AAWO,SAAS,gBACd,QACA,UACA,kBACa;AACb,QAAM,eAAuC,CAAC;AAC9C,QAAM,eAAuC,CAAC;AAC9C,QAAM,gBAAwD,CAAC;AAC/D,QAAM,gBAAwD,CAAC;AAE/D,aAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,UAAM,YAAY,MAAM,QAAQ;AAChC,iBAAa,SAAS,IAAI;AAC1B,iBAAa,SAAS,IAAI;AAE1B,UAAM,qBAA6C,CAAC;AACpD,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AAC7D,YAAM,aAAa,MAAM;AACzB,yBAAmB,SAAS,IAAI;AAEhC,UAAI,CAAC,cAAc,SAAS,GAAG;AAC7B,sBAAc,SAAS,IAAI,CAAC;AAAA,MAC9B;AACA,oBAAc,SAAS,EAAE,UAAU,IAAI;AAAA,IACzC;AACA,kBAAc,SAAS,IAAI;AAAA,EAC7B;AAGA,SAAO;AAAA,IACL,cAAc,kBAAkB,gBAAgB;AAAA,IAChD,cAAc,kBAAkB,gBAAgB;AAAA,IAChD,eAAe,kBAAkB,iBAAiB;AAAA,IAClD,eAAe,kBAAkB,iBAAiB;AAAA,IAClD,YAAY,kBAAkB,cAAc,CAAC;AAAA,IAC7C,gBAAgB,kBAAkB,kBAAkB,CAAC;AAAA,EACvD;AACF;AAUA,SAAS,sBAAsB,+BAA8D;AAC3F,QAAM,EAAE,SAAS,OAAO,IAAI;AAC5B,QAAM,aAAa,IAAI,IAAI,OAAO,KAAK,QAAQ,MAAM,CAAC;AAGtD,aAAW,CAAC,WAAW,YAAY,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC9D,UAAM,QAAQ;AAEd,QAAI,CAAC,MAAM,SAAS,OAAO;AAEzB,YAAM,IAAI,MAAM,UAAU,SAAS,4BAA4B;AAAA,IACjE;AAEA,UAAM,YAAY,MAAM,QAAQ;AAGhC,QAAI,CAAC,WAAW,IAAI,SAAS,GAAG;AAE9B,YAAM,IAAI,MAAM,UAAU,SAAS,oCAAoC,SAAS,GAAG;AAAA,IACrF;AAEA,UAAM,QAAQ,QAAQ,OAAO,SAAS;AACtC,QAAI,CAAC,OAAO;AAEV,YAAM,IAAI,MAAM,UAAU,SAAS,oCAAoC,SAAS,GAAG;AAAA,IACrF;AAGA,QAAI,CAAC,MAAM,YAAY;AAErB,YAAM,IAAI,MAAM,UAAU,SAAS,YAAY,SAAS,4BAA4B;AAAA,IACtF;AAEA,UAAM,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,OAAO,CAAC;AAGtD,QAAI,CAAC,MAAM,QAAQ;AAEjB,YAAM,IAAI,MAAM,UAAU,SAAS,qBAAqB;AAAA,IAC1D;AAEA,eAAW,CAAC,WAAW,YAAY,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AACpE,YAAM,QAAQ;AAEd,UAAI,CAAC,MAAM,QAAQ;AAEjB,cAAM,IAAI,MAAM,UAAU,SAAS,YAAY,SAAS,8BAA8B;AAAA,MACxF;AAGA,UAAI,CAAC,YAAY,IAAI,MAAM,MAAM,GAAG;AAElC,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,YAAY,SAAS,qCAAqC,MAAM,MAAM,eAAe,SAAS;AAAA,QACnH;AAAA,MACF;AAAA,IACF;AAGA,QAAI,MAAM,WAAW;AACnB,iBAAW,CAAC,cAAc,QAAQ,KAAK,OAAO,QAAQ,MAAM,SAAS,GAAG;AAGtE,YACE,OAAO,aAAa,YACpB,aAAa,QACb,QAAQ,YACR,QAAQ,UACR;AACA,gBAAM,KAAK,SAAS;AACpB,gBAAM,cAAe,SAAsC;AAC3D,cAAI,GAAG,cAAc,GAAG,WAAW;AAKjC,gBAAI,gBAAgB,OAAO;AAGzB;AAAA,YACF;AAGA,kBAAM,gBAAgB,MAAM,aAAa,KAAK,CAAC,OAAO;AACpD,qBACE,GAAG,QAAQ,WAAW,GAAG,WAAW,UACpC,GAAG,QAAQ,MAAM,CAAC,KAAK,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,KACtD,GAAG,WAAW,SACd,GAAG,WAAW,QAAQ,WAAW,GAAG,YAAY,UAChD,GAAG,WAAW,QAAQ,MAAM,CAAC,KAAK,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;AAAA,YAEtE,CAAC;AAED,gBAAI,CAAC,eAAe;AAElB,oBAAM,IAAI;AAAA,gBACR,UAAU,SAAS,eAAe,YAAY,yDAAyD,SAAS;AAAA,cAClH;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,aAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,QAAQ,MAAM,GAAG;AAC/D,UAAM,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,OAAO,CAAC;AAGtD,QAAI,MAAM,YAAY;AACpB,iBAAW,WAAW,MAAM,WAAW,SAAS;AAC9C,YAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAE7B,gBAAM,IAAI;AAAA,YACR,UAAU,SAAS,gDAAgD,OAAO;AAAA,UAC5E;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,eAAW,UAAU,MAAM,SAAS;AAClC,iBAAW,WAAW,OAAO,SAAS;AACpC,YAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAE7B,gBAAM,IAAI;AAAA,YACR,UAAU,SAAS,uDAAuD,OAAO;AAAA,UACnF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,eAAW,SAAS,MAAM,SAAS;AACjC,iBAAW,WAAW,MAAM,SAAS;AACnC,YAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAE7B,gBAAM,IAAI,MAAM,UAAU,SAAS,2CAA2C,OAAO,GAAG;AAAA,QAC1F;AAAA,MACF;AAAA,IACF;AAGA,eAAW,MAAM,MAAM,aAAa;AAElC,iBAAW,WAAW,GAAG,SAAS;AAChC,YAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAE7B,gBAAM,IAAI;AAAA,YACR,UAAU,SAAS,gDAAgD,OAAO;AAAA,UAC5E;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAC,WAAW,IAAI,GAAG,WAAW,KAAK,GAAG;AAExC,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,+CAA+C,GAAG,WAAW,KAAK;AAAA,QACvF;AAAA,MACF;AAGA,YAAM,kBAAkB,QAAQ,OAAO,GAAG,WAAW,KAAK;AAC1D,UAAI,CAAC,iBAAiB;AAEpB,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,+CAA+C,GAAG,WAAW,KAAK;AAAA,QACvF;AAAA,MACF;AACA,YAAM,wBAAwB,IAAI,IAAI,OAAO,KAAK,gBAAgB,OAAO,CAAC;AAE1E,iBAAW,WAAW,GAAG,WAAW,SAAS;AAC3C,YAAI,CAAC,sBAAsB,IAAI,OAAO,GAAG;AAEvC,gBAAM,IAAI;AAAA,YACR,UAAU,SAAS,gDAAgD,OAAO,eAAe,GAAG,WAAW,KAAK;AAAA,UAC9G;AAAA,QACF;AAAA,MACF;AAEA,UAAI,GAAG,QAAQ,WAAW,GAAG,WAAW,QAAQ,QAAQ;AAEtD,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,8BAA8B,GAAG,QAAQ,MAAM,6CAA6C,GAAG,WAAW,QAAQ,MAAM;AAAA,QAC7I;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,UAA4C;AAC5E,QAAM,cAAc;AAGpB,MAAI,oBAAoB,YAAY,SAAS;AAC7C,MAAI,qBAAqB,OAAO,sBAAsB,YAAY,sBAAsB,MAAM;AAC5F,UAAM,UAAU;AAChB,UAAM,SAAS,QAAQ,QAAQ;AAE/B,QAAI,QAAQ;AAEV,YAAM,mBAA4C,CAAC;AACnD,iBAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,cAAM,WAAW;AACjB,cAAM,UAAU,SAAS,SAAS;AAElC,YAAI,SAAS;AAEX,gBAAM,oBAA6C,CAAC;AACpD,qBAAW,CAAC,YAAY,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC1D,kBAAM,YAAY;AAClB,8BAAkB,UAAU,IAAI;AAAA,cAC9B,GAAG;AAAA,cACH,UAAU,UAAU,UAAU,KAAK;AAAA,YACrC;AAAA,UACF;AAGA,2BAAiB,SAAS,IAAI;AAAA,YAC5B,GAAG;AAAA,YACH,SAAS;AAAA,YACT,SAAS,SAAS,SAAS,KAAK,CAAC;AAAA,YACjC,SAAS,SAAS,SAAS,KAAK,CAAC;AAAA,YACjC,aAAa,SAAS,aAAa,KAAK,CAAC;AAAA,UAC3C;AAAA,QACF,OAAO;AACL,2BAAiB,SAAS,IAAI;AAAA,QAChC;AAAA,MACF;AAEA,0BAAoB;AAAA,QAClB,GAAG;AAAA,QACH,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAGA,MAAI,mBAAmB,YAAY,QAAQ;AAC3C,MAAI,oBAAoB,OAAO,qBAAqB,YAAY,qBAAqB,MAAM;AACzF,UAAM,SAAS;AACf,UAAM,sBAA+C,CAAC;AACtD,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,YAAM,WAAW;AACjB,0BAAoB,SAAS,IAAI;AAAA,QAC/B,GAAG;AAAA,QACH,WAAW,SAAS,WAAW,KAAK,CAAC;AAAA,MACvC;AAAA,IACF;AACA,uBAAmB;AAAA,EACrB;AAGA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,WAAW,YAAY,WAAW,KAAK,CAAC;AAAA,IACxC,SAAS;AAAA,IACT,YAAY,YAAY,YAAY,KAAK,CAAC;AAAA,IAC1C,cAAc,YAAY,cAAc,KAAK,CAAC;AAAA,IAC9C,MAAM,YAAY,MAAM,KAAK,CAAC;AAAA,IAC9B,SAAS,YAAY,SAAS,KAAK,CAAC;AAAA,EACtC;AACF;AAsCO,SAAS,iBACd,OACW;AAEX,QAAM,aAAa,kBAAkB,KAAK;AAE1C,QAAM,oBAAoB,0BAAmD,UAAU;AAEvF,QAAM,wBAAwB;AAG9B,wBAAsB,qBAAqB;AAG3C,QAAM,mBAAoB,sBAA8D;AAGxF,QAAM,WAAW;AAAA,IACf,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB;AAAA,EACF;AAGA,QAAM,uBAAuB;AAAA,IAC3B,GAAG;AAAA,IACH,QAAQ,sBAAsB;AAAA,IAC9B,WAAW,sBAAsB;AAAA,IACjC,SAAS,sBAAsB;AAAA,IAC/B;AAAA,EACF;AAIA,SAAO;AACT;","names":[]}
@@ -0,0 +1,85 @@
1
+ import { ColumnBuilderState, TableBuilderState, ModelBuilderState, RelationDefinition, ContractBuilder, BuildStorageColumn, ExtractColumns, ExtractPrimaryKey, BuildModels, BuildRelations, TableBuilder, ModelBuilder } from '@prisma-next/contract-authoring';
2
+ import { SqlContract, SqlMappings } from '@prisma-next/sql-contract/types';
3
+
4
+ /**
5
+ * Type-level mappings structure for contracts built via `defineContract()`.
6
+ *
7
+ * Compile-time type helper (not a runtime object) that ensures mappings match what the builder
8
+ * produces. `codecTypes` uses the generic `CodecTypes` parameter; `operationTypes` is always
9
+ * empty since operations are added via extensions at runtime.
10
+ *
11
+ * **Difference from RuntimeContext**: This is a compile-time type for contract construction.
12
+ * `RuntimeContext` is a runtime object with populated registries for query execution.
13
+ *
14
+ * @template C - The `CodecTypes` generic parameter passed to `defineContract<CodecTypes>()`
15
+ */
16
+ type ContractBuilderMappings<C extends Record<string, {
17
+ output: unknown;
18
+ }>> = Omit<SqlMappings, 'codecTypes' | 'operationTypes'> & {
19
+ readonly codecTypes: C;
20
+ readonly operationTypes: Record<string, never>;
21
+ };
22
+ type BuildStorageTable<_TableName extends string, Columns extends Record<string, ColumnBuilderState<string, boolean, string>>, PK extends readonly string[] | undefined> = {
23
+ readonly columns: {
24
+ readonly [K in keyof Columns]: Columns[K] extends ColumnBuilderState<string, infer Null, infer TType> ? BuildStorageColumn<Null & boolean, TType> : never;
25
+ };
26
+ readonly uniques: ReadonlyArray<never>;
27
+ readonly indexes: ReadonlyArray<never>;
28
+ readonly foreignKeys: ReadonlyArray<never>;
29
+ } & (PK extends readonly string[] ? {
30
+ readonly primaryKey: {
31
+ readonly columns: PK;
32
+ };
33
+ } : Record<string, never>);
34
+ type BuildStorage<Tables extends Record<string, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>>> = {
35
+ readonly tables: {
36
+ readonly [K in keyof Tables]: BuildStorageTable<K & string, ExtractColumns<Tables[K]>, ExtractPrimaryKey<Tables[K]>>;
37
+ };
38
+ };
39
+ interface ColumnBuilder<Name extends string, Nullable extends boolean, Type extends string> {
40
+ nullable<Value extends boolean>(value?: Value): ColumnBuilder<Name, Value, Type>;
41
+ type<Id extends string>(id: Id): ColumnBuilder<Name, Nullable, Id>;
42
+ build(): ColumnBuilderState<Name, Nullable, Type>;
43
+ }
44
+ declare class SqlContractBuilder<CodecTypes extends Record<string, {
45
+ output: unknown;
46
+ }> = Record<string, never>, Target extends string | undefined = undefined, Tables extends Record<string, TableBuilderState<string, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>> = Record<never, never>, Models extends Record<string, ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>> = Record<never, never>, CoreHash extends string | undefined = undefined, Extensions extends Record<string, unknown> | undefined = undefined, Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined> extends ContractBuilder<Target, Tables, Models, CoreHash, Extensions, Capabilities> {
47
+ /**
48
+ * Builds and normalizes the contract.
49
+ *
50
+ * **Responsibility: Normalization**
51
+ * This method is responsible for normalizing the contract IR by setting default values
52
+ * for all required fields:
53
+ * - `nullable`: defaults to `false` if not provided
54
+ * - `uniques`: defaults to `[]` (empty array)
55
+ * - `indexes`: defaults to `[]` (empty array)
56
+ * - `foreignKeys`: defaults to `[]` (empty array)
57
+ * - `relations`: defaults to `{}` (empty object) for both model-level and contract-level
58
+ *
59
+ * The contract builder is the **only** place where normalization should occur.
60
+ * Validators, parsers, and emitters should assume the contract is already normalized.
61
+ *
62
+ * @returns A normalized SqlContract with all required fields present
63
+ */
64
+ build(): Target extends string ? SqlContract<BuildStorage<Tables>, BuildModels<Models>, BuildRelations<Models>, ContractBuilderMappings<CodecTypes>> & {
65
+ readonly schemaVersion: '1';
66
+ readonly target: Target;
67
+ readonly targetFamily: 'sql';
68
+ readonly coreHash: CoreHash extends string ? CoreHash : string;
69
+ } & (Extensions extends Record<string, unknown> ? {
70
+ readonly extensions: Extensions;
71
+ } : Record<string, never>) & (Capabilities extends Record<string, Record<string, boolean>> ? {
72
+ readonly capabilities: Capabilities;
73
+ } : Record<string, never>) : never;
74
+ target<T extends string>(target: T): SqlContractBuilder<CodecTypes, T, Tables, Models, CoreHash, Extensions, Capabilities>;
75
+ extensions<E extends Record<string, unknown>>(extensions: E): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, E, Capabilities>;
76
+ capabilities<C extends Record<string, Record<string, boolean>>>(capabilities: C): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, Extensions, C>;
77
+ coreHash<H extends string>(hash: H): SqlContractBuilder<CodecTypes, Target, Tables, Models, H, Extensions, Capabilities>;
78
+ table<TableName extends string, T extends TableBuilder<TableName, Record<string, ColumnBuilderState<string, boolean, string>>, readonly string[] | undefined>>(name: TableName, callback: (t: TableBuilder<TableName>) => T | undefined): SqlContractBuilder<CodecTypes, Target, Tables & Record<TableName, ReturnType<T['build']>>, Models, CoreHash, Extensions, Capabilities>;
79
+ model<ModelName extends string, TableName extends string, M extends ModelBuilder<ModelName, TableName, Record<string, string>, Record<string, RelationDefinition>>>(name: ModelName, table: TableName, callback: (m: ModelBuilder<ModelName, TableName, Record<string, string>, Record<never, never>>) => M | undefined): SqlContractBuilder<CodecTypes, Target, Tables, Models & Record<ModelName, ReturnType<M['build']>>, CoreHash, Extensions, Capabilities>;
80
+ }
81
+ declare function defineContract<CodecTypes extends Record<string, {
82
+ output: unknown;
83
+ }> = Record<string, never>>(): SqlContractBuilder<CodecTypes>;
84
+
85
+ export { type ColumnBuilder, defineContract };
@@ -0,0 +1,182 @@
1
+ import {
2
+ computeMappings
3
+ } from "./chunk-BJOMVCU6.js";
4
+
5
+ // src/contract-builder.ts
6
+ import {
7
+ ContractBuilder,
8
+ ModelBuilder,
9
+ TableBuilder
10
+ } from "@prisma-next/contract-authoring";
11
+ var SqlContractBuilder = class _SqlContractBuilder extends ContractBuilder {
12
+ /**
13
+ * Builds and normalizes the contract.
14
+ *
15
+ * **Responsibility: Normalization**
16
+ * This method is responsible for normalizing the contract IR by setting default values
17
+ * for all required fields:
18
+ * - `nullable`: defaults to `false` if not provided
19
+ * - `uniques`: defaults to `[]` (empty array)
20
+ * - `indexes`: defaults to `[]` (empty array)
21
+ * - `foreignKeys`: defaults to `[]` (empty array)
22
+ * - `relations`: defaults to `{}` (empty object) for both model-level and contract-level
23
+ *
24
+ * The contract builder is the **only** place where normalization should occur.
25
+ * Validators, parsers, and emitters should assume the contract is already normalized.
26
+ *
27
+ * @returns A normalized SqlContract with all required fields present
28
+ */
29
+ build() {
30
+ if (!this.state.target) {
31
+ throw new Error("target is required. Call .target() before .build()");
32
+ }
33
+ const target = this.state.target;
34
+ const storageTables = {};
35
+ for (const tableName of Object.keys(this.state.tables)) {
36
+ const tableState = this.state.tables[tableName];
37
+ if (!tableState) continue;
38
+ const columns = {};
39
+ for (const columnName in tableState.columns) {
40
+ const columnState = tableState.columns[columnName];
41
+ if (!columnState) continue;
42
+ columns[columnName] = {
43
+ type: columnState.type,
44
+ nullable: columnState.nullable ?? false
45
+ };
46
+ }
47
+ const table = {
48
+ columns,
49
+ uniques: [],
50
+ indexes: [],
51
+ foreignKeys: [],
52
+ ...tableState.primaryKey ? {
53
+ primaryKey: {
54
+ columns: tableState.primaryKey
55
+ }
56
+ } : {}
57
+ };
58
+ storageTables[tableName] = table;
59
+ }
60
+ const storage = { tables: storageTables };
61
+ const modelsPartial = {};
62
+ for (const modelName in this.state.models) {
63
+ const modelState = this.state.models[modelName];
64
+ if (!modelState) continue;
65
+ const modelStateTyped = modelState;
66
+ const fields = {};
67
+ for (const fieldName in modelStateTyped.fields) {
68
+ const columnName = modelStateTyped.fields[fieldName];
69
+ if (columnName) {
70
+ fields[fieldName] = {
71
+ column: columnName
72
+ };
73
+ }
74
+ }
75
+ modelsPartial[modelName] = {
76
+ storage: {
77
+ table: modelStateTyped.table
78
+ },
79
+ fields,
80
+ relations: {}
81
+ };
82
+ }
83
+ const relationsPartial = {};
84
+ for (const modelName in this.state.models) {
85
+ const modelState = this.state.models[modelName];
86
+ if (!modelState) continue;
87
+ const modelStateTyped = modelState;
88
+ const tableName = modelStateTyped.table;
89
+ if (!tableName) continue;
90
+ if (modelStateTyped.relations && Object.keys(modelStateTyped.relations).length > 0) {
91
+ if (!relationsPartial[tableName]) {
92
+ relationsPartial[tableName] = {};
93
+ }
94
+ const tableRelations = relationsPartial[tableName];
95
+ if (tableRelations) {
96
+ for (const relationName in modelStateTyped.relations) {
97
+ const relation = modelStateTyped.relations[relationName];
98
+ if (relation) {
99
+ tableRelations[relationName] = relation;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ const models = modelsPartial;
106
+ const baseMappings = computeMappings(
107
+ models,
108
+ storage
109
+ );
110
+ const mappings = {
111
+ ...baseMappings,
112
+ codecTypes: {},
113
+ operationTypes: {}
114
+ };
115
+ const contract = {
116
+ schemaVersion: "1",
117
+ target,
118
+ targetFamily: "sql",
119
+ coreHash: this.state.coreHash || "sha256:ts-builder-placeholder",
120
+ models,
121
+ relations: relationsPartial,
122
+ storage,
123
+ mappings,
124
+ extensions: this.state.extensions || {},
125
+ capabilities: this.state.capabilities || {},
126
+ meta: {},
127
+ sources: {}
128
+ };
129
+ return contract;
130
+ }
131
+ target(target) {
132
+ return new _SqlContractBuilder({
133
+ ...this.state,
134
+ target
135
+ });
136
+ }
137
+ extensions(extensions) {
138
+ return new _SqlContractBuilder({
139
+ ...this.state,
140
+ extensions
141
+ });
142
+ }
143
+ capabilities(capabilities) {
144
+ return new _SqlContractBuilder({
145
+ ...this.state,
146
+ capabilities
147
+ });
148
+ }
149
+ coreHash(hash) {
150
+ return new _SqlContractBuilder({
151
+ ...this.state,
152
+ coreHash: hash
153
+ });
154
+ }
155
+ table(name, callback) {
156
+ const tableBuilder = new TableBuilder(name);
157
+ const result = callback(tableBuilder);
158
+ const finalBuilder = result instanceof TableBuilder ? result : tableBuilder;
159
+ const tableState = finalBuilder.build();
160
+ return new _SqlContractBuilder({
161
+ ...this.state,
162
+ tables: { ...this.state.tables, [name]: tableState }
163
+ });
164
+ }
165
+ model(name, table, callback) {
166
+ const modelBuilder = new ModelBuilder(name, table);
167
+ const result = callback(modelBuilder);
168
+ const finalBuilder = result instanceof ModelBuilder ? result : modelBuilder;
169
+ const modelState = finalBuilder.build();
170
+ return new _SqlContractBuilder({
171
+ ...this.state,
172
+ models: { ...this.state.models, [name]: modelState }
173
+ });
174
+ }
175
+ };
176
+ function defineContract() {
177
+ return new SqlContractBuilder();
178
+ }
179
+ export {
180
+ defineContract
181
+ };
182
+ //# sourceMappingURL=contract-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/contract-builder.ts"],"sourcesContent":["import type {\n ColumnBuilderState,\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 type ExtractColumns,\n type ExtractPrimaryKey,\n ModelBuilder,\n type Mutable,\n TableBuilder,\n} from '@prisma-next/contract-authoring';\nimport type {\n ModelDefinition,\n ModelField,\n SqlContract,\n SqlMappings,\n SqlStorage,\n} from '@prisma-next/sql-contract/types';\nimport { computeMappings } from './contract';\n\n/**\n * Type-level mappings structure for contracts built via `defineContract()`.\n *\n * Compile-time type helper (not a runtime object) that ensures mappings match what the builder\n * produces. `codecTypes` uses the generic `CodecTypes` parameter; `operationTypes` is always\n * empty since operations are added via extensions at runtime.\n *\n * **Difference from RuntimeContext**: This is a compile-time type for contract construction.\n * `RuntimeContext` is a runtime object with populated registries for query execution.\n *\n * @template C - The `CodecTypes` generic parameter passed to `defineContract<CodecTypes>()`\n */\ntype ContractBuilderMappings<C extends Record<string, { output: unknown }>> = Omit<\n SqlMappings,\n 'codecTypes' | 'operationTypes'\n> & {\n readonly codecTypes: C;\n readonly operationTypes: Record<string, never>;\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<never>;\n readonly indexes: ReadonlyArray<never>;\n readonly foreignKeys: ReadonlyArray<never>;\n} & (PK extends readonly string[]\n ? { readonly primaryKey: { readonly columns: PK } }\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> = {\n readonly tables: {\n readonly [K in keyof Tables]: BuildStorageTable<\n K & string,\n ExtractColumns<Tables[K]>,\n ExtractPrimaryKey<Tables[K]>\n >;\n };\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\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 CoreHash extends string | undefined = undefined,\n Extensions extends Record<string, unknown> | undefined = undefined,\n Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,\n> extends ContractBuilder<Target, Tables, Models, CoreHash, Extensions, Capabilities> {\n /**\n * Builds and normalizes the contract.\n *\n * **Responsibility: Normalization**\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 *\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 * @returns A normalized SqlContract with all required fields present\n */\n build(): Target extends string\n ? SqlContract<\n BuildStorage<Tables>,\n BuildModels<Models>,\n BuildRelations<Models>,\n ContractBuilderMappings<CodecTypes>\n > & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly coreHash: CoreHash extends string ? CoreHash : string;\n } & (Extensions extends Record<string, unknown>\n ? { readonly extensions: Extensions }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>)\n : never {\n // Type helper to ensure literal types are preserved in return type\n type BuiltContract = Target extends string\n ? SqlContract<\n BuildStorage<Tables>,\n BuildModels<Models>,\n BuildRelations<Models>,\n ContractBuilderMappings<CodecTypes>\n > & {\n readonly schemaVersion: '1';\n readonly target: Target;\n readonly targetFamily: 'sql';\n readonly coreHash: CoreHash extends string ? CoreHash : string;\n } & (Extensions extends Record<string, unknown>\n ? { readonly extensions: Extensions }\n : Record<string, never>) &\n (Capabilities extends Record<string, Record<string, boolean>>\n ? { readonly capabilities: Capabilities }\n : Record<string, never>)\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\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 columns[columnName as keyof ColumnDefs] = {\n type: columnState.type,\n nullable: (columnState.nullable ?? false) as ColumnDefs[keyof ColumnDefs]['nullable'] &\n boolean,\n } as BuildStorageColumn<\n ColumnDefs[keyof ColumnDefs]['nullable'] & boolean,\n ColumnDefs[keyof ColumnDefs]['type']\n >;\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 },\n }\n : {}),\n } as unknown as BuildStorageTable<TableKey & string, ColumnDefs, PrimaryKey>;\n\n (storageTables as Mutable<BuildStorageTables<Tables>>)[tableName] = table;\n }\n\n const storage = { tables: storageTables as BuildStorageTables<Tables> } as BuildStorage<Tables>;\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 = {\n ...baseMappings,\n codecTypes: {} as CodecTypes,\n operationTypes: {} as Record<string, never>,\n } as ContractBuilderMappings<CodecTypes>;\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 coreHash: this.state.coreHash || 'sha256:ts-builder-placeholder',\n models,\n relations: relationsPartial,\n storage,\n mappings,\n extensions: this.state.extensions || {},\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 CoreHash,\n Extensions,\n Capabilities\n >['build']\n >;\n }\n\n override target<T extends string>(\n target: T,\n ): SqlContractBuilder<CodecTypes, T, Tables, Models, CoreHash, Extensions, Capabilities> {\n return new SqlContractBuilder<\n CodecTypes,\n T,\n Tables,\n Models,\n CoreHash,\n Extensions,\n Capabilities\n >({\n ...this.state,\n target,\n });\n }\n\n override extensions<E extends Record<string, unknown>>(\n extensions: E,\n ): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, E, Capabilities> {\n return new SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, E, Capabilities>({\n ...this.state,\n extensions,\n });\n }\n\n override capabilities<C extends Record<string, Record<string, boolean>>>(\n capabilities: C,\n ): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, Extensions, C> {\n return new SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, Extensions, C>({\n ...this.state,\n capabilities,\n });\n }\n\n override coreHash<H extends string>(\n hash: H,\n ): SqlContractBuilder<CodecTypes, Target, Tables, Models, H, Extensions, Capabilities> {\n return new SqlContractBuilder<CodecTypes, Target, Tables, Models, H, Extensions, Capabilities>({\n ...this.state,\n coreHash: 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 CoreHash,\n Extensions,\n Capabilities\n > {\n const tableBuilder = new TableBuilder<TableName>(name);\n const result = callback(tableBuilder);\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 CoreHash,\n Extensions,\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<string, string>, Record<never, never>>,\n ) => M | undefined,\n ): SqlContractBuilder<\n CodecTypes,\n Target,\n Tables,\n Models & Record<ModelName, ReturnType<M['build']>>,\n CoreHash,\n Extensions,\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 CoreHash,\n Extensions,\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\nexport function defineContract<\n CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,\n>(): SqlContractBuilder<CodecTypes> {\n return new SqlContractBuilder<CodecTypes>();\n}\n"],"mappings":";;;;;AAMA;AAAA,EAIE;AAAA,EAGA;AAAA,EAEA;AAAA,OACK;AA6FP,IAAM,qBAAN,MAAM,4BAkBI,gBAA4E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBpF,QAiBU;AAoBR,QAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACtE;AAEA,UAAM,SAAS,KAAK,MAAM;AAE1B,UAAM,gBAAgB,CAAC;AAEvB,eAAW,aAAa,OAAO,KAAK,KAAK,MAAM,MAAM,GAAmC;AACtF,YAAM,aAAa,KAAK,MAAM,OAAO,SAAS;AAC9C,UAAI,CAAC,WAAY;AAMjB,YAAM,UAAU,CAAC;AAOjB,iBAAW,cAAc,WAAW,SAAS;AAC3C,cAAM,cAAc,WAAW,QAAQ,UAAU;AACjD,YAAI,CAAC,YAAa;AAClB,gBAAQ,UAA8B,IAAI;AAAA,UACxC,MAAM,YAAY;AAAA,UAClB,UAAW,YAAY,YAAY;AAAA,QAErC;AAAA,MAIF;AAEA,YAAM,QAAQ;AAAA,QACZ;AAAA,QAMA,SAAS,CAAC;AAAA,QACV,SAAS,CAAC;AAAA,QACV,aAAa,CAAC;AAAA,QACd,GAAI,WAAW,aACX;AAAA,UACE,YAAY;AAAA,YACV,SAAS,WAAW;AAAA,UACtB;AAAA,QACF,IACA,CAAC;AAAA,MACP;AAEA,MAAC,cAAsD,SAAS,IAAI;AAAA,IACtE;AAEA,UAAM,UAAU,EAAE,QAAQ,cAA4C;AAGtE,UAAM,gBAA8C,CAAC;AAGrD,eAAW,aAAa,KAAK,MAAM,QAAQ;AACzC,YAAM,aAAa,KAAK,MAAM,OAAO,SAAS;AAC9C,UAAI,CAAC,WAAY;AAEjB,YAAM,kBAAkB;AAOxB,YAAM,SAA8C,CAAC;AAGrD,iBAAW,aAAa,gBAAgB,QAAQ;AAC9C,cAAM,aAAa,gBAAgB,OAAO,SAAS;AACnD,YAAI,YAAY;AACd,iBAAO,SAAS,IAAI;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAGA,MAAC,cAA6D,SAAS,IAAI;AAAA,QACzE,SAAS;AAAA,UACP,OAAO,gBAAgB;AAAA,QACzB;AAAA,QACA;AAAA,QACA,WAAW,CAAC;AAAA,MACd;AAAA,IACF;AAGA,UAAM,mBAAgF,CAAC;AAGvF,eAAW,aAAa,KAAK,MAAM,QAAQ;AACzC,YAAM,aAAa,KAAK,MAAM,OAAO,SAAS;AAC9C,UAAI,CAAC,WAAY;AAEjB,YAAM,kBAAkB;AAOxB,YAAM,YAAY,gBAAgB;AAClC,UAAI,CAAC,UAAW;AAGhB,UAAI,gBAAgB,aAAa,OAAO,KAAK,gBAAgB,SAAS,EAAE,SAAS,GAAG;AAClF,YAAI,CAAC,iBAAiB,SAAS,GAAG;AAChC,2BAAiB,SAAS,IAAI,CAAC;AAAA,QACjC;AAGA,cAAM,iBAAiB,iBAAiB,SAAS;AACjD,YAAI,gBAAgB;AAClB,qBAAW,gBAAgB,gBAAgB,WAAW;AACpD,kBAAM,WAAW,gBAAgB,UAAU,YAAY;AACvD,gBAAI,UAAU;AACZ,6BAAe,YAAY,IAAI;AAAA,YACjC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS;AAEf,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AAEA,UAAM,WAAW;AAAA,MACf,GAAG;AAAA,MACH,YAAY,CAAC;AAAA,MACb,gBAAgB,CAAC;AAAA,IACnB;AAKA,UAAM,WAAW;AAAA,MACf,eAAe;AAAA,MACf;AAAA,MACA,cAAc;AAAA,MACd,UAAU,KAAK,MAAM,YAAY;AAAA,MACjC;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,YAAY,KAAK,MAAM,cAAc,CAAC;AAAA,MACtC,cAAc,KAAK,MAAM,gBAAgB,CAAC;AAAA,MAC1C,MAAM,CAAC;AAAA,MACP,SAAS,CAAC;AAAA,IACZ;AAEA,WAAO;AAAA,EAWT;AAAA,EAES,OACP,QACuF;AACvF,WAAO,IAAI,oBAQT;AAAA,MACA,GAAG,KAAK;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAES,WACP,YACmF;AACnF,WAAO,IAAI,oBAAkF;AAAA,MAC3F,GAAG,KAAK;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAES,aACP,cACiF;AACjF,WAAO,IAAI,oBAAgF;AAAA,MACzF,GAAG,KAAK;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAES,SACP,MACqF;AACrF,WAAO,IAAI,oBAAoF;AAAA,MAC7F,GAAG,KAAK;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAES,MAQP,MACA,UASA;AACA,UAAM,eAAe,IAAI,aAAwB,IAAI;AACrD,UAAM,SAAS,SAAS,YAAY;AACpC,UAAM,eAAe,kBAAkB,eAAe,SAAS;AAC/D,UAAM,aAAa,aAAa,MAAM;AAEtC,WAAO,IAAI,oBAQT;AAAA,MACA,GAAG,KAAK;AAAA,MACR,QAAQ,EAAE,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI,GAAG,WAAW;AAAA,IAErD,CAAC;AAAA,EACH;AAAA,EAES,MAUP,MACA,OACA,UAWA;AACA,UAAM,eAAe,IAAI,aAAmC,MAAM,KAAK;AACvE,UAAM,SAAS,SAAS,YAAY;AACpC,UAAM,eAAe,kBAAkB,eAAe,SAAS;AAC/D,UAAM,aAAa,aAAa,MAAM;AAEtC,WAAO,IAAI,oBAQT;AAAA,MACA,GAAG,KAAK;AAAA,MACR,QAAQ,EAAE,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI,GAAG,WAAW;AAAA,IAErD,CAAC;AAAA,EACH;AACF;AAEO,SAAS,iBAEoB;AAClC,SAAO,IAAI,mBAA+B;AAC5C;","names":[]}
@@ -0,0 +1,51 @@
1
+ import { ModelDefinition, SqlStorage, SqlMappings, SqlContract } from '@prisma-next/sql-contract/types';
2
+
3
+ /**
4
+ * Computes mapping dictionaries from models and storage structures.
5
+ * Assumes valid input - validation happens separately in validateContractLogic().
6
+ *
7
+ * @param models - Models object from contract
8
+ * @param storage - Storage object from contract
9
+ * @param existingMappings - Existing mappings from contract input (optional)
10
+ * @returns Computed mappings dictionary
11
+ */
12
+ declare function computeMappings(models: Record<string, ModelDefinition>, _storage: SqlStorage, existingMappings?: Partial<SqlMappings>): SqlMappings;
13
+ /**
14
+ * Validates that a JSON import conforms to the SqlContract structure
15
+ * and returns a fully typed SqlContract.
16
+ *
17
+ * This function is specifically for validating JSON imports (e.g., from contract.json).
18
+ * Contracts created via the builder API (defineContract) are already valid and should
19
+ * not be passed to this function - use them directly without validation.
20
+ *
21
+ * Performs both structural validation (using Arktype) and logical validation
22
+ * (ensuring all references are valid).
23
+ *
24
+ *
25
+ * The type parameter `TContract` must be a fully-typed contract type (e.g., from `contract.d.ts`),
26
+ * NOT a generic `SqlContract<SqlStorage>`.
27
+ *
28
+ * **Correct:**
29
+ * ```typescript
30
+ * import type { Contract } from './contract.d';
31
+ * const contract = validateContract<Contract>(contractJson);
32
+ * ```
33
+ *
34
+ * **Incorrect:**
35
+ * ```typescript
36
+ * import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
37
+ * const contract = validateContract<SqlContract<SqlStorage>>(contractJson);
38
+ * // ❌ Types will be inferred as 'unknown' - this won't work!
39
+ * ```
40
+ *
41
+ * The type parameter provides the specific table structure, column types, and model definitions.
42
+ * This function validates the runtime structure matches the type, but does not infer types
43
+ * from JSON (as JSON imports lose literal type information).
44
+ *
45
+ * @param value - The contract value to validate (must be from a JSON import, not a builder)
46
+ * @returns A validated contract matching the TContract type
47
+ * @throws Error if the contract structure or logic is invalid
48
+ */
49
+ declare function validateContract<TContract extends SqlContract<SqlStorage>>(value: unknown): TContract;
50
+
51
+ export { computeMappings, validateContract };
@@ -0,0 +1,9 @@
1
+ import {
2
+ computeMappings,
3
+ validateContract
4
+ } from "./chunk-BJOMVCU6.js";
5
+ export {
6
+ computeMappings,
7
+ validateContract
8
+ };
9
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@prisma-next/sql-contract-ts",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
7
+ "dependencies": {
8
+ "arktype": "^2.1.25",
9
+ "ts-toolbelt": "^9.6.0",
10
+ "@prisma-next/contract": "0.0.1",
11
+ "@prisma-next/sql-contract": "0.0.1",
12
+ "@prisma-next/contract-authoring": "0.0.1"
13
+ },
14
+ "devDependencies": {
15
+ "@types/pg": "^8.11.10",
16
+ "pg": "^8.11.5",
17
+ "tsup": "^8.3.0",
18
+ "typescript": "^5.9.3",
19
+ "vitest": "^2.1.1",
20
+ "@prisma-next/test-utils": "0.0.1"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "schemas"
25
+ ],
26
+ "exports": {
27
+ "./contract-builder": {
28
+ "types": "./dist/exports/contract-builder.d.ts",
29
+ "import": "./dist/exports/contract-builder.js"
30
+ },
31
+ "./contract": {
32
+ "types": "./dist/exports/contract.d.ts",
33
+ "import": "./dist/exports/contract.js"
34
+ },
35
+ "./schema-sql": "./schemas/data-contract-sql-v1.json"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup --config tsup.config.ts",
39
+ "test": "vitest run",
40
+ "test:coverage": "vitest run --coverage",
41
+ "typecheck": "tsc --project tsconfig.json --noEmit",
42
+ "lint": "biome check . --config-path ../../../../biome.json --error-on-warnings",
43
+ "clean": "node ../../../../scripts/clean.mjs"
44
+ }
45
+ }
@@ -0,0 +1,386 @@
1
+ {
2
+ "$id": "https://prisma.dev/schemas/data-contract-sql-v1.json",
3
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
4
+ "title": "Prisma Next Data Contract SQL v1",
5
+ "description": "Schema for Prisma Next contract.json files for the SQL family (postgres, mysql, sqlite, etc.)",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "Reference to this JSON schema for IDE validation"
12
+ },
13
+ "schemaVersion": {
14
+ "type": "string",
15
+ "enum": ["1"],
16
+ "description": "Contract schema version"
17
+ },
18
+ "target": {
19
+ "type": "string",
20
+ "description": "Database target identifier (e.g., 'postgres', 'mysql', 'sqlite')"
21
+ },
22
+ "targetFamily": {
23
+ "type": "string",
24
+ "enum": ["sql"],
25
+ "description": "Target family classification"
26
+ },
27
+ "coreHash": {
28
+ "type": "string",
29
+ "pattern": "^sha256:[a-f0-9]{64}$",
30
+ "description": "SHA-256 hash of the core schema structure (models, fields, relations, storage layout)"
31
+ },
32
+ "profileHash": {
33
+ "type": "string",
34
+ "pattern": "^sha256:[a-f0-9]{64}$",
35
+ "description": "SHA-256 hash of the pinned capability profile (capability keys/variants and adapter pins)"
36
+ },
37
+ "capabilities": {
38
+ "type": "object",
39
+ "description": "Capability flags declared by the contract",
40
+ "additionalProperties": {
41
+ "type": "object",
42
+ "additionalProperties": {
43
+ "type": "boolean"
44
+ }
45
+ }
46
+ },
47
+ "extensions": {
48
+ "type": "object",
49
+ "description": "Extension packs and their configuration",
50
+ "additionalProperties": true
51
+ },
52
+ "meta": {
53
+ "type": "object",
54
+ "description": "Non-semantic metadata (excluded from hashing)",
55
+ "additionalProperties": true
56
+ },
57
+ "sources": {
58
+ "type": "object",
59
+ "description": "Read-only sources (views, etc.) available for querying",
60
+ "additionalProperties": {
61
+ "$ref": "#/$defs/Source"
62
+ }
63
+ },
64
+ "models": {
65
+ "type": "object",
66
+ "description": "Model definitions mapping application-level models to storage tables",
67
+ "additionalProperties": {
68
+ "$ref": "#/$defs/Model"
69
+ }
70
+ },
71
+ "storage": {
72
+ "type": "object",
73
+ "description": "Storage layout definition",
74
+ "additionalProperties": false,
75
+ "properties": {
76
+ "tables": {
77
+ "type": "object",
78
+ "description": "Table definitions keyed by table name",
79
+ "additionalProperties": {
80
+ "$ref": "#/$defs/StorageTable"
81
+ }
82
+ }
83
+ },
84
+ "required": ["tables"]
85
+ }
86
+ },
87
+ "required": ["schemaVersion", "target", "targetFamily", "coreHash", "models", "storage"],
88
+ "$defs": {
89
+ "StorageTable": {
90
+ "type": "object",
91
+ "description": "Table definition with columns, constraints, and indexes",
92
+ "additionalProperties": false,
93
+ "properties": {
94
+ "columns": {
95
+ "type": "object",
96
+ "description": "Column definitions keyed by column name",
97
+ "additionalProperties": {
98
+ "$ref": "#/$defs/StorageColumn"
99
+ }
100
+ },
101
+ "primaryKey": {
102
+ "$ref": "#/$defs/PrimaryKey",
103
+ "description": "Primary key constraint definition"
104
+ },
105
+ "uniques": {
106
+ "type": "array",
107
+ "description": "Unique constraint definitions",
108
+ "items": {
109
+ "$ref": "#/$defs/UniqueConstraint"
110
+ }
111
+ },
112
+ "indexes": {
113
+ "type": "array",
114
+ "description": "Index definitions",
115
+ "items": {
116
+ "$ref": "#/$defs/Index"
117
+ }
118
+ },
119
+ "foreignKeys": {
120
+ "type": "array",
121
+ "description": "Foreign key constraint definitions",
122
+ "items": {
123
+ "$ref": "#/$defs/ForeignKey"
124
+ }
125
+ }
126
+ },
127
+ "required": ["columns"]
128
+ },
129
+ "StorageColumn": {
130
+ "type": "object",
131
+ "description": "Column definition with type and nullability",
132
+ "additionalProperties": false,
133
+ "properties": {
134
+ "type": {
135
+ "type": "string",
136
+ "description": "Column type (e.g., 'text', 'int4', 'timestamptz', 'bool')"
137
+ },
138
+ "nullable": {
139
+ "type": "boolean",
140
+ "default": false,
141
+ "description": "Whether the column allows NULL values"
142
+ }
143
+ }
144
+ },
145
+ "PrimaryKey": {
146
+ "type": "object",
147
+ "description": "Primary key constraint",
148
+ "additionalProperties": false,
149
+ "properties": {
150
+ "columns": {
151
+ "type": "array",
152
+ "description": "Column names comprising the primary key",
153
+ "items": {
154
+ "type": "string"
155
+ },
156
+ "minItems": 1
157
+ },
158
+ "name": {
159
+ "type": "string",
160
+ "description": "Constraint name (e.g., 'user_pkey')"
161
+ }
162
+ },
163
+ "required": ["columns"]
164
+ },
165
+ "UniqueConstraint": {
166
+ "type": "object",
167
+ "description": "Unique constraint",
168
+ "additionalProperties": false,
169
+ "properties": {
170
+ "columns": {
171
+ "type": "array",
172
+ "description": "Column names comprising the unique constraint",
173
+ "items": {
174
+ "type": "string"
175
+ },
176
+ "minItems": 1
177
+ },
178
+ "name": {
179
+ "type": "string",
180
+ "description": "Constraint name (e.g., 'user_email_key')"
181
+ }
182
+ },
183
+ "required": ["columns"]
184
+ },
185
+ "Index": {
186
+ "type": "object",
187
+ "description": "Index definition (structure may vary by target)",
188
+ "additionalProperties": false,
189
+ "properties": {
190
+ "columns": {
191
+ "type": "array",
192
+ "description": "Column names indexed",
193
+ "items": {
194
+ "type": "string"
195
+ }
196
+ },
197
+ "name": {
198
+ "type": "string",
199
+ "description": "Index name"
200
+ }
201
+ }
202
+ },
203
+ "ForeignKey": {
204
+ "type": "object",
205
+ "description": "Foreign key constraint definition",
206
+ "additionalProperties": false,
207
+ "properties": {
208
+ "columns": {
209
+ "type": "array",
210
+ "description": "Column names in the referencing table",
211
+ "items": {
212
+ "type": "string"
213
+ }
214
+ },
215
+ "references": {
216
+ "type": "object",
217
+ "description": "Referenced table and columns",
218
+ "additionalProperties": false,
219
+ "properties": {
220
+ "table": {
221
+ "type": "string",
222
+ "description": "Referenced table name"
223
+ },
224
+ "columns": {
225
+ "type": "array",
226
+ "description": "Referenced column names",
227
+ "items": {
228
+ "type": "string"
229
+ }
230
+ }
231
+ },
232
+ "required": ["table", "columns"]
233
+ },
234
+ "name": {
235
+ "type": "string",
236
+ "description": "Foreign key constraint name"
237
+ }
238
+ },
239
+ "required": ["columns", "references"]
240
+ },
241
+ "FieldType": {
242
+ "type": "object",
243
+ "description": "Neutral field type definition for sources and document fields",
244
+ "additionalProperties": false,
245
+ "properties": {
246
+ "type": {
247
+ "type": "string",
248
+ "description": "Field type identifier (e.g., 'int4', 'text', 'bool', 'object', 'array', 'date', 'bytes', 'id')"
249
+ },
250
+ "nullable": {
251
+ "type": "boolean",
252
+ "default": false,
253
+ "description": "Whether the field allows null values"
254
+ },
255
+ "items": {
256
+ "$ref": "#/$defs/FieldType",
257
+ "description": "Item type for array fields"
258
+ },
259
+ "properties": {
260
+ "type": "object",
261
+ "description": "Property definitions for object fields",
262
+ "additionalProperties": {
263
+ "$ref": "#/$defs/FieldType"
264
+ }
265
+ }
266
+ },
267
+ "required": ["type", "nullable"]
268
+ },
269
+ "Source": {
270
+ "type": "object",
271
+ "description": "Read-only source definition for views and other queryable sources",
272
+ "additionalProperties": false,
273
+ "properties": {
274
+ "readOnly": {
275
+ "type": "boolean",
276
+ "description": "Whether mutations (INSERT/UPDATE/DELETE) are disallowed"
277
+ },
278
+ "projection": {
279
+ "type": "object",
280
+ "description": "Field projection mapping column/field name to FieldType",
281
+ "additionalProperties": {
282
+ "$ref": "#/$defs/FieldType"
283
+ }
284
+ },
285
+ "origin": {
286
+ "type": "object",
287
+ "description": "Provenance link to pack-owned block (e.g., view, enum)",
288
+ "additionalProperties": true
289
+ },
290
+ "capabilities": {
291
+ "type": "object",
292
+ "description": "Feature flags relevant to this source",
293
+ "additionalProperties": {
294
+ "type": "boolean"
295
+ }
296
+ }
297
+ },
298
+ "required": ["readOnly", "projection"]
299
+ },
300
+ "Model": {
301
+ "type": "object",
302
+ "description": "Model definition mapping to storage table",
303
+ "additionalProperties": false,
304
+ "properties": {
305
+ "storage": {
306
+ "type": "object",
307
+ "description": "Storage mapping for the model",
308
+ "additionalProperties": false,
309
+ "properties": {
310
+ "table": {
311
+ "type": "string",
312
+ "description": "Table name in storage.tables"
313
+ }
314
+ },
315
+ "required": ["table"]
316
+ },
317
+ "fields": {
318
+ "type": "object",
319
+ "description": "Field definitions mapping model fields to storage columns",
320
+ "additionalProperties": {
321
+ "$ref": "#/$defs/ModelField"
322
+ }
323
+ },
324
+ "relations": {
325
+ "type": "object",
326
+ "description": "Relation definitions for the model",
327
+ "additionalProperties": {
328
+ "$ref": "#/$defs/ModelRelation"
329
+ }
330
+ }
331
+ },
332
+ "required": ["storage", "fields"]
333
+ },
334
+ "ModelField": {
335
+ "type": "object",
336
+ "description": "Model field definition mapping to storage column",
337
+ "additionalProperties": false,
338
+ "properties": {
339
+ "column": {
340
+ "type": "string",
341
+ "description": "Column name in the model's backing table"
342
+ }
343
+ },
344
+ "required": ["column"]
345
+ },
346
+ "ModelRelation": {
347
+ "type": "object",
348
+ "description": "Model relation definition",
349
+ "additionalProperties": false,
350
+ "properties": {
351
+ "to": {
352
+ "type": "string",
353
+ "description": "Target model name"
354
+ },
355
+ "cardinality": {
356
+ "type": "string",
357
+ "enum": ["1:1", "1:N", "N:1", "N:M"],
358
+ "description": "Relation cardinality"
359
+ },
360
+ "on": {
361
+ "type": "object",
362
+ "description": "Relation field mappings",
363
+ "additionalProperties": false,
364
+ "properties": {
365
+ "parentCols": {
366
+ "type": "array",
367
+ "description": "Parent table columns",
368
+ "items": {
369
+ "type": "string"
370
+ }
371
+ },
372
+ "childCols": {
373
+ "type": "array",
374
+ "description": "Child table columns",
375
+ "items": {
376
+ "type": "string"
377
+ }
378
+ }
379
+ },
380
+ "required": ["parentCols", "childCols"]
381
+ }
382
+ },
383
+ "required": ["to", "cardinality", "on"]
384
+ }
385
+ }
386
+ }