@prisma-next/mongo-orm 0.3.0-dev.135 → 0.3.0-dev.162
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/dist/index.d.mts +240 -20
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +581 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -5
- package/src/collection-state.ts +29 -0
- package/src/collection.ts +821 -0
- package/src/compile.ts +86 -0
- package/src/executor.ts +6 -0
- package/src/exports/index.ts +26 -5
- package/src/field-accessor.ts +265 -0
- package/src/mongo-orm.ts +19 -124
- package/src/mongo-raw.ts +33 -0
- package/src/raw-collection.ts +95 -0
- package/src/types.ts +126 -41
package/src/compile.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
+
import type { MongoPipelineStage, MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';
|
|
3
|
+
import {
|
|
4
|
+
AggregateCommand,
|
|
5
|
+
MongoAndExpr,
|
|
6
|
+
MongoLimitStage,
|
|
7
|
+
MongoLookupStage,
|
|
8
|
+
MongoMatchStage,
|
|
9
|
+
MongoProjectStage,
|
|
10
|
+
MongoSkipStage,
|
|
11
|
+
MongoSortStage,
|
|
12
|
+
MongoUnwindStage,
|
|
13
|
+
} from '@prisma-next/mongo-query-ast/execution';
|
|
14
|
+
import type { MongoCollectionState, MongoIncludeExpr } from './collection-state';
|
|
15
|
+
|
|
16
|
+
function compileIncludes(includes: readonly MongoIncludeExpr[]): MongoPipelineStage[] {
|
|
17
|
+
const stages: MongoPipelineStage[] = [];
|
|
18
|
+
|
|
19
|
+
for (const inc of includes) {
|
|
20
|
+
stages.push(
|
|
21
|
+
new MongoLookupStage({
|
|
22
|
+
from: inc.from,
|
|
23
|
+
localField: inc.localField,
|
|
24
|
+
foreignField: inc.foreignField,
|
|
25
|
+
as: inc.relationName,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (inc.cardinality === 'N:1' || inc.cardinality === '1:1') {
|
|
30
|
+
stages.push(new MongoUnwindStage(`$${inc.relationName}`, true));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return stages;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function compileMongoQuery<Row = unknown>(
|
|
38
|
+
collection: string,
|
|
39
|
+
state: MongoCollectionState,
|
|
40
|
+
storageHash: string,
|
|
41
|
+
): MongoQueryPlan<Row> {
|
|
42
|
+
const stages: MongoPipelineStage[] = [];
|
|
43
|
+
|
|
44
|
+
const singleFilter = state.filters.length === 1 ? state.filters[0] : undefined;
|
|
45
|
+
if (singleFilter) {
|
|
46
|
+
stages.push(new MongoMatchStage(singleFilter));
|
|
47
|
+
} else if (state.filters.length > 1) {
|
|
48
|
+
stages.push(new MongoMatchStage(MongoAndExpr.of([...state.filters])));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (state.includes.length > 0) {
|
|
52
|
+
stages.push(...compileIncludes(state.includes));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (state.orderBy) {
|
|
56
|
+
stages.push(new MongoSortStage(state.orderBy));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (state.offset !== undefined) {
|
|
60
|
+
stages.push(new MongoSkipStage(state.offset));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (state.limit !== undefined) {
|
|
64
|
+
stages.push(new MongoLimitStage(state.limit));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (state.selectedFields && state.selectedFields.length > 0) {
|
|
68
|
+
const projection: Record<string, 0 | 1> = {};
|
|
69
|
+
for (const field of state.selectedFields) {
|
|
70
|
+
projection[field] = 1;
|
|
71
|
+
}
|
|
72
|
+
if (!Object.hasOwn(projection, '_id')) {
|
|
73
|
+
projection['_id'] = 0;
|
|
74
|
+
}
|
|
75
|
+
stages.push(new MongoProjectStage(projection));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const meta: PlanMeta = {
|
|
79
|
+
target: 'mongo',
|
|
80
|
+
storageHash,
|
|
81
|
+
lane: 'mongo-orm',
|
|
82
|
+
paramDescriptors: [],
|
|
83
|
+
};
|
|
84
|
+
const command = new AggregateCommand(collection, stages);
|
|
85
|
+
return { collection, command, meta };
|
|
86
|
+
}
|
package/src/executor.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AsyncIterableResult } from '@prisma-next/framework-components/runtime';
|
|
2
|
+
import type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';
|
|
3
|
+
|
|
4
|
+
export interface MongoQueryExecutor {
|
|
5
|
+
execute<Row>(plan: MongoQueryPlan<Row>): AsyncIterableResult<Row>;
|
|
6
|
+
}
|
package/src/exports/index.ts
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
+
export type { MongoCollection } from '../collection';
|
|
2
|
+
export { createMongoCollection } from '../collection';
|
|
3
|
+
export { compileMongoQuery } from '../compile';
|
|
4
|
+
export type { MongoQueryExecutor } from '../executor';
|
|
5
|
+
export type {
|
|
6
|
+
DotPath,
|
|
7
|
+
FieldAccessor,
|
|
8
|
+
FieldExpression,
|
|
9
|
+
FieldOperation,
|
|
10
|
+
ResolveDotPathType,
|
|
11
|
+
UpdateOperator,
|
|
12
|
+
} from '../field-accessor';
|
|
13
|
+
export { createFieldAccessor } from '../field-accessor';
|
|
14
|
+
export type { MongoOrmClient, MongoOrmOptions } from '../mongo-orm';
|
|
1
15
|
export { mongoOrm } from '../mongo-orm';
|
|
16
|
+
export type { MongoRawClient } from '../mongo-raw';
|
|
17
|
+
export { mongoRaw } from '../mongo-raw';
|
|
18
|
+
export type { RawMongoCollection } from '../raw-collection';
|
|
2
19
|
export type {
|
|
20
|
+
CreateInput,
|
|
21
|
+
DefaultModelRow,
|
|
22
|
+
IncludedRow,
|
|
3
23
|
IncludeResultFields,
|
|
4
24
|
InferFullRow,
|
|
5
25
|
InferRootRow,
|
|
6
|
-
MongoCollection,
|
|
7
|
-
MongoFindManyOptions,
|
|
8
26
|
MongoIncludeSpec,
|
|
9
|
-
MongoOrmClient,
|
|
10
|
-
MongoOrmOptions,
|
|
11
|
-
MongoQueryExecutor,
|
|
12
27
|
MongoWhereFilter,
|
|
28
|
+
NoIncludes,
|
|
29
|
+
ResolvedCreateInput,
|
|
30
|
+
SimplifyDeep,
|
|
31
|
+
VariantCreateInput,
|
|
32
|
+
VariantModelRow,
|
|
33
|
+
VariantNames,
|
|
13
34
|
} from '../types';
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { ContractField, ContractValueObject } from '@prisma-next/contract/types';
|
|
2
|
+
import type {
|
|
3
|
+
ExtractMongoCodecTypes,
|
|
4
|
+
ExtractMongoFieldOutputTypes,
|
|
5
|
+
InferModelRow,
|
|
6
|
+
MongoContract,
|
|
7
|
+
MongoContractWithTypeMaps,
|
|
8
|
+
MongoTypeMaps,
|
|
9
|
+
} from '@prisma-next/mongo-contract';
|
|
10
|
+
import type { MongoValue } from '@prisma-next/mongo-value';
|
|
11
|
+
import { MongoParamRef } from '@prisma-next/mongo-value';
|
|
12
|
+
|
|
13
|
+
// ── Runtime types ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type UpdateOperator =
|
|
16
|
+
| '$set'
|
|
17
|
+
| '$unset'
|
|
18
|
+
| '$inc'
|
|
19
|
+
| '$mul'
|
|
20
|
+
| '$push'
|
|
21
|
+
| '$pull'
|
|
22
|
+
| '$addToSet'
|
|
23
|
+
| '$pop';
|
|
24
|
+
|
|
25
|
+
export interface FieldOperation {
|
|
26
|
+
readonly operator: UpdateOperator;
|
|
27
|
+
readonly field: string;
|
|
28
|
+
readonly value: MongoValue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Compile-time types ───────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
type ScalarFieldKeys<
|
|
34
|
+
TContract extends MongoContract,
|
|
35
|
+
ModelName extends string & keyof TContract['models'],
|
|
36
|
+
> = {
|
|
37
|
+
[K in keyof TContract['models'][ModelName]['fields'] &
|
|
38
|
+
string]: TContract['models'][ModelName]['fields'][K] extends {
|
|
39
|
+
readonly type: { readonly kind: 'scalar' };
|
|
40
|
+
}
|
|
41
|
+
? K
|
|
42
|
+
: never;
|
|
43
|
+
}[keyof TContract['models'][ModelName]['fields'] & string];
|
|
44
|
+
|
|
45
|
+
type ValueObjectFieldKeys<
|
|
46
|
+
TContract extends MongoContract,
|
|
47
|
+
ModelName extends string & keyof TContract['models'],
|
|
48
|
+
> = {
|
|
49
|
+
[K in keyof TContract['models'][ModelName]['fields'] &
|
|
50
|
+
string]: TContract['models'][ModelName]['fields'][K] extends {
|
|
51
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: string };
|
|
52
|
+
}
|
|
53
|
+
? K
|
|
54
|
+
: never;
|
|
55
|
+
}[keyof TContract['models'][ModelName]['fields'] & string];
|
|
56
|
+
|
|
57
|
+
type ResolvedModelRow<
|
|
58
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
59
|
+
ModelName extends string & keyof TContract['models'],
|
|
60
|
+
> = string extends keyof ExtractMongoFieldOutputTypes<TContract>
|
|
61
|
+
? InferModelRow<TContract, ModelName>
|
|
62
|
+
: ModelName extends keyof ExtractMongoFieldOutputTypes<TContract>
|
|
63
|
+
? {
|
|
64
|
+
-readonly [K in keyof ExtractMongoFieldOutputTypes<TContract>[ModelName]]: ExtractMongoFieldOutputTypes<TContract>[ModelName][K];
|
|
65
|
+
}
|
|
66
|
+
: InferModelRow<TContract, ModelName>;
|
|
67
|
+
|
|
68
|
+
type ResolveFieldType<
|
|
69
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
70
|
+
ModelName extends string & keyof TContract['models'],
|
|
71
|
+
K extends keyof TContract['models'][ModelName]['fields'] & string,
|
|
72
|
+
> = K extends keyof ResolvedModelRow<TContract, ModelName>
|
|
73
|
+
? ResolvedModelRow<TContract, ModelName>[K]
|
|
74
|
+
: unknown;
|
|
75
|
+
|
|
76
|
+
type NumericOps = {
|
|
77
|
+
inc(value: number): FieldOperation;
|
|
78
|
+
mul(value: number): FieldOperation;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type FieldExpression<T = unknown> = {
|
|
82
|
+
set(value: T): FieldOperation;
|
|
83
|
+
unset(): FieldOperation;
|
|
84
|
+
push(value: T extends readonly (infer E)[] ? E : unknown): FieldOperation;
|
|
85
|
+
pull(match: T extends readonly (infer E)[] ? E | Partial<E> : unknown): FieldOperation;
|
|
86
|
+
addToSet(value: T extends readonly (infer E)[] ? E : unknown): FieldOperation;
|
|
87
|
+
pop(end: 1 | -1): FieldOperation;
|
|
88
|
+
} & (T extends number ? NumericOps : unknown);
|
|
89
|
+
|
|
90
|
+
type HasValueObjects = { readonly valueObjects?: Record<string, ContractValueObject> };
|
|
91
|
+
|
|
92
|
+
type VOFields<TContract extends HasValueObjects, VOName extends string> = TContract extends {
|
|
93
|
+
readonly valueObjects: infer VOs extends Record<string, ContractValueObject>;
|
|
94
|
+
}
|
|
95
|
+
? VOName extends keyof VOs
|
|
96
|
+
? VOs[VOName]['fields']
|
|
97
|
+
: never
|
|
98
|
+
: never;
|
|
99
|
+
|
|
100
|
+
type VOScalarFieldKeys<Fields extends Record<string, ContractField>> = {
|
|
101
|
+
[K in keyof Fields & string]: Fields[K] extends { readonly type: { readonly kind: 'scalar' } }
|
|
102
|
+
? K
|
|
103
|
+
: never;
|
|
104
|
+
}[keyof Fields & string];
|
|
105
|
+
|
|
106
|
+
type VOValueObjectFieldKeys<Fields extends Record<string, ContractField>> = {
|
|
107
|
+
[K in keyof Fields & string]: Fields[K] extends {
|
|
108
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: string };
|
|
109
|
+
}
|
|
110
|
+
? K
|
|
111
|
+
: never;
|
|
112
|
+
}[keyof Fields & string];
|
|
113
|
+
|
|
114
|
+
type VODotPaths<
|
|
115
|
+
TContract extends HasValueObjects,
|
|
116
|
+
Fields extends Record<string, ContractField>,
|
|
117
|
+
Prefix extends string,
|
|
118
|
+
> =
|
|
119
|
+
| { [K in VOScalarFieldKeys<Fields>]: `${Prefix}${K}` }[VOScalarFieldKeys<Fields>]
|
|
120
|
+
| {
|
|
121
|
+
[K in VOValueObjectFieldKeys<Fields>]: Fields[K] extends {
|
|
122
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string };
|
|
123
|
+
}
|
|
124
|
+
? VODotPaths<TContract, VOFields<TContract, N>, `${Prefix}${K}.`>
|
|
125
|
+
: never;
|
|
126
|
+
}[VOValueObjectFieldKeys<Fields>];
|
|
127
|
+
|
|
128
|
+
export type DotPath<
|
|
129
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
130
|
+
ModelName extends string & keyof TContract['models'],
|
|
131
|
+
> = {
|
|
132
|
+
[K in ValueObjectFieldKeys<
|
|
133
|
+
TContract,
|
|
134
|
+
ModelName
|
|
135
|
+
>]: TContract['models'][ModelName]['fields'][K] extends {
|
|
136
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string };
|
|
137
|
+
}
|
|
138
|
+
? VODotPaths<TContract, VOFields<TContract, N>, `${K}.`>
|
|
139
|
+
: never;
|
|
140
|
+
}[ValueObjectFieldKeys<TContract, ModelName>];
|
|
141
|
+
|
|
142
|
+
type ResolveDotPathInFields<
|
|
143
|
+
TContract extends HasValueObjects,
|
|
144
|
+
Fields extends Record<string, ContractField>,
|
|
145
|
+
Path extends string,
|
|
146
|
+
TCodecTypes extends Record<string, { output: unknown }>,
|
|
147
|
+
> = Path extends `${infer Head}.${infer Rest}`
|
|
148
|
+
? Head extends keyof Fields & string
|
|
149
|
+
? Fields[Head] extends {
|
|
150
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string };
|
|
151
|
+
}
|
|
152
|
+
? ResolveDotPathInFields<TContract, VOFields<TContract, N>, Rest, TCodecTypes>
|
|
153
|
+
: never
|
|
154
|
+
: never
|
|
155
|
+
: Path extends keyof Fields & string
|
|
156
|
+
? Fields[Path] extends {
|
|
157
|
+
readonly type: {
|
|
158
|
+
readonly kind: 'scalar';
|
|
159
|
+
readonly codecId: infer CId extends string & keyof TCodecTypes;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
? TCodecTypes[CId]['output']
|
|
163
|
+
: unknown
|
|
164
|
+
: never;
|
|
165
|
+
|
|
166
|
+
export type ResolveDotPathType<
|
|
167
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
168
|
+
ModelName extends string & keyof TContract['models'],
|
|
169
|
+
Path extends string,
|
|
170
|
+
TCodecTypes extends Record<string, { output: unknown }> = ExtractMongoCodecTypes<TContract>,
|
|
171
|
+
> = Path extends `${infer Head}.${infer Rest}`
|
|
172
|
+
? Head extends keyof TContract['models'][ModelName]['fields'] & string
|
|
173
|
+
? TContract['models'][ModelName]['fields'][Head] extends {
|
|
174
|
+
readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string };
|
|
175
|
+
}
|
|
176
|
+
? ResolveDotPathInFields<TContract, VOFields<TContract, N>, Rest, TCodecTypes>
|
|
177
|
+
: never
|
|
178
|
+
: never
|
|
179
|
+
: never;
|
|
180
|
+
|
|
181
|
+
export type FieldAccessor<
|
|
182
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
183
|
+
ModelName extends string & keyof TContract['models'],
|
|
184
|
+
> = {
|
|
185
|
+
readonly [K in ScalarFieldKeys<TContract, ModelName>]: FieldExpression<
|
|
186
|
+
ResolveFieldType<TContract, ModelName, K>
|
|
187
|
+
>;
|
|
188
|
+
} & {
|
|
189
|
+
readonly [K in ValueObjectFieldKeys<TContract, ModelName>]: FieldExpression<
|
|
190
|
+
ResolveFieldType<TContract, ModelName, K>
|
|
191
|
+
>;
|
|
192
|
+
} & (<P extends DotPath<TContract, ModelName>>(
|
|
193
|
+
path: P,
|
|
194
|
+
) => FieldExpression<ResolveDotPathType<TContract, ModelName, P>>);
|
|
195
|
+
|
|
196
|
+
// ── Runtime implementation ───────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
// Runtime expression has all methods; type-level gating happens via FieldExpression<T>
|
|
199
|
+
interface RuntimeFieldExpression extends NumericOps {
|
|
200
|
+
set(value: unknown): FieldOperation;
|
|
201
|
+
unset(): FieldOperation;
|
|
202
|
+
push(value: unknown): FieldOperation;
|
|
203
|
+
pull(match: unknown): FieldOperation;
|
|
204
|
+
addToSet(value: unknown): FieldOperation;
|
|
205
|
+
pop(end: 1 | -1): FieldOperation;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function createFieldExpression(fieldPath: string): RuntimeFieldExpression {
|
|
209
|
+
return {
|
|
210
|
+
set(value: unknown): FieldOperation {
|
|
211
|
+
return { operator: '$set', field: fieldPath, value: new MongoParamRef(value) };
|
|
212
|
+
},
|
|
213
|
+
unset(): FieldOperation {
|
|
214
|
+
return { operator: '$unset', field: fieldPath, value: new MongoParamRef('') };
|
|
215
|
+
},
|
|
216
|
+
inc(value: number): FieldOperation {
|
|
217
|
+
return { operator: '$inc', field: fieldPath, value: new MongoParamRef(value) };
|
|
218
|
+
},
|
|
219
|
+
mul(value: number): FieldOperation {
|
|
220
|
+
return { operator: '$mul', field: fieldPath, value: new MongoParamRef(value) };
|
|
221
|
+
},
|
|
222
|
+
push(value: unknown): FieldOperation {
|
|
223
|
+
return { operator: '$push', field: fieldPath, value: new MongoParamRef(value) };
|
|
224
|
+
},
|
|
225
|
+
pull(match: unknown): FieldOperation {
|
|
226
|
+
return { operator: '$pull', field: fieldPath, value: new MongoParamRef(match) };
|
|
227
|
+
},
|
|
228
|
+
addToSet(value: unknown): FieldOperation {
|
|
229
|
+
return { operator: '$addToSet', field: fieldPath, value: new MongoParamRef(value) };
|
|
230
|
+
},
|
|
231
|
+
pop(end: 1 | -1): FieldOperation {
|
|
232
|
+
return { operator: '$pop', field: fieldPath, value: new MongoParamRef(end) };
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createFieldAccessor<
|
|
238
|
+
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
239
|
+
ModelName extends string & keyof TContract['models'],
|
|
240
|
+
>(): FieldAccessor<TContract, ModelName> {
|
|
241
|
+
return new Proxy((() => {}) as unknown as FieldAccessor<TContract, ModelName>, {
|
|
242
|
+
get(_target, prop: string): RuntimeFieldExpression {
|
|
243
|
+
return createFieldExpression(prop);
|
|
244
|
+
},
|
|
245
|
+
apply(_target, _thisArg, args: [string]): RuntimeFieldExpression {
|
|
246
|
+
return createFieldExpression(args[0]);
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function compileFieldOperations(
|
|
252
|
+
ops: readonly FieldOperation[],
|
|
253
|
+
wrapValue: (field: string, value: MongoValue, operator: UpdateOperator) => MongoValue,
|
|
254
|
+
): Record<string, Record<string, MongoValue>> {
|
|
255
|
+
const grouped: Record<string, Record<string, MongoValue>> = {};
|
|
256
|
+
for (const op of ops) {
|
|
257
|
+
let group = grouped[op.operator];
|
|
258
|
+
if (!group) {
|
|
259
|
+
group = {};
|
|
260
|
+
grouped[op.operator] = group;
|
|
261
|
+
}
|
|
262
|
+
group[op.field] = wrapValue(op.field, op.value, op.operator);
|
|
263
|
+
}
|
|
264
|
+
return grouped;
|
|
265
|
+
}
|
package/src/mongo-orm.ts
CHANGED
|
@@ -1,130 +1,25 @@
|
|
|
1
|
-
import type { DomainReferenceRelation, PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
-
import {
|
|
3
|
-
AggregateCommand,
|
|
4
|
-
FindCommand,
|
|
5
|
-
type MongoContract,
|
|
6
|
-
type MongoContractWithTypeMaps,
|
|
7
|
-
type MongoExpr,
|
|
8
|
-
type MongoModelDefinition,
|
|
9
|
-
type MongoQueryPlan,
|
|
10
|
-
type MongoTypeMaps,
|
|
11
|
-
} from '@prisma-next/mongo-core';
|
|
12
|
-
import type { AsyncIterableResult } from '@prisma-next/runtime-executor';
|
|
13
1
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from './
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
paramDescriptors: [],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function resolveCollection(model: MongoModelDefinition, modelName: string): string {
|
|
29
|
-
return model.storage.collection ?? modelName;
|
|
2
|
+
MongoContract,
|
|
3
|
+
MongoContractWithTypeMaps,
|
|
4
|
+
MongoTypeMaps,
|
|
5
|
+
} from '@prisma-next/mongo-contract';
|
|
6
|
+
import type { MongoCollection } from './collection';
|
|
7
|
+
import { createMongoCollection } from './collection';
|
|
8
|
+
import type { MongoQueryExecutor } from './executor';
|
|
9
|
+
|
|
10
|
+
export interface MongoOrmOptions<TContract extends MongoContract> {
|
|
11
|
+
readonly contract: TContract;
|
|
12
|
+
readonly executor: MongoQueryExecutor;
|
|
30
13
|
}
|
|
31
14
|
|
|
32
|
-
|
|
33
|
-
contract: MongoContract,
|
|
34
|
-
model: MongoModelDefinition,
|
|
35
|
-
include: Record<string, true>,
|
|
36
|
-
): Record<string, unknown>[] {
|
|
37
|
-
const stages: Record<string, unknown>[] = [];
|
|
38
|
-
|
|
39
|
-
for (const [relName, shouldInclude] of Object.entries(include)) {
|
|
40
|
-
if (!shouldInclude) continue;
|
|
41
|
-
|
|
42
|
-
const relation = model.relations?.[relName];
|
|
43
|
-
if (!relation) continue;
|
|
44
|
-
|
|
45
|
-
const targetModel = contract.models[relation.to];
|
|
46
|
-
if (!targetModel || targetModel.owner) continue;
|
|
47
|
-
|
|
48
|
-
const ref = relation as DomainReferenceRelation;
|
|
49
|
-
|
|
50
|
-
if (ref.on.localFields.length !== 1 || ref.on.targetFields.length !== 1) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Compound references are not yet supported: relation "${relName}" has ${ref.on.localFields.length} local field(s) and ${ref.on.targetFields.length} target field(s)`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const targetCollection = resolveCollection(targetModel, ref.to);
|
|
57
|
-
|
|
58
|
-
stages.push({
|
|
59
|
-
$lookup: {
|
|
60
|
-
from: targetCollection,
|
|
61
|
-
localField: ref.on.localFields[0],
|
|
62
|
-
foreignField: ref.on.targetFields[0],
|
|
63
|
-
as: relName,
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (ref.cardinality === 'N:1' || ref.cardinality === '1:1') {
|
|
68
|
-
stages.push({
|
|
69
|
-
$unwind: {
|
|
70
|
-
path: `$${relName}`,
|
|
71
|
-
preserveNullAndEmptyArrays: true,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return stages;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
class MongoCollectionImpl<
|
|
15
|
+
export type MongoOrmClient<
|
|
81
16
|
TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
constructor(contract: TContract, modelName: ModelName, executor: MongoQueryExecutor) {
|
|
89
|
-
this.#contract = contract;
|
|
90
|
-
this.#modelName = modelName;
|
|
91
|
-
this.#executor = executor;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
findMany<TInclude extends MongoIncludeSpec<TContract, ModelName> = Record<string, never>>(
|
|
95
|
-
options?: MongoFindManyOptions<TContract, ModelName, TInclude>,
|
|
96
|
-
): AsyncIterableResult<unknown> {
|
|
97
|
-
const model = this.#contract.models[this.#modelName] as MongoModelDefinition;
|
|
98
|
-
const collection = resolveCollection(model, this.#modelName as string);
|
|
99
|
-
const filter = options?.where ? (options.where as unknown as MongoExpr) : undefined;
|
|
100
|
-
const include = options?.include as Record<string, true> | undefined;
|
|
101
|
-
|
|
102
|
-
const hasIncludes = include && Object.keys(include).length > 0;
|
|
103
|
-
|
|
104
|
-
let plan: MongoQueryPlan;
|
|
105
|
-
if (hasIncludes) {
|
|
106
|
-
const pipeline: Record<string, unknown>[] = [];
|
|
107
|
-
|
|
108
|
-
if (filter && Object.keys(filter as Record<string, unknown>).length > 0) {
|
|
109
|
-
pipeline.push({ $match: filter });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
pipeline.push(...buildLookupStages(this.#contract, model, include));
|
|
113
|
-
|
|
114
|
-
plan = {
|
|
115
|
-
command: new AggregateCommand(collection, pipeline),
|
|
116
|
-
meta: stubMeta,
|
|
117
|
-
};
|
|
118
|
-
} else {
|
|
119
|
-
plan = {
|
|
120
|
-
command: new FindCommand(collection, filter),
|
|
121
|
-
meta: stubMeta,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return this.#executor.execute(plan);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
17
|
+
> = {
|
|
18
|
+
readonly [K in keyof TContract['roots']]: TContract['roots'][K] extends string &
|
|
19
|
+
keyof TContract['models']
|
|
20
|
+
? MongoCollection<TContract, TContract['roots'][K]>
|
|
21
|
+
: never;
|
|
22
|
+
};
|
|
128
23
|
|
|
129
24
|
export function mongoOrm<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(
|
|
130
25
|
options: MongoOrmOptions<TContract>,
|
|
@@ -133,7 +28,7 @@ export function mongoOrm<TContract extends MongoContractWithTypeMaps<MongoContra
|
|
|
133
28
|
const client: Record<string, unknown> = {};
|
|
134
29
|
|
|
135
30
|
for (const [rootName, modelName] of Object.entries(contract.roots)) {
|
|
136
|
-
client[rootName] =
|
|
31
|
+
client[rootName] = createMongoCollection(
|
|
137
32
|
contract,
|
|
138
33
|
modelName as string & keyof TContract['models'],
|
|
139
34
|
executor,
|
package/src/mongo-raw.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
+
import type { MongoContract, MongoModelDefinition } from '@prisma-next/mongo-contract';
|
|
3
|
+
import { createRawMongoCollection, type RawMongoCollection } from './raw-collection';
|
|
4
|
+
|
|
5
|
+
export interface MongoRawClient<TContract extends MongoContract> {
|
|
6
|
+
collection<K extends keyof TContract['roots'] & string>(rootName: K): RawMongoCollection;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function mongoRaw<TContract extends MongoContract>(options: {
|
|
10
|
+
contract: TContract;
|
|
11
|
+
}): MongoRawClient<TContract> {
|
|
12
|
+
const { contract } = options;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
collection<K extends keyof TContract['roots'] & string>(rootName: K): RawMongoCollection {
|
|
16
|
+
const modelName = contract.roots[rootName] as string;
|
|
17
|
+
if (!Object.hasOwn(contract.models, modelName)) {
|
|
18
|
+
throw new Error(`Unknown model "${modelName}" for root "${rootName}"`);
|
|
19
|
+
}
|
|
20
|
+
const model = contract.models[modelName] as MongoModelDefinition;
|
|
21
|
+
const collectionName = model.storage.collection ?? modelName;
|
|
22
|
+
|
|
23
|
+
const meta: PlanMeta = {
|
|
24
|
+
target: 'mongo',
|
|
25
|
+
storageHash: contract.storage.storageHash,
|
|
26
|
+
lane: 'mongo-raw',
|
|
27
|
+
paramDescriptors: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return createRawMongoCollection(collectionName, meta);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
+
import type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';
|
|
3
|
+
import {
|
|
4
|
+
RawAggregateCommand,
|
|
5
|
+
RawDeleteManyCommand,
|
|
6
|
+
RawDeleteOneCommand,
|
|
7
|
+
RawFindOneAndDeleteCommand,
|
|
8
|
+
RawFindOneAndUpdateCommand,
|
|
9
|
+
RawInsertManyCommand,
|
|
10
|
+
RawInsertOneCommand,
|
|
11
|
+
RawUpdateManyCommand,
|
|
12
|
+
RawUpdateOneCommand,
|
|
13
|
+
} from '@prisma-next/mongo-query-ast/execution';
|
|
14
|
+
import type { Document } from '@prisma-next/mongo-value';
|
|
15
|
+
|
|
16
|
+
interface Buildable<Row = unknown> {
|
|
17
|
+
build(): MongoQueryPlan<Row>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RawMongoCollection {
|
|
21
|
+
aggregate<Row = Record<string, unknown>>(pipeline: ReadonlyArray<Document>): Buildable<Row>;
|
|
22
|
+
|
|
23
|
+
insertOne(document: Document): Buildable;
|
|
24
|
+
insertMany(documents: ReadonlyArray<Document>): Buildable;
|
|
25
|
+
|
|
26
|
+
updateOne(filter: Document, update: Document | ReadonlyArray<Document>): Buildable;
|
|
27
|
+
|
|
28
|
+
updateMany(filter: Document, update: Document | ReadonlyArray<Document>): Buildable;
|
|
29
|
+
|
|
30
|
+
deleteOne(filter: Document): Buildable;
|
|
31
|
+
deleteMany(filter: Document): Buildable;
|
|
32
|
+
|
|
33
|
+
findOneAndUpdate(
|
|
34
|
+
filter: Document,
|
|
35
|
+
update: Document | ReadonlyArray<Document>,
|
|
36
|
+
options?: { upsert?: boolean },
|
|
37
|
+
): Buildable;
|
|
38
|
+
|
|
39
|
+
findOneAndDelete(filter: Document): Buildable;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createRawMongoCollection(
|
|
43
|
+
collectionName: string,
|
|
44
|
+
meta: PlanMeta,
|
|
45
|
+
): RawMongoCollection {
|
|
46
|
+
function buildable<Row>(command: MongoQueryPlan['command']): Buildable<Row> {
|
|
47
|
+
return {
|
|
48
|
+
build: () => ({ collection: collectionName, command, meta }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
aggregate<Row = Record<string, unknown>>(pipeline: ReadonlyArray<Document>) {
|
|
54
|
+
return buildable<Row>(new RawAggregateCommand(collectionName, pipeline));
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
insertOne(document: Document) {
|
|
58
|
+
return buildable(new RawInsertOneCommand(collectionName, document));
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
insertMany(documents: ReadonlyArray<Document>) {
|
|
62
|
+
return buildable(new RawInsertManyCommand(collectionName, documents));
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
updateOne(filter: Document, update: Document | ReadonlyArray<Document>) {
|
|
66
|
+
return buildable(new RawUpdateOneCommand(collectionName, filter, update));
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
updateMany(filter: Document, update: Document | ReadonlyArray<Document>) {
|
|
70
|
+
return buildable(new RawUpdateManyCommand(collectionName, filter, update));
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
deleteOne(filter: Document) {
|
|
74
|
+
return buildable(new RawDeleteOneCommand(collectionName, filter));
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
deleteMany(filter: Document) {
|
|
78
|
+
return buildable(new RawDeleteManyCommand(collectionName, filter));
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
findOneAndUpdate(
|
|
82
|
+
filter: Document,
|
|
83
|
+
update: Document | ReadonlyArray<Document>,
|
|
84
|
+
options?: { upsert?: boolean },
|
|
85
|
+
) {
|
|
86
|
+
return buildable(
|
|
87
|
+
new RawFindOneAndUpdateCommand(collectionName, filter, update, options?.upsert ?? false),
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
findOneAndDelete(filter: Document) {
|
|
92
|
+
return buildable(new RawFindOneAndDeleteCommand(collectionName, filter));
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|