@prisma-next/contract 0.12.0-dev.60 → 0.12.0-dev.61

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.
@@ -74,7 +74,10 @@ function validateVariantsAndBases(modelIndex, errors) {
74
74
  }
75
75
  }
76
76
  function validateRelationTargets(modelIndex, errors) {
77
- for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) for (const [relName, relation] of Object.entries(model.relations ?? {})) if (!lookupModel(modelIndex, relation.to)) errors.push(`Relation "${relName}" on model "${namespaceId}:${modelName}" targets "${relation.to.namespace}:${relation.to.model}" which does not exist in domain.namespaces`);
77
+ for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) for (const [relName, relation] of Object.entries(model.relations ?? {})) {
78
+ if (relation.to.space !== void 0) continue;
79
+ if (!lookupModel(modelIndex, relation.to)) errors.push(`Relation "${relName}" on model "${namespaceId}:${modelName}" targets "${relation.to.namespace}:${relation.to.model}" which does not exist in domain.namespaces`);
80
+ }
78
81
  }
79
82
  function validateDiscriminators(modelIndex, errors) {
80
83
  for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {
@@ -1 +1 @@
1
- {"version":3,"file":"validate-domain.mjs","names":["f"],"sources":["../src/validate-domain.ts"],"sourcesContent":["import { ContractValidationError } from './contract-validation-error';\nimport type { CrossReference } from './cross-reference';\nimport type { ContractWithDomain } from './domain-envelope';\nimport { asNamespaceId, type NamespaceId } from './namespace-id';\n\nexport interface DomainModelShape {\n readonly fields: Record<string, unknown>;\n readonly relations?: Record<string, { readonly to: CrossReference }>;\n readonly discriminator?: { readonly field: string };\n readonly variants?: Record<string, unknown>;\n readonly base?: CrossReference;\n readonly owner?: string;\n}\n\nexport interface DomainContractShape extends ContractWithDomain {\n readonly roots: Record<string, CrossReference>;\n}\n\ninterface IndexedModel {\n readonly namespaceId: NamespaceId;\n readonly name: string;\n readonly model: DomainModelShape;\n}\n\ntype ModelIndex = Map<NamespaceId, Map<string, IndexedModel>>;\n\nfunction indexDomainModels(contract: DomainContractShape): ModelIndex {\n const index: ModelIndex = new Map();\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n let modelsInNamespace = index.get(namespaceId);\n if (modelsInNamespace === undefined) {\n modelsInNamespace = new Map();\n index.set(namespaceId, modelsInNamespace);\n }\n for (const [name, model] of Object.entries(namespace.models)) {\n modelsInNamespace.set(name, { namespaceId, name, model });\n }\n }\n return index;\n}\n\nfunction lookupModel(index: ModelIndex, ref: CrossReference): IndexedModel | undefined {\n return index.get(ref.namespace)?.get(ref.model);\n}\n\nfunction* iterateIndexedModels(index: ModelIndex): IterableIterator<IndexedModel> {\n for (const modelsInNamespace of index.values()) {\n for (const entry of modelsInNamespace.values()) {\n yield entry;\n }\n }\n}\n\nexport function validateContractDomain(contract: DomainContractShape): void {\n const errors: string[] = [];\n const modelIndex = indexDomainModels(contract);\n\n validateRoots(contract, modelIndex, errors);\n validateVariantsAndBases(modelIndex, errors);\n validateRelationTargets(modelIndex, errors);\n validateDiscriminators(modelIndex, errors);\n validateOwnership(contract, modelIndex, errors);\n validateValueObjectReferences(contract, errors);\n validateFieldModifiers(modelIndex, contract, 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 modelIndex: ModelIndex,\n errors: string[],\n): void {\n const seenRootTargets = new Map<NamespaceId, Set<string>>();\n for (const [rootKey, crossRef] of Object.entries(contract.roots)) {\n let modelsInNamespace = seenRootTargets.get(crossRef.namespace);\n if (modelsInNamespace === undefined) {\n modelsInNamespace = new Set();\n seenRootTargets.set(crossRef.namespace, modelsInNamespace);\n }\n if (modelsInNamespace.has(crossRef.model)) {\n errors.push(\n `Duplicate root value: \"${crossRef.namespace}:${crossRef.model}\" is mapped by multiple root keys`,\n );\n }\n modelsInNamespace.add(crossRef.model);\n\n if (!lookupModel(modelIndex, crossRef)) {\n errors.push(\n `Root \"${rootKey}\" references model \"${crossRef.namespace}:${crossRef.model}\" which does not exist in domain.namespaces`,\n );\n }\n }\n}\n\nfunction validateVariantsAndBases(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (model.variants) {\n for (const variantName of Object.keys(model.variants)) {\n const variantRef: CrossReference = { namespace: namespaceId, model: variantName };\n const variantEntry = lookupModel(modelIndex, variantRef);\n if (!variantEntry) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" lists variant \"${variantName}\" which does not exist at that namespace coordinate`,\n );\n continue;\n }\n const variantBase = variantEntry.model.base;\n if (variantBase?.namespace !== namespaceId || variantBase?.model !== modelName) {\n errors.push(\n `Variant \"${namespaceId}:${variantName}\" has base \"${variantBase?.namespace ?? '?'}:${variantBase?.model ?? '(none)'}\" but expected \"${namespaceId}:${modelName}\"`,\n );\n }\n }\n }\n\n if (model.base) {\n const baseEntry = lookupModel(modelIndex, model.base);\n if (!baseEntry) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has base \"${model.base.namespace}:${model.base.model}\" which does not exist in domain.namespaces`,\n );\n continue;\n }\n if (!baseEntry.model.variants || !Object.hasOwn(baseEntry.model.variants, modelName)) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has base \"${model.base.namespace}:${model.base.model}\" which does not list it as a variant`,\n );\n }\n }\n }\n}\n\nfunction validateRelationTargets(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n for (const [relName, relation] of Object.entries(model.relations ?? {})) {\n if (!lookupModel(modelIndex, relation.to)) {\n errors.push(\n `Relation \"${relName}\" on model \"${namespaceId}:${modelName}\" targets \"${relation.to.namespace}:${relation.to.model}\" which does not exist in domain.namespaces`,\n );\n }\n }\n }\n}\n\nfunction validateDiscriminators(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (model.discriminator) {\n if (!model.variants || Object.keys(model.variants).length === 0) {\n errors.push(`Model \"${namespaceId}:${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 \"${namespaceId}:${modelName}\"`,\n );\n }\n }\n\n if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has variants but no discriminator`);\n }\n\n if (model.base) {\n if (model.discriminator) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has base and must not have discriminator`);\n }\n if (model.variants && Object.keys(model.variants).length > 0) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has base and must not have variants`);\n }\n }\n }\n}\n\nfunction validateOwnership(\n contract: DomainContractShape,\n modelIndex: ModelIndex,\n errors: string[],\n): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (!model.owner) continue;\n\n if (model.owner === modelName) {\n errors.push(`Model \"${namespaceId}:${modelName}\" cannot own itself`);\n }\n\n const ownerRef: CrossReference = { namespace: namespaceId, model: model.owner };\n if (!lookupModel(modelIndex, ownerRef)) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has owner \"${namespaceId}:${model.owner}\" which does not exist in domain.namespaces`,\n );\n }\n\n for (const [rootKey, rootRef] of Object.entries(contract.roots)) {\n if (rootRef.namespace === namespaceId && rootRef.model === modelName) {\n errors.push(\n `Owned model \"${namespaceId}:${modelName}\" must not appear in roots (found as root \"${rootKey}\")`,\n );\n }\n }\n }\n}\n\ninterface FieldTypeLike {\n readonly kind?: string;\n readonly name?: string;\n readonly members?: readonly FieldTypeLike[];\n}\n\ninterface FieldLike {\n readonly type?: FieldTypeLike;\n readonly many?: boolean;\n readonly dict?: boolean;\n}\n\nfunction validateValueObjectReferences(contract: DomainContractShape, errors: string[]): void {\n const voNamesByNamespace = new Map<NamespaceId, Set<string>>();\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n voNamesByNamespace.set(namespaceId, new Set(Object.keys(namespace.valueObjects ?? {})));\n }\n\n function checkType(\n type: FieldTypeLike | undefined,\n location: string,\n namespaceId: NamespaceId,\n ): void {\n if (!type) return;\n const voNames = voNamesByNamespace.get(namespaceId) ?? new Set<string>();\n if (type.kind === 'valueObject' && type.name && !voNames.has(type.name)) {\n errors.push(\n `${location} references value object \"${namespaceId}:${type.name}\" which does not exist in that namespace's valueObjects`,\n );\n return;\n }\n if (type.kind === 'union') {\n for (const member of type.members ?? []) checkType(member, location, namespaceId);\n }\n }\n\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n for (const [modelName, model] of Object.entries(namespace.models)) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const f = field as FieldLike | undefined;\n checkType(f?.type, `Model \"${namespaceId}:${modelName}\" field \"${fieldName}\"`, namespaceId);\n }\n }\n for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) {\n for (const [fieldName, field] of Object.entries(vo.fields)) {\n const f = field as FieldLike | undefined;\n checkType(\n f?.type,\n `Value object \"${namespaceId}:${voName}\" field \"${fieldName}\"`,\n namespaceId,\n );\n }\n }\n }\n}\n\nfunction validateFieldModifiers(\n modelIndex: ModelIndex,\n contract: DomainContractShape,\n errors: string[],\n): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const f = field as FieldLike | undefined;\n if (f?.many && f?.dict) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" field \"${fieldName}\" cannot have both \"many\" and \"dict\" modifiers`,\n );\n }\n }\n }\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) {\n for (const [fieldName, field] of Object.entries(vo.fields)) {\n const f = field as FieldLike | undefined;\n if (f?.many && f?.dict) {\n errors.push(\n `Value object \"${namespaceId}:${voName}\" field \"${fieldName}\" cannot have both \"many\" and \"dict\" modifiers`,\n );\n }\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate-domain.mjs","names":["f"],"sources":["../src/validate-domain.ts"],"sourcesContent":["import { ContractValidationError } from './contract-validation-error';\nimport type { CrossReference } from './cross-reference';\nimport type { ContractWithDomain } from './domain-envelope';\nimport { asNamespaceId, type NamespaceId } from './namespace-id';\n\nexport interface DomainModelShape {\n readonly fields: Record<string, unknown>;\n readonly relations?: Record<string, { readonly to: CrossReference }>;\n readonly discriminator?: { readonly field: string };\n readonly variants?: Record<string, unknown>;\n readonly base?: CrossReference;\n readonly owner?: string;\n}\n\nexport interface DomainContractShape extends ContractWithDomain {\n readonly roots: Record<string, CrossReference>;\n}\n\ninterface IndexedModel {\n readonly namespaceId: NamespaceId;\n readonly name: string;\n readonly model: DomainModelShape;\n}\n\ntype ModelIndex = Map<NamespaceId, Map<string, IndexedModel>>;\n\nfunction indexDomainModels(contract: DomainContractShape): ModelIndex {\n const index: ModelIndex = new Map();\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n let modelsInNamespace = index.get(namespaceId);\n if (modelsInNamespace === undefined) {\n modelsInNamespace = new Map();\n index.set(namespaceId, modelsInNamespace);\n }\n for (const [name, model] of Object.entries(namespace.models)) {\n modelsInNamespace.set(name, { namespaceId, name, model });\n }\n }\n return index;\n}\n\nfunction lookupModel(index: ModelIndex, ref: CrossReference): IndexedModel | undefined {\n return index.get(ref.namespace)?.get(ref.model);\n}\n\nfunction* iterateIndexedModels(index: ModelIndex): IterableIterator<IndexedModel> {\n for (const modelsInNamespace of index.values()) {\n for (const entry of modelsInNamespace.values()) {\n yield entry;\n }\n }\n}\n\nexport function validateContractDomain(contract: DomainContractShape): void {\n const errors: string[] = [];\n const modelIndex = indexDomainModels(contract);\n\n validateRoots(contract, modelIndex, errors);\n validateVariantsAndBases(modelIndex, errors);\n validateRelationTargets(modelIndex, errors);\n validateDiscriminators(modelIndex, errors);\n validateOwnership(contract, modelIndex, errors);\n validateValueObjectReferences(contract, errors);\n validateFieldModifiers(modelIndex, contract, 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 modelIndex: ModelIndex,\n errors: string[],\n): void {\n const seenRootTargets = new Map<NamespaceId, Set<string>>();\n for (const [rootKey, crossRef] of Object.entries(contract.roots)) {\n let modelsInNamespace = seenRootTargets.get(crossRef.namespace);\n if (modelsInNamespace === undefined) {\n modelsInNamespace = new Set();\n seenRootTargets.set(crossRef.namespace, modelsInNamespace);\n }\n if (modelsInNamespace.has(crossRef.model)) {\n errors.push(\n `Duplicate root value: \"${crossRef.namespace}:${crossRef.model}\" is mapped by multiple root keys`,\n );\n }\n modelsInNamespace.add(crossRef.model);\n\n if (!lookupModel(modelIndex, crossRef)) {\n errors.push(\n `Root \"${rootKey}\" references model \"${crossRef.namespace}:${crossRef.model}\" which does not exist in domain.namespaces`,\n );\n }\n }\n}\n\nfunction validateVariantsAndBases(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (model.variants) {\n for (const variantName of Object.keys(model.variants)) {\n const variantRef: CrossReference = { namespace: namespaceId, model: variantName };\n const variantEntry = lookupModel(modelIndex, variantRef);\n if (!variantEntry) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" lists variant \"${variantName}\" which does not exist at that namespace coordinate`,\n );\n continue;\n }\n const variantBase = variantEntry.model.base;\n if (variantBase?.namespace !== namespaceId || variantBase?.model !== modelName) {\n errors.push(\n `Variant \"${namespaceId}:${variantName}\" has base \"${variantBase?.namespace ?? '?'}:${variantBase?.model ?? '(none)'}\" but expected \"${namespaceId}:${modelName}\"`,\n );\n }\n }\n }\n\n if (model.base) {\n const baseEntry = lookupModel(modelIndex, model.base);\n if (!baseEntry) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has base \"${model.base.namespace}:${model.base.model}\" which does not exist in domain.namespaces`,\n );\n continue;\n }\n if (!baseEntry.model.variants || !Object.hasOwn(baseEntry.model.variants, modelName)) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has base \"${model.base.namespace}:${model.base.model}\" which does not list it as a variant`,\n );\n }\n }\n }\n}\n\nfunction validateRelationTargets(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n for (const [relName, relation] of Object.entries(model.relations ?? {})) {\n // Cross-space relations (relation.to.space is defined) target a model in a\n // different contract space. The local domain index only contains this\n // contract's own models, so the target is intentionally absent here.\n // Existence of the target is verified by the aggregate verifier at\n // deploy time (after the spaces are composed), not at parse time.\n if (relation.to.space !== undefined) continue;\n if (!lookupModel(modelIndex, relation.to)) {\n errors.push(\n `Relation \"${relName}\" on model \"${namespaceId}:${modelName}\" targets \"${relation.to.namespace}:${relation.to.model}\" which does not exist in domain.namespaces`,\n );\n }\n }\n }\n}\n\nfunction validateDiscriminators(modelIndex: ModelIndex, errors: string[]): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (model.discriminator) {\n if (!model.variants || Object.keys(model.variants).length === 0) {\n errors.push(`Model \"${namespaceId}:${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 \"${namespaceId}:${modelName}\"`,\n );\n }\n }\n\n if (model.variants && Object.keys(model.variants).length > 0 && !model.discriminator) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has variants but no discriminator`);\n }\n\n if (model.base) {\n if (model.discriminator) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has base and must not have discriminator`);\n }\n if (model.variants && Object.keys(model.variants).length > 0) {\n errors.push(`Model \"${namespaceId}:${modelName}\" has base and must not have variants`);\n }\n }\n }\n}\n\nfunction validateOwnership(\n contract: DomainContractShape,\n modelIndex: ModelIndex,\n errors: string[],\n): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n if (!model.owner) continue;\n\n if (model.owner === modelName) {\n errors.push(`Model \"${namespaceId}:${modelName}\" cannot own itself`);\n }\n\n const ownerRef: CrossReference = { namespace: namespaceId, model: model.owner };\n if (!lookupModel(modelIndex, ownerRef)) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" has owner \"${namespaceId}:${model.owner}\" which does not exist in domain.namespaces`,\n );\n }\n\n for (const [rootKey, rootRef] of Object.entries(contract.roots)) {\n if (rootRef.namespace === namespaceId && rootRef.model === modelName) {\n errors.push(\n `Owned model \"${namespaceId}:${modelName}\" must not appear in roots (found as root \"${rootKey}\")`,\n );\n }\n }\n }\n}\n\ninterface FieldTypeLike {\n readonly kind?: string;\n readonly name?: string;\n readonly members?: readonly FieldTypeLike[];\n}\n\ninterface FieldLike {\n readonly type?: FieldTypeLike;\n readonly many?: boolean;\n readonly dict?: boolean;\n}\n\nfunction validateValueObjectReferences(contract: DomainContractShape, errors: string[]): void {\n const voNamesByNamespace = new Map<NamespaceId, Set<string>>();\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n voNamesByNamespace.set(namespaceId, new Set(Object.keys(namespace.valueObjects ?? {})));\n }\n\n function checkType(\n type: FieldTypeLike | undefined,\n location: string,\n namespaceId: NamespaceId,\n ): void {\n if (!type) return;\n const voNames = voNamesByNamespace.get(namespaceId) ?? new Set<string>();\n if (type.kind === 'valueObject' && type.name && !voNames.has(type.name)) {\n errors.push(\n `${location} references value object \"${namespaceId}:${type.name}\" which does not exist in that namespace's valueObjects`,\n );\n return;\n }\n if (type.kind === 'union') {\n for (const member of type.members ?? []) checkType(member, location, namespaceId);\n }\n }\n\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n for (const [modelName, model] of Object.entries(namespace.models)) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const f = field as FieldLike | undefined;\n checkType(f?.type, `Model \"${namespaceId}:${modelName}\" field \"${fieldName}\"`, namespaceId);\n }\n }\n for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) {\n for (const [fieldName, field] of Object.entries(vo.fields)) {\n const f = field as FieldLike | undefined;\n checkType(\n f?.type,\n `Value object \"${namespaceId}:${voName}\" field \"${fieldName}\"`,\n namespaceId,\n );\n }\n }\n }\n}\n\nfunction validateFieldModifiers(\n modelIndex: ModelIndex,\n contract: DomainContractShape,\n errors: string[],\n): void {\n for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const f = field as FieldLike | undefined;\n if (f?.many && f?.dict) {\n errors.push(\n `Model \"${namespaceId}:${modelName}\" field \"${fieldName}\" cannot have both \"many\" and \"dict\" modifiers`,\n );\n }\n }\n }\n for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) {\n const namespaceId = asNamespaceId(namespaceKey);\n for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) {\n for (const [fieldName, field] of Object.entries(vo.fields)) {\n const f = field as FieldLike | undefined;\n if (f?.many && f?.dict) {\n errors.push(\n `Value object \"${namespaceId}:${voName}\" field \"${fieldName}\" cannot have both \"many\" and \"dict\" modifiers`,\n );\n }\n }\n }\n }\n}\n"],"mappings":";;;AA0BA,SAAS,kBAAkB,UAA2C;CACpE,MAAM,wBAAoB,IAAI,IAAI;CAClC,KAAK,MAAM,CAAC,cAAc,cAAc,OAAO,QAAQ,SAAS,OAAO,UAAU,GAAG;EAClF,MAAM,cAAc,cAAc,YAAY;EAC9C,IAAI,oBAAoB,MAAM,IAAI,WAAW;EAC7C,IAAI,sBAAsB,KAAA,GAAW;GACnC,oCAAoB,IAAI,IAAI;GAC5B,MAAM,IAAI,aAAa,iBAAiB;EAC1C;EACA,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,UAAU,MAAM,GACzD,kBAAkB,IAAI,MAAM;GAAE;GAAa;GAAM;EAAM,CAAC;CAE5D;CACA,OAAO;AACT;AAEA,SAAS,YAAY,OAAmB,KAA+C;CACrF,OAAO,MAAM,IAAI,IAAI,SAAS,CAAC,EAAE,IAAI,IAAI,KAAK;AAChD;AAEA,UAAU,qBAAqB,OAAmD;CAChF,KAAK,MAAM,qBAAqB,MAAM,OAAO,GAC3C,KAAK,MAAM,SAAS,kBAAkB,OAAO,GAC3C,MAAM;AAGZ;AAEA,SAAgB,uBAAuB,UAAqC;CAC1E,MAAM,SAAmB,CAAC;CAC1B,MAAM,aAAa,kBAAkB,QAAQ;CAE7C,cAAc,UAAU,YAAY,MAAM;CAC1C,yBAAyB,YAAY,MAAM;CAC3C,wBAAwB,YAAY,MAAM;CAC1C,uBAAuB,YAAY,MAAM;CACzC,kBAAkB,UAAU,YAAY,MAAM;CAC9C,8BAA8B,UAAU,MAAM;CAC9C,uBAAuB,YAAY,UAAU,MAAM;CAEnD,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,wBACR,yCAAyC,OAAO,KAAK,MAAM,KAC3D,QACF;AAEJ;AAEA,SAAS,cACP,UACA,YACA,QACM;CACN,MAAM,kCAAkB,IAAI,IAA8B;CAC1D,KAAK,MAAM,CAAC,SAAS,aAAa,OAAO,QAAQ,SAAS,KAAK,GAAG;EAChE,IAAI,oBAAoB,gBAAgB,IAAI,SAAS,SAAS;EAC9D,IAAI,sBAAsB,KAAA,GAAW;GACnC,oCAAoB,IAAI,IAAI;GAC5B,gBAAgB,IAAI,SAAS,WAAW,iBAAiB;EAC3D;EACA,IAAI,kBAAkB,IAAI,SAAS,KAAK,GACtC,OAAO,KACL,0BAA0B,SAAS,UAAU,GAAG,SAAS,MAAM,kCACjE;EAEF,kBAAkB,IAAI,SAAS,KAAK;EAEpC,IAAI,CAAC,YAAY,YAAY,QAAQ,GACnC,OAAO,KACL,SAAS,QAAQ,sBAAsB,SAAS,UAAU,GAAG,SAAS,MAAM,4CAC9E;CAEJ;AACF;AAEA,SAAS,yBAAyB,YAAwB,QAAwB;CAChF,KAAK,MAAM,EAAE,aAAa,MAAM,WAAW,WAAW,qBAAqB,UAAU,GAAG;EACtF,IAAI,MAAM,UACR,KAAK,MAAM,eAAe,OAAO,KAAK,MAAM,QAAQ,GAAG;GAErD,MAAM,eAAe,YAAY,YAAY;IADR,WAAW;IAAa,OAAO;GACd,CAAC;GACvD,IAAI,CAAC,cAAc;IACjB,OAAO,KACL,UAAU,YAAY,GAAG,UAAU,mBAAmB,YAAY,oDACpE;IACA;GACF;GACA,MAAM,cAAc,aAAa,MAAM;GACvC,IAAI,aAAa,cAAc,eAAe,aAAa,UAAU,WACnE,OAAO,KACL,YAAY,YAAY,GAAG,YAAY,cAAc,aAAa,aAAa,IAAI,GAAG,aAAa,SAAS,SAAS,kBAAkB,YAAY,GAAG,UAAU,EAClK;EAEJ;EAGF,IAAI,MAAM,MAAM;GACd,MAAM,YAAY,YAAY,YAAY,MAAM,IAAI;GACpD,IAAI,CAAC,WAAW;IACd,OAAO,KACL,UAAU,YAAY,GAAG,UAAU,cAAc,MAAM,KAAK,UAAU,GAAG,MAAM,KAAK,MAAM,4CAC5F;IACA;GACF;GACA,IAAI,CAAC,UAAU,MAAM,YAAY,CAAC,OAAO,OAAO,UAAU,MAAM,UAAU,SAAS,GACjF,OAAO,KACL,UAAU,YAAY,GAAG,UAAU,cAAc,MAAM,KAAK,UAAU,GAAG,MAAM,KAAK,MAAM,sCAC5F;EAEJ;CACF;AACF;AAEA,SAAS,wBAAwB,YAAwB,QAAwB;CAC/E,KAAK,MAAM,EAAE,aAAa,MAAM,WAAW,WAAW,qBAAqB,UAAU,GACnF,KAAK,MAAM,CAAC,SAAS,aAAa,OAAO,QAAQ,MAAM,aAAa,CAAC,CAAC,GAAG;EAMvE,IAAI,SAAS,GAAG,UAAU,KAAA,GAAW;EACrC,IAAI,CAAC,YAAY,YAAY,SAAS,EAAE,GACtC,OAAO,KACL,aAAa,QAAQ,cAAc,YAAY,GAAG,UAAU,aAAa,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,4CACtH;CAEJ;AAEJ;AAEA,SAAS,uBAAuB,YAAwB,QAAwB;CAC9E,KAAK,MAAM,EAAE,aAAa,MAAM,WAAW,WAAW,qBAAqB,UAAU,GAAG;EACtF,IAAI,MAAM,eAAe;GACvB,IAAI,CAAC,MAAM,YAAY,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,WAAW,GAC5D,OAAO,KAAK,UAAU,YAAY,GAAG,UAAU,oCAAoC;GAErF,IAAI,CAAC,OAAO,OAAO,MAAM,QAAQ,MAAM,cAAc,KAAK,GACxD,OAAO,KACL,wBAAwB,MAAM,cAAc,MAAM,6BAA6B,YAAY,GAAG,UAAU,EAC1G;EAEJ;EAEA,IAAI,MAAM,YAAY,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,SAAS,KAAK,CAAC,MAAM,eACrE,OAAO,KAAK,UAAU,YAAY,GAAG,UAAU,oCAAoC;EAGrF,IAAI,MAAM,MAAM;GACd,IAAI,MAAM,eACR,OAAO,KAAK,UAAU,YAAY,GAAG,UAAU,2CAA2C;GAE5F,IAAI,MAAM,YAAY,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,SAAS,GACzD,OAAO,KAAK,UAAU,YAAY,GAAG,UAAU,sCAAsC;EAEzF;CACF;AACF;AAEA,SAAS,kBACP,UACA,YACA,QACM;CACN,KAAK,MAAM,EAAE,aAAa,MAAM,WAAW,WAAW,qBAAqB,UAAU,GAAG;EACtF,IAAI,CAAC,MAAM,OAAO;EAElB,IAAI,MAAM,UAAU,WAClB,OAAO,KAAK,UAAU,YAAY,GAAG,UAAU,oBAAoB;EAIrE,IAAI,CAAC,YAAY,YAAY;GADM,WAAW;GAAa,OAAO,MAAM;EACpC,CAAC,GACnC,OAAO,KACL,UAAU,YAAY,GAAG,UAAU,eAAe,YAAY,GAAG,MAAM,MAAM,4CAC/E;EAGF,KAAK,MAAM,CAAC,SAAS,YAAY,OAAO,QAAQ,SAAS,KAAK,GAC5D,IAAI,QAAQ,cAAc,eAAe,QAAQ,UAAU,WACzD,OAAO,KACL,gBAAgB,YAAY,GAAG,UAAU,6CAA6C,QAAQ,GAChG;CAGN;AACF;AAcA,SAAS,8BAA8B,UAA+B,QAAwB;CAC5F,MAAM,qCAAqB,IAAI,IAA8B;CAC7D,KAAK,MAAM,CAAC,cAAc,cAAc,OAAO,QAAQ,SAAS,OAAO,UAAU,GAAG;EAClF,MAAM,cAAc,cAAc,YAAY;EAC9C,mBAAmB,IAAI,aAAa,IAAI,IAAI,OAAO,KAAK,UAAU,gBAAgB,CAAC,CAAC,CAAC,CAAC;CACxF;CAEA,SAAS,UACP,MACA,UACA,aACM;EACN,IAAI,CAAC,MAAM;EACX,MAAM,UAAU,mBAAmB,IAAI,WAAW,qBAAK,IAAI,IAAY;EACvE,IAAI,KAAK,SAAS,iBAAiB,KAAK,QAAQ,CAAC,QAAQ,IAAI,KAAK,IAAI,GAAG;GACvE,OAAO,KACL,GAAG,SAAS,4BAA4B,YAAY,GAAG,KAAK,KAAK,wDACnE;GACA;EACF;EACA,IAAI,KAAK,SAAS,SAChB,KAAK,MAAM,UAAU,KAAK,WAAW,CAAC,GAAG,UAAU,QAAQ,UAAU,WAAW;CAEpF;CAEA,KAAK,MAAM,CAAC,cAAc,cAAc,OAAO,QAAQ,SAAS,OAAO,UAAU,GAAG;EAClF,MAAM,cAAc,cAAc,YAAY;EAC9C,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,UAAU,MAAM,GAC9D,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,MAAM,MAAM,GAE1D,UAAUA,OAAG,MAAM,UAAU,YAAY,GAAG,UAAU,WAAW,UAAU,IAAI,WAAW;EAG9F,KAAK,MAAM,CAAC,QAAQ,OAAO,OAAO,QAAQ,UAAU,gBAAgB,CAAC,CAAC,GACpE,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,GAAG,MAAM,GAEvD,UACEA,OAAG,MACH,iBAAiB,YAAY,GAAG,OAAO,WAAW,UAAU,IAC5D,WACF;CAGN;AACF;AAEA,SAAS,uBACP,YACA,UACA,QACM;CACN,KAAK,MAAM,EAAE,aAAa,MAAM,WAAW,WAAW,qBAAqB,UAAU,GACnF,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,MAAM,MAAM,GAAG;EAC7D,MAAM,IAAI;EACV,IAAI,GAAG,QAAQ,GAAG,MAChB,OAAO,KACL,UAAU,YAAY,GAAG,UAAU,WAAW,UAAU,+CAC1D;CAEJ;CAEF,KAAK,MAAM,CAAC,cAAc,cAAc,OAAO,QAAQ,SAAS,OAAO,UAAU,GAAG;EAClF,MAAM,cAAc,cAAc,YAAY;EAC9C,KAAK,MAAM,CAAC,QAAQ,OAAO,OAAO,QAAQ,UAAU,gBAAgB,CAAC,CAAC,GACpE,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,GAAG,MAAM,GAAG;GAC1D,MAAM,IAAI;GACV,IAAI,GAAG,QAAQ,GAAG,MAChB,OAAO,KACL,iBAAiB,YAAY,GAAG,OAAO,WAAW,UAAU,+CAC9D;EAEJ;CAEJ;AACF"}
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@prisma-next/contract",
3
- "version": "0.12.0-dev.60",
3
+ "version": "0.12.0-dev.61",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Data contract type definitions and JSON schema for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/utils": "0.12.0-dev.60",
9
+ "@prisma-next/utils": "0.12.0-dev.61",
10
10
  "@standard-schema/spec": "^1.1.0",
11
11
  "arktype": "^2.2.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@prisma-next/tsconfig": "0.12.0-dev.60",
15
- "@prisma-next/tsdown": "0.12.0-dev.60",
14
+ "@prisma-next/tsconfig": "0.12.0-dev.61",
15
+ "@prisma-next/tsdown": "0.12.0-dev.61",
16
16
  "tsdown": "0.22.1",
17
17
  "typescript": "5.9.3",
18
18
  "vitest": "4.1.7"
@@ -140,6 +140,12 @@ function validateVariantsAndBases(modelIndex: ModelIndex, errors: string[]): voi
140
140
  function validateRelationTargets(modelIndex: ModelIndex, errors: string[]): void {
141
141
  for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {
142
142
  for (const [relName, relation] of Object.entries(model.relations ?? {})) {
143
+ // Cross-space relations (relation.to.space is defined) target a model in a
144
+ // different contract space. The local domain index only contains this
145
+ // contract's own models, so the target is intentionally absent here.
146
+ // Existence of the target is verified by the aggregate verifier at
147
+ // deploy time (after the spaces are composed), not at parse time.
148
+ if (relation.to.space !== undefined) continue;
143
149
  if (!lookupModel(modelIndex, relation.to)) {
144
150
  errors.push(
145
151
  `Relation "${relName}" on model "${namespaceId}:${modelName}" targets "${relation.to.namespace}:${relation.to.model}" which does not exist in domain.namespaces`,