@prisma-next/contract 0.3.0-dev.143 → 0.3.0-dev.144

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.
Files changed (40) hide show
  1. package/dist/{types-D6WADlh6.d.mts → contract-types-Cd9ic3zL.d.mts} +46 -46
  2. package/dist/contract-types-Cd9ic3zL.d.mts.map +1 -0
  3. package/dist/{hashing-vJBMVN8S.mjs → hashing-BHjb-IPM.mjs} +36 -59
  4. package/dist/hashing-BHjb-IPM.mjs.map +1 -0
  5. package/dist/hashing.d.mts +8 -19
  6. package/dist/hashing.d.mts.map +1 -1
  7. package/dist/hashing.mjs +1 -1
  8. package/dist/testing.d.mts +1 -2
  9. package/dist/testing.d.mts.map +1 -1
  10. package/dist/testing.mjs +2 -2
  11. package/dist/{types-DYikGC04.mjs → types-BP4dqfP-.mjs} +2 -8
  12. package/dist/types-BP4dqfP-.mjs.map +1 -0
  13. package/dist/types.d.mts +2 -3
  14. package/dist/types.mjs +2 -2
  15. package/dist/validate-contract.d.mts +10 -8
  16. package/dist/validate-contract.d.mts.map +1 -1
  17. package/dist/validate-contract.mjs +2 -60
  18. package/dist/{validate-domain-CTQiBiei.mjs → validate-domain-BlwWyv01.mjs} +70 -17
  19. package/dist/validate-domain-BlwWyv01.mjs.map +1 -0
  20. package/dist/validate-domain.d.mts +2 -5
  21. package/dist/validate-domain.d.mts.map +1 -1
  22. package/dist/validate-domain.mjs +1 -1
  23. package/package.json +4 -4
  24. package/src/canonicalization.ts +24 -49
  25. package/src/domain-types.ts +0 -17
  26. package/src/exports/hashing.ts +1 -5
  27. package/src/exports/types.ts +0 -12
  28. package/src/exports/validate-contract.ts +2 -1
  29. package/src/exports/validate-domain.ts +0 -1
  30. package/src/hashing.ts +21 -37
  31. package/src/types.ts +0 -50
  32. package/src/validate-contract.ts +25 -19
  33. package/src/validate-domain.ts +7 -47
  34. package/dist/contract-types-d-PtSVGD.d.mts +0 -49
  35. package/dist/contract-types-d-PtSVGD.d.mts.map +0 -1
  36. package/dist/hashing-vJBMVN8S.mjs.map +0 -1
  37. package/dist/types-D6WADlh6.d.mts.map +0 -1
  38. package/dist/types-DYikGC04.mjs.map +0 -1
  39. package/dist/validate-contract.mjs.map +0 -1
  40. package/dist/validate-domain-CTQiBiei.mjs.map +0 -1
@@ -1,16 +1,80 @@
1
+ import { type } from "arktype";
2
+
3
+ //#region src/validate-contract.ts
4
+ var ContractValidationError = class extends Error {
5
+ code = "CONTRACT.VALIDATION_FAILED";
6
+ phase;
7
+ constructor(message, phase) {
8
+ super(message);
9
+ this.name = "ContractValidationError";
10
+ this.phase = phase;
11
+ }
12
+ };
13
+ const ContractSchema = type({
14
+ target: "string",
15
+ targetFamily: "string",
16
+ roots: "Record<string, string>",
17
+ models: "Record<string, unknown>",
18
+ storage: "Record<string, unknown>",
19
+ capabilities: "Record<string, Record<string, boolean>>",
20
+ extensionPacks: "Record<string, unknown>",
21
+ meta: "Record<string, unknown>",
22
+ "execution?": {
23
+ "executionHash?": "string",
24
+ mutations: { defaults: "unknown[]" }
25
+ },
26
+ profileHash: "string"
27
+ });
28
+ function stripPersistenceFields(raw) {
29
+ const { schemaVersion: _, _generated: _g, ...rest } = raw;
30
+ return rest;
31
+ }
32
+ function extractDomainShape(contract) {
33
+ return {
34
+ roots: contract.roots,
35
+ models: contract.models
36
+ };
37
+ }
38
+ /**
39
+ * Framework-level contract validation (ADR 182).
40
+ *
41
+ * Three-pass validation:
42
+ * 1. **Structural validation** (arktype): verifies required fields exist with
43
+ * correct base types.
44
+ * 2. **Domain validation** (framework-owned): roots, relation targets,
45
+ * variant/base consistency, discriminators, ownership, orphans.
46
+ * 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
47
+ * Mongo validates collections/embedding.
48
+ *
49
+ * JSON persistence fields (`schemaVersion`, `_generated`) are stripped before
50
+ * validation — they are not part of the in-memory contract representation.
51
+ *
52
+ * @template TContract The fully-typed contract type (preserves literal types).
53
+ * @param value Raw contract value (e.g. parsed from JSON).
54
+ * @param storageValidator Family-specific storage validation function.
55
+ * @returns The validated contract with full literal types.
56
+ */
57
+ function validateContract(value, storageValidator) {
58
+ if (typeof value !== "object" || value === null) throw new ContractValidationError("Contract must be a non-null object", "structural");
59
+ const parsed = ContractSchema(stripPersistenceFields(value));
60
+ if (parsed instanceof type.errors) throw new ContractValidationError(`Invalid contract structure: ${parsed.summary}`, "structural");
61
+ const contract = parsed;
62
+ validateContractDomain(extractDomainShape(contract));
63
+ storageValidator(contract);
64
+ return contract;
65
+ }
66
+
67
+ //#endregion
1
68
  //#region src/validate-domain.ts
