@prisma-next/sql-orm-lane 0.3.0-dev.3 → 0.3.0-dev.31
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.
- package/README.md +2 -2
- package/dist/{chunk-3DNKIXXB.js → chunk-C4EECZ4E.js} +106 -119
- package/dist/chunk-C4EECZ4E.js.map +1 -0
- package/dist/exports/orm.d.ts +3 -5
- package/dist/exports/orm.d.ts.map +1 -0
- package/dist/exports/orm.js +1 -1
- package/dist/index.d.ts +4 -42
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/mutations/delete-builder.d.ts +9 -0
- package/dist/mutations/delete-builder.d.ts.map +1 -0
- package/dist/mutations/insert-builder.d.ts +7 -0
- package/dist/mutations/insert-builder.d.ts.map +1 -0
- package/dist/mutations/update-builder.d.ts +9 -0
- package/dist/mutations/update-builder.d.ts.map +1 -0
- package/dist/orm/builder.d.ts +38 -0
- package/dist/orm/builder.d.ts.map +1 -0
- package/dist/orm/capabilities.d.ts +3 -0
- package/dist/orm/capabilities.d.ts.map +1 -0
- package/dist/orm/context.d.ts +5 -0
- package/dist/orm/context.d.ts.map +1 -0
- package/dist/orm/state.d.ts +45 -0
- package/dist/orm/state.d.ts.map +1 -0
- package/dist/orm-include-child.d.ts +35 -0
- package/dist/orm-include-child.d.ts.map +1 -0
- package/dist/orm-relation-filter.d.ts +19 -0
- package/dist/orm-relation-filter.d.ts.map +1 -0
- package/dist/{orm-DAnGd7z2.d.ts → orm-types.d.ts} +16 -27
- package/dist/orm-types.d.ts.map +1 -0
- package/dist/orm.d.ts +5 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/plan/lowering.d.ts +2 -0
- package/dist/plan/lowering.d.ts.map +1 -0
- package/dist/plan/plan-assembly.d.ts +24 -0
- package/dist/plan/plan-assembly.d.ts.map +1 -0
- package/dist/plan/result-typing.d.ts +2 -0
- package/dist/plan/result-typing.d.ts.map +1 -0
- package/dist/relations/include-plan.d.ts +38 -0
- package/dist/relations/include-plan.d.ts.map +1 -0
- package/dist/selection/join.d.ts +3 -0
- package/dist/selection/join.d.ts.map +1 -0
- package/dist/selection/ordering.d.ts +11 -0
- package/dist/selection/ordering.d.ts.map +1 -0
- package/dist/selection/pagination.d.ts +6 -0
- package/dist/selection/pagination.d.ts.map +1 -0
- package/dist/selection/predicates.d.ts +10 -0
- package/dist/selection/predicates.d.ts.map +1 -0
- package/dist/selection/projection.d.ts +28 -0
- package/dist/selection/projection.d.ts.map +1 -0
- package/dist/selection/select-builder.d.ts +22 -0
- package/dist/selection/select-builder.d.ts.map +1 -0
- package/dist/types/internal.d.ts +4 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/utils/ast.d.ts +2 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +29 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/param-descriptor.d.ts +10 -0
- package/dist/utils/param-descriptor.d.ts.map +1 -0
- package/package.json +21 -21
- package/src/exports/orm.ts +12 -0
- package/src/index.ts +11 -0
- package/src/mutations/delete-builder.ts +87 -0
- package/src/mutations/insert-builder.ts +148 -0
- package/src/mutations/update-builder.ts +141 -0
- package/src/orm/builder.ts +744 -0
- package/src/orm/capabilities.ts +14 -0
- package/src/orm/context.ts +10 -0
- package/src/orm/state.ts +52 -0
- package/src/orm-include-child.ts +169 -0
- package/src/orm-relation-filter.ts +93 -0
- package/src/orm-types.ts +271 -0
- package/src/orm.ts +51 -0
- package/src/plan/lowering.ts +1 -0
- package/src/plan/plan-assembly.ts +312 -0
- package/src/plan/result-typing.ts +1 -0
- package/src/relations/include-plan.ts +324 -0
- package/src/selection/join.ts +13 -0
- package/src/selection/ordering.ts +52 -0
- package/src/selection/pagination.ts +11 -0
- package/src/selection/predicates.ts +104 -0
- package/src/selection/projection.ts +142 -0
- package/src/selection/select-builder.ts +80 -0
- package/src/types/internal.ts +3 -0
- package/src/utils/ast.ts +12 -0
- package/src/utils/errors.ts +130 -0
- package/src/utils/param-descriptor.ts +19 -0
- package/dist/chunk-3DNKIXXB.js.map +0 -1
|
@@ -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
|
+
}
|
package/src/orm/state.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
2
|
+
import type {
|
|
3
|
+
AnyBinaryBuilder,
|
|
4
|
+
AnyExpressionSource,
|
|
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, AnyExpressionSource | 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, AnyExpressionSource | 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
|
+
}
|
package/src/orm-types.ts
ADDED
|
@@ -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 {};
|