@prisma-next/contract 0.3.0-dev.15 → 0.3.0-dev.150

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 (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +43 -254
  3. package/dist/contract-types-MYdoYIIh.d.mts +314 -0
  4. package/dist/contract-types-MYdoYIIh.d.mts.map +1 -0
  5. package/dist/hashing-CyaA_Qvf.mjs +196 -0
  6. package/dist/hashing-CyaA_Qvf.mjs.map +1 -0
  7. package/dist/hashing.d.mts +29 -0
  8. package/dist/hashing.d.mts.map +1 -0
  9. package/dist/hashing.mjs +3 -0
  10. package/dist/testing.d.mts +29 -0
  11. package/dist/testing.d.mts.map +1 -0
  12. package/dist/testing.mjs +58 -0
  13. package/dist/testing.mjs.map +1 -0
  14. package/dist/types-aMyNgejf.mjs +14 -0
  15. package/dist/types-aMyNgejf.mjs.map +1 -0
  16. package/dist/types.d.mts +2 -0
  17. package/dist/types.mjs +3 -0
  18. package/dist/validate-contract.d.mts +37 -0
  19. package/dist/validate-contract.d.mts.map +1 -0
  20. package/dist/validate-contract.mjs +3 -0
  21. package/dist/validate-domain-CpCcTlqJ.mjs +165 -0
  22. package/dist/validate-domain-CpCcTlqJ.mjs.map +1 -0
  23. package/dist/validate-domain.d.mts +24 -0
  24. package/dist/validate-domain.d.mts.map +1 -0
  25. package/dist/validate-domain.mjs +3 -0
  26. package/package.json +24 -25
  27. package/schemas/data-contract-document-v1.json +5 -5
  28. package/src/canonicalization.ts +263 -0
  29. package/src/contract-types.ts +55 -0
  30. package/src/domain-types.ts +95 -0
  31. package/src/exports/hashing.ts +2 -0
  32. package/src/exports/testing.ts +1 -0
  33. package/src/exports/types.ts +36 -13
  34. package/src/exports/validate-contract.ts +6 -0
  35. package/src/exports/validate-domain.ts +5 -0
  36. package/src/hashing.ts +53 -0
  37. package/src/testing-factories.ts +101 -0
  38. package/src/types.ts +102 -121
  39. package/src/validate-contract.ts +101 -0
  40. package/src/validate-domain.ts +227 -0
  41. package/dist/exports/framework-components.d.ts +0 -3
  42. package/dist/exports/framework-components.d.ts.map +0 -1
  43. package/dist/exports/framework-components.js +0 -24
  44. package/dist/exports/framework-components.js.map +0 -1
  45. package/dist/exports/ir.d.ts +0 -2
  46. package/dist/exports/ir.d.ts.map +0 -1
  47. package/dist/exports/ir.js +0 -35
  48. package/dist/exports/ir.js.map +0 -1
  49. package/dist/exports/pack-manifest-types.d.ts +0 -2
  50. package/dist/exports/pack-manifest-types.d.ts.map +0 -1
  51. package/dist/exports/pack-manifest-types.js +0 -1
  52. package/dist/exports/pack-manifest-types.js.map +0 -1
  53. package/dist/exports/types.d.ts +0 -3
  54. package/dist/exports/types.d.ts.map +0 -1
  55. package/dist/exports/types.js +0 -8
  56. package/dist/exports/types.js.map +0 -1
  57. package/dist/framework-components.d.ts +0 -408
  58. package/dist/framework-components.d.ts.map +0 -1
  59. package/dist/ir.d.ts +0 -76
  60. package/dist/ir.d.ts.map +0 -1
  61. package/dist/types.d.ts +0 -222
  62. package/dist/types.d.ts.map +0 -1
  63. package/src/exports/framework-components.ts +0 -26
  64. package/src/exports/ir.ts +0 -1
  65. package/src/exports/pack-manifest-types.ts +0 -6
  66. package/src/framework-components.ts +0 -525
  67. package/src/ir.ts +0 -113
package/src/types.ts CHANGED
@@ -1,16 +1,60 @@
1
- import type { OperationRegistry } from '@prisma-next/operations';
2
- import type { ContractIR } from './ir';
1
+ /**
2
+ * Unique symbol used as the key for branding types.
3
+ */
4
+ export const $: unique symbol = Symbol('__prisma_next_brand__');
3
5
 
4
- export interface ContractBase {
5
- readonly schemaVersion: string;
6
- readonly target: string;
7
- readonly targetFamily: string;
8
- readonly coreHash: string;
9
- readonly profileHash?: string;
10
- readonly capabilities: Record<string, Record<string, boolean>>;
11
- readonly extensionPacks: Record<string, unknown>;
12
- readonly meta: Record<string, unknown>;
13
- readonly sources: Record<string, Source>;
6
+ /**
7
+ * A helper type to brand a given type with a unique identifier.
8
+ *
9
+ * @template TKey Text used as the brand key.
10
+ * @template TValue Optional value associated with the brand key. Defaults to `true`.
11
+ */
12
+ export type Brand<TKey extends string | number | symbol, TValue = true> = {
13
+ [$]: {
14
+ [K in TKey]: TValue;
15
+ };
16
+ };
17
+
18
+ /**
19
+ * Base type for storage contract hashes.
20
+ * Emitted contract.d.ts files use this with the hash value as a type parameter:
21
+ * `type StorageHash = StorageHashBase<'sha256:abc123...'>`
22
+ */
23
+ export type StorageHashBase<THash extends string> = THash & Brand<'StorageHash'>;
24
+
25
+ /**
26
+ * Base type for execution contract hashes.
27
+ * Emitted contract.d.ts files use this with the hash value as a type parameter:
28
+ * `type ExecutionHash = ExecutionHashBase<'sha256:def456...'>`
29
+ */
30
+ export type ExecutionHashBase<THash extends string> = THash & Brand<'ExecutionHash'>;
31
+
32
+ export function executionHash<const T extends string>(value: T): ExecutionHashBase<T> {
33
+ return value as ExecutionHashBase<T>;
34
+ }
35
+
36
+ export function coreHash<const T extends string>(value: T): StorageHashBase<T> {
37
+ return value as StorageHashBase<T>;
38
+ }
39
+
40
+ /**
41
+ * Base type for profile contract hashes.
42
+ * Emitted contract.d.ts files use this with the hash value as a type parameter:
43
+ * `type ProfileHash = ProfileHashBase<'sha256:def456...'>`
44
+ */
45
+ export type ProfileHashBase<THash extends string> = THash & Brand<'ProfileHash'>;
46
+
47
+ export function profileHash<const T extends string>(value: T): ProfileHashBase<T> {
48
+ return value as ProfileHashBase<T>;
49
+ }
50
+
51
+ /**
52
+ * Base type for family-specific storage blocks.
53
+ * Family storage types (SqlStorage, MongoStorage, etc.) extend this to carry the
54
+ * storage hash alongside family-specific data (tables, collections, etc.).
55
+ */
56
+ export interface StorageBase<THash extends string = string> {
57
+ readonly storageHash: StorageHashBase<THash>;
14
58
  }
15
59
 
16
60
  export interface FieldType {
@@ -20,6 +64,48 @@ export interface FieldType {
20
64
  readonly properties?: Record<string, FieldType>;
21
65
  }
22
66
 
67
+ export type GeneratedValueSpec = {
68
+ readonly id: string;
69
+ readonly params?: Record<string, unknown>;
70
+ };
71
+
72
+ export type JsonPrimitive = string | number | boolean | null;
73
+
74
+ export type JsonValue =
75
+ | JsonPrimitive
76
+ | { readonly [key: string]: JsonValue }
77
+ | readonly JsonValue[];
78
+
79
+ export type ColumnDefaultLiteralValue = JsonValue;
80
+
81
+ export type ColumnDefaultLiteralInputValue = ColumnDefaultLiteralValue | Date;
82
+
83
+ export type ColumnDefault =
84
+ | {
85
+ readonly kind: 'literal';
86
+ readonly value: ColumnDefaultLiteralInputValue;
87
+ }
88
+ | { readonly kind: 'function'; readonly expression: string };
89
+
90
+ export type ExecutionMutationDefaultValue = {
91
+ readonly kind: 'generator';
92
+ readonly id: GeneratedValueSpec['id'];
93
+ readonly params?: Record<string, unknown>;
94
+ };
95
+
96
+ export type ExecutionMutationDefault = {
97
+ readonly ref: { readonly table: string; readonly column: string };
98
+ readonly onCreate?: ExecutionMutationDefaultValue;
99
+ readonly onUpdate?: ExecutionMutationDefaultValue;
100
+ };
101
+
102
+ export type ExecutionSection<THash extends string = string> = {
103
+ readonly executionHash: ExecutionHashBase<THash>;
104
+ readonly mutations: {
105
+ readonly defaults: ReadonlyArray<ExecutionMutationDefault>;
106
+ };
107
+ };
108
+
23
109
  export interface Source {
24
110
  readonly readOnly: boolean;
25
111
  readonly projection: Record<string, FieldType>;
@@ -42,25 +128,13 @@ export type Expr =
42
128
  export interface DocCollection {
43
129
  readonly name: string;
44
130
  readonly id?: {
45
- readonly strategy: 'auto' | 'client' | 'uuid' | 'cuid' | 'objectId';
131
+ readonly strategy: 'auto' | 'client' | 'uuid' | 'objectId';
46
132
  };
47
133
  readonly fields: Record<string, FieldType>;
48
134
  readonly indexes?: ReadonlyArray<DocIndex>;
49
135
  readonly readOnly?: boolean;
50
136
  }
51
137
 
52
- export interface DocumentStorage {
53
- readonly document: {
54
- readonly collections: Record<string, DocCollection>;
55
- };
56
- }
57
-
58
- export interface DocumentContract extends ContractBase {
59
- // Accept string to work with JSON imports; runtime validation ensures 'document'
60
- readonly targetFamily: string;
61
- readonly storage: DocumentStorage;
62
- }
63
-
64
138
  // Plan types - target-family agnostic execution types
65
139
  export interface ParamDescriptor {
66
140
  readonly index?: number;
@@ -68,7 +142,7 @@ export interface ParamDescriptor {
68
142
  readonly codecId?: string;
69
143
  readonly nativeType?: string;
70
144
  readonly nullable?: boolean;
71
- readonly source: 'dsl' | 'raw';
145
+ readonly source: 'dsl' | 'raw' | 'lane';
72
146
  readonly refs?: { table: string; column: string };
73
147
  }
74
148
 
@@ -85,7 +159,7 @@ export interface PlanRefs {
85
159
  export interface PlanMeta {
86
160
  readonly target: string;
87
161
  readonly targetFamily?: string;
88
- readonly coreHash: string;
162
+ readonly storageHash: string;
89
163
  readonly profileHash?: string;
90
164
  readonly lane: string;
91
165
  readonly annotations?: {
@@ -134,24 +208,12 @@ export interface ExecutionPlan<Row = unknown, Ast = unknown> {
134
208
  export type ResultType<P> =
135
209
  P extends ExecutionPlan<infer R, unknown> ? R : P extends { readonly _Row?: infer R } ? R : never;
136
210
 
137
- /**
138
- * Type guard to check if a contract is a Document contract
139
- */
140
- export function isDocumentContract(contract: unknown): contract is DocumentContract {
141
- return (
142
- typeof contract === 'object' &&
143
- contract !== null &&
144
- 'targetFamily' in contract &&
145
- contract.targetFamily === 'document'
146
- );
147
- }
148
-
149
211
  /**
150
212
  * Contract marker record stored in the database.
151
213
  * Represents the current contract identity for a database.
152
214
  */
153
215
  export interface ContractMarkerRecord {
154
- readonly coreHash: string;
216
+ readonly storageHash: string;
155
217
  readonly profileHash: string;
156
218
  readonly contractJson: unknown | null;
157
219
  readonly canonicalVersion: number | null;
@@ -159,84 +221,3 @@ export interface ContractMarkerRecord {
159
221
  readonly appTag: string | null;
160
222
  readonly meta: Record<string, unknown>;
161
223
  }
162
-
163
- // Emitter types - moved from @prisma-next/emitter to shared location
164
- /**
165
- * Specifies how to import TypeScript types from a package.
166
- * Used in extension pack manifests to declare codec and operation type imports.
167
- */
168
- export interface TypesImportSpec {
169
- readonly package: string;
170
- readonly named: string;
171
- readonly alias: string;
172
- }
173
-
174
- /**
175
- * Validation context passed to TargetFamilyHook.validateTypes().
176
- * Contains pre-assembled operation registry, type imports, and extension IDs.
177
- */
178
- export interface ValidationContext {
179
- readonly operationRegistry?: OperationRegistry;
180
- readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
181
- readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
182
- readonly extensionIds?: ReadonlyArray<string>;
183
- }
184
-
185
- /**
186
- * SPI interface for target family hooks that extend emission behavior.
187
- * Implemented by family-specific emitter hooks (e.g., SQL family).
188
- */
189
- export interface TargetFamilyHook {
190
- readonly id: string;
191
-
192
- /**
193
- * Validates that all type IDs in the contract come from referenced extension packs.
194
- * @param ir - Contract IR to validate
195
- * @param ctx - Validation context with operation registry and extension IDs
196
- */
197
- validateTypes(ir: ContractIR, ctx: ValidationContext): void;
198
-
199
- /**
200
- * Validates family-specific contract structure.
201
- * @param ir - Contract IR to validate
202
- */
203
- validateStructure(ir: ContractIR): void;
204
-
205
- /**
206
- * Generates contract.d.ts file content.
207
- * @param ir - Contract IR
208
- * @param codecTypeImports - Array of codec type import specs
209
- * @param operationTypeImports - Array of operation type import specs
210
- * @returns Generated TypeScript type definitions as string
211
- */
212
- generateContractTypes(
213
- ir: ContractIR,
214
- codecTypeImports: ReadonlyArray<TypesImportSpec>,
215
- operationTypeImports: ReadonlyArray<TypesImportSpec>,
216
- ): string;
217
- }
218
-
219
- // Extension pack manifest types - moved from @prisma-next/core-control-plane to shared location
220
- export type ArgSpecManifest =
221
- | { readonly kind: 'typeId'; readonly type: string }
222
- | { readonly kind: 'param' }
223
- | { readonly kind: 'literal' };
224
-
225
- export type ReturnSpecManifest =
226
- | { readonly kind: 'typeId'; readonly type: string }
227
- | { readonly kind: 'builtin'; readonly type: 'number' | 'boolean' | 'string' };
228
-
229
- export interface LoweringSpecManifest {
230
- readonly targetFamily: 'sql';
231
- readonly strategy: 'infix' | 'function';
232
- readonly template: string;
233
- }
234
-
235
- export interface OperationManifest {
236
- readonly for: string;
237
- readonly method: string;
238
- readonly args: ReadonlyArray<ArgSpecManifest>;
239
- readonly returns: ReturnSpecManifest;
240
- readonly lowering: LoweringSpecManifest;
241
- readonly capabilities?: ReadonlyArray<string>;
242
- }
@@ -0,0 +1,101 @@
1
+ import { type } from 'arktype';
2
+ import type { Contract } from './contract-types';
3
+ import type { DomainContractShape } from './validate-domain';
4
+ import { validateContractDomain } from './validate-domain';
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
+
19
+ /**
20
+ * Family-provided storage validator.
21
+ * SQL validates tables/columns/FKs; Mongo validates collections/embedding.
22
+ */
23
+ export type StorageValidator = (contract: Contract) => void;
24
+
25
+ const ContractSchema = type({
26
+ target: 'string',
27
+ targetFamily: 'string',
28
+ roots: 'Record<string, string>',
29
+ models: 'Record<string, unknown>',
30
+ 'valueObjects?': 'Record<string, unknown>',
31
+ storage: 'Record<string, unknown>',
32
+ capabilities: 'Record<string, Record<string, boolean>>',
33
+ extensionPacks: 'Record<string, unknown>',
34
+ meta: 'Record<string, unknown>',
35
+ 'execution?': {
36
+ 'executionHash?': 'string',
37
+ mutations: {
38
+ defaults: 'unknown[]',
39
+ },
40
+ },
41
+ profileHash: 'string',
42
+ });
43
+
44
+ function stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {
45
+ const { schemaVersion: _, _generated: _g, ...rest } = raw;
46
+ return rest;
47
+ }
48
+
49
+ function extractDomainShape(contract: Contract): DomainContractShape {
50
+ return {
51
+ roots: contract.roots,
52
+ models: contract.models,
53
+ ...(contract.valueObjects ? { valueObjects: contract.valueObjects } : {}),
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Framework-level contract validation (ADR 182).
59
+ *
60
+ * Three-pass validation:
61
+ * 1. **Structural validation** (arktype): verifies required fields exist with
62
+ * correct base types.
63
+ * 2. **Domain validation** (framework-owned): roots, relation targets,
64
+ * variant/base consistency, discriminators, ownership, orphans.
65
+ * 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
66
+ * Mongo validates collections/embedding.
67
+ *
68
+ * JSON persistence fields (`schemaVersion`, `_generated`) are stripped before
69
+ * validation — they are not part of the in-memory contract representation.
70
+ *
71
+ * @template TContract The fully-typed contract type (preserves literal types).
72
+ * @param value Raw contract value (e.g. parsed from JSON).
73
+ * @param storageValidator Family-specific storage validation function.
74
+ * @returns The validated contract with full literal types.
75
+ */
76
+ export function validateContract<TContract extends Contract>(
77
+ value: unknown,
78
+ storageValidator: StorageValidator,
79
+ ): TContract {
80
+ if (typeof value !== 'object' || value === null) {
81
+ throw new ContractValidationError('Contract must be a non-null object', 'structural');
82
+ }
83
+
84
+ const stripped = stripPersistenceFields(value as Record<string, unknown>);
85
+
86
+ const parsed = ContractSchema(stripped);
87
+ if (parsed instanceof type.errors) {
88
+ throw new ContractValidationError(
89
+ `Invalid contract structure: ${parsed.summary}`,
90
+ 'structural',
91
+ );
92
+ }
93
+
94
+ const contract = parsed as unknown as Contract;
95
+
96
+ validateContractDomain(extractDomainShape(contract));
97
+
98
+ storageValidator(contract);
99
+
100
+ return contract as unknown as TContract;
101
+ }
@@ -0,0 +1,227 @@
1
+ import { ContractValidationError } from './validate-contract';
2
+
3
+ export interface DomainModelShape {
4
+ readonly fields: Record<string, unknown>;
5
+ readonly relations?: Record<string, { readonly to: string }>;
6
+ readonly discriminator?: { readonly field: string };
7
+ readonly variants?: Record<string, unknown>;
8
+ readonly base?: string;
9
+ readonly owner?: string;
10
+ }
11
+
12
+ export interface DomainContractShape {
13
+ readonly roots: Record<string, string>;
14
+ readonly models: Record<string, DomainModelShape>;
15
+ readonly valueObjects?: Record<string, { readonly fields: Record<string, unknown> }>;
16
+ }
17
+
18
+ export function validateContractDomain(contract: DomainContractShape): void {
19
+ const errors: string[] = [];
20
+ const modelNames = new Set(Object.keys(contract.models));
21
+
22
+ validateRoots(contract, modelNames, errors);
23
+ validateVariantsAndBases(contract, modelNames, errors);
24
+ validateRelationTargets(contract, modelNames, errors);
25
+ validateDiscriminators(contract, errors);
26
+ validateOwnership(contract, modelNames, errors);
27
+ validateValueObjectReferences(contract, errors);
28
+ validateFieldModifiers(contract, errors);
29
+
30
+ if (errors.length > 0) {
31
+ throw new ContractValidationError(
32
+ `Contract domain validation failed:\n- ${errors.join('\n- ')}`,
33
+ 'domain',
34
+ );
35
+ }
36
+ }
37
+
38
+ function validateRoots(
39
+ contract: DomainContractShape,
40
+ modelNames: Set<string>,
41
+ errors: string[],
42
+ ): void {
43
+ const seenValues = new Set<string>();
44
+ for (const [rootKey, modelName] of Object.entries(contract.roots)) {
45
+ if (seenValues.has(modelName)) {
46
+ errors.push(`Duplicate root value: "${modelName}" is mapped by multiple root keys`);
47
+ }
48
+ seenValues.add(modelName);
49
+
50
+ if (!modelNames.has(modelName)) {
51
+ errors.push(
52
+ `Root "${rootKey}" references model "${modelName}" which does not exist in models`,
53
+ );
54
+ }
55
+ }
56
+ }
57
+
58
+ function validateVariantsAndBases(
59
+ contract: DomainContractShape,
60
+ modelNames: Set<string>,
61
+ errors: string[],
62
+ ): void {
63
+ const models = new Map(Object.entries(contract.models));
64
+
65
+ for (const [modelName, model] of models) {
66
+ if (model.variants) {
67
+ for (const variantName of Object.keys(model.variants)) {
68
+ if (!modelNames.has(variantName)) {
69
+ errors.push(
70
+ `Model "${modelName}" lists variant "${variantName}" which does not exist in models`,
71
+ );
72
+ continue;
73
+ }
74
+ const variantModel = models.get(variantName);
75
+ if (!variantModel) continue;
76
+ if (variantModel.base !== modelName) {
77
+ errors.push(
78
+ `Variant "${variantName}" has base "${variantModel.base ?? '(none)'}" but expected "${modelName}"`,
79
+ );
80
+ }
81
+ }
82
+ }
83
+
84
+ if (model.base) {
85
+ if (!modelNames.has(model.base)) {
86
+ errors.push(`Model "${modelName}" has base "${model.base}" which does not exist in models`);
87
+ continue;
88
+ }
89
+ const baseModel = models.get(model.base);
90
+ if (!baseModel) continue;
91
+ if (!baseModel.variants || !Object.hasOwn(baseModel.variants, modelName)) {
92
+ errors.push(
93
+ `Model "${modelName}" has base "${model.base}" which does not list it as a variant`,
94
+ );
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ function validateRelationTargets(
101
+ contract: DomainContractShape,
102
+ modelNames: Set<string>,
103
+ errors: string[],
104
+ ): void {
105
+ for (const [modelName, model] of Object.entries(contract.models)) {
106
+ for (const [relName, relation] of Object.entries(model.relations ?? {})) {
107
+ if (!modelNames.has(relation.to)) {
108
+ errors.push(
109
+ `Relation "${relName}" on model "${modelName}" targets "${relation.to}" which does not exist in models`,
110
+ );
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ function validateDiscriminators(contract: DomainContractShape, errors: string[]): void {
117
+ for (const [modelName, model] of Object.entries(contract.models)) {
118
+ if (model.discriminator) {
119
+ if (!model.variants || Object.keys(model.variants).length === 0) {
120
+ errors.push(`Model "${modelName}" has discriminator but no variants`);
121
+ }
122
+ if (!Object.hasOwn(model.fields, model.discriminator.field)) {
123
+ errors.push(
124
+ `Discriminator field "${model.discriminator.field}" is not a field on model "${modelName}"`,
125
+ );
126
+ }
127
+ }
128
+
129
+ if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) {
130
+ errors.push(`Model "${modelName}" has variants but no discriminator`);
131
+ }
132
+
133
+ if (model.base) {
134
+ if (model.discriminator) {
135
+ errors.push(`Model "${modelName}" has base and must not have discriminator`);
136
+ }
137
+ if (model.variants && Object.keys(model.variants).length > 0) {
138
+ errors.push(`Model "${modelName}" has base and must not have variants`);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ function validateOwnership(
145
+ contract: DomainContractShape,
146
+ modelNames: Set<string>,
147
+ errors: string[],
148
+ ): void {
149
+ for (const [modelName, model] of Object.entries(contract.models)) {
150
+ if (!model.owner) continue;
151
+
152
+ if (model.owner === modelName) {
153
+ errors.push(`Model "${modelName}" cannot own itself`);
154
+ }
155
+
156
+ if (!modelNames.has(model.owner)) {
157
+ errors.push(`Model "${modelName}" has owner "${model.owner}" which does not exist in models`);
158
+ }
159
+
160
+ for (const [rootKey, rootModel] of Object.entries(contract.roots)) {
161
+ if (rootModel === modelName) {
162
+ errors.push(
163
+ `Owned model "${modelName}" must not appear in roots (found as root "${rootKey}")`,
164
+ );
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ interface FieldTypeLike {
171
+ readonly kind?: string;
172
+ readonly name?: string;
173
+ readonly members?: readonly FieldTypeLike[];
174
+ }
175
+
176
+ interface FieldLike {
177
+ readonly type?: FieldTypeLike;
178
+ readonly many?: boolean;
179
+ readonly dict?: boolean;
180
+ }
181
+
182
+ function forEachContractField(
183
+ contract: DomainContractShape,
184
+ callback: (field: unknown, location: string) => void,
185
+ ): void {
186
+ for (const [modelName, model] of Object.entries(contract.models)) {
187
+ for (const [fieldName, field] of Object.entries(model.fields)) {
188
+ callback(field, `Model "${modelName}" field "${fieldName}"`);
189
+ }
190
+ }
191
+ for (const [voName, vo] of Object.entries(contract.valueObjects ?? {})) {
192
+ for (const [fieldName, field] of Object.entries(vo.fields)) {
193
+ callback(field, `Value object "${voName}" field "${fieldName}"`);
194
+ }
195
+ }
196
+ }
197
+
198
+ function validateValueObjectReferences(contract: DomainContractShape, errors: string[]): void {
199
+ const voNames = new Set(Object.keys(contract.valueObjects ?? {}));
200
+
201
+ function checkType(type: FieldTypeLike | undefined, location: string): void {
202
+ if (!type) return;
203
+ if (type.kind === 'valueObject' && type.name && !voNames.has(type.name)) {
204
+ errors.push(
205
+ `${location} references value object "${type.name}" which does not exist in valueObjects`,
206
+ );
207
+ return;
208
+ }
209
+ if (type.kind === 'union') {
210
+ for (const member of type.members ?? []) checkType(member, location);
211
+ }
212
+ }
213
+
214
+ forEachContractField(contract, (field, location) => {
215
+ const f = field as FieldLike | undefined;
216
+ checkType(f?.type, location);
217
+ });
218
+ }
219
+
220
+ function validateFieldModifiers(contract: DomainContractShape, errors: string[]): void {
221
+ forEachContractField(contract, (field, location) => {
222
+ const f = field as FieldLike | undefined;
223
+ if (f?.many && f?.dict) {
224
+ errors.push(`${location} cannot have both "many" and "dict" modifiers`);
225
+ }
226
+ });
227
+ }
@@ -1,3 +0,0 @@
1
- export type { AdapterDescriptor, AdapterInstance, AdapterPackRef, ComponentDescriptor, ComponentMetadata, ContractComponentRequirementsCheckInput, ContractComponentRequirementsCheckResult, DriverDescriptor, DriverInstance, DriverPackRef, ExtensionDescriptor, ExtensionInstance, ExtensionPackRef, FamilyDescriptor, FamilyInstance, PackRefBase, TargetBoundComponentDescriptor, TargetDescriptor, TargetInstance, TargetPackRef, } from '../framework-components';
2
- export { checkContractComponentRequirements } from '../framework-components';
3
- //# sourceMappingURL=framework-components.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"framework-components.d.ts","sourceRoot":"","sources":["../../src/exports/framework-components.ts"],"names":[],"mappings":"AAAA,YAAY,EAEV,iBAAiB,EAEjB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uCAAuC,EACvC,wCAAwC,EACxC,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,8BAA8B,EAC9B,gBAAgB,EAChB,cAAc,EACd,aAAa,GACd,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,kCAAkC,EAAE,MAAM,yBAAyB,CAAC"}
@@ -1,24 +0,0 @@
1
- // src/framework-components.ts
2
- function checkContractComponentRequirements(input) {
3
- const providedIds = /* @__PURE__ */ new Set();
4
- for (const id of input.providedComponentIds) {
5
- providedIds.add(id);
6
- }
7
- const requiredExtensionPackIds = input.contract.extensionPacks ? Object.keys(input.contract.extensionPacks) : [];
8
- const missingExtensionPackIds = requiredExtensionPackIds.filter((id) => !providedIds.has(id));
9
- const expectedTargetFamily = input.expectedTargetFamily;
10
- const contractTargetFamily = input.contract.targetFamily;
11
- const familyMismatch = expectedTargetFamily !== void 0 && contractTargetFamily !== void 0 && contractTargetFamily !== expectedTargetFamily ? { expected: expectedTargetFamily, actual: contractTargetFamily } : void 0;
12
- const expectedTargetId = input.expectedTargetId;
13
- const contractTargetId = input.contract.target;
14
- const targetMismatch = expectedTargetId !== void 0 && contractTargetId !== expectedTargetId ? { expected: expectedTargetId, actual: contractTargetId } : void 0;
15
- return {
16
- ...familyMismatch ? { familyMismatch } : {},
17
- ...targetMismatch ? { targetMismatch } : {},
18
- missingExtensionPackIds
19
- };
20
- }
21
- export {
22
- checkContractComponentRequirements
23
- };
24
- //# sourceMappingURL=framework-components.js.map