2
69
  function validateContractDomain(contract) {
3
70
  const errors = [];
4
- const warnings = [];
5
71
  const modelNames = new Set(Object.keys(contract.models));
6
72
  validateRoots(contract, modelNames, errors);
7
73
  validateVariantsAndBases(contract, modelNames, errors);
8
74
  validateRelationTargets(contract, modelNames, errors);
9
75
  validateDiscriminators(contract, errors);
10
76
  validateOwnership(contract, modelNames, errors);
11
- detectOrphanedModels(contract, modelNames, warnings);
12
- if (errors.length > 0) throw new Error(`Contract domain validation failed:\n- ${errors.join("\n- ")}`);
13
- return { warnings };
77
+ if (errors.length > 0) throw new ContractValidationError(`Contract domain validation failed:\n- ${errors.join("\n- ")}`, "domain");
14
78
  }
15
79
  function validateRoots(contract, modelNames, errors) {
16
80
  const seenValues = /* @__PURE__ */ new Set();
@@ -67,18 +131,7 @@ function validateOwnership(contract, modelNames, errors) {
67
131
  for (const [rootKey, rootModel] of Object.entries(contract.roots)) if (rootModel === modelName) errors.push(`Owned model "${modelName}" must not appear in roots (found as root "${rootKey}")`);
68
132
  }
69
133
  }
70
- function detectOrphanedModels(contract, modelNames, warnings) {
71
- const referenced = /* @__PURE__ */ new Set();
72
- for (const modelName of Object.values(contract.roots)) referenced.add(modelName);
73
- for (const [modelName, model] of Object.entries(contract.models)) {
74
- for (const relation of Object.values(model.relations ?? {})) referenced.add(relation.to);
75
- if (model.variants) for (const variantName of Object.keys(model.variants)) referenced.add(variantName);
76
- if (model.base) referenced.add(model.base);
77
- if (model.owner) referenced.add(modelName);
78
- }
79
- for (const modelName of modelNames) if (!referenced.has(modelName)) warnings.push(`Orphaned model: "${modelName}" is not referenced by any root, relation, or variant`);
80
- }
81
134
 
82
135
  //#endregion
