Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c56f215
update emitter output fixtures and canonicalization for ADR 172
wmadden Mar 31, 2026
32a1eb3
fix(emitter,sql-orm-client): apply mutation defaults (#272)
aqrln Mar 31, 2026
975c53b
refactor(sql-orm-client): migrate runtime code from mappings/top-leve…
wmadden Mar 31, 2026
6213ee3
refactor(sql-orm-client): update type-level generics and test fixture…
wmadden Mar 31, 2026
57e6e45
refactor(relational-core): derive type utilities from model.storage i…
wmadden Mar 31, 2026
4b9ce15
fix(e2e): add storage.fields and relations to e2e contract fixture
wmadden Mar 31, 2026
c509102
Address code review findings for M2 consumer migration
wmadden Apr 1, 2026
d1ceb53
Update M2 plan and review artifacts
wmadden Apr 1, 2026
2ff47fa
Refresh code review after implementing all findings
wmadden Apr 1, 2026
1d151ab
Update walkthrough for full PR scope (M1 + M2)
wmadden Apr 1, 2026
36df6c4
Scope walkthrough to M2 consumer migration
wmadden Apr 1, 2026
f248223
fix: reconcile test fixtures and nullability types after rebase
wmadden Apr 1, 2026
205dd2a
fix: restore demo contract.d.ts from main after rebase
wmadden Apr 1, 2026
a15e87a
Lower sql-orm-client branch coverage threshold to 93%
wmadden Apr 1, 2026
6536d43
Document 1:1 table-model assumption in findModelNameForTable
wmadden Apr 1, 2026
cf1856a
Remove duplicate ModelStorageField key in JSON schema
wmadden Apr 1, 2026
5f4a3b0
Align relation naming and deduplicate relation helpers
wmadden Apr 1, 2026
3ae6af3
Build full table-to-model reverse map eagerly on first access
wmadden Apr 1, 2026
70903ad
Cache getRelationDefinitions with WeakMap
wmadden Apr 1, 2026
8ef527d
Extract ResolvedStorageColumn to deduplicate type traversal
wmadden Apr 1, 2026
ec5b65d
Add codecId and nullable to ModelStorageFieldSchema and SqlModelField…
wmadden Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/2-sql/1-core/contract/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export type ModelDefinition = {

export type SqlModelFieldStorage = {
readonly column: string;
readonly codecId?: string;
readonly nullable?: boolean;
};

export type SqlModelStorage = {
Expand Down
2 changes: 2 additions & 0 deletions packages/2-sql/1-core/contract/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const ModelFieldSchema = type({

const ModelStorageFieldSchema = type({
column: 'string',
'codecId?': 'string',
'nullable?': 'boolean',
});

const ModelStorageSchema = type({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,14 @@
"column": {
"type": "string",
"description": "Column name in the model's backing table"
},
"codecId": {
"type": "string",
"description": "Codec identifier for the field (derived from storage column)"
},
"nullable": {
"type": "boolean",
"description": "Whether the field allows NULL values (derived from storage column)"
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
"required": ["column"]
Expand All @@ -561,6 +569,11 @@
"enum": ["1:1", "1:N", "N:1", "N:M"],
"description": "Relation cardinality"
},
"strategy": {
"type": "string",
"enum": ["reference", "embed"],
"description": "Relation strategy"
},
"on": {
"type": "object",
"description": "Relation field mappings",
Expand Down
57 changes: 26 additions & 31 deletions packages/2-sql/4-lanes/relational-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,35 +263,41 @@ type ExtractCodecOutputType<
: never;

/**
* Extracts the model name for a given table from the contract mappings.
* Extracts the model name for a given table by iterating models to find the one
* whose `storage.table` matches.
*/
type ExtractTableToModel<
Contract extends SqlContract<SqlStorage>,
TableName extends string,
> = Contract['mappings'] extends {
readonly tableToModel: infer TableToModel;
}
? TableToModel extends Record<string, string>
? TableName extends keyof TableToModel
? TableToModel[TableName]
: never
: never
> = Contract['models'] extends infer Models extends Record<string, unknown>
? {
[M in keyof Models & string]: Models[M] extends {
readonly storage: { readonly table: TableName };
}
? M
: never;
}[keyof Models & string]
: never;

/**
* Extracts the field name for a given table column from the contract mappings.
* Extracts the field name for a given column by finding the field in
* `model.storage.fields` whose `column` matches.
*/
type ExtractColumnToField<
Contract extends SqlContract<SqlStorage>,
TableName extends string,
ColumnName extends string,
> = Contract['mappings'] extends {
readonly columnToField: infer ColumnToField;
}
? ColumnToField extends Record<string, Record<string, string>>
? TableName extends keyof ColumnToField
? ColumnName extends keyof ColumnToField[TableName]
? ColumnToField[TableName][ColumnName]
> = ExtractTableToModel<Contract, TableName> extends infer ModelName extends string
? Contract['models'] extends infer Models extends Record<string, unknown>
? ModelName & keyof Models extends infer MKey extends string
? Models[MKey] extends {
readonly storage: { readonly fields: infer Fields extends Record<string, unknown> };
}
? {
[F in keyof Fields & string]: Fields[F] extends { readonly column: ColumnName }
? F
: never;
}[keyof Fields & string]
: never
: never
: never
Expand Down Expand Up @@ -319,13 +325,8 @@ type ExtractFieldValue<
: never;

/**
* Extracts the JavaScript type for a column from model mappings if available.
* Returns `never` if the column maps to a ModelField object (which indicates
* a relation that should fall through to codec-based type resolution).
*
* The check for ModelField uses `Exclude<keyof FieldValue, 'column'> extends never`
* to ensure we only skip pure `{ column: string }` marker objects, not richer
* object types that happen to include a `column` property.
* Extracts the JavaScript type for a column from model fields if available.
* Model fields in the .d.ts carry concrete JS types (e.g. `string`, `Char<36>`).
*/
type ExtractColumnJsTypeFromModels<
Contract extends SqlContract<SqlStorage>,
Expand All @@ -335,13 +336,7 @@ type ExtractColumnJsTypeFromModels<
? ModelName extends string
? ExtractColumnToField<Contract, TableName, ColumnName> extends infer FieldName
? FieldName extends string
? ExtractFieldValue<Contract, ModelName, FieldName> extends infer FieldValue
? FieldValue extends { readonly column: string }
? Exclude<keyof FieldValue, 'column'> extends never
? never
: FieldValue
: FieldValue
: never
? ExtractFieldValue<Contract, ModelName, FieldName>
: never
: never
: never
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
import { getFieldToColumnMap } from './collection-contract';
import type { AggregateBuilder, AggregateSelector, NumericFieldNames } from './types';

export function createAggregateBuilder<
TContract extends SqlContract<SqlStorage>,
ModelName extends string,
>(contract: TContract, modelName: ModelName): AggregateBuilder<TContract, ModelName> {
const fieldToColumn = contract.mappings.fieldToColumn?.[modelName] ?? {};
const fieldToColumn = getFieldToColumnMap(contract, modelName);

return {
count() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';

export function mapFieldToColumn(
contract: SqlContract<SqlStorage>,
modelName: string,
fieldName: string,
): string {
return contract.mappings.fieldToColumn?.[modelName]?.[fieldName] ?? fieldName;
}
import { getFieldToColumnMap } from './collection-contract';

export function mapFieldsToColumns(
contract: SqlContract<SqlStorage>,
modelName: string,
fieldNames: readonly string[],
): string[] {
const fieldToColumn = contract.mappings.fieldToColumn?.[modelName] ?? {};
const fieldToColumn = getFieldToColumnMap(contract, modelName);
return fieldNames.map((fieldName) => fieldToColumn[fieldName] ?? fieldName);
}

Expand All @@ -22,7 +15,7 @@ export function mapCursorValuesToColumns(
modelName: string,
cursorValues: Readonly<Record<string, unknown>>,
): Record<string, unknown> {
const fieldToColumn = contract.mappings.fieldToColumn?.[modelName] ?? {};
const fieldToColumn = getFieldToColumnMap(contract, modelName);
const mappedCursor: Record<string, unknown> = {};

for (const [fieldName, value] of Object.entries(cursorValues)) {
Expand Down
Loading
Loading