@prisma-next/contract 0.3.0-dev.137 → 0.3.0-dev.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ //#region src/validate-domain.ts
2
+ function validateContractDomain(contract) {
3
+ const errors = [];
4
+ const warnings = [];
5
+ const modelNames = new Set(Object.keys(contract.models));
6
+ validateRoots(contract, modelNames, errors);
7
+ validateVariantsAndBases(contract, modelNames, errors);
8
+ validateRelationTargets(contract, modelNames, errors);
9
+ validateDiscriminators(contract, errors);
10
+ 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 };
14
+ }
15
+ function validateRoots(contract, modelNames, errors) {
16
+ const seenValues = /* @__PURE__ */ new Set();
17
+ for (const [rootKey, modelName] of Object.entries(contract.roots)) {
18
+ if (seenValues.has(modelName)) errors.push(`Duplicate root value: "${modelName}" is mapped by multiple root keys`);
19
+ seenValues.add(modelName);
20
+ if (!modelNames.has(modelName)) errors.push(`Root "${rootKey}" references model "${modelName}" which does not exist in models`);
21
+ }
22
+ }
23
+ function validateVariantsAndBases(contract, modelNames, errors) {
24
+ const models = new Map(Object.entries(contract.models));
25
+ for (const [modelName, model] of models) {
26
+ if (model.variants) for (const variantName of Object.keys(model.variants)) {
27
+ if (!modelNames.has(variantName)) {
28
+ errors.push(`Model "${modelName}" lists variant "${variantName}" which does not exist in models`);
29
+ continue;
30
+ }
31
+ const variantModel = models.get(variantName);
32
+ if (!variantModel) continue;
33
+ if (variantModel.base !== modelName) errors.push(`Variant "${variantName}" has base "${variantModel.base ?? "(none)"}" but expected "${modelName}"`);
34
+ }
35
+ if (model.base) {
36
+ if (!modelNames.has(model.base)) {
37
+ errors.push(`Model "${modelName}" has base "${model.base}" which does not exist in models`);
38
+ continue;
39
+ }
40
+ const baseModel = models.get(model.base);
41
+ if (!baseModel) continue;
42
+ if (!baseModel.variants || !Object.hasOwn(baseModel.variants, modelName)) errors.push(`Model "${modelName}" has base "${model.base}" which does not list it as a variant`);
43
+ }
44
+ }
45
+ }
46
+ function validateRelationTargets(contract, modelNames, errors) {
47
+ for (const [modelName, model] of Object.entries(contract.models)) for (const [relName, relation] of Object.entries(model.relations ?? {})) if (!modelNames.has(relation.to)) errors.push(`Relation "${relName}" on model "${modelName}" targets "${relation.to}" which does not exist in models`);
48
+ }
49
+ function validateDiscriminators(contract, errors) {
50
+ for (const [modelName, model] of Object.entries(contract.models)) {
51
+ if (model.discriminator) {
52
+ if (!model.variants || Object.keys(model.variants).length === 0) errors.push(`Model "${modelName}" has discriminator but no variants`);
53
+ if (!Object.hasOwn(model.fields, model.discriminator.field)) errors.push(`Discriminator field "${model.discriminator.field}" is not a field on model "${modelName}"`);
54
+ }
55
+ if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) errors.push(`Model "${modelName}" has variants but no discriminator`);
56
+ if (model.base) {
57
+ if (model.discriminator) errors.push(`Model "${modelName}" has base and must not have discriminator`);
58
+ if (model.variants && Object.keys(model.variants).length > 0) errors.push(`Model "${modelName}" has base and must not have variants`);
59
+ }
60
+ }
61
+ }
62
+ function validateOwnership(contract, modelNames, errors) {
63
+ for (const [modelName, model] of Object.entries(contract.models)) {
64
+ if (!model.owner) continue;
65
+ if (model.owner === modelName) errors.push(`Model "${modelName}" cannot own itself`);
66
+ if (!modelNames.has(model.owner)) errors.push(`Model "${modelName}" has owner "${model.owner}" which does not exist in models`);
67
+ 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
+ }
69
+ }
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
+
82
+ //#endregion
83
+ export { validateContractDomain as t };
84
+ //# sourceMappingURL=validate-domain-WfuBWGsF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-domain-WfuBWGsF.mjs","names":["errors: string[]","warnings: string[]"],"sources":["../src/validate-domain.ts"],"sourcesContent":["export 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 interface DomainValidationResult {\n readonly warnings: string[];\n}\n\nexport function validateContractDomain(contract: DomainContractShape): DomainValidationResult {\n const errors: string[] = [];\n const warnings: 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 detectOrphanedModels(contract, modelNames, warnings);\n\n if (errors.length > 0) {\n throw new Error(`Contract domain validation failed:\\n- ${errors.join('\\n- ')}`);\n }\n\n return { warnings };\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\nfunction detectOrphanedModels(\n contract: DomainContractShape,\n modelNames: Set<string>,\n warnings: string[],\n): void {\n const referenced = new Set<string>();\n\n for (const modelName of Object.values(contract.roots)) {\n referenced.add(modelName);\n }\n\n for (const [modelName, model] of Object.entries(contract.models)) {\n for (const relation of Object.values(model.relations ?? {})) {\n referenced.add(relation.to);\n }\n if (model.variants) {\n for (const variantName of Object.keys(model.variants)) {\n referenced.add(variantName);\n }\n }\n if (model.base) {\n referenced.add(model.base);\n }\n if (model.owner) {\n referenced.add(modelName);\n }\n }\n\n for (const modelName of modelNames) {\n if (!referenced.has(modelName)) {\n warnings.push(\n `Orphaned model: \"${modelName}\" is not referenced by any root, relation, or variant`,\n );\n }\n }\n}\n"],"mappings":"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"}
@@ -1,84 +1,3 @@
1
- //#region src/validate-domain.ts
2
- function validateContractDomain(contract) {
3
- const errors = [];
4
- const warnings = [];
5
- const modelNames = new Set(Object.keys(contract.models));
6
- validateRoots(contract, modelNames, errors);
7
- validateVariantsAndBases(contract, modelNames, errors);
8
- validateRelationTargets(contract, modelNames, errors);
9
- validateDiscriminators(contract, errors);
10
- 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 };
14
- }
15
- function validateRoots(contract, modelNames, errors) {
16
- const seenValues = /* @__PURE__ */ new Set();
17
- for (const [rootKey, modelName] of Object.entries(contract.roots)) {
18
- if (seenValues.has(modelName)) errors.push(`Duplicate root value: "${modelName}" is mapped by multiple root keys`);
19
- seenValues.add(modelName);
20
- if (!modelNames.has(modelName)) errors.push(`Root "${rootKey}" references model "${modelName}" which does not exist in models`);
21
- }
22
- }
23
- function validateVariantsAndBases(contract, modelNames, errors) {
24
- const models = new Map(Object.entries(contract.models));
25
- for (const [modelName, model] of models) {
26
- if (model.variants) for (const variantName of Object.keys(model.variants)) {
27
- if (!modelNames.has(variantName)) {
28
- errors.push(`Model "${modelName}" lists variant "${variantName}" which does not exist in models`);
29
- continue;
30
- }
31
- const variantModel = models.get(variantName);
32
- if (!variantModel) continue;
33
- if (variantModel.base !== modelName) errors.push(`Variant "${variantName}" has base "${variantModel.base ?? "(none)"}" but expected "${modelName}"`);
34
- }
35
- if (model.base) {
36
- if (!modelNames.has(model.base)) {
37
- errors.push(`Model "${modelName}" has base "${model.base}" which does not exist in models`);
38
- continue;
39
- }
40
- const baseModel = models.get(model.base);
41
- if (!baseModel) continue;
42
- if (!baseModel.variants || !Object.hasOwn(baseModel.variants, modelName)) errors.push(`Model "${modelName}" has base "${model.base}" which does not list it as a variant`);
43
- }
44
- }
45
- }
46
- function validateRelationTargets(contract, modelNames, errors) {
47
- for (const [modelName, model] of Object.entries(contract.models)) for (const [relName, relation] of Object.entries(model.relations ?? {})) if (!modelNames.has(relation.to)) errors.push(`Relation "${relName}" on model "${modelName}" targets "${relation.to}" which does not exist in models`);
48
- }
49
- function validateDiscriminators(contract, errors) {
50
- for (const [modelName, model] of Object.entries(contract.models)) {
51
- if (model.discriminator) {
52
- if (!model.variants || Object.keys(model.variants).length === 0) errors.push(`Model "${modelName}" has discriminator but no variants`);
53
- if (!Object.hasOwn(model.fields, model.discriminator.field)) errors.push(`Discriminator field "${model.discriminator.field}" is not a field on model "${modelName}"`);
54
- }
55
- if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) errors.push(`Model "${modelName}" has variants but no discriminator`);
56
- if (model.base) {
57
- if (model.discriminator) errors.push(`Model "${modelName}" has base and must not have discriminator`);
58
- if (model.variants && Object.keys(model.variants).length > 0) errors.push(`Model "${modelName}" has base and must not have variants`);
59
- }
60
- }
61
- }
62
- function validateOwnership(contract, modelNames, errors) {
63
- for (const [modelName, model] of Object.entries(contract.models)) {
64
- if (!model.owner) continue;
65
- if (model.owner === modelName) errors.push(`Model "${modelName}" cannot own itself`);
66
- if (!modelNames.has(model.owner)) errors.push(`Model "${modelName}" has owner "${model.owner}" which does not exist in models`);
67
- 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
- }
69
- }
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
- }
1
+ import { t as validateContractDomain } from "./validate-domain-WfuBWGsF.mjs";
81
2
 