83
- export { validateContractDomain as t };
84
- //# sourceMappingURL=validate-domain-CTQiBiei.mjs.map
136
+ export { ContractValidationError as n, validateContract as r, validateContractDomain as t };
137
+ //# sourceMappingURL=validate-domain-BlwWyv01.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-domain-BlwWyv01.mjs","names":["errors: string[]"],"sources":["../src/validate-contract.ts","../src/validate-domain.ts"],"sourcesContent":["import { type } from 'arktype';\nimport type { Contract } from './contract-types';\nimport type { DomainContractShape } from './validate-domain';\nimport { validateContractDomain } from './validate-domain';\n\nexport type ContractValidationPhase = 'structural' | 'domain' | 'storage';\n\nexport class ContractValidationError extends Error {\n readonly code = 'CONTRACT.VALIDATION_FAILED';\n readonly phase: ContractValidationPhase;\n\n constructor(message: string, phase: ContractValidationPhase) {\n super(message);\n this.name = 'ContractValidationError';\n this.phase = phase;\n }\n}\n\n/**\n * Family-provided storage validator.\n * SQL validates tables/columns/FKs; Mongo validates collections/embedding.\n */\nexport type StorageValidator = (contract: Contract) => void;\n\nconst ContractSchema = type({\n target: 'string',\n targetFamily: 'string',\n roots: 'Record<string, string>',\n models: 'Record<string, unknown>',\n storage: 'Record<string, unknown>',\n capabilities: 'Record<string, Record<string, boolean>>',\n extensionPacks: 'Record<string, unknown>',\n meta: 'Record<string, unknown>',\n 'execution?': {\n 'executionHash?': 'string',\n mutations: {\n defaults: 'unknown[]',\n },\n },\n profileHash: 'string',\n});\n\nfunction stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {\n const { schemaVersion: _, _generated: _g, ...rest } = raw;\n return rest;\n}\n\nfunction extractDomainShape(contract: Contract): DomainContractShape {\n return {\n roots: contract.roots,\n models: contract.models,\n };\n}\n\n/**\n * Framework-level contract validation (ADR 182).\n *\n * Three-pass validation:\n * 1. **Structural validation** (arktype): verifies required fields exist with\n * correct base types.\n * 2. **Domain validation** (framework-owned): roots, relation targets,\n * variant/base consistency, discriminators, ownership, orphans.\n * 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;\n * Mongo validates collections/embedding.\n *\n * JSON persistence fields (`schemaVersion`, `_generated`) are stripped before\n * validation — they are not part of the in-memory contract representation.\n *\n * @template TContract The fully-typed contract type (preserves literal types).\n * @param value Raw contract value (e.g. parsed from JSON).\n * @param storageValidator Family-specific storage validation function.\n * @returns The validated contract with full literal types.\n */\nexport function validateContract<TContract extends Contract>(\n value: unknown,\n storageValidator: StorageValidator,\n): TContract {\n if (typeof value !== 'object' || value === null) {\n throw new ContractValidationError('Contract must be a non-null object', 'structural');\n }\n\n const stripped = stripPersistenceFields(value as Record<string, unknown>);\n\n const parsed = ContractSchema(stripped);\n if (parsed instanceof type.errors) {\n throw new ContractValidationError(\n `Invalid contract structure: ${parsed.summary}`,\n 'structural',\n );\n }\n\n const contract = parsed as unknown as Contract;\n\n validateContractDomain(extractDomainShape(contract));\n\n storageValidator(contract);\n\n return contract as unknown as TContract;\n}\n","import { ContractValidationError } from './validate-contract';\n\nexport interface DomainModelShape {\n readonly fields: Record<string, unknown>;\n readonly relations?: Record<string, { readonly to: string }>;\n readonly discriminator?: { readonly field: string };\n readonly variants?: Record<string, unknown>;\n readonly base?: string;\n readonly owner?: string;\n}\n\nexport interface DomainContractShape {\n readonly roots: Record<string, string>;\n readonly models: Record<string, DomainModelShape>;\n}\n\nexport function validateContractDomain(contract: DomainContractShape): void {\n const errors: string[] = [];\n const modelNames = new Set(Object.keys(contract.models));\n\n validateRoots(contract, modelNames, errors);\n validateVariantsAndBases(contract, modelNames, errors);\n validateRelationTargets(contract, modelNames, errors);\n validateDiscriminators(contract, errors);\n validateOwnership(contract, modelNames, errors);\n\n if (errors.length > 0) {\n throw new ContractValidationError(\n `Contract domain validation failed:\\n- ${errors.join('\\n- ')}`,\n 'domain',\n );\n }\n}\n\nfunction validateRoots(\n contract: DomainContractShape,\n modelNames: Set<string>,\n errors: string[],\n): void {\n const seenValues = new Set<string>();\n for (const [rootKey, modelName] of Object.entries(contract.roots)) {\n if (seenValues.has(modelName)) {\n errors.push(`Duplicate root value: \"${modelName}\" is mapped by multiple root keys`);\n }\n seenValues.add(modelName);\n\n if (!modelNames.has(modelName)) {\n errors.push(\n `Root \"${rootKey}\" references model \"${modelName}\" which does not exist in models`,\n );\n }\n }\n}\n\nfunction validateVariantsAndBases(\n contract: DomainContractShape,\n modelNames: Set<string>,\n errors: string[],\n): void {\n const models = new Map(Object.entries(contract.models));\n\n for (const [modelName, model] of models) {\n if (model.variants) {\n for (const variantName of Object.keys(model.variants)) {\n if (!modelNames.has(variantName)) {\n errors.push(\n `Model \"${modelName}\" lists variant \"${variantName}\" which does not exist in models`,\n );\n continue;\n }\n const variantModel = models.get(variantName);\n if (!variantModel) continue;\n if (variantModel.base !== modelName) {\n errors.push(\n `Variant \"${variantName}\" has base \"${variantModel.base ?? '(none)'}\" but expected \"${modelName}\"`,\n );\n }\n }\n }\n\n if (model.base) {\n if (!modelNames.has(model.base)) {\n errors.push(`Model \"${modelName}\" has base \"${model.base}\" which does not exist in models`);\n continue;\n }\n const baseModel = models.get(model.base);\n if (!baseModel) continue;\n if (!baseModel.variants || !Object.hasOwn(baseModel.variants, modelName)) {\n errors.push(\n `Model \"${modelName}\" has base \"${model.base}\" which does not list it as a variant`,\n );\n }\n }\n }\n}\n\nfunction validateRelationTargets(\n contract: DomainContractShape,\n modelNames: Set<string>,\n errors: string[],\n): void {\n for (const [modelName, model] of Object.entries(contract.models)) {\n for (const [relName, relation] of Object.entries(model.relations ?? {})) {\n if (!modelNames.has(relation.to)) {\n errors.push(\n `Relation \"${relName}\" on model \"${modelName}\" targets \"${relation.to}\" which does not exist in models`,\n );\n }\n }\n }\n}\n\nfunction validateDiscriminators(contract: DomainContractShape, errors: string[]): void {\n for (const [modelName, model] of Object.entries(contract.models)) {\n if (model.discriminator) {\n if (!model.variants || Object.keys(model.variants).length === 0) {\n errors.push(`Model \"${modelName}\" has discriminator but no variants`);\n }\n if (!Object.hasOwn(model.fields, model.discriminator.field)) {\n errors.push(\n `Discriminator field \"${model.discriminator.field}\" is not a field on model \"${modelName}\"`,\n );\n }\n }\n\n if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) {\n errors.push(`Model \"${modelName}\" has variants but no discriminator`);\n }\n\n if (model.base) {\n if (model.discriminator) {\n errors.push(`Model \"${modelName}\" has base and must not have discriminator`);\n }\n if (model.variants && Object.keys(model.variants).length > 0) {\n errors.push(`Model \"${modelName}\" has base and must not have variants`);\n }\n }\n }\n}\n\nfunction validateOwnership(\n contract: DomainContractShape,\n modelNames: Set<string>,\n errors: string[],\n): void {\n for (const [modelName, model] of Object.entries(contract.models)) {\n if (!model.owner) continue;\n\n if (model.owner === modelName) {\n errors.push(`Model \"${modelName}\" cannot own itself`);\n }\n\n if (!modelNames.has(model.owner)) {\n errors.push(`Model \"${modelName}\" has owner \"${model.owner}\" which does not exist in models`);\n }\n\n for (const [rootKey, rootModel] of Object.entries(contract.roots)) {\n if (rootModel === modelName) {\n errors.push(\n `Owned model \"${modelName}\" must not appear in roots (found as root \"${rootKey}\")`,\n );\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -15,10 +15,7 @@ interface DomainContractShape {
15
15
  readonly roots: Record<string, string>;
