@prisma-next/sql-orm-lane 0.3.0-dev.1 → 0.3.0-dev.10

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 (83) hide show
  1. package/README.md +2 -2
  2. package/dist/exports/orm.d.ts +3 -5
  3. package/dist/exports/orm.d.ts.map +1 -0
  4. package/dist/index.d.ts +4 -42
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/mutations/delete-builder.d.ts +9 -0
  7. package/dist/mutations/delete-builder.d.ts.map +1 -0
  8. package/dist/mutations/insert-builder.d.ts +7 -0
  9. package/dist/mutations/insert-builder.d.ts.map +1 -0
  10. package/dist/mutations/update-builder.d.ts +9 -0
  11. package/dist/mutations/update-builder.d.ts.map +1 -0
  12. package/dist/orm/builder.d.ts +38 -0
  13. package/dist/orm/builder.d.ts.map +1 -0
  14. package/dist/orm/capabilities.d.ts +3 -0
  15. package/dist/orm/capabilities.d.ts.map +1 -0
  16. package/dist/orm/context.d.ts +5 -0
  17. package/dist/orm/context.d.ts.map +1 -0
  18. package/dist/orm/state.d.ts +45 -0
  19. package/dist/orm/state.d.ts.map +1 -0
  20. package/dist/orm-include-child.d.ts +35 -0
  21. package/dist/orm-include-child.d.ts.map +1 -0
  22. package/dist/orm-relation-filter.d.ts +19 -0
  23. package/dist/orm-relation-filter.d.ts.map +1 -0
  24. package/dist/{orm-DAnGd7z2.d.ts → orm-types.d.ts} +16 -27
  25. package/dist/orm-types.d.ts.map +1 -0
  26. package/dist/orm.d.ts +5 -0
  27. package/dist/orm.d.ts.map +1 -0
  28. package/dist/plan/lowering.d.ts +2 -0
  29. package/dist/plan/lowering.d.ts.map +1 -0
  30. package/dist/plan/plan-assembly.d.ts +24 -0
  31. package/dist/plan/plan-assembly.d.ts.map +1 -0
  32. package/dist/plan/result-typing.d.ts +2 -0
  33. package/dist/plan/result-typing.d.ts.map +1 -0
  34. package/dist/relations/include-plan.d.ts +38 -0
  35. package/dist/relations/include-plan.d.ts.map +1 -0
  36. package/dist/selection/join.d.ts +3 -0
  37. package/dist/selection/join.d.ts.map +1 -0
  38. package/dist/selection/ordering.d.ts +11 -0
  39. package/dist/selection/ordering.d.ts.map +1 -0
  40. package/dist/selection/pagination.d.ts +6 -0
  41. package/dist/selection/pagination.d.ts.map +1 -0
  42. package/dist/selection/predicates.d.ts +10 -0
  43. package/dist/selection/predicates.d.ts.map +1 -0
  44. package/dist/selection/projection.d.ts +28 -0
  45. package/dist/selection/projection.d.ts.map +1 -0
  46. package/dist/selection/select-builder.d.ts +22 -0
  47. package/dist/selection/select-builder.d.ts.map +1 -0
  48. package/dist/types/internal.d.ts +4 -0
  49. package/dist/types/internal.d.ts.map +1 -0
  50. package/dist/utils/ast.d.ts +2 -0
  51. package/dist/utils/ast.d.ts.map +1 -0
  52. package/dist/utils/errors.d.ts +29 -0
  53. package/dist/utils/errors.d.ts.map +1 -0
  54. package/dist/utils/param-descriptor.d.ts +10 -0
  55. package/dist/utils/param-descriptor.d.ts.map +1 -0
  56. package/package.json +16 -16
  57. package/src/exports/orm.ts +12 -0
  58. package/src/index.ts +11 -0
  59. package/src/mutations/delete-builder.ts +87 -0
  60. package/src/mutations/insert-builder.ts +148 -0
  61. package/src/mutations/update-builder.ts +141 -0
  62. package/src/orm/builder.ts +744 -0
  63. package/src/orm/capabilities.ts +14 -0
  64. package/src/orm/context.ts +10 -0
  65. package/src/orm/state.ts +52 -0
  66. package/src/orm-include-child.ts +169 -0
  67. package/src/orm-relation-filter.ts +93 -0
  68. package/src/orm-types.ts +271 -0
  69. package/src/orm.ts +51 -0
  70. package/src/plan/lowering.ts +1 -0
  71. package/src/plan/plan-assembly.ts +297 -0
  72. package/src/plan/result-typing.ts +1 -0
  73. package/src/relations/include-plan.ts +324 -0
  74. package/src/selection/join.ts +13 -0
  75. package/src/selection/ordering.ts +52 -0
  76. package/src/selection/pagination.ts +11 -0
  77. package/src/selection/predicates.ts +120 -0
  78. package/src/selection/projection.ts +136 -0
  79. package/src/selection/select-builder.ts +82 -0
  80. package/src/types/internal.ts +3 -0
  81. package/src/utils/ast.ts +12 -0
  82. package/src/utils/errors.ts +130 -0
  83. package/src/utils/param-descriptor.ts +19 -0
@@ -0,0 +1,14 @@
1
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
2
+ import { errorIncludeCapabilitiesNotTrue, errorIncludeRequiresCapabilities } from '../utils/errors';
3
+
4
+ export function checkIncludeCapabilities(contract: SqlContract<SqlStorage>): void {
5
+ const target = contract.target;
6
+ const capabilities = contract.capabilities;
7
+ if (!capabilities || !capabilities[target]) {
8
+ errorIncludeRequiresCapabilities();
9
+ }
10
+ const targetCapabilities = capabilities[target];
11
+ if (capabilities[target]['lateral'] !== true || targetCapabilities['jsonAgg'] !== true) {
12
+ errorIncludeCapabilitiesNotTrue();
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
2
+ import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
3
+
4
+ export type OrmContext<TContract extends SqlContract<SqlStorage>> = QueryLaneContext<TContract>;
5
+
6
+ export function createOrmContext<TContract extends SqlContract<SqlStorage>>(
7
+ context: QueryLaneContext<TContract>,
8
+ ): OrmContext<TContract> {
9
+ return context;
10
+ }
@@ -0,0 +1,52 @@
1
+ import type { TableRef } from '@prisma-next/sql-relational-core/ast';
2
+ import type {
3
+ AnyBinaryBuilder,
4
+ AnyColumnBuilder,
5
+ AnyOrderBuilder,
6
+ NestedProjection,
7
+ } from '@prisma-next/sql-relational-core/types';
8
+
9
+ export interface RelationFilter {
10
+ relationName: string;
11
+ childModelName: string;
12
+ filterType: 'some' | 'none' | 'every';
13
+ childWhere: AnyBinaryBuilder | undefined;
14
+ relation: {
15
+ to: string;
16
+ cardinality: string;
17
+ on: {
18
+ parentCols: readonly string[];
19
+ childCols: readonly string[];
20
+ };
21
+ };
22
+ }
23
+
24
+ export interface OrmIncludeState {
25
+ relationName: string;
26
+ childModelName: string;
27
+ childTable: TableRef;
28
+ childWhere: AnyBinaryBuilder | undefined;
29
+ childOrderBy: AnyOrderBuilder | undefined;
30
+ childLimit: number | undefined;
31
+ childProjection: Record<string, AnyColumnBuilder | boolean | NestedProjection> | undefined;
32
+ alias: string;
33
+ relation: {
34
+ to: string;
35
+ cardinality: string;
36
+ on: {
37
+ parentCols: readonly string[];
38
+ childCols: readonly string[];
39
+ };
40
+ };
41
+ }
42
+
43
+ export interface OrmBuilderState {
44
+ table: TableRef;
45
+ wherePredicate: AnyBinaryBuilder | undefined;
46
+ relationFilters: RelationFilter[];
47
+ includes: OrmIncludeState[];
48
+ orderByExpr: AnyOrderBuilder | undefined;
49
+ limitValue: number | undefined;
50
+ offsetValue: number | undefined;
51
+ projection: Record<string, AnyColumnBuilder | boolean | NestedProjection> | undefined;
52
+ }
@@ -0,0 +1,169 @@
1
+ import { planInvalid } from '@prisma-next/plan';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
4
+ import { schema } from '@prisma-next/sql-relational-core/schema';
5
+ import type {
6
+ AnyBinaryBuilder,
7
+ AnyColumnBuilder,
8
+ AnyOrderBuilder,
9
+ InferNestedProjectionRow,
10
+ NestedProjection,
11
+ } from '@prisma-next/sql-relational-core/types';
12
+ import type { ModelColumnAccessor, OrmBuilderOptions } from './orm-types';
13
+
14
+ export interface OrmIncludeChildBuilder<
15
+ TContract extends SqlContract<SqlStorage>,
16
+ CodecTypes extends Record<string, { output: unknown }>,
17
+ ChildModelName extends string,
18
+ ChildRow = unknown,
19
+ > {
20
+ where(
21
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyBinaryBuilder,
22
+ ): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow>;
23
+ orderBy(
24
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyOrderBuilder,
25
+ ): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow>;
26
+ take(n: number): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow>;
27
+ select<Projection extends Record<string, AnyColumnBuilder | boolean | NestedProjection>>(
28
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => Projection,
29
+ ): OrmIncludeChildBuilder<
30
+ TContract,
31
+ CodecTypes,
32
+ ChildModelName,
33
+ InferNestedProjectionRow<Projection, CodecTypes>
34
+ >;
35
+ }
36
+
37
+ export class OrmIncludeChildBuilderImpl<
38
+ TContract extends SqlContract<SqlStorage>,
39
+ CodecTypes extends Record<string, { output: unknown }>,
40
+ ChildModelName extends string,
41
+ ChildRow = unknown,
42
+ > implements OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow>
43
+ {
44
+ private readonly context: QueryLaneContext<TContract>;
45
+ private readonly contract: TContract;
46
+ private readonly childModelName: ChildModelName;
47
+ private childWhere: AnyBinaryBuilder | undefined;
48
+ private childOrderBy: AnyOrderBuilder | undefined;
49
+ private childLimit: number | undefined;
50
+ private childProjection:
51
+ | Record<string, AnyColumnBuilder | boolean | NestedProjection>
52
+ | undefined = undefined;
53
+
54
+ constructor(options: OrmBuilderOptions<TContract>, childModelName: ChildModelName) {
55
+ this.context = options.context;
56
+ this.contract = options.context.contract;
57
+ this.childModelName = childModelName;
58
+ }
59
+
60
+ where(
61
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyBinaryBuilder,
62
+ ): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow> {
63
+ const builder = new OrmIncludeChildBuilderImpl<TContract, CodecTypes, ChildModelName, ChildRow>(
64
+ { context: this.context },
65
+ this.childModelName,
66
+ );
67
+ builder.childWhere = fn(this._getModelAccessor());
68
+ builder.childOrderBy = this.childOrderBy;
69
+ builder.childLimit = this.childLimit;
70
+ builder.childProjection = this.childProjection;
71
+ return builder;
72
+ }
73
+
74
+ orderBy(
75
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyOrderBuilder,
76
+ ): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow> {
77
+ const builder = new OrmIncludeChildBuilderImpl<TContract, CodecTypes, ChildModelName, ChildRow>(
78
+ { context: this.context },
79
+ this.childModelName,
80
+ );
81
+ builder.childWhere = this.childWhere;
82
+ builder.childOrderBy = fn(this._getModelAccessor());
83
+ builder.childLimit = this.childLimit;
84
+ builder.childProjection = this.childProjection;
85
+ return builder;
86
+ }
87
+
88
+ take(n: number): OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow> {
89
+ const builder = new OrmIncludeChildBuilderImpl<TContract, CodecTypes, ChildModelName, ChildRow>(
90
+ { context: this.context },
91
+ this.childModelName,
92
+ );
93
+ builder.childWhere = this.childWhere;
94
+ builder.childOrderBy = this.childOrderBy;
95
+ builder.childLimit = n;
96
+ builder.childProjection = this.childProjection;
97
+ return builder;
98
+ }
99
+
100
+ select<Projection extends Record<string, AnyColumnBuilder | boolean | NestedProjection>>(
101
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => Projection,
102
+ ): OrmIncludeChildBuilder<
103
+ TContract,
104
+ CodecTypes,
105
+ ChildModelName,
106
+ InferNestedProjectionRow<Projection, CodecTypes>
107
+ > {
108
+ const builder = new OrmIncludeChildBuilderImpl<
109
+ TContract,
110
+ CodecTypes,
111
+ ChildModelName,
112
+ InferNestedProjectionRow<Projection, CodecTypes>
113
+ >({ context: this.context }, this.childModelName);
114
+ builder.childWhere = this.childWhere;
115
+ builder.childOrderBy = this.childOrderBy;
116
+ builder.childLimit = this.childLimit;
117
+ builder.childProjection = fn(this._getModelAccessor());
118
+ return builder;
119
+ }
120
+
121
+ getState(): {
122
+ childWhere?: AnyBinaryBuilder;
123
+ childOrderBy?: AnyOrderBuilder;
124
+ childLimit?: number;
125
+ childProjection?: Record<string, AnyColumnBuilder | boolean | NestedProjection>;
126
+ } {
127
+ return {
128
+ ...(this.childWhere !== undefined ? { childWhere: this.childWhere } : {}),
129
+ ...(this.childOrderBy !== undefined ? { childOrderBy: this.childOrderBy } : {}),
130
+ ...(this.childLimit !== undefined ? { childLimit: this.childLimit } : {}),
131
+ ...(this.childProjection !== undefined ? { childProjection: this.childProjection } : {}),
132
+ };
133
+ }
134
+
135
+ private _getModelAccessor(): ModelColumnAccessor<TContract, CodecTypes, ChildModelName> {
136
+ const tableName = this.contract.mappings.modelToTable?.[this.childModelName];
137
+ if (!tableName) {
138
+ throw planInvalid(`Model ${this.childModelName} not found in mappings`);
139
+ }
140
+ const schemaHandle = schema(this.context);
141
+ const table = schemaHandle.tables[tableName];
142
+ if (!table) {
143
+ throw planInvalid(`Table ${tableName} not found in schema`);
144
+ }
145
+
146
+ const accessor = {} as ModelColumnAccessor<TContract, CodecTypes, ChildModelName>;
147
+ const model = this.contract.models[this.childModelName];
148
+ if (!model || typeof model !== 'object' || !('fields' in model)) {
149
+ throw planInvalid(`Model ${this.childModelName} does not have fields`);
150
+ }
151
+ const modelFields = model.fields as Record<string, { column?: string }>;
152
+
153
+ for (const fieldName in modelFields) {
154
+ const field = modelFields[fieldName];
155
+ if (!field) continue;
156
+ const columnName =
157
+ this.contract.mappings.fieldToColumn?.[this.childModelName]?.[fieldName] ??
158
+ field.column ??
159
+ fieldName;
160
+ const column = table.columns[columnName];
161
+ if (column) {
162
+ // ModelColumnAccessor alignment is ensured by contract validation; cast for compatibility
163
+ (accessor as Record<string, AnyColumnBuilder>)[fieldName] = column;
164
+ }
165
+ }
166
+
167
+ return accessor;
168
+ }
169
+ }
@@ -0,0 +1,93 @@
1
+ import { planInvalid } from '@prisma-next/plan';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
4
+ import { schema } from '@prisma-next/sql-relational-core/schema';
5
+ import type { AnyBinaryBuilder, AnyColumnBuilder } from '@prisma-next/sql-relational-core/types';
6
+ import type { ModelColumnAccessor, OrmBuilderOptions, OrmRelationFilterBuilder } from './orm-types';
7
+
8
+ export class OrmRelationFilterBuilderImpl<
9
+ TContract extends SqlContract<SqlStorage>,
10
+ CodecTypes extends Record<string, { output: unknown }>,
11
+ ChildModelName extends string,
12
+ > implements OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>
13
+ {
14
+ private readonly context: QueryLaneContext<TContract>;
15
+ private readonly contract: TContract;
16
+ private readonly childModelName: ChildModelName;
17
+ private wherePredicate: AnyBinaryBuilder | undefined = undefined;
18
+ private modelAccessor: ModelColumnAccessor<TContract, CodecTypes, ChildModelName> | undefined =
19
+ undefined;
20
+
21
+ constructor(options: OrmBuilderOptions<TContract>, childModelName: ChildModelName) {
22
+ this.context = options.context;
23
+ this.contract = options.context.contract;
24
+ this.childModelName = childModelName;
25
+ this.modelAccessor = this._getModelAccessor();
26
+ }
27
+
28
+ where(
29
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyBinaryBuilder,
30
+ ): OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName> {
31
+ const builder = new OrmRelationFilterBuilderImpl<TContract, CodecTypes, ChildModelName>(
32
+ { context: this.context },
33
+ this.childModelName,
34
+ );
35
+ builder.modelAccessor = this.modelAccessor;
36
+ if (this.modelAccessor) {
37
+ builder.wherePredicate = fn(this.modelAccessor);
38
+ }
39
+ return builder;
40
+ }
41
+
42
+ getWherePredicate(): AnyBinaryBuilder | undefined {
43
+ return this.wherePredicate;
44
+ }
45
+
46
+ getChildModelName(): ChildModelName {
47
+ return this.childModelName;
48
+ }
49
+
50
+ getModelAccessor(): ModelColumnAccessor<TContract, CodecTypes, ChildModelName> {
51
+ if (!this.modelAccessor) {
52
+ this.modelAccessor = this._getModelAccessor();
53
+ }
54
+ if (!this.modelAccessor) {
55
+ throw planInvalid(`Failed to get model accessor for ${this.childModelName}`);
56
+ }
57
+ return this.modelAccessor;
58
+ }
59
+
60
+ private _getModelAccessor(): ModelColumnAccessor<TContract, CodecTypes, ChildModelName> {
61
+ const tableName = this.contract.mappings.modelToTable?.[this.childModelName];
62
+ if (!tableName) {
63
+ throw planInvalid(`Model ${this.childModelName} not found in mappings`);
64
+ }
65
+ const schemaHandle = schema(this.context);
66
+ const table = schemaHandle.tables[tableName];
67
+ if (!table) {
68
+ throw planInvalid(`Table ${tableName} not found in schema`);
69
+ }
70
+
71
+ const accessor: Record<string, AnyColumnBuilder> = {};
72
+ const model = this.contract.models[this.childModelName];
73
+ if (!model || typeof model !== 'object' || !('fields' in model)) {
74
+ throw planInvalid(`Model ${this.childModelName} does not have fields`);
75
+ }
76
+ const modelFields = model.fields as Record<string, { column?: string }>;
77
+
78
+ for (const fieldName in modelFields) {
79
+ const field = modelFields[fieldName];
80
+ if (!field) continue;
81
+ const columnName =
82
+ this.contract.mappings.fieldToColumn?.[this.childModelName]?.[fieldName] ??
83
+ field.column ??
84
+ fieldName;
85
+ const column = table.columns[columnName];
86
+ if (column) {
87
+ accessor[fieldName] = column as AnyColumnBuilder;
88
+ }
89
+ }
90
+
91
+ return accessor as ModelColumnAccessor<TContract, CodecTypes, ChildModelName>;
92
+ }
93
+ }
@@ -0,0 +1,271 @@
1
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
2
+ import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
3
+ import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
4
+ import type {
5
+ AnyBinaryBuilder,
6
+ AnyColumnBuilder,
7
+ AnyOrderBuilder,
8
+ BuildOptions,
9
+ ColumnBuilder,
10
+ ComputeColumnJsType,
11
+ InferNestedProjectionRow,
12
+ NestedProjection,
13
+ } from '@prisma-next/sql-relational-core/types';
14
+ import type { OrmIncludeChildBuilder } from './orm-include-child';
15
+
16
+ export interface OrmBuilderOptions<TContract extends SqlContract<SqlStorage>> {
17
+ readonly context: QueryLaneContext<TContract>;
18
+ }
19
+
20
+ type ModelName<TContract extends SqlContract<SqlStorage>> = keyof TContract['models'] & string;
21
+
22
+ type LowercaseModelName<M extends string> = M extends `${infer First}${infer Rest}`
23
+ ? `${Lowercase<First>}${Rest}`
24
+ : M;
25
+
26
+ // Helper to get table name from model name
27
+ type ModelToTableName<
28
+ TContract extends SqlContract<SqlStorage>,
29
+ ModelName extends string,
30
+ > = TContract['mappings']['modelToTable'] extends Record<string, string>
31
+ ? TContract['mappings']['modelToTable'][ModelName] extends string
32
+ ? TContract['mappings']['modelToTable'][ModelName]
33
+ : never
34
+ : never;
35
+
36
+ // Helper to get relations for a model (via table name)
37
+ type ModelRelations<
38
+ TContract extends SqlContract<SqlStorage>,
39
+ ModelName extends string,
40
+ > = ModelToTableName<TContract, ModelName> extends string
41
+ ? TContract['relations'][ModelToTableName<TContract, ModelName>] extends Record<
42
+ string,
43
+ { to: string }
44
+ >
45
+ ? TContract['relations'][ModelToTableName<TContract, ModelName>]
46
+ : Record<string, never>
47
+ : Record<string, never>;
48
+
49
+ type ModelFieldToColumnMap<
50
+ TContract extends SqlContract<SqlStorage>,
51
+ ModelName extends string,
52
+ > = TContract['mappings']['fieldToColumn'] extends Record<string, Record<string, string>>
53
+ ? ModelName extends keyof TContract['mappings']['fieldToColumn']
54
+ ? TContract['mappings']['fieldToColumn'][ModelName]
55
+ : never
56
+ : never;
57
+
58
+ type FieldToColumnMapSafe<
59
+ TContract extends SqlContract<SqlStorage>,
60
+ ModelName extends string,
61
+ > = ModelFieldToColumnMap<TContract, ModelName> extends Record<string, string>
62
+ ? ModelFieldToColumnMap<TContract, ModelName>
63
+ : never;
64
+
65
+ type FieldColumnName<
66
+ TContract extends SqlContract<SqlStorage>,
67
+ ModelName extends string,
68
+ FieldName extends string,
69
+ > = (FieldToColumnMapSafe<TContract, ModelName> extends never
70
+ ? FieldName
71
+ : FieldName extends keyof FieldToColumnMapSafe<TContract, ModelName>
72
+ ? FieldToColumnMapSafe<TContract, ModelName>[FieldName]
73
+ : FieldName) &
74
+ string;
75
+
76
+ type ModelColumnMeta<
77
+ TContract extends SqlContract<SqlStorage>,
78
+ ModelName extends string,
79
+ ColumnName extends string,
80
+ > = ModelToTableName<TContract, ModelName> extends infer TableName extends string
81
+ ? TableName extends keyof TContract['storage']['tables']
82
+ ? ColumnName extends keyof TContract['storage']['tables'][TableName]['columns']
83
+ ? TContract['storage']['tables'][TableName]['columns'][ColumnName]
84
+ : never
85
+ : never
86
+ : never;
87
+
88
+ type _IndexKeys = string | number | symbol;
89
+
90
+ export type IncludeAccumulator<
91
+ Includes extends Record<string, unknown>,
92
+ Key extends string,
93
+ Value,
94
+ > = {
95
+ readonly [K in Exclude<keyof Includes, _IndexKeys> | Key]: K extends Key ? Value : Includes[K];
96
+ };
97
+
98
+ export type OrmRegistry<
99
+ TContract extends SqlContract<SqlStorage>,
100
+ CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,
101
+ > = {
102
+ readonly [K in ModelName<TContract>]: () => OrmModelBuilder<TContract, CodecTypes, K>;
103
+ } & {
104
+ readonly [K in ModelName<TContract> as LowercaseModelName<K>]: () => OrmModelBuilder<
105
+ TContract,
106
+ CodecTypes,
107
+ K
108
+ >;
109
+ };
110
+
111
+ // Relation filter builder - filter-only scope (no ordering/limit/select)
112
+ export interface OrmRelationFilterBuilder<
113
+ TContract extends SqlContract<SqlStorage>,
114
+ CodecTypes extends Record<string, { output: unknown }>,
115
+ ChildModelName extends string,
116
+ > {
117
+ where(
118
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ChildModelName>) => AnyBinaryBuilder,
119
+ ): OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>;
120
+ }
121
+
122
+ // Relation accessor - exposes relations with some/none/every methods
123
+ export type OrmRelationAccessor<
124
+ TContract extends SqlContract<SqlStorage>,
125
+ CodecTypes extends Record<string, { output: unknown }>,
126
+ ModelName extends string,
127
+ ChildModelName extends string,
128
+ Includes extends Record<string, unknown>,
129
+ Row,
130
+ > = {
131
+ some(
132
+ fn: (
133
+ child: OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
134
+ ) => OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
135
+ ): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
136
+ none(
137
+ fn: (
138
+ child: OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
139
+ ) => OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
140
+ ): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
141
+ every(
142
+ fn: (
143
+ child: OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
144
+ ) => OrmRelationFilterBuilder<TContract, CodecTypes, ChildModelName>,
145
+ ): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
146
+ };
147
+
148
+ // Where property - both a function and an object with related
149
+ export type OrmWhereProperty<
150
+ TContract extends SqlContract<SqlStorage>,
151
+ CodecTypes extends Record<string, { output: unknown }>,
152
+ ModelName extends string,
153
+ Includes extends Record<string, unknown>,
154
+ Row,
155
+ > = ((
156
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
157
+ ) => OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>) & {
158
+ related: ModelRelations<TContract, ModelName> extends Record<string, { to: infer To }>
159
+ ? To extends string
160
+ ? {
161
+ readonly [K in keyof ModelRelations<TContract, ModelName>]: ModelRelations<
162
+ TContract,
163
+ ModelName
164
+ >[K] extends {
165
+ to: infer ChildModelName;
166
+ }
167
+ ? ChildModelName extends string
168
+ ? OrmRelationAccessor<TContract, CodecTypes, ModelName, ChildModelName, Includes, Row>
169
+ : never
170
+ : never;
171
+ }
172
+ : Record<string, never>
173
+ : Record<string, never>;
174
+ };
175
+
176
+ // Include accessor - exposes relations with include methods
177
+ export type OrmIncludeAccessor<
178
+ TContract extends SqlContract<SqlStorage>,
179
+ CodecTypes extends Record<string, { output: unknown }>,
180
+ ModelName extends string,
181
+ Includes extends Record<string, unknown>,
182
+ Row,
183
+ > = ModelRelations<TContract, ModelName> extends Record<string, { to: infer To }>
184
+ ? To extends string
185
+ ? {
186
+ readonly [K in keyof ModelRelations<TContract, ModelName>]: ModelRelations<
187
+ TContract,
188
+ ModelName
189
+ >[K] extends {
190
+ to: infer ChildModelName;
191
+ }
192
+ ? ChildModelName extends string
193
+ ? <ChildRow>(
194
+ child: (
195
+ child: OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName>,
196
+ ) => OrmIncludeChildBuilder<TContract, CodecTypes, ChildModelName, ChildRow>,
197
+ ) => OrmModelBuilder<
198
+ TContract,
199
+ CodecTypes,
200
+ ModelName,
201
+ IncludeAccumulator<Includes, K & string, ChildRow>,
202
+ Row
203
+ >
204
+ : never
205
+ : never;
206
+ }
207
+ : Record<string, never>
208
+ : Record<string, never>;
209
+
210
+ export interface OrmModelBuilder<
211
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
212
+ CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,
213
+ ModelName extends string = string,
214
+ Includes extends Record<string, unknown> = Record<string, never>,
215
+ Row = unknown,
216
+ > {
217
+ where: OrmWhereProperty<TContract, CodecTypes, ModelName, Includes, Row>;
218
+ include: OrmIncludeAccessor<TContract, CodecTypes, ModelName, Includes, Row>;
219
+ orderBy(
220
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyOrderBuilder,
221
+ ): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
222
+ take(n: number): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
223
+ skip(n: number): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>;
224
+ select<Projection extends Record<string, AnyColumnBuilder | boolean | NestedProjection>>(
225
+ fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => Projection,
226
+ ): OrmModelBuilder<
227
+ TContract,
228
+ CodecTypes,
229
+ ModelName,
230
+ Includes,
231
+ InferNestedProjectionRow<Projection, CodecTypes, Includes>
232
+ >;
233
+ findMany(options?: BuildOptions): SqlQueryPlan<Row>;
234
+ findFirst(options?: BuildOptions): SqlQueryPlan<Row>;
235
+ findUnique(
236
+ where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
237
+ options?: BuildOptions,
238
+ ): SqlQueryPlan<Row>;
239
+ create(data: Record<string, unknown>, options?: BuildOptions): SqlQueryPlan<number>;
240
+ update(
241
+ where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
242
+ data: Record<string, unknown>,
243
+ options?: BuildOptions,
244
+ ): SqlQueryPlan<number>;
245
+ delete(
246
+ where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
247
+ options?: BuildOptions,
248
+ ): SqlQueryPlan<number>;
249
+ }
250
+
251
+ export type ModelColumnAccessor<
252
+ TContract extends SqlContract<SqlStorage>,
253
+ CodecTypes extends Record<string, { output: unknown }>,
254
+ ModelName extends string,
255
+ > = TContract['models'][ModelName] extends { fields: infer Fields }
256
+ ? Fields extends Record<string, unknown>
257
+ ? {
258
+ readonly [K in keyof Fields & string]: ColumnBuilder<
259
+ K,
260
+ ModelColumnMeta<TContract, ModelName, FieldColumnName<TContract, ModelName, K>>,
261
+ ComputeColumnJsType<
262
+ TContract,
263
+ ModelToTableName<TContract, ModelName>,
264
+ FieldColumnName<TContract, ModelName, K>,
265
+ ModelColumnMeta<TContract, ModelName, FieldColumnName<TContract, ModelName, K>>,
266
+ CodecTypes
267
+ >
268
+ >;
269
+ }
270
+ : never
271
+ : never;
package/src/orm.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { planInvalid } from '@prisma-next/plan';
2
+ import type { ExtractCodecTypes, SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import { OrmModelBuilderImpl } from './orm/builder';
4
+ import type { OrmBuilderOptions, OrmRegistry } from './orm-types';
5
+
6
+ type ModelName<TContract extends SqlContract<SqlStorage>> = keyof TContract['models'] & string;
7
+
8
+ export function orm<TContract extends SqlContract<SqlStorage>>(
9
+ options: OrmBuilderOptions<TContract>,
10
+ ): OrmRegistry<TContract, ExtractCodecTypes<TContract>> {
11
+ const contract = options.context.contract;
12
+ type CodecTypes = ExtractCodecTypes<TContract>;
13
+
14
+ return new Proxy({} as OrmRegistry<TContract, CodecTypes>, {
15
+ get(_target, prop) {
16
+ if (typeof prop !== 'string') {
17
+ return undefined;
18
+ }
19
+
20
+ const modelName = (prop.charAt(0).toUpperCase() + prop.slice(1)) as ModelName<TContract>;
21
+ if (
22
+ !contract.models ||
23
+ typeof contract.models !== 'object' ||
24
+ !(modelName in contract.models)
25
+ ) {
26
+ throw planInvalid(`Model ${prop} (resolved to ${modelName}) not found in contract`);
27
+ }
28
+
29
+ return () =>
30
+ new OrmModelBuilderImpl<TContract, CodecTypes, typeof modelName>(options, modelName);
31
+ },
32
+ has(_target, prop) {
33
+ if (typeof prop !== 'string') {
34
+ return false;
35
+ }
36
+ const modelName = (prop.charAt(0).toUpperCase() + prop.slice(1)) as ModelName<TContract>;
37
+ return contract.models && typeof contract.models === 'object' && modelName in contract.models;
38
+ },
39
+ });
40
+ }
41
+
42
+ // Re-export types for convenience
43
+ export type {
44
+ ModelColumnAccessor,
45
+ OrmBuilderOptions,
46
+ OrmModelBuilder,
47
+ OrmRegistry,
48
+ OrmRelationAccessor,
49
+ OrmRelationFilterBuilder,
50
+ OrmWhereProperty,
51
+ } from './orm-types';
@@ -0,0 +1 @@
1
+ export type {};