@prisma-next/contract 0.3.0-pr.99.6 → 0.3.0

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 (58) 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 -2
  17. package/dist/types.mjs +2 -10
  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 +15 -8
  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 -20
  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 -248
  39. package/src/validate-contract.ts +101 -0
  40. package/src/validate-domain.ts +227 -0
  41. package/dist/framework-components-B-XOtXw3.d.mts +0 -854
  42. package/dist/framework-components-B-XOtXw3.d.mts.map +0 -1
  43. package/dist/framework-components.d.mts +0 -2
  44. package/dist/framework-components.mjs +0 -70
  45. package/dist/framework-components.mjs.map +0 -1
  46. package/dist/ir-B8zNqals.d.mts +0 -79
  47. package/dist/ir-B8zNqals.d.mts.map +0 -1
  48. package/dist/ir.d.mts +0 -2
  49. package/dist/ir.mjs +0 -46
  50. package/dist/ir.mjs.map +0 -1
  51. package/dist/pack-manifest-types.d.mts +0 -2
  52. package/dist/pack-manifest-types.mjs +0 -1
  53. package/dist/types.mjs.map +0 -1
  54. package/src/exports/framework-components.ts +0 -36
  55. package/src/exports/ir.ts +0 -1
  56. package/src/exports/pack-manifest-types.ts +0 -6
  57. package/src/framework-components.ts +0 -717
  58. package/src/ir.ts +0 -113
@@ -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
+ }