16
16
  readonly models: Record<string, DomainModelShape>;
17
17
  }
18
- interface DomainValidationResult {
19
- readonly warnings: string[];
20
- }
21
- declare function validateContractDomain(contract: DomainContractShape): DomainValidationResult;
18
+ declare function validateContractDomain(contract: DomainContractShape): void;
22
19
  //#endregion
23
- export { type DomainContractShape, type DomainModelShape, type DomainValidationResult, validateContractDomain };
20
+ export { type DomainContractShape, type DomainModelShape, validateContractDomain };
24
21
  //# sourceMappingURL=validate-domain.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-domain.d.mts","names":[],"sources":["../src/validate-domain.ts"],"sourcesContent":[],"mappings":";UAAiB,gBAAA;EAAA,SAAA,MAAA,EACE,MADc,CAAA,MAAA,EAAA,OAAA,CAAA;EACd,SAAA,SAAA,CAAA,EACI,MADJ,CAAA,MAAA,EAAA;IACI,SAAA,EAAA,EAAA,MAAA;EAED,CAAA,CAAA;EAAM,SAAA,aAAA,CAAA,EAAA;IAKX,SAAA,KAAA,EAAA,MAAmB;EAClB,CAAA;EACgB,SAAA,QAAA,CAAA,EAPZ,MAOY,CAAA,MAAA,EAAA,OAAA,CAAA;EAAf,SAAA,IAAA,CAAA,EAAA,MAAA;EAAM,SAAA,KAAA,CAAA,EAAA,MAAA;AAGzB;AAIgB,UATC,mBAAA,CASqB;kBARpB;mBACC,eAAe;;UAGjB,sBAAA;;;iBAID,sBAAA,WAAiC,sBAAsB"}
1
+ {"version":3,"file":"validate-domain.d.mts","names":[],"sources":["../src/validate-domain.ts"],"sourcesContent":[],"mappings":";UAEiB,gBAAA;EAAA,SAAA,MAAA,EACE,MADc,CAAA,MAAA,EAAA,OAAA,CAAA;EACd,SAAA,SAAA,CAAA,EACI,MADJ,CAAA,MAAA,EAAA;IACI,SAAA,EAAA,EAAA,MAAA;EAED,CAAA,CAAA;EAAM,SAAA,aAAA,CAAA,EAAA;IAKX,SAAA,KAAA,EAAA,MAAmB;EAClB,CAAA;EACgB,SAAA,QAAA,CAAA,EAPZ,MAOY,CAAA,MAAA,EAAA,OAAA,CAAA;EAAf,SAAA,IAAA,CAAA,EAAA,MAAA;EAAM,SAAA,KAAA,CAAA,EAAA,MAAA;AAGzB;UALiB,mBAAA;kBACC;mBACC,eAAe;;iBAGlB,sBAAA,WAAiC"}
@@ -1,3 +1,3 @@
1
- import { t as validateContractDomain } from "./validate-domain-CTQiBiei.mjs";
1
+ import { t as validateContractDomain } from "./validate-domain-BlwWyv01.mjs";
2
2
 
3
3
  export { validateContractDomain };
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@prisma-next/contract",
3
- "version": "0.3.0-dev.143",
3
+ "version": "0.3.0-dev.144",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Data contract type definitions and JSON schema for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.29",
9
- "@prisma-next/utils": "0.3.0-dev.143"
9
+ "@prisma-next/utils": "0.3.0-dev.144"
10
10
  },
11
11
  "devDependencies": {
12
12
  "tsdown": "0.18.4",
13
13
  "typescript": "5.9.3",
14
14
  "vitest": "4.0.17",
15
15
  "@prisma-next/test-utils": "0.0.1",
16
- "@prisma-next/tsconfig": "0.0.0",
17
- "@prisma-next/tsdown": "0.0.0"
16
+ "@prisma-next/tsdown": "0.0.0",
17
+ "@prisma-next/tsconfig": "0.0.0"
18
18
  },
19
19
  "files": [
20
20
  "dist",
@@ -1,6 +1,7 @@
1
1
  import { isArrayEqual } from '@prisma-next/utils/array-equal';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
2
3
 
3
- import type { StorageBase } from './types';
4
+ import type { Contract } from './contract-types';
4
5
  import { bigintJsonReplacer } from './types';
5
6
 
6
7
  const TOP_LEVEL_ORDER = [
@@ -8,8 +9,6 @@ const TOP_LEVEL_ORDER = [
8
9
  'canonicalVersion',
9
10
  'targetFamily',
10
11
  'target',
11
- 'storageHash',
12
- 'executionHash',
13
12
  'profileHash',
14
13
  'roots',
15
14
  'models',
@@ -54,10 +53,6 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
54
53
  continue;
55
54
  }
56
55
 
57
- if (key === 'nullable' && value === false) {
58
- continue;
59
- }
60
-
61
56
  if (key === 'generated' && value === false) {
62
57
  continue;
63
58
  }
@@ -115,6 +110,8 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
115
110
  currentPath[3] === 'foreignKeys' &&
116
111
  (key === 'constraint' || key === 'index');
117
112
 
113
+ const isNullableField = key === 'nullable';
114
+
118
115
  if (
119
116
  !isRequiredModels &&
120
117
  !isRequiredTables &&
@@ -131,7 +128,8 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
131
128
  !isTableUniques &&
132
129
  !isTableIndexes &&
133
130
  !isTableForeignKeys &&
134
- !isFkBooleanField
131
+ !isFkBooleanField &&
132
+ !isNullableField
135
133
  ) {
136
134
  continue;
137
135
  }
@@ -241,49 +239,23 @@ export function orderTopLevel(obj: Record<string, unknown>): Record<string, unkn
241
239
  return ordered;
242
240
  }
243
241
 
244
- export type CanonicalContractInput = {
245
- readonly schemaVersion?: string | undefined;
246
- readonly targetFamily: string;
247
- readonly target: string;
248
- readonly roots: Record<string, string>;
249
- readonly models: Record<string, unknown>;
250
- readonly storage: StorageBase | Record<string, unknown>;
251
- readonly execution?: Record<string, unknown> | undefined;
252
- readonly extensionPacks: Record<string, unknown>;
253
- readonly capabilities: Record<string, Record<string, boolean>>;
254
- readonly meta: Record<string, unknown>;
255
- readonly storageHash?: string | undefined;
256
- readonly executionHash?: string | undefined;
257
- readonly profileHash?: string | undefined;
258
- };
259
-
260
242
  export function canonicalizeContractToObject(
261
- input: CanonicalContractInput,
243
+ contract: Contract,
244
+ options?: { schemaVersion?: string },
262
245
  ): Record<string, unknown> {
263
- const i = input as Record<string, unknown>;
264
246
  const normalized: Record<string, unknown> = {
265
- ...(i['schemaVersion'] !== undefined ? { schemaVersion: i['schemaVersion'] } : {}),
266
- targetFamily: i['targetFamily'],
267
- target: i['target'],
268
- ...(i['profileHash'] !== undefined ? { profileHash: i['profileHash'] } : {}),
269
- roots: i['roots'],
270
- models: i['models'],
271
- storage: i['storage'],
272
- ...(i['execution'] !== undefined ? { execution: i['execution'] } : {}),
273
- extensionPacks: i['extensionPacks'],
274
- capabilities: i['capabilities'],
275
- meta: i['meta'],
247
+ ...ifDefined('schemaVersion', options?.schemaVersion),
248
+ targetFamily: contract.targetFamily,
249
+ target: contract.target,
250
+ profileHash: contract.profileHash,
251
+ roots: contract.roots,
252
+ models: contract.models,
253
+ storage: contract.storage,
254
+ ...ifDefined('execution', contract.execution),
255
+ extensionPacks: contract.extensionPacks,
256
+ capabilities: contract.capabilities,
257
+ meta: contract.meta,
276
258
  };
277
- if (i['storageHash'] !== undefined) {
278
- normalized['storageHash'] = i['storageHash'];
279
- }
280
- if (i['executionHash'] !== undefined) {
281
- normalized['executionHash'] = i['executionHash'];
282
- }
283
- if (i['profileHash'] !== undefined) {
284
- normalized['profileHash'] = i['profileHash'];
285
- }
286
-
287
259
  const withDefaultsOmitted = omitDefaults(normalized, []) as Record<string, unknown>;
288
260
  const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted['storage']);
289
261
  const withSortedStorage = { ...withDefaultsOmitted, storage: withSortedIndexes };
@@ -291,6 +263,9 @@ export function canonicalizeContractToObject(
291
263
  return orderTopLevel(withSortedKeys);
292
264
  }
293
265
 
294
- export function canonicalizeContract(input: CanonicalContractInput): string {
295
- return JSON.stringify(canonicalizeContractToObject(input), bigintJsonReplacer, 2);
266
+ export function canonicalizeContract(
267
+ contract: Contract,
268
+ options?: { schemaVersion?: string },
269
+ ): string {
270
+ return JSON.stringify(canonicalizeContractToObject(contract, options), bigintJsonReplacer, 2);
296
271
  }
@@ -43,23 +43,6 @@ export interface ContractModel<TModelStorage extends ModelStorageBase = ModelSto
43
43
 
44
44
  // ── Backward-compatible aliases ──────────────────────────────────────────────
45
45
 
46
- /** @deprecated Use {@link ContractField} */
47
- export type DomainField = ContractField;
48
- /** @deprecated Use {@link ContractRelationOn} */
49
- export type DomainRelationOn = ContractRelationOn;
50
- /** @deprecated Use {@link ContractReferenceRelation} */
51
- export type DomainReferenceRelation = ContractReferenceRelation;
52
- /** @deprecated Use {@link ContractEmbedRelation} */
53
- export type DomainEmbedRelation = ContractEmbedRelation;
54
- /** @deprecated Use {@link ContractRelation} */
55
- export type DomainRelation = ContractRelation;
56
- /** @deprecated Use {@link ContractDiscriminator} */
57
- export type DomainDiscriminator = ContractDiscriminator;
58
- /** @deprecated Use {@link ContractVariantEntry} */
59
- export type DomainVariantEntry = ContractVariantEntry;
60
- /** @deprecated Use {@link ContractModel} */
61
- export type DomainModel = ContractModel;
62
-
63
46
  // ── Relation key helpers ─────────────────────────────────────────────────────
64
47
 
65
48
  type HasModelsWithRelations = {
@@ -1,6 +1,2 @@
1
- export {
2
- type CanonicalContractInput,
3
- canonicalizeContract,
4
- canonicalizeContractToObject,
5
- } from '../canonicalization';
1
+ export { canonicalizeContract, canonicalizeContractToObject } from '../canonicalization';
6
2
  export { computeExecutionHash, computeProfileHash, computeStorageHash } from '../hashing';
@@ -8,14 +8,6 @@ export type {
8
8
  ContractRelation,
9
9
  ContractRelationOn,
10
10
  ContractVariantEntry,
11
- DomainDiscriminator,
12
- DomainEmbedRelation,
13
- DomainField,
14
- DomainModel,
15
- DomainReferenceRelation,
16
- DomainRelation,
17
- DomainRelationOn,
18
- DomainVariantEntry,
19
11
  EmbedRelationKeys,
20
12
  ModelStorageBase,
21
13
  ReferenceRelationKeys,
@@ -26,12 +18,9 @@ export type {
26
18
  ColumnDefault,
27
19
  ColumnDefaultLiteralInputValue,
28
20
  ColumnDefaultLiteralValue,
29
- ContractBase,
30
21
  ContractMarkerRecord,
31
22
  DocCollection,
32
23
  DocIndex,
33
- DocumentContract,
34
- DocumentStorage,
35
24
  ExecutionHashBase,
36
25
  ExecutionMutationDefault,
37
26
  ExecutionMutationDefaultValue,
@@ -58,7 +47,6 @@ export {
58
47
  bigintJsonReplacer,
59
48
  coreHash,
60
49
  executionHash,
61
- isDocumentContract,
62
50
  isTaggedBigInt,
63
51
  isTaggedRaw,
64
52
  profileHash,
@@ -1,5 +1,6 @@
1
1
  export {
2
+ ContractValidationError,
3
+ type ContractValidationPhase,
2
4
  type StorageValidator,
3
- type ValidateContractResult,
4
5
  validateContract,
5
6
  } from '../validate-contract';
@@ -1,6 +1,5 @@
1
1
  export {
2
2
  type DomainContractShape,
3
3
  type DomainModelShape,
4
- type DomainValidationResult,
5
4
  validateContractDomain,
6
5
  } from '../validate-domain';
package/src/hashing.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { canonicalizeContract } from './canonicalization';
3
+ import type { Contract } from './contract-types';
3
4
  import type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';
4
5
 
5
6
  const SCHEMA_VERSION = '1';
@@ -10,23 +11,29 @@ function sha256(content: string): string {
10
11
  return `sha256:${hash.digest('hex')}`;
11
12
  }
12
13
 
14
+ function hashContract(section: Record<string, unknown>): string {
15
+ const contract = {
16
+ targetFamily: section['targetFamily'],
17
+ target: section['target'],
18
+ roots: {},
19
+ models: {},
20
+ storage: section['storage'] ?? {},
21
+ execution: section['execution'],
22
+ extensionPacks: {},
23
+ capabilities: section['capabilities'] ?? {},
24
+ meta: {},
25
+ profileHash: '',
26
+ ...section,
27
+ } as Contract;
28
+ return canonicalizeContract(contract, { schemaVersion: SCHEMA_VERSION });
29
+ }
30
+
13
31
  export function computeStorageHash(args: {
14
32
  target: string;
15
33
  targetFamily: string;
16
34
  storage: Record<string, unknown>;
17
35
  }): StorageHashBase<string> {
18
- const canonical = canonicalizeContract({
19
- schemaVersion: SCHEMA_VERSION,
20
- targetFamily: args.targetFamily,
21
- target: args.target,
22
- storage: args.storage,
23
- roots: {},
24
- models: {},
25
- extensionPacks: {},
26
- capabilities: {},
27
- meta: {},
28
- });
29
- return sha256(canonical) as StorageHashBase<string>;
36
+ return sha256(hashContract(args)) as StorageHashBase<string>;
30
37
  }
31
38
 
32
39
  export function computeExecutionHash(args: {
@@ -34,19 +41,7 @@ export function computeExecutionHash(args: {
34
41
  targetFamily: string;
35
42
  execution: Record<string, unknown>;
36
43
  }): ExecutionHashBase<string> {
37
- const canonical = canonicalizeContract({
38
- schemaVersion: SCHEMA_VERSION,
39
- targetFamily: args.targetFamily,
40
- target: args.target,
41
- execution: args.execution,
42
- roots: {},
43
- models: {},
44
- storage: {},
45
- extensionPacks: {},
46
- capabilities: {},
47
- meta: {},
48
- });
49
- return sha256(canonical) as ExecutionHashBase<string>;
44
+ return sha256(hashContract(args)) as ExecutionHashBase<string>;
50
45
  }
51
46
 
52
47
  export function computeProfileHash(args: {
@@ -54,16 +49,5 @@ export function computeProfileHash(args: {
54
49
  targetFamily: string;
55
50
  capabilities: Record<string, Record<string, boolean>>;
56
51
  }): ProfileHashBase<string> {
57
- const canonical = canonicalizeContract({
58
- schemaVersion: SCHEMA_VERSION,
59
- targetFamily: args.targetFamily,
60
- target: args.target,
61
- capabilities: args.capabilities,
62
- roots: {},
63
- models: {},
64
- storage: {},
65
- extensionPacks: {},
66
- meta: {},
67
- });
68
- return sha256(canonical) as ProfileHashBase<string>;
52
+ return sha256(hashContract(args)) as ProfileHashBase<string>;
69
53
  }
package/src/types.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { DomainModel } from './domain-types';
2
-
3
1
  /**
4
2
  * Unique symbol used as the key for branding types.
5
3
  */
@@ -59,26 +57,6 @@ export interface StorageBase<THash extends string = string> {
59
57
  readonly storageHash: StorageHashBase<THash>;
60
58
  }
61
59
 
62
- export interface ContractBase<
63
- TStorageHash extends StorageHashBase<string> = StorageHashBase<string>,
64
- TExecutionHash extends ExecutionHashBase<string> = ExecutionHashBase<string>,
65
- TProfileHash extends ProfileHashBase<string> = ProfileHashBase<string>,
66
- > {
67
- readonly schemaVersion: string;
68
- readonly target: string;
69
- readonly targetFamily: string;
70
- readonly storageHash: TStorageHash;
71
- readonly executionHash?: TExecutionHash;
72
- readonly profileHash?: TProfileHash;
73
- readonly capabilities: Record<string, Record<string, boolean>>;
74
- readonly extensionPacks: Record<string, unknown>;
75
- readonly meta: Record<string, unknown>;
76
- readonly sources: Record<string, Source>;
77
- readonly execution?: ExecutionSection;
78
- readonly roots: Record<string, string>;
79
- readonly models: Record<string, DomainModel>;
80
- }
81
-
82
60
  export interface FieldType {
83
61
  readonly type: string;
84
62
  readonly nullable: boolean;
@@ -188,22 +166,6 @@ export interface DocCollection {
188
166
  readonly readOnly?: boolean;
189
167
  }
190
168
 
191
- export interface DocumentStorage {
192
- readonly document: {
193
- readonly collections: Record<string, DocCollection>;
194
- };
195
- }
196
-
197
- export interface DocumentContract<
198
- TStorageHash extends StorageHashBase<string> = StorageHashBase<string>,
199
- TExecutionHash extends ExecutionHashBase<string> = ExecutionHashBase<string>,
200
- TProfileHash extends ProfileHashBase<string> = ProfileHashBase<string>,
201
- > extends ContractBase<TStorageHash, TExecutionHash, TProfileHash> {
202
- // Accept string to work with JSON imports; runtime validation ensures 'document'
203
- readonly targetFamily: string;
204
- readonly storage: DocumentStorage;
205
- }
206
-
207
169
  // Plan types - target-family agnostic execution types
208
170
  export interface ParamDescriptor {
209
171
  readonly index?: number;
@@ -277,18 +239,6 @@ export interface ExecutionPlan<Row = unknown, Ast = unknown> {
277
239
  export type ResultType<P> =
278
240
  P extends ExecutionPlan<infer R, unknown> ? R : P extends { readonly _Row?: infer R } ? R : never;
279
241
 
280
- /**
281
- * Type guard to check if a contract is a Document contract
282
- */
283
- export function isDocumentContract(contract: unknown): contract is DocumentContract {
284
- return (
285
- typeof contract === 'object' &&
286
- contract !== null &&
287
- 'targetFamily' in contract &&
288
- contract.targetFamily === 'document'
289
- );
290
- }
291
-
292
242
  /**
293
243
  * Contract marker record stored in the database.
294
244
  * Represents the current contract identity for a database.
@@ -1,18 +1,27 @@
1
1
  import { type } from 'arktype';
2
2
  import type { Contract } from './contract-types';
3
- import type { DomainContractShape, DomainValidationResult } from './validate-domain';
3
+ import type { DomainContractShape } from './validate-domain';
4
4
  import { validateContractDomain } from './validate-domain';
5
5
 
6
+ export type ContractValidationPhase = 'structural' | 'domain' | 'storage';
7
+
8
+ export class ContractValidationError extends Error {
9
+ readonly code = 'CONTRACT.VALIDATION_FAILED';
10
+ readonly phase: ContractValidationPhase;
11
+
12
+ constructor(message: string, phase: ContractValidationPhase) {
13
+ super(message);
14
+ this.name = 'ContractValidationError';
15
+ this.phase = phase;
16
+ }
17
+ }
18
+
6
19
  /**
7
20
  * Family-provided storage validator.
8
21
  * SQL validates tables/columns/FKs; Mongo validates collections/embedding.
9
22
  */
10
23
  export type StorageValidator = (contract: Contract) => void;
11
24
 
12
- export interface ValidateContractResult {
13
- readonly warnings: string[];
14
- }
15
-
16
25
  const ContractSchema = type({
17
26
  target: 'string',
18
27
  targetFamily: 'string',
@@ -28,11 +37,11 @@ const ContractSchema = type({
28
37
  defaults: 'unknown[]',
29
38
  },
30
39
  },
31
- 'profileHash?': 'string',
40
+ profileHash: 'string',
32
41
  });
33
42
 
34
43
  function stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {
35
- const { schemaVersion: _, sources: _s, ...rest } = raw;
44
+ const { schemaVersion: _, _generated: _g, ...rest } = raw;
36
45
  return rest;
37
46
  }
38
47
 
@@ -54,7 +63,7 @@ function extractDomainShape(contract: Contract): DomainContractShape {
54
63
  * 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
55
64
  * Mongo validates collections/embedding.
56
65
  *
57
- * JSON persistence fields (`schemaVersion`, `sources`) are stripped before
66
+ * JSON persistence fields (`schemaVersion`, `_generated`) are stripped before
58
67
  * validation — they are not part of the in-memory contract representation.
59
68
  *
60
69
  * @template TContract The fully-typed contract type (preserves literal types).
@@ -65,29 +74,26 @@ function extractDomainShape(contract: Contract): DomainContractShape {
65
74
  export function validateContract<TContract extends Contract>(
66
75
  value: unknown,
67
76
  storageValidator: StorageValidator,
68
- ): TContract & ValidateContractResult {
77
+ ): TContract {
69
78
  if (typeof value !== 'object' || value === null) {
70
- throw new Error('Contract must be a non-null object');
79
+ throw new ContractValidationError('Contract must be a non-null object', 'structural');
71
80
  }
72
81
 
73
82
  const stripped = stripPersistenceFields(value as Record<string, unknown>);
74
83
 
75
84
  const parsed = ContractSchema(stripped);
76
85
  if (parsed instanceof type.errors) {
77
- throw new Error(`Invalid contract structure: ${parsed.summary}`);
86
+ throw new ContractValidationError(
87
+ `Invalid contract structure: ${parsed.summary}`,
88
+ 'structural',
89
+ );
78
90
  }
79
91
 
80
- // Arktype verified the structural shape; Contract adds branded hash types and
81
- // ContractModel generics that can't be expressed in the schema.
82
92
  const contract = parsed as unknown as Contract;
83
93
 
84
- const domainResult: DomainValidationResult = validateContractDomain(extractDomainShape(contract));
94
+ validateContractDomain(extractDomainShape(contract));
85
95
 
86
96
  storageValidator(contract);
87
97
 
88
- // TContract narrows Contract with literal types from the caller's contract.d.ts;
89
- // the runtime object is the same — the cast preserves the caller's type parameter.
90
- return Object.assign(contract as unknown as TContract, {
91
- warnings: domainResult.warnings,
92
- });
98
+ return contract as unknown as TContract;
93
99
  }