@prisma-next/sql-contract 0.5.0-dev.52 → 0.5.0-dev.53

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/dist/validate.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { d as validateStorageSemantics, l as validateSqlContract } from "./validators-BjZ6lOS1.mjs";
1
+ import { d as validateStorageSemantics, l as validateSqlContract } from "./validators-DWoHkCUr.mjs";
2
2
  import { ContractValidationError, validateContract as validateContract$1 } from "@prisma-next/contract/validate-contract";
3
3
 
4
4
  //#region src/validate.ts
@@ -103,6 +103,13 @@ const StorageSchema = type({
103
103
  function isPlainRecord(value) {
104
104
  return typeof value === "object" && value !== null && !Array.isArray(value);
105
105
  }
106
+ function findDuplicateValue(values) {
107
+ const seen = /* @__PURE__ */ new Set();
108
+ for (const value of values) {
109
+ if (seen.has(value)) return value;
110
+ seen.add(value);
111
+ }
112
+ }
106
113
  function isContractFieldType(value) {
107
114
  if (!isPlainRecord(value)) return false;
108
115
  const kind = value["kind"];
@@ -210,6 +217,8 @@ function validateSqlContract(value) {
210
217
  * Currently checks:
211
218
  * - duplicate named primary key / unique / index / foreign key objects within a table
212
219
  * - duplicate unique, index, or foreign key declarations within a table
220
+ * - duplicate columns within primary key / unique / index definitions
221
+ * - nullable columns in primary key definitions
213
222
  * - `setNull` referential action on a non-nullable FK column (would fail at runtime)
214
223
  * - `setDefault` referential action on a non-nullable FK column without a DEFAULT (would fail at runtime)
215
224
  */
@@ -226,8 +235,15 @@ function validateStorageSemantics(storage) {
226
235
  for (const index of table.indexes) registerNamedObject("index", index.name);
227
236
  for (const fk of table.foreignKeys) registerNamedObject("foreign key", fk.name);
228
237
  for (const [name, kinds] of namedObjects) if (kinds.length > 1) errors.push(`Table "${tableName}": named object "${name}" is declared multiple times (${kinds.join(", ")})`);
238
+ if (table.primaryKey) {
239
+ const duplicateColumn = findDuplicateValue(table.primaryKey.columns);
240
+ if (duplicateColumn !== void 0) errors.push(`Table "${tableName}": primary key contains duplicate column "${duplicateColumn}"`);
241
+ for (const columnName of table.primaryKey.columns) if (table.columns[columnName]?.nullable === true) errors.push(`Table "${tableName}": primary key column "${columnName}" is nullable; primary key columns must be NOT NULL`);
242
+ }
229
243
  const seenUniqueDefinitions = /* @__PURE__ */ new Set();
230
244
  for (const unique of table.uniques) {
245
+ const duplicateColumn = findDuplicateValue(unique.columns);
246
+ if (duplicateColumn !== void 0) errors.push(`Table "${tableName}": unique constraint contains duplicate column "${duplicateColumn}"`);
231
247
  const signature = JSON.stringify({ columns: unique.columns });
232
248
  if (seenUniqueDefinitions.has(signature)) {
233
249
  errors.push(`Table "${tableName}": duplicate unique constraint definition on columns [${unique.columns.join(", ")}]`);
@@ -237,6 +253,8 @@ function validateStorageSemantics(storage) {
237
253
  }
238
254
  const seenIndexDefinitions = /* @__PURE__ */ new Set();
239
255
  for (const index of table.indexes) {
256
+ const duplicateColumn = findDuplicateValue(index.columns);
257
+ if (duplicateColumn !== void 0) errors.push(`Table "${tableName}": index contains duplicate column "${duplicateColumn}"`);
240
258
  const signature = JSON.stringify({
241
259
  columns: index.columns,
242
260
  using: index.using ?? null,
@@ -278,4 +296,4 @@ function validateStorageSemantics(storage) {
278
296
 
279
297
  //#endregion
280
298
  export { ForeignKeySchema as a, validateModel as c, validateStorageSemantics as d, ForeignKeyReferencesSchema as i, validateSqlContract as l, ColumnDefaultLiteralSchema as n, IndexSchema as o, ColumnDefaultSchema as r, ReferentialActionSchema as s, ColumnDefaultFunctionSchema as t, validateStorage as u };
281
- //# sourceMappingURL=validators-BjZ6lOS1.mjs.map
299
+ //# sourceMappingURL=validators-DWoHkCUr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators-DWoHkCUr.mjs","names":["errors: string[]"],"sources":["../src/validators.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport { ContractValidationError } from '@prisma-next/contract/validate-contract';\nimport { type } from 'arktype';\nimport type {\n ForeignKey,\n ForeignKeyReferences,\n PrimaryKey,\n ReferentialAction,\n SqlStorage,\n StorageTypeInstance,\n UniqueConstraint,\n} from './types';\n\ntype ColumnDefaultLiteral = {\n readonly kind: 'literal';\n readonly value: string | number | boolean | Record<string, unknown> | unknown[] | null;\n};\ntype ColumnDefaultFunction = { readonly kind: 'function'; readonly expression: string };\nconst literalKindSchema = type(\"'literal'\");\nconst functionKindSchema = type(\"'function'\");\nconst generatorKindSchema = type(\"'generator'\");\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\nexport const ColumnDefaultLiteralSchema = type.declare<ColumnDefaultLiteral>().type({\n kind: literalKindSchema,\n value: 'string | number | boolean | null | unknown[] | Record<string, unknown>',\n});\n\nexport const ColumnDefaultFunctionSchema = type.declare<ColumnDefaultFunction>().type({\n kind: functionKindSchema,\n expression: 'string',\n});\n\nexport const ColumnDefaultSchema = ColumnDefaultLiteralSchema.or(ColumnDefaultFunctionSchema);\n\nconst ExecutionMutationDefaultValueSchema = type({\n '+': 'reject',\n kind: generatorKindSchema,\n id: generatorIdSchema,\n 'params?': 'Record<string, unknown>',\n});\n\nconst ExecutionMutationDefaultSchema = type({\n '+': 'reject',\n ref: {\n '+': 'reject',\n table: 'string',\n column: 'string',\n },\n 'onCreate?': ExecutionMutationDefaultValueSchema,\n 'onUpdate?': ExecutionMutationDefaultValueSchema,\n});\n\nconst ExecutionSchema = type({\n '+': 'reject',\n executionHash: 'string',\n mutations: {\n '+': 'reject',\n defaults: ExecutionMutationDefaultSchema.array().readonly(),\n },\n});\n\nconst StorageColumnSchema = type({\n '+': 'reject',\n nativeType: 'string',\n codecId: 'string',\n nullable: 'boolean',\n 'typeParams?': 'Record<string, unknown>',\n 'typeRef?': 'string',\n 'default?': ColumnDefaultSchema,\n}).narrow((col, ctx) => {\n if (col.typeParams !== undefined && col.typeRef !== undefined) {\n return ctx.mustBe('a column with either typeParams or typeRef, not both');\n }\n return true;\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\nexport const IndexSchema = type({\n columns: type.string.array().readonly(),\n 'name?': 'string',\n 'using?': 'string',\n 'config?': 'Record<string, unknown>',\n});\n\nexport const ForeignKeyReferencesSchema = type.declare<ForeignKeyReferences>().type({\n table: 'string',\n columns: type.string.array().readonly(),\n});\n\nexport const ReferentialActionSchema = type\n .declare<ReferentialAction>()\n .type(\"'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'\");\n\nexport const ForeignKeySchema = type.declare<ForeignKey>().type({\n columns: type.string.array().readonly(),\n references: ForeignKeyReferencesSchema,\n 'name?': 'string',\n 'onDelete?': ReferentialActionSchema,\n 'onUpdate?': ReferentialActionSchema,\n constraint: 'boolean',\n index: 'boolean',\n});\n\nconst StorageTableSchema = type({\n '+': 'reject',\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 '+': 'reject',\n storageHash: 'string',\n tables: type({ '[string]': StorageTableSchema }),\n 'types?': type({ '[string]': StorageTypeInstanceSchema }),\n});\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction findDuplicateValue(values: readonly string[]): string | undefined {\n const seen = new Set<string>();\n for (const value of values) {\n if (seen.has(value)) {\n return value;\n }\n seen.add(value);\n }\n return undefined;\n}\n\nfunction isContractFieldType(value: unknown): boolean {\n if (!isPlainRecord(value)) return false;\n const kind = value['kind'];\n if (kind === 'scalar') {\n if (typeof value['codecId'] !== 'string') return false;\n const typeParams = value['typeParams'];\n if (typeParams !== undefined && !isPlainRecord(typeParams)) return false;\n return true;\n }\n if (kind === 'valueObject') {\n return typeof value['name'] === 'string';\n }\n if (kind === 'union') {\n const members = value['members'];\n if (!Array.isArray(members)) return false;\n return members.every((m) => isContractFieldType(m));\n }\n return false;\n}\n\nconst ContractFieldTypeSchema = type('unknown').narrow((value, ctx) =>\n isContractFieldType(value) ? true : ctx.mustBe('scalar, valueObject, or union field type'),\n);\n\nconst ModelFieldSchema = type({\n '+': 'reject',\n nullable: 'boolean',\n type: ContractFieldTypeSchema,\n 'many?': 'true',\n 'dict?': 'true',\n});\n\nconst ModelStorageFieldSchema = type({\n column: 'string',\n 'codecId?': 'string',\n 'nullable?': 'boolean',\n});\n\nconst ModelStorageSchema = type({\n table: 'string',\n fields: type({ '[string]': ModelStorageFieldSchema }),\n});\n\nconst ModelSchema = type({\n storage: ModelStorageSchema,\n 'fields?': type({ '[string]': ModelFieldSchema }),\n 'relations?': type({ '[string]': 'unknown' }),\n 'discriminator?': 'unknown',\n 'variants?': 'unknown',\n 'base?': 'string',\n 'owner?': 'string',\n});\n\nconst ContractMetaSchema = type({\n '[string]': 'unknown',\n});\n\nconst SqlContractSchema = type({\n '+': 'reject',\n target: 'string',\n targetFamily: \"'sql'\",\n 'coreHash?': 'string',\n profileHash: 'string',\n 'capabilities?': 'Record<string, Record<string, boolean>>',\n 'extensionPacks?': 'Record<string, unknown>',\n 'meta?': ContractMetaSchema,\n 'roots?': 'Record<string, string>',\n models: type({ '[string]': ModelSchema }),\n 'valueObjects?': 'Record<string, unknown>',\n storage: StorageSchema,\n 'execution?': ExecutionSchema,\n});\n\n// NOTE: StorageColumnSchema, StorageTableSchema, and StorageSchema use bare type()\n// instead of type.declare<T>().type() because the ColumnDefault union's value field\n// includes bigint | Date (runtime-only types after decoding) which cannot be expressed\n// in Arktype's JSON validation DSL. The `as SqlStorage` cast in validateStorage() bridges\n// the gap between the JSON-safe Arktype output and the runtime TypeScript type.\n// See decodeContractDefaults() in validate.ts for the decoding step.\n\n/**\n * Validates the structural shape of SqlStorage using Arktype.\n *\n * @param value - The storage value to validate\n * @returns The validated storage if structure is valid\n * @throws Error if the storage structure is invalid\n */\nexport function validateStorage(value: unknown): SqlStorage {\n const result = StorageSchema(value);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Storage validation failed: ${messages}`);\n }\n return result as SqlStorage;\n}\n\nexport function validateModel(value: unknown): unknown {\n const result = ModelSchema(value);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Model validation failed: ${messages}`);\n }\n return result;\n}\n\n/**\n * Validates the structural shape of an SQL contract using Arktype.\n *\n * Ensures all required fields are present and have the correct types,\n * including SQL-specific storage structure (tables, columns, constraints).\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 ContractValidationError if the contract structure is invalid\n */\nexport function validateSqlContract<T extends Contract<SqlStorage>>(value: unknown): T {\n if (typeof value !== 'object' || value === null) {\n throw new ContractValidationError(\n 'Contract structural validation failed: value must be an object',\n 'structural',\n );\n }\n\n const rawValue = value as { targetFamily?: string };\n if (rawValue.targetFamily !== undefined && rawValue.targetFamily !== 'sql') {\n throw new ContractValidationError(\n `Unsupported target family: ${rawValue.targetFamily}`,\n 'structural',\n );\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 ContractValidationError(\n `Contract structural validation failed: ${messages}`,\n 'structural',\n );\n }\n\n // Arktype's inferred output type differs from T due to exactOptionalPropertyTypes\n // and branded hash types — the runtime value is structurally compatible after validation\n return contractResult as unknown as T;\n}\n\n/**\n * Validates semantic constraints on SqlStorage that cannot be expressed in Arktype schemas.\n *\n * Returns an array of human-readable error strings. Empty array = valid.\n *\n * Currently checks:\n * - duplicate named primary key / unique / index / foreign key objects within a table\n * - duplicate unique, index, or foreign key declarations within a table\n * - duplicate columns within primary key / unique / index definitions\n * - nullable columns in primary key definitions\n * - `setNull` referential action on a non-nullable FK column (would fail at runtime)\n * - `setDefault` referential action on a non-nullable FK column without a DEFAULT (would fail at runtime)\n */\nexport function validateStorageSemantics(storage: SqlStorage): string[] {\n const errors: string[] = [];\n\n for (const [tableName, table] of Object.entries(storage.tables)) {\n const namedObjects = new Map<string, string[]>();\n const registerNamedObject = (kind: string, name: string | undefined) => {\n if (!name) return;\n namedObjects.set(name, [...(namedObjects.get(name) ?? []), kind]);\n };\n\n registerNamedObject('primary key', table.primaryKey?.name);\n for (const unique of table.uniques) {\n registerNamedObject('unique constraint', unique.name);\n }\n for (const index of table.indexes) {\n registerNamedObject('index', index.name);\n }\n for (const fk of table.foreignKeys) {\n registerNamedObject('foreign key', fk.name);\n }\n\n for (const [name, kinds] of namedObjects) {\n if (kinds.length > 1) {\n errors.push(\n `Table \"${tableName}\": named object \"${name}\" is declared multiple times (${kinds.join(', ')})`,\n );\n }\n }\n\n if (table.primaryKey) {\n const duplicateColumn = findDuplicateValue(table.primaryKey.columns);\n if (duplicateColumn !== undefined) {\n errors.push(\n `Table \"${tableName}\": primary key contains duplicate column \"${duplicateColumn}\"`,\n );\n }\n\n for (const columnName of table.primaryKey.columns) {\n const column = table.columns[columnName];\n if (column?.nullable === true) {\n errors.push(\n `Table \"${tableName}\": primary key column \"${columnName}\" is nullable; primary key columns must be NOT NULL`,\n );\n }\n }\n }\n\n const seenUniqueDefinitions = new Set<string>();\n for (const unique of table.uniques) {\n const duplicateColumn = findDuplicateValue(unique.columns);\n if (duplicateColumn !== undefined) {\n errors.push(\n `Table \"${tableName}\": unique constraint contains duplicate column \"${duplicateColumn}\"`,\n );\n }\n\n const signature = JSON.stringify({ columns: unique.columns });\n if (seenUniqueDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate unique constraint definition on columns [${unique.columns.join(', ')}]`,\n );\n continue;\n }\n seenUniqueDefinitions.add(signature);\n }\n\n const seenIndexDefinitions = new Set<string>();\n for (const index of table.indexes) {\n const duplicateColumn = findDuplicateValue(index.columns);\n if (duplicateColumn !== undefined) {\n errors.push(`Table \"${tableName}\": index contains duplicate column \"${duplicateColumn}\"`);\n }\n\n const signature = JSON.stringify({\n columns: index.columns,\n using: index.using ?? null,\n config: index.config ?? null,\n });\n if (seenIndexDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate index definition on columns [${index.columns.join(', ')}]`,\n );\n continue;\n }\n seenIndexDefinitions.add(signature);\n }\n\n const seenForeignKeyDefinitions = new Set<string>();\n for (const fk of table.foreignKeys) {\n const signature = JSON.stringify({\n columns: fk.columns,\n references: fk.references,\n onDelete: fk.onDelete ?? null,\n onUpdate: fk.onUpdate ?? null,\n constraint: fk.constraint,\n index: fk.index,\n });\n if (seenForeignKeyDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate foreign key definition on columns [${fk.columns.join(', ')}]`,\n );\n continue;\n }\n seenForeignKeyDefinitions.add(signature);\n }\n\n for (const fk of table.foreignKeys) {\n for (const colName of fk.columns) {\n const column = table.columns[colName];\n if (!column) continue;\n\n if (fk.onDelete === 'setNull' && !column.nullable) {\n errors.push(\n `Table \"${tableName}\": onDelete setNull on foreign key column \"${colName}\" which is NOT NULL`,\n );\n }\n if (fk.onUpdate === 'setNull' && !column.nullable) {\n errors.push(\n `Table \"${tableName}\": onUpdate setNull on foreign key column \"${colName}\" which is NOT NULL`,\n );\n }\n if (fk.onDelete === 'setDefault' && !column.nullable && column.default === undefined) {\n errors.push(\n `Table \"${tableName}\": onDelete setDefault on foreign key column \"${colName}\" which is NOT NULL and has no DEFAULT`,\n );\n }\n if (fk.onUpdate === 'setDefault' && !column.nullable && column.default === undefined) {\n errors.push(\n `Table \"${tableName}\": onUpdate setDefault on foreign key column \"${colName}\" which is NOT NULL and has no DEFAULT`,\n );\n }\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}
@@ -52,6 +52,8 @@ declare function validateSqlContract<T extends Contract<SqlStorage>>(value: unkn
52
52
  * Currently checks:
53
53
  * - duplicate named primary key / unique / index / foreign key objects within a table
54
54
  * - duplicate unique, index, or foreign key declarations within a table
55
+ * - duplicate columns within primary key / unique / index definitions
56
+ * - nullable columns in primary key definitions
55
57
  * - `setNull` referential action on a non-nullable FK column (would fail at runtime)
56
58
  * - `setDefault` referential action on a non-nullable FK column without a DEFAULT (would fail at runtime)
57
59
  */
@@ -1 +1 @@
1
- {"version":3,"file":"validators.d.mts","names":[],"sources":["../src/validators.ts"],"sourcesContent":[],"mappings":";;;;;;KAaK,oBAAA;;8CAEyC;;AAJ7B,KAMZ,qBAAA,GAJoB;EAIpB,SAAA,IAAA,EAAA,UAAqB;EAQb,SAAA,UAAA,EAAA,MAAA;AAKb,CAAA;AAKa,cAVA,0BAUgF,EAVtD,oCAAA,CAAA,UAUsD,CAVtD,oBAUsD,EAAA,CAAA,CAAA,CAAA;AAA7D,cALnB,2BAKmB,EALQ,oCAAA,CAAA,UAKR,CALQ,qBAKR,EAAA,CAAA,CAAA,CAAA;AAAA,cAAnB,mBAAmB,EAAA,oCAAA,CAAA,UAAA,CAAA,oBAAA,GAAA,qBAAA,EAAA,CAAA,CAAA,CAAA;AAAA,cA4DnB,WA5DmB,EAiE9B,oCAAA,CALsB,UA5DQ,CAAA;EAAA,OAAA,EAAA,SAAA,MAAA,EAAA;EA4DnB,IAAA,CAAA,EAAA,MAAA;EAOA,KAAA,CAAA,EAAA,MAAA;EAKA,MAAA,CAAA,EAPX,MAOW,CAAA,MAAA,EAAA,OAE4D,CAAA;AAEzE,CAAA,EAAA,CAAa,CAAA,CAAA;AAqHG,cA9HH,0BA8H8C,EA9HpB,oCAAA,CAAA,UA8HoB,CA9HpB,oBA8HoB,EAAA,CAAA,CAAA,CAAA;AAS3C,cAlIH,uBAkIgB,EAlIO,oCAAA,CAAA,UAkIP,CAlIO,iBAkIP,EAAA,CAAA,CAAA,CAAA;AAmBb,cAjJH,gBAiJsB,EAjJN,oCAAA,CAAA,UAiJM,CAjJN,UAiJM,EAAA,CAAA,CAAA,CAAA;;;;;AA0CnC;;;iBAtEgB,eAAA,kBAAiC;iBASjC,aAAA;;;;;;;;;;;iBAmBA,8BAA8B,SAAS,8BAA8B;;;;;;;;;;;;iBA0CrE,wBAAA,UAAkC"}
1
+ {"version":3,"file":"validators.d.mts","names":[],"sources":["../src/validators.ts"],"sourcesContent":[],"mappings":";;;;;;KAaK,oBAAA;;8CAEyC;;AAJ7B,KAMZ,qBAAA,GAJoB;EAIpB,SAAA,IAAA,EAAA,UAAqB;EAQb,SAAA,UAAA,EAAA,MAAA;AAKb,CAAA;AAKa,cAVA,0BAUgF,EAVtD,oCAAA,CAAA,UAUsD,CAVtD,oBAUsD,EAAA,CAAA,CAAA,CAAA;AAA7D,cALnB,2BAKmB,EALQ,oCAAA,CAAA,UAKR,CALQ,qBAKR,EAAA,CAAA,CAAA,CAAA;AAAA,cAAnB,mBAAmB,EAAA,oCAAA,CAAA,UAAA,CAAA,oBAAA,GAAA,qBAAA,EAAA,CAAA,CAAA,CAAA;AAAA,cA4DnB,WA5DmB,EAiE9B,oCAAA,CALsB,UA5DQ,CAAA;EAAA,OAAA,EAAA,SAAA,MAAA,EAAA;EA4DnB,IAAA,CAAA,EAAA,MAAA;EAOA,KAAA,CAAA,EAAA,MAAA;EAKA,MAAA,CAAA,EAPX,MAOW,CAAA,MAAA,EAAA,OAE4D,CAAA;AAEzE,CAAA,EAAA,CAAa,CAAA,CAAA;AAgIG,cAzIH,0BAyI8C,EAzIpB,oCAAA,CAAA,UAyIoB,CAzIpB,oBAyIoB,EAAA,CAAA,CAAA,CAAA;AAS3C,cA7IH,uBA6IgB,EA7IO,oCAAA,CAAA,UA6IP,CA7IO,iBA6IP,EAAA,CAAA,CAAA,CAAA;AAmBb,cA5JH,gBA4JsB,EA5JN,oCAAA,CAAA,UA4JM,CA5JN,UA4JM,EAAA,CAAA,CAAA,CAAA;;;;;AA4CnC;;;iBAxEgB,eAAA,kBAAiC;iBASjC,aAAA;;;;;;;;;;;iBAmBA,8BAA8B,SAAS,8BAA8B;;;;;;;;;;;;;;iBA4CrE,wBAAA,UAAkC"}
@@ -1,3 +1,3 @@
1
- import { a as ForeignKeySchema, c as validateModel, d as validateStorageSemantics, i as ForeignKeyReferencesSchema, l as validateSqlContract, n as ColumnDefaultLiteralSchema, o as IndexSchema, r as ColumnDefaultSchema, s as ReferentialActionSchema, t as ColumnDefaultFunctionSchema, u as validateStorage } from "./validators-BjZ6lOS1.mjs";
1
+ import { a as ForeignKeySchema, c as validateModel, d as validateStorageSemantics, i as ForeignKeyReferencesSchema, l as validateSqlContract, n as ColumnDefaultLiteralSchema, o as IndexSchema, r as ColumnDefaultSchema, s as ReferentialActionSchema, t as ColumnDefaultFunctionSchema, u as validateStorage } from "./validators-DWoHkCUr.mjs";
2
2
 
3
3
  export { ColumnDefaultFunctionSchema, ColumnDefaultLiteralSchema, ColumnDefaultSchema, ForeignKeyReferencesSchema, ForeignKeySchema, IndexSchema, ReferentialActionSchema, validateModel, validateSqlContract, validateStorage, validateStorageSemantics };
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract",
3
- "version": "0.5.0-dev.52",
3
+ "version": "0.5.0-dev.53",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "SQL contract types, validators, and IR factories for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.25",
9
- "@prisma-next/contract": "0.5.0-dev.52",
10
- "@prisma-next/framework-components": "0.5.0-dev.52"
9
+ "@prisma-next/framework-components": "0.5.0-dev.53",
10
+ "@prisma-next/contract": "0.5.0-dev.53"
11
11
  },
12
12
  "devDependencies": {
13
13
  "tsdown": "0.18.4",
14
14
  "typescript": "5.9.3",
15
15
  "vitest": "4.0.17",
16
16
  "@prisma-next/test-utils": "0.0.1",
17
- "@prisma-next/tsdown": "0.0.0",
18
- "@prisma-next/tsconfig": "0.0.0"
17
+ "@prisma-next/tsconfig": "0.0.0",
18
+ "@prisma-next/tsdown": "0.0.0"
19
19
  },
20
20
  "files": [
21
21
  "dist",
package/src/validators.ts CHANGED
@@ -139,6 +139,17 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
139
139
  return typeof value === 'object' && value !== null && !Array.isArray(value);
140
140
  }
141
141
 
142
+ function findDuplicateValue(values: readonly string[]): string | undefined {
143
+ const seen = new Set<string>();
144
+ for (const value of values) {
145
+ if (seen.has(value)) {
146
+ return value;
147
+ }
148
+ seen.add(value);
149
+ }
150
+ return undefined;
151
+ }
152
+
142
153
  function isContractFieldType(value: unknown): boolean {
143
154
  if (!isPlainRecord(value)) return false;
144
155
  const kind = value['kind'];
@@ -293,6 +304,8 @@ export function validateSqlContract<T extends Contract<SqlStorage>>(value: unkno
293
304
  * Currently checks:
294
305
  * - duplicate named primary key / unique / index / foreign key objects within a table
295
306
  * - duplicate unique, index, or foreign key declarations within a table
307
+ * - duplicate columns within primary key / unique / index definitions
308
+ * - nullable columns in primary key definitions
296
309
  * - `setNull` referential action on a non-nullable FK column (would fail at runtime)
297
310
  * - `setDefault` referential action on a non-nullable FK column without a DEFAULT (would fail at runtime)
298
311
  */
@@ -325,8 +338,33 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
325
338
  }
326
339
  }
327
340
 
341
+ if (table.primaryKey) {
342
+ const duplicateColumn = findDuplicateValue(table.primaryKey.columns);
343
+ if (duplicateColumn !== undefined) {
344
+ errors.push(
345
+ `Table "${tableName}": primary key contains duplicate column "${duplicateColumn}"`,
346
+ );
347
+ }
348
+
349
+ for (const columnName of table.primaryKey.columns) {
350
+ const column = table.columns[columnName];
351
+ if (column?.nullable === true) {
352
+ errors.push(
353
+ `Table "${tableName}": primary key column "${columnName}" is nullable; primary key columns must be NOT NULL`,
354
+ );
355
+ }
356
+ }
357
+ }
358
+
328
359
  const seenUniqueDefinitions = new Set<string>();
329
360
  for (const unique of table.uniques) {
361
+ const duplicateColumn = findDuplicateValue(unique.columns);
362
+ if (duplicateColumn !== undefined) {
363
+ errors.push(
364
+ `Table "${tableName}": unique constraint contains duplicate column "${duplicateColumn}"`,
365
+ );
366
+ }
367
+
330
368
  const signature = JSON.stringify({ columns: unique.columns });
331
369
  if (seenUniqueDefinitions.has(signature)) {
332
370
  errors.push(
@@ -339,6 +377,11 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
339
377
 
340
378
  const seenIndexDefinitions = new Set<string>();
341
379
  for (const index of table.indexes) {
380
+ const duplicateColumn = findDuplicateValue(index.columns);
381
+ if (duplicateColumn !== undefined) {
382
+ errors.push(`Table "${tableName}": index contains duplicate column "${duplicateColumn}"`);
383
+ }
384
+
342
385
  const signature = JSON.stringify({
343
386
  columns: index.columns,
344
387
  using: index.using ?? null,
@@ -1 +0,0 @@
1
- {"version":3,"file":"validators-BjZ6lOS1.mjs","names":["errors: string[]"],"sources":["../src/validators.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport { ContractValidationError } from '@prisma-next/contract/validate-contract';\nimport { type } from 'arktype';\nimport type {\n ForeignKey,\n ForeignKeyReferences,\n PrimaryKey,\n ReferentialAction,\n SqlStorage,\n StorageTypeInstance,\n UniqueConstraint,\n} from './types';\n\ntype ColumnDefaultLiteral = {\n readonly kind: 'literal';\n readonly value: string | number | boolean | Record<string, unknown> | unknown[] | null;\n};\ntype ColumnDefaultFunction = { readonly kind: 'function'; readonly expression: string };\nconst literalKindSchema = type(\"'literal'\");\nconst functionKindSchema = type(\"'function'\");\nconst generatorKindSchema = type(\"'generator'\");\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\nexport const ColumnDefaultLiteralSchema = type.declare<ColumnDefaultLiteral>().type({\n kind: literalKindSchema,\n value: 'string | number | boolean | null | unknown[] | Record<string, unknown>',\n});\n\nexport const ColumnDefaultFunctionSchema = type.declare<ColumnDefaultFunction>().type({\n kind: functionKindSchema,\n expression: 'string',\n});\n\nexport const ColumnDefaultSchema = ColumnDefaultLiteralSchema.or(ColumnDefaultFunctionSchema);\n\nconst ExecutionMutationDefaultValueSchema = type({\n '+': 'reject',\n kind: generatorKindSchema,\n id: generatorIdSchema,\n 'params?': 'Record<string, unknown>',\n});\n\nconst ExecutionMutationDefaultSchema = type({\n '+': 'reject',\n ref: {\n '+': 'reject',\n table: 'string',\n column: 'string',\n },\n 'onCreate?': ExecutionMutationDefaultValueSchema,\n 'onUpdate?': ExecutionMutationDefaultValueSchema,\n});\n\nconst ExecutionSchema = type({\n '+': 'reject',\n executionHash: 'string',\n mutations: {\n '+': 'reject',\n defaults: ExecutionMutationDefaultSchema.array().readonly(),\n },\n});\n\nconst StorageColumnSchema = type({\n '+': 'reject',\n nativeType: 'string',\n codecId: 'string',\n nullable: 'boolean',\n 'typeParams?': 'Record<string, unknown>',\n 'typeRef?': 'string',\n 'default?': ColumnDefaultSchema,\n}).narrow((col, ctx) => {\n if (col.typeParams !== undefined && col.typeRef !== undefined) {\n return ctx.mustBe('a column with either typeParams or typeRef, not both');\n }\n return true;\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\nexport const IndexSchema = type({\n columns: type.string.array().readonly(),\n 'name?': 'string',\n 'using?': 'string',\n 'config?': 'Record<string, unknown>',\n});\n\nexport const ForeignKeyReferencesSchema = type.declare<ForeignKeyReferences>().type({\n table: 'string',\n columns: type.string.array().readonly(),\n});\n\nexport const ReferentialActionSchema = type\n .declare<ReferentialAction>()\n .type(\"'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'\");\n\nexport const ForeignKeySchema = type.declare<ForeignKey>().type({\n columns: type.string.array().readonly(),\n references: ForeignKeyReferencesSchema,\n 'name?': 'string',\n 'onDelete?': ReferentialActionSchema,\n 'onUpdate?': ReferentialActionSchema,\n constraint: 'boolean',\n index: 'boolean',\n});\n\nconst StorageTableSchema = type({\n '+': 'reject',\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 '+': 'reject',\n storageHash: 'string',\n tables: type({ '[string]': StorageTableSchema }),\n 'types?': type({ '[string]': StorageTypeInstanceSchema }),\n});\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isContractFieldType(value: unknown): boolean {\n if (!isPlainRecord(value)) return false;\n const kind = value['kind'];\n if (kind === 'scalar') {\n if (typeof value['codecId'] !== 'string') return false;\n const typeParams = value['typeParams'];\n if (typeParams !== undefined && !isPlainRecord(typeParams)) return false;\n return true;\n }\n if (kind === 'valueObject') {\n return typeof value['name'] === 'string';\n }\n if (kind === 'union') {\n const members = value['members'];\n if (!Array.isArray(members)) return false;\n return members.every((m) => isContractFieldType(m));\n }\n return false;\n}\n\nconst ContractFieldTypeSchema = type('unknown').narrow((value, ctx) =>\n isContractFieldType(value) ? true : ctx.mustBe('scalar, valueObject, or union field type'),\n);\n\nconst ModelFieldSchema = type({\n '+': 'reject',\n nullable: 'boolean',\n type: ContractFieldTypeSchema,\n 'many?': 'true',\n 'dict?': 'true',\n});\n\nconst ModelStorageFieldSchema = type({\n column: 'string',\n 'codecId?': 'string',\n 'nullable?': 'boolean',\n});\n\nconst ModelStorageSchema = type({\n table: 'string',\n fields: type({ '[string]': ModelStorageFieldSchema }),\n});\n\nconst ModelSchema = type({\n storage: ModelStorageSchema,\n 'fields?': type({ '[string]': ModelFieldSchema }),\n 'relations?': type({ '[string]': 'unknown' }),\n 'discriminator?': 'unknown',\n 'variants?': 'unknown',\n 'base?': 'string',\n 'owner?': 'string',\n});\n\nconst ContractMetaSchema = type({\n '[string]': 'unknown',\n});\n\nconst SqlContractSchema = type({\n '+': 'reject',\n target: 'string',\n targetFamily: \"'sql'\",\n 'coreHash?': 'string',\n profileHash: 'string',\n 'capabilities?': 'Record<string, Record<string, boolean>>',\n 'extensionPacks?': 'Record<string, unknown>',\n 'meta?': ContractMetaSchema,\n 'roots?': 'Record<string, string>',\n models: type({ '[string]': ModelSchema }),\n 'valueObjects?': 'Record<string, unknown>',\n storage: StorageSchema,\n 'execution?': ExecutionSchema,\n});\n\n// NOTE: StorageColumnSchema, StorageTableSchema, and StorageSchema use bare type()\n// instead of type.declare<T>().type() because the ColumnDefault union's value field\n// includes bigint | Date (runtime-only types after decoding) which cannot be expressed\n// in Arktype's JSON validation DSL. The `as SqlStorage` cast in validateStorage() bridges\n// the gap between the JSON-safe Arktype output and the runtime TypeScript type.\n// See decodeContractDefaults() in validate.ts for the decoding step.\n\n/**\n * Validates the structural shape of SqlStorage using Arktype.\n *\n * @param value - The storage value to validate\n * @returns The validated storage if structure is valid\n * @throws Error if the storage structure is invalid\n */\nexport function validateStorage(value: unknown): SqlStorage {\n const result = StorageSchema(value);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Storage validation failed: ${messages}`);\n }\n return result as SqlStorage;\n}\n\nexport function validateModel(value: unknown): unknown {\n const result = ModelSchema(value);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Model validation failed: ${messages}`);\n }\n return result;\n}\n\n/**\n * Validates the structural shape of an SQL contract using Arktype.\n *\n * Ensures all required fields are present and have the correct types,\n * including SQL-specific storage structure (tables, columns, constraints).\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 ContractValidationError if the contract structure is invalid\n */\nexport function validateSqlContract<T extends Contract<SqlStorage>>(value: unknown): T {\n if (typeof value !== 'object' || value === null) {\n throw new ContractValidationError(\n 'Contract structural validation failed: value must be an object',\n 'structural',\n );\n }\n\n const rawValue = value as { targetFamily?: string };\n if (rawValue.targetFamily !== undefined && rawValue.targetFamily !== 'sql') {\n throw new ContractValidationError(\n `Unsupported target family: ${rawValue.targetFamily}`,\n 'structural',\n );\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 ContractValidationError(\n `Contract structural validation failed: ${messages}`,\n 'structural',\n );\n }\n\n // Arktype's inferred output type differs from T due to exactOptionalPropertyTypes\n // and branded hash types — the runtime value is structurally compatible after validation\n return contractResult as unknown as T;\n}\n\n/**\n * Validates semantic constraints on SqlStorage that cannot be expressed in Arktype schemas.\n *\n * Returns an array of human-readable error strings. Empty array = valid.\n *\n * Currently checks:\n * - duplicate named primary key / unique / index / foreign key objects within a table\n * - duplicate unique, index, or foreign key declarations within a table\n * - `setNull` referential action on a non-nullable FK column (would fail at runtime)\n * - `setDefault` referential action on a non-nullable FK column without a DEFAULT (would fail at runtime)\n */\nexport function validateStorageSemantics(storage: SqlStorage): string[] {\n const errors: string[] = [];\n\n for (const [tableName, table] of Object.entries(storage.tables)) {\n const namedObjects = new Map<string, string[]>();\n const registerNamedObject = (kind: string, name: string | undefined) => {\n if (!name) return;\n namedObjects.set(name, [...(namedObjects.get(name) ?? []), kind]);\n };\n\n registerNamedObject('primary key', table.primaryKey?.name);\n for (const unique of table.uniques) {\n registerNamedObject('unique constraint', unique.name);\n }\n for (const index of table.indexes) {\n registerNamedObject('index', index.name);\n }\n for (const fk of table.foreignKeys) {\n registerNamedObject('foreign key', fk.name);\n }\n\n for (const [name, kinds] of namedObjects) {\n if (kinds.length > 1) {\n errors.push(\n `Table \"${tableName}\": named object \"${name}\" is declared multiple times (${kinds.join(', ')})`,\n );\n }\n }\n\n const seenUniqueDefinitions = new Set<string>();\n for (const unique of table.uniques) {\n const signature = JSON.stringify({ columns: unique.columns });\n if (seenUniqueDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate unique constraint definition on columns [${unique.columns.join(', ')}]`,\n );\n continue;\n }\n seenUniqueDefinitions.add(signature);\n }\n\n const seenIndexDefinitions = new Set<string>();\n for (const index of table.indexes) {\n const signature = JSON.stringify({\n columns: index.columns,\n using: index.using ?? null,\n config: index.config ?? null,\n });\n if (seenIndexDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate index definition on columns [${index.columns.join(', ')}]`,\n );\n continue;\n }\n seenIndexDefinitions.add(signature);\n }\n\n const seenForeignKeyDefinitions = new Set<string>();\n for (const fk of table.foreignKeys) {\n const signature = JSON.stringify({\n columns: fk.columns,\n references: fk.references,\n onDelete: fk.onDelete ?? null,\n onUpdate: fk.onUpdate ?? null,\n constraint: fk.constraint,\n index: fk.index,\n });\n if (seenForeignKeyDefinitions.has(signature)) {\n errors.push(\n `Table \"${tableName}\": duplicate foreign key definition on columns [${fk.columns.join(', ')}]`,\n );\n continue;\n }\n seenForeignKeyDefinitions.add(signature);\n }\n\n for (const fk of table.foreignKeys) {\n for (const colName of fk.columns) {\n const column = table.columns[colName];\n if (!column) continue;\n\n if (fk.onDelete === 'setNull' && !column.nullable) {\n errors.push(\n `Table \"${tableName}\": onDelete setNull on foreign key column \"${colName}\" which is NOT NULL`,\n );\n }\n if (fk.onUpdate === 'setNull' && !column.nullable) {\n errors.push(\n `Table \"${tableName}\": onUpdate setNull on foreign key column \"${colName}\" which is NOT NULL`,\n );\n }\n if (fk.onDelete === 'setDefault' && !column.nullable && column.default === undefined) {\n errors.push(\n `Table \"${tableName}\": onDelete setDefault on foreign key column \"${colName}\" which is NOT NULL and has no DEFAULT`,\n );\n }\n if (fk.onUpdate === 'setDefault' && !column.nullable && column.default === undefined) {\n errors.push(\n `Table \"${tableName}\": onUpdate setDefault on foreign key column \"${colName}\" which is NOT NULL and has no DEFAULT`,\n );\n }\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}