Skip to content

Commit 67f9537

Browse files
committed
fix(slice-0): repair typecheck against the cardinality-discriminated relation union
Three type errors surfaced by `tsc` (not by vitest) after rebasing onto main, all from the N:M discriminated-union work: - emitter: `castAs<ContractManyToManyRelation>` cannot widen a `Record<string, unknown>`; use `blindCast` (the established escape hatch, matching the sibling `to` access). - build-contract: branch relation construction on `cardinality === "N:M"` so the literal matches a single union variant instead of the wide `{ cardinality; through? }` shape that is assignable to neither. - fixture: split `ProjectBase` out of the self-referential composite-key M:N so the `() => Project` initializer self-reference (TS7022) resolves, mirroring the existing UserBase/User pattern. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
1 parent 031fc63 commit 67f9537

3 files changed

Lines changed: 46 additions & 42 deletions

File tree

packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
import type { CodecLookup } from '@prisma-next/framework-components/codec';
99
import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
1010
import { type ImportRequirement, renderImports } from '@prisma-next/ts-render';
11-
import { blindCast, castAs } from '@prisma-next/utils/casts';
11+
import { blindCast } from '@prisma-next/utils/casts';
1212
import { isSafeTypeExpression } from './type-expression-safety';
1313

1414
export function serializeValue(value: unknown): string {
@@ -150,7 +150,10 @@ export function generateModelRelationsType(relations: Record<string, unknown>):
150150
}
151151

152152
if (relObj['cardinality'] === 'N:M') {
153-
const { through } = castAs<ContractManyToManyRelation>(relObj);
153+
const { through } = blindCast<
154+
ContractManyToManyRelation,
155+
'contract JSON schema-validated before serialization; cardinality N:M check above confirms the junction variant carries through'
156+
>(relObj);
154157
const table = serializeValue(through.table);
155158
const namespaceId = serializeValue(through.namespaceId);
156159
const parentColumns = through.parentColumns.map((c) => serializeValue(c)).join(', ');

packages/2-sql/2-authoring/contract-ts/src/build-contract.ts

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -619,38 +619,40 @@ export function buildSqlContractFromDefinition(
619619
);
620620
assertTargetTableMatches(semanticModel.modelName, targetModel, relation.toTable, 'Relation');
621621

622-
if (relation.cardinality === 'N:M' && !relation.through) {
623-
throw new Error(
624-
`Relation "${semanticModel.modelName}.${relation.fieldName}" with cardinality "N:M" requires through metadata`,
625-
);
626-
}
627-
628622
const targetColumnToField = new Map(
629623
targetModel.fields.map((f) => [f.columnName, f.fieldName]),
630624
);
631625

632-
modelRelations[relation.fieldName] = {
633-
to: crossRef(
634-
relation.toModel,
635-
resolveModelNamespaceId(targetModel, modelNameToNamespaceId, defaultNamespaceId),
636-
),
637-
cardinality: relation.cardinality,
638-
on: {
639-
localFields: relation.on.parentColumns.map((col) => columnToField.get(col) ?? col),
640-
targetFields: relation.on.childColumns.map((col) => targetColumnToField.get(col) ?? col),
641-
},
642-
...(relation.through
643-
? {
644-
through: buildThroughDescriptor(
645-
relation.through,
646-
tableNamespaceByName,
647-
targetModel,
648-
semanticModel.modelName,
649-
relation.fieldName,
650-
),
651-
}
652-
: undefined),
626+
const to = crossRef(
627+
relation.toModel,
628+
resolveModelNamespaceId(targetModel, modelNameToNamespaceId, defaultNamespaceId),
629+
);
630+
const on = {
631+
localFields: relation.on.parentColumns.map((col) => columnToField.get(col) ?? col),
632+
targetFields: relation.on.childColumns.map((col) => targetColumnToField.get(col) ?? col),
653633
};
634+
635+
if (relation.cardinality === 'N:M') {
636+
if (!relation.through) {
637+
throw new Error(
638+
`Relation "${semanticModel.modelName}.${relation.fieldName}" with cardinality "N:M" requires through metadata`,
639+
);
640+
}
641+
modelRelations[relation.fieldName] = {
642+
to,
643+
cardinality: 'N:M',
644+
on,
645+
through: buildThroughDescriptor(
646+
relation.through,
647+
tableNamespaceByName,
648+
targetModel,
649+
semanticModel.modelName,
650+
relation.fieldName,
651+
),
652+
};
653+
} else {
654+
modelRelations[relation.fieldName] = { to, cardinality: relation.cardinality, on };
655+
}
654656
}
655657

656658
let namespaceModels = modelsByNamespace[namespaceId];

test/integration/test/sql-orm-client/fixtures/contract.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,23 @@ const UserRole = model('UserRole', {
9696
}))
9797
.sql({ table: 'user_roles' });
9898

99-
const Project = model('Project', {
99+
const ProjectBase = model('Project', {
100100
fields: {
101101
tenantId: field.column(int4Column).column('tenant_id'),
102102
id: field.column(int4Column),
103103
name: field.column(textColumn),
104104
},
105-
})
106-
.attributes(({ fields, constraints }) => ({
107-
id: constraints.id([fields.tenantId, fields.id]),
108-
}))
109-
.relations({
110-
related: rel.manyToMany(() => Project, {
111-
through: () => ProjectLink,
112-
from: ['srcTenantId', 'srcId'],
113-
to: ['dstTenantId', 'dstId'],
114-
}),
115-
})
116-
.sql({ table: 'projects' });
105+
}).attributes(({ fields, constraints }) => ({
106+
id: constraints.id([fields.tenantId, fields.id]),
107+
}));
108+
109+
const Project = ProjectBase.relations({
110+
related: rel.manyToMany(() => ProjectBase, {
111+
through: () => ProjectLink,
112+
from: ['srcTenantId', 'srcId'],
113+
to: ['dstTenantId', 'dstId'],
114+
}),
115+
}).sql({ table: 'projects' });
117116

118117
const ProjectLink = model('ProjectLink', {
119118
fields: {

0 commit comments

Comments
 (0)