82
- //#endregion
83
- export { validateContractDomain };
84
- //# sourceMappingURL=validate-domain.mjs.map
3
+ export { validateContractDomain };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@prisma-next/contract",
3
- "version": "0.3.0-dev.137",
3
+ "version": "0.3.0-dev.138",
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
- "@prisma-next/operations": "0.3.0-dev.137",
9
- "@prisma-next/utils": "0.3.0-dev.137"
8
+ "arktype": "^2.1.29",
9
+ "@prisma-next/utils": "0.3.0-dev.138",
10
+ "@prisma-next/operations": "0.3.0-dev.138"
10
11
  },
11
12
  "devDependencies": {
12
13
  "tsdown": "0.18.4",
@@ -28,6 +29,7 @@
28
29
  "./ir": "./dist/ir.mjs",
29
30
  "./types": "./dist/types.mjs",
30
31
  "./validate-domain": "./dist/validate-domain.mjs",
32
+ "./validate-contract": "./dist/validate-contract.mjs",
31
33
  "./package.json": "./package.json"
32
34
  },
33
35
  "repository": {
@@ -0,0 +1,54 @@
1
+ import type { ContractModel } from './domain-types';
2
+ import type {
3
+ ExecutionHashBase,
4
+ ExecutionMutationDefault,
5
+ ProfileHashBase,
6
+ StorageBase,
7
+ } from './types';
8
+
9
+ /**
10
+ * Execution section for the unified contract (ADR 182).
11
+ *
12
+ * Unlike the legacy {@link import('./types').ExecutionSection}, this type
13
+ * requires `executionHash` — when an execution section is present, its
14
+ * hash must be too (consistent with `StorageBase.storageHash`).
15
+ *
16
+ * @template THash Literal hash string type for type-safe hash tracking.
17
+ */
18
+ export type ContractExecutionSection<THash extends string = string> = {
19
+ readonly executionHash: ExecutionHashBase<THash>;
20
+ readonly mutations: {
21
+ readonly defaults: ReadonlyArray<ExecutionMutationDefault>;
22
+ };
23
+ };
24
+
25
+ /**
26
+ * Unified contract representation (ADR 182).
27
+ *
28
+ * A `Contract` is the canonical in-memory representation of a data contract.
29
+ * It is model-first (domain models carry their own storage bridge) and
30
+ * family-parameterized (SQL, Mongo, etc. specialize via `TStorage` and model
31
+ * storage generics on `ContractModel`).
32
+ *
33
+ * JSON persistence fields (`schemaVersion`, `sources`) are not represented
34
+ * here — they are handled at the serialization boundary.
35
+ *
36
+ * @template TStorage Family-specific storage block (extends {@link StorageBase}).
37
+ * @template TModels Record of model name → {@link ContractModel} with
38
+ * family-specific model storage.
39
+ */
40
+ export interface Contract<
41
+ TStorage extends StorageBase = StorageBase,
42
+ TModels extends Record<string, ContractModel> = Record<string, ContractModel>,
43
+ > {
44
+ readonly target: string;
45
+ readonly targetFamily: string;
46
+ readonly roots: Record<string, string>;
47
+ readonly models: TModels;
48
+ readonly storage: TStorage;
49
+ readonly capabilities: Record<string, Record<string, boolean>>;
50
+ readonly extensionPacks: Record<string, unknown>;
51
+ readonly execution?: ContractExecutionSection;
52
+ readonly profileHash?: ProfileHashBase<string>;
53
+ readonly meta: Record<string, unknown>;
54
+ }
@@ -1,53 +1,76 @@
1
- export type DomainField = {
1
+ export type ContractField = {
2
2
  readonly nullable: boolean;
3
3
  readonly codecId: string;
4
4
  };
5
5
 
6
- export type DomainRelationOn = {
6
+ export type ContractRelationOn = {
7
7
  readonly localFields: readonly string[];
8
8
  readonly targetFields: readonly string[];
9
9
  };
10
10
 
11
- export type DomainReferenceRelation = {
11
+ export type ContractReferenceRelation = {
12
12
  readonly to: string;
13
13
  readonly cardinality: '1:1' | '1:N' | 'N:1';
14
- readonly on: DomainRelationOn;
14
+ readonly on: ContractRelationOn;
15
15
  };
16
16
 
17
- export type DomainEmbedRelation = {
17
+ export type ContractEmbedRelation = {
18
18
  readonly to: string;
19
19
  readonly cardinality: '1:1' | '1:N';
20
20
  };
21
21
 
22
- export type DomainRelation = DomainReferenceRelation | DomainEmbedRelation;
22
+ export type ContractRelation = ContractReferenceRelation | ContractEmbedRelation;
23
23
 
24
- export type DomainDiscriminator = {
24
+ export type ContractDiscriminator = {
25
25
  readonly field: string;
26
26
  };
27
27
 
28
- export type DomainVariantEntry = {
28
+ export type ContractVariantEntry = {
29
29
  readonly value: string;
30
30
  };
31
31
 
32
- export type DomainModel = {
33
- readonly fields: Record<string, DomainField>;
34
- readonly relations: Record<string, DomainRelation>;
35
- readonly storage: Record<string, unknown>;
36
- readonly discriminator?: DomainDiscriminator;
37
- readonly variants?: Record<string, DomainVariantEntry>;
32
+ export type ModelStorageBase = Readonly<Record<string, unknown>>;
33
+
34
+ export interface ContractModel<TModelStorage extends ModelStorageBase = ModelStorageBase> {
35
+ readonly fields: Record<string, ContractField>;
36
+ readonly relations: Record<string, ContractRelation>;
37
+ readonly storage: TModelStorage;
38
+ readonly discriminator?: ContractDiscriminator;
39
+ readonly variants?: Record<string, ContractVariantEntry>;
38
40
  readonly base?: string;
39
41
  readonly owner?: string;
40
- };
42
+ }
43
+
44
+ // ── Backward-compatible aliases ──────────────────────────────────────────────
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
+ // ── Relation key helpers ─────────────────────────────────────────────────────
41
64
 
42
65
  type HasModelsWithRelations = {
43
- readonly models: Record<string, { readonly relations: Record<string, DomainRelation> }>;
66
+ readonly models: Record<string, { readonly relations: Record<string, ContractRelation> }>;
44
67
  };
45
68
 
46
69
  export type ReferenceRelationKeys<
47
70
  TContract extends HasModelsWithRelations,
48
71
  ModelName extends string & keyof TContract['models'],
49
72
  > = {
50
- [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends DomainReferenceRelation
73
+ [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
51
74
  ? K
52
75
  : never;
53
76
  }[keyof TContract['models'][ModelName]['relations']];
@@ -56,7 +79,7 @@ export type EmbedRelationKeys<
56
79
  TContract extends HasModelsWithRelations,
57
80
  ModelName extends string & keyof TContract['models'],
58
81
  > = {
59
- [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends DomainReferenceRelation
82
+ [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
60
83
  ? never
61
84
  : K;
62
85
  }[keyof TContract['models'][ModelName]['relations']];
@@ -1,4 +1,13 @@
1
+ export type { Contract, ContractExecutionSection } from '../contract-types';
1
2
  export type {
3
+ ContractDiscriminator,
4
+ ContractEmbedRelation,
5
+ ContractField,
6
+ ContractModel,
7
+ ContractReferenceRelation,
8
+ ContractRelation,
9
+ ContractRelationOn,
10
+ ContractVariantEntry,
2
11
  DomainDiscriminator,
3
12
  DomainEmbedRelation,
4
13
  DomainField,
@@ -8,6 +17,7 @@ export type {
8
17
  DomainRelationOn,
9
18
  DomainVariantEntry,
10
19
  EmbedRelationKeys,
20
+ ModelStorageBase,
11
21
  ReferenceRelationKeys,
12
22
  } from '../domain-types';
13
23
  export type {
@@ -41,6 +51,7 @@ export type {
41
51
  RenderTypeContext,
42
52
  ResultType,
43
53
  Source,
54
+ StorageBase,
44
55
  StorageHashBase,
45
56
  TaggedBigInt,
46
57
  TaggedLiteralValue,
@@ -0,0 +1,5 @@
1
+ export {
2
+ type StorageValidator,
3
+ type ValidateContractResult,
4
+ validateContract,
5
+ } from '../validate-contract';
package/src/types.ts CHANGED
@@ -56,6 +56,15 @@ export function profileHash<const T extends string>(value: T): ProfileHashBase<T
56
56
  return value as ProfileHashBase<T>;
57
57
  }
58
58
 
59
+ /**
60
+ * Base type for family-specific storage blocks.
61
+ * Family storage types (SqlStorage, MongoStorage, etc.) extend this to carry the
62
+ * storage hash alongside family-specific data (tables, collections, etc.).
63
+ */
64
+ export interface StorageBase<THash extends string = string> {
65
+ readonly storageHash: StorageHashBase<THash>;
66
+ }
67
+
59
68
  export interface ContractBase<
60
69
  TStorageHash extends StorageHashBase<string> = StorageHashBase<string>,
61
70
  TExecutionHash extends ExecutionHashBase<string> = ExecutionHashBase<string>,
@@ -0,0 +1,93 @@
1
+ import { type } from 'arktype';
2
+ import type { Contract } from './contract-types';
3
+ import type { DomainContractShape, DomainValidationResult } from './validate-domain';
4
+ import { validateContractDomain } from './validate-domain';
5
+
6
+ /**
7
+ * Family-provided storage validator.
8
+ * SQL validates tables/columns/FKs; Mongo validates collections/embedding.
9
+ */
10
+ export type StorageValidator = (contract: Contract) => void;
11
+
12
+ export interface ValidateContractResult {
13
+ readonly warnings: string[];
14
+ }
15
+
16
+ const ContractSchema = type({
17
+ target: 'string',
18
+ targetFamily: 'string',
19
+ roots: 'Record<string, string>',
20
+ models: 'Record<string, unknown>',
21
+ storage: 'Record<string, unknown>',
22
+ capabilities: 'Record<string, Record<string, boolean>>',
23
+ extensionPacks: 'Record<string, unknown>',
24
+ meta: 'Record<string, unknown>',
25
+ 'execution?': {
26
+ 'executionHash?': 'string',
27
+ mutations: {
28
+ defaults: 'unknown[]',
29
+ },
30
+ },
31
+ 'profileHash?': 'string',
32
+ });
33
+
34
+ function stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {
35
+ const { schemaVersion: _, sources: _s, ...rest } = raw;
36
+ return rest;
37
+ }
38
+
39
+ function extractDomainShape(contract: Contract): DomainContractShape {
40
+ return {
41
+ roots: contract.roots,
42
+ models: contract.models,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Framework-level contract validation (ADR 182).
48
+ *
49
+ * Three-pass validation:
50
+ * 1. **Structural validation** (arktype): verifies required fields exist with
51
+ * correct base types.
52
+ * 2. **Domain validation** (framework-owned): roots, relation targets,
53
+ * variant/base consistency, discriminators, ownership, orphans.
54
+ * 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
55
+ * Mongo validates collections/embedding.
56
+ *
57
+ * JSON persistence fields (`schemaVersion`, `sources`) are stripped before
58
+ * validation — they are not part of the in-memory contract representation.
59
+ *
60
+ * @template TContract The fully-typed contract type (preserves literal types).
61
+ * @param value Raw contract value (e.g. parsed from JSON).
62
+ * @param storageValidator Family-specific storage validation function.
63
+ * @returns The validated contract with full literal types.
64
+ */
65
+ export function validateContract<TContract extends Contract>(
66
+ value: unknown,
67
+ storageValidator: StorageValidator,
68
+ ): TContract & ValidateContractResult {
69
+ if (typeof value !== 'object' || value === null) {
70
+ throw new Error('Contract must be a non-null object');
71
+ }
72
+
73
+ const stripped = stripPersistenceFields(value as Record<string, unknown>);
74
+
75
+ const parsed = ContractSchema(stripped);
76
+ if (parsed instanceof type.errors) {
77
+ throw new Error(`Invalid contract structure: ${parsed.summary}`);
78
+ }
79
+
80
+ // Arktype verified the structural shape; Contract adds branded hash types and
81
+ // ContractModel generics that can't be expressed in the schema.
82
+ const contract = parsed as unknown as Contract;
83
+
84
+ const domainResult: DomainValidationResult = validateContractDomain(extractDomainShape(contract));
85
+
86
+ storageValidator(contract);
87
+
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
+ });
93
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/domain-types.ts","../src/types.ts"],"sourcesContent":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-domain.mjs","names":["errors: string[]","warnings: string[]"],"sources":["../src/validate-domain.ts"],"sourcesContent":["export 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 interface DomainValidationResult {\n readonly warnings: string[];\n}\n\nexport function validateContractDomain(contract: DomainContractShape): DomainValidationResult {\n const errors: string[] = [];\n const warnings: 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 detectOrphanedModels(contract, modelNames, warnings);\n\n if (errors.length > 0) {\n throw new Error(`Contract domain validation failed:\\n- ${errors.join('\\n- ')}`);\n }\n\n return { warnings };\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\nfunction detectOrphanedModels(\n contract: DomainContractShape,\n modelNames: Set<string>,\n warnings: string[],\n): void {\n const referenced = new Set<string>();\n\n for (const modelName of Object.values(contract.roots)) {\n referenced.add(modelName);\n }\n\n for (const [modelName, model] of Object.entries(contract.models)) {\n for (const relation of Object.values(model.relations ?? {})) {\n referenced.add(relation.to);\n }\n if (model.variants) {\n for (const variantName of Object.keys(model.variants)) {\n referenced.add(variantName);\n }\n }\n if (model.base) {\n referenced.add(model.base);\n }\n if (model.owner) {\n referenced.add(modelName);\n }\n }\n\n for (const modelName of modelNames) {\n if (!referenced.has(modelName)) {\n warnings.push(\n `Orphaned model: \"${modelName}\" is not referenced by any root, relation, or variant`,\n );\n }\n }\n}\n"],"mappings":"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"}