@prisma-next/mongo-query-builder 0.12.0-dev.6 → 0.12.0-dev.60
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/exports/contract-free.d.mts +78 -0
- package/dist/exports/contract-free.d.mts.map +1 -0
- package/dist/exports/contract-free.mjs +98 -0
- package/dist/exports/contract-free.mjs.map +1 -0
- package/dist/field-accessor-BmcQj0lD.d.mts +586 -0
- package/dist/field-accessor-BmcQj0lD.d.mts.map +1 -0
- package/dist/field-accessor-Dh8GUEe7.mjs +230 -0
- package/dist/field-accessor-Dh8GUEe7.mjs.map +1 -0
- package/dist/index.d.mts +4 -567
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +8 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
- package/src/builder.ts +4 -2
- package/src/contract-free/collection.ts +178 -0
- package/src/exports/contract-free.ts +6 -0
- package/src/exports/index.ts +1 -1
- package/src/field-accessor.ts +21 -0
- package/src/lookup-builder.ts +7 -3
- package/src/resolve-path.ts +2 -2
- package/src/state-classes.ts +4 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { MongoAddFieldsStage, MongoAggFieldRef, MongoExistsExpr, MongoExprFilter, MongoFieldFilter, MongoProjectStage, MongoReplaceRootStage } from "@prisma-next/mongo-query-ast/execution";
|
|
2
|
+
//#region src/update-ops.ts
|
|
3
|
+
const setOp = (path, value) => ({
|
|
4
|
+
op: "$set",
|
|
5
|
+
path,
|
|
6
|
+
value
|
|
7
|
+
});
|
|
8
|
+
const unsetOp = (path) => ({
|
|
9
|
+
op: "$unset",
|
|
10
|
+
path
|
|
11
|
+
});
|
|
12
|
+
const renameOp = (path, newName) => ({
|
|
13
|
+
op: "$rename",
|
|
14
|
+
path,
|
|
15
|
+
newName
|
|
16
|
+
});
|
|
17
|
+
const incOp = (path, amount) => ({
|
|
18
|
+
op: "$inc",
|
|
19
|
+
path,
|
|
20
|
+
amount
|
|
21
|
+
});
|
|
22
|
+
const mulOp = (path, factor) => ({
|
|
23
|
+
op: "$mul",
|
|
24
|
+
path,
|
|
25
|
+
factor
|
|
26
|
+
});
|
|
27
|
+
const minOp = (path, value) => ({
|
|
28
|
+
op: "$min",
|
|
29
|
+
path,
|
|
30
|
+
value
|
|
31
|
+
});
|
|
32
|
+
const maxOp = (path, value) => ({
|
|
33
|
+
op: "$max",
|
|
34
|
+
path,
|
|
35
|
+
value
|
|
36
|
+
});
|
|
37
|
+
const pushOp = (path, value) => ({
|
|
38
|
+
op: "$push",
|
|
39
|
+
path,
|
|
40
|
+
value
|
|
41
|
+
});
|
|
42
|
+
const addToSetOp = (path, value) => ({
|
|
43
|
+
op: "$addToSet",
|
|
44
|
+
path,
|
|
45
|
+
value
|
|
46
|
+
});
|
|
47
|
+
const popOp = (path, direction) => ({
|
|
48
|
+
op: "$pop",
|
|
49
|
+
path,
|
|
50
|
+
direction
|
|
51
|
+
});
|
|
52
|
+
const pullOp = (path, value) => ({
|
|
53
|
+
op: "$pull",
|
|
54
|
+
path,
|
|
55
|
+
value
|
|
56
|
+
});
|
|
57
|
+
const pullAllOp = (path, values) => ({
|
|
58
|
+
op: "$pullAll",
|
|
59
|
+
path,
|
|
60
|
+
values
|
|
61
|
+
});
|
|
62
|
+
const currentDateOp = (path) => ({
|
|
63
|
+
op: "$currentDate",
|
|
64
|
+
path
|
|
65
|
+
});
|
|
66
|
+
const setOnInsertOp = (path, value) => ({
|
|
67
|
+
op: "$setOnInsert",
|
|
68
|
+
path,
|
|
69
|
+
value
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Fold an array of `TypedUpdateOp` into the non-pipeline variant of
|
|
73
|
+
* `MongoUpdateSpec` (`{ $set: { … }, $inc: { … }, … }`).
|
|
74
|
+
*
|
|
75
|
+
* Throws if the same operator targets the same path twice — a clear authoring
|
|
76
|
+
* error that Mongo would otherwise silently coalesce.
|
|
77
|
+
*/
|
|
78
|
+
function foldUpdateOps(ops) {
|
|
79
|
+
const buckets = {};
|
|
80
|
+
const seen = /* @__PURE__ */ new Set();
|
|
81
|
+
const ensure = (key) => {
|
|
82
|
+
let bucket = buckets[key];
|
|
83
|
+
if (!bucket) {
|
|
84
|
+
bucket = {};
|
|
85
|
+
buckets[key] = bucket;
|
|
86
|
+
}
|
|
87
|
+
return bucket;
|
|
88
|
+
};
|
|
89
|
+
const claim = (op, path) => {
|
|
90
|
+
const k = `${op}::${path}`;
|
|
91
|
+
if (seen.has(k)) throw new Error(`Update spec collision: ${op} on '${path}' was specified more than once. Combine the operations into a single call site.`);
|
|
92
|
+
seen.add(k);
|
|
93
|
+
};
|
|
94
|
+
for (const entry of ops) {
|
|
95
|
+
claim(entry.op, entry.path);
|
|
96
|
+
switch (entry.op) {
|
|
97
|
+
case "$set":
|
|
98
|
+
case "$min":
|
|
99
|
+
case "$max":
|
|
100
|
+
case "$push":
|
|
101
|
+
case "$addToSet":
|
|
102
|
+
case "$pull":
|
|
103
|
+
case "$setOnInsert":
|
|
104
|
+
ensure(entry.op)[entry.path] = entry.value;
|
|
105
|
+
break;
|
|
106
|
+
case "$unset":
|
|
107
|
+
ensure("$unset")[entry.path] = "";
|
|
108
|
+
break;
|
|
109
|
+
case "$rename":
|
|
110
|
+
ensure("$rename")[entry.path] = entry.newName;
|
|
111
|
+
break;
|
|
112
|
+
case "$inc":
|
|
113
|
+
ensure("$inc")[entry.path] = entry.amount;
|
|
114
|
+
break;
|
|
115
|
+
case "$mul":
|
|
116
|
+
ensure("$mul")[entry.path] = entry.factor;
|
|
117
|
+
break;
|
|
118
|
+
case "$pop":
|
|
119
|
+
ensure("$pop")[entry.path] = entry.direction;
|
|
120
|
+
break;
|
|
121
|
+
case "$pullAll":
|
|
122
|
+
ensure("$pullAll")[entry.path] = entry.values;
|
|
123
|
+
break;
|
|
124
|
+
case "$currentDate":
|
|
125
|
+
ensure("$currentDate")[entry.path] = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return buckets;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Classify an array of updater items and produce a `MongoUpdateSpec`.
|
|
133
|
+
*
|
|
134
|
+
* - All `TypedUpdateOp` → fold via `foldUpdateOps` (classic `{ $set, $inc, … }`)
|
|
135
|
+
* - All `MongoUpdatePipelineStage` → return as-is (pipeline-style update)
|
|
136
|
+
* - Mixed → throw (also a type error at the call site via the union shape)
|
|
137
|
+
*/
|
|
138
|
+
function resolveUpdaterResult(items) {
|
|
139
|
+
if (items.length === 0) throw new Error("Updater returned no operations. Return at least one update from the callback (e.g. `[f.amount.set(0)]`).");
|
|
140
|
+
const isOp = (item) => "op" in item && typeof item.op === "string";
|
|
141
|
+
const first = items[0];
|
|
142
|
+
if (first === void 0) throw new Error("Unreachable: items.length > 0 but first is undefined");
|
|
143
|
+
const firstIsOp = isOp(first);
|
|
144
|
+
for (let i = 1; i < items.length; i++) {
|
|
145
|
+
const item = items[i];
|
|
146
|
+
if (item === void 0) continue;
|
|
147
|
+
if (isOp(item) !== firstIsOp) throw new Error("Cannot mix TypedUpdateOp values and pipeline stages in a single updater. Use either `[f.amount.set(0)]` (operator form) or `[f.stage.set({...})]` (pipeline form), not both.");
|
|
148
|
+
}
|
|
149
|
+
if (firstIsOp) return foldUpdateOps(items);
|
|
150
|
+
return items;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/field-accessor.ts
|
|
154
|
+
function buildStageEmitters() {
|
|
155
|
+
return {
|
|
156
|
+
set: (fields) => new MongoAddFieldsStage(fields),
|
|
157
|
+
unset: (...paths) => {
|
|
158
|
+
const spec = {};
|
|
159
|
+
for (const p of paths) spec[p] = 0;
|
|
160
|
+
return new MongoProjectStage(spec);
|
|
161
|
+
},
|
|
162
|
+
replaceRoot: (newRoot) => new MongoReplaceRootStage(newRoot),
|
|
163
|
+
replaceWith: (newRoot) => new MongoReplaceRootStage(newRoot)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Wrap a boolean aggregation expression as an `$expr` filter
|
|
168
|
+
* (`MongoExprFilter`). Lets a `$match` express an aggregation-expression
|
|
169
|
+
* predicate — e.g. comparing two field references via `fn.eq(a, b)` — in
|
|
170
|
+
* the typed AST rather than via a raw escape hatch. Pairs with `.type()`
|
|
171
|
+
* to express filters like `{ _id: { $type: 'string' }, $expr: { $eq: ['$_id', '$space'] } }`.
|
|
172
|
+
*/
|
|
173
|
+
function expr(predicate) {
|
|
174
|
+
return MongoExprFilter.of(predicate.node);
|
|
175
|
+
}
|
|
176
|
+
function buildExpression(path) {
|
|
177
|
+
return {
|
|
178
|
+
_field: void 0,
|
|
179
|
+
_path: path,
|
|
180
|
+
node: MongoAggFieldRef.of(path),
|
|
181
|
+
eq: (value) => MongoFieldFilter.eq(path, value),
|
|
182
|
+
ne: (value) => MongoFieldFilter.neq(path, value),
|
|
183
|
+
gt: (value) => MongoFieldFilter.gt(path, value),
|
|
184
|
+
gte: (value) => MongoFieldFilter.gte(path, value),
|
|
185
|
+
lt: (value) => MongoFieldFilter.lt(path, value),
|
|
186
|
+
lte: (value) => MongoFieldFilter.lte(path, value),
|
|
187
|
+
in: (values) => MongoFieldFilter.in(path, values),
|
|
188
|
+
nin: (values) => MongoFieldFilter.nin(path, values),
|
|
189
|
+
exists: (flag) => flag === false ? MongoExistsExpr.notExists(path) : MongoExistsExpr.exists(path),
|
|
190
|
+
type: (bsonType) => MongoFieldFilter.of(path, "$type", bsonType),
|
|
191
|
+
set: (value) => setOp(path, value),
|
|
192
|
+
unset: () => unsetOp(path),
|
|
193
|
+
rename: (newName) => renameOp(path, newName),
|
|
194
|
+
inc: (amount) => incOp(path, amount),
|
|
195
|
+
mul: (factor) => mulOp(path, factor),
|
|
196
|
+
min: (value) => minOp(path, value),
|
|
197
|
+
max: (value) => maxOp(path, value),
|
|
198
|
+
push: (value) => pushOp(path, value),
|
|
199
|
+
addToSet: (value) => addToSetOp(path, value),
|
|
200
|
+
pop: (direction = 1) => popOp(path, direction),
|
|
201
|
+
pull: (value) => pullOp(path, value),
|
|
202
|
+
pullAll: (values) => pullAllOp(path, values),
|
|
203
|
+
currentDate: () => currentDateOp(path),
|
|
204
|
+
setOnInsert: (value) => setOnInsertOp(path, value)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Construct a unified `FieldAccessor<S, N>` proxy. Property access creates
|
|
209
|
+
* an `Expression` using the property name as the field path; callable
|
|
210
|
+
* form accepts a dot-path string validated against `N` at compile time.
|
|
211
|
+
*
|
|
212
|
+
* The proxy target is a function so the resulting object is both callable
|
|
213
|
+
* and indexable. Symbol-keyed accesses (e.g. `Symbol.toPrimitive`) return
|
|
214
|
+
* `undefined` to keep accidental coercion behaviour unsurprising —
|
|
215
|
+
* matching the previous `FieldProxy` / `FilterProxy` semantics.
|
|
216
|
+
*/
|
|
217
|
+
function createFieldAccessor() {
|
|
218
|
+
const stageInstance = buildStageEmitters();
|
|
219
|
+
const callable = ((path) => buildExpression(path));
|
|
220
|
+
return new Proxy(callable, { get(target, prop, receiver) {
|
|
221
|
+
if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
|
|
222
|
+
if (prop === "stage") return stageInstance;
|
|
223
|
+
if (prop === "rawPath") return (path) => buildExpression(path);
|
|
224
|
+
return buildExpression(prop);
|
|
225
|
+
} });
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
export { expr as n, resolveUpdaterResult as r, createFieldAccessor as t };
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=field-accessor-Dh8GUEe7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-accessor-Dh8GUEe7.mjs","names":[],"sources":["../src/update-ops.ts","../src/field-accessor.ts"],"sourcesContent":["import type {\n MongoUpdatePipelineStage,\n MongoUpdateSpec,\n} from '@prisma-next/mongo-query-ast/execution';\nimport type { MongoValue } from '@prisma-next/mongo-value';\n\n/**\n * Per-field update operations produced by `Expression`'s update methods\n * (`set`, `inc`, `push`, …). A write terminal folds an array of these into a\n * `MongoUpdateSpec` record (`{ $set: { … }, $inc: { … }, … }`) before\n * constructing the underlying `UpdateManyCommand` / `UpdateOneCommand` AST node.\n *\n * One `TypedUpdateOp` value corresponds to one Mongo update operator applied\n * to one field path. The `op` string is the wire-level operator name (`$set`,\n * `$inc`, …); the `path` is the dot-path to the field (or its top-level name).\n */\nexport type TypedUpdateOp =\n | { readonly op: '$set'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$unset'; readonly path: string }\n | { readonly op: '$rename'; readonly path: string; readonly newName: string }\n | { readonly op: '$inc'; readonly path: string; readonly amount: number }\n | { readonly op: '$mul'; readonly path: string; readonly factor: number }\n | { readonly op: '$min'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$max'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$push'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$addToSet'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$pop'; readonly path: string; readonly direction: 1 | -1 }\n | { readonly op: '$pull'; readonly path: string; readonly value: MongoValue }\n | { readonly op: '$pullAll'; readonly path: string; readonly values: ReadonlyArray<MongoValue> }\n | { readonly op: '$currentDate'; readonly path: string }\n | { readonly op: '$setOnInsert'; readonly path: string; readonly value: MongoValue };\n\nexport const setOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$set',\n path,\n value,\n});\nexport const unsetOp = (path: string): TypedUpdateOp => ({ op: '$unset', path });\nexport const renameOp = (path: string, newName: string): TypedUpdateOp => ({\n op: '$rename',\n path,\n newName,\n});\nexport const incOp = (path: string, amount: number): TypedUpdateOp => ({\n op: '$inc',\n path,\n amount,\n});\nexport const mulOp = (path: string, factor: number): TypedUpdateOp => ({\n op: '$mul',\n path,\n factor,\n});\nexport const minOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$min',\n path,\n value,\n});\nexport const maxOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$max',\n path,\n value,\n});\nexport const pushOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$push',\n path,\n value,\n});\nexport const addToSetOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$addToSet',\n path,\n value,\n});\nexport const popOp = (path: string, direction: 1 | -1): TypedUpdateOp => ({\n op: '$pop',\n path,\n direction,\n});\nexport const pullOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$pull',\n path,\n value,\n});\nexport const pullAllOp = (path: string, values: ReadonlyArray<MongoValue>): TypedUpdateOp => ({\n op: '$pullAll',\n path,\n values,\n});\nexport const currentDateOp = (path: string): TypedUpdateOp => ({ op: '$currentDate', path });\nexport const setOnInsertOp = (path: string, value: MongoValue): TypedUpdateOp => ({\n op: '$setOnInsert',\n path,\n value,\n});\n\n/**\n * Per-operator bucket: `{ '<fieldPath>': <operatorValue> }`. Every value is\n * already a `MongoValue` (operators store numbers/strings/booleans/arrays\n * directly), so no blind casts are needed at assignment sites.\n */\ntype UpdateOpBucket = Record<string, MongoValue>;\n\n/**\n * The full nested shape that `foldUpdateOps` accumulates before returning a\n * `MongoUpdateSpec`: `operator → fieldPath → value`. Each inner bucket is\n * itself a `MongoDocument`-compatible record, which is why the outer map is\n * structurally a `MongoUpdateSpec` (`Record<string, MongoValue>` where every\n * value is a document).\n */\ntype UpdateOpBuckets = Record<string, UpdateOpBucket>;\n\n/**\n * Fold an array of `TypedUpdateOp` into the non-pipeline variant of\n * `MongoUpdateSpec` (`{ $set: { … }, $inc: { … }, … }`).\n *\n * Throws if the same operator targets the same path twice — a clear authoring\n * error that Mongo would otherwise silently coalesce.\n */\nexport function foldUpdateOps(ops: ReadonlyArray<TypedUpdateOp>): MongoUpdateSpec {\n const buckets: UpdateOpBuckets = {};\n const seen = new Set<string>();\n\n const ensure = (key: string): UpdateOpBucket => {\n let bucket = buckets[key];\n if (!bucket) {\n bucket = {};\n buckets[key] = bucket;\n }\n return bucket;\n };\n\n const claim = (op: string, path: string): void => {\n const k = `${op}::${path}`;\n if (seen.has(k)) {\n throw new Error(\n `Update spec collision: ${op} on '${path}' was specified more than once. Combine the operations into a single call site.`,\n );\n }\n seen.add(k);\n };\n\n for (const entry of ops) {\n claim(entry.op, entry.path);\n switch (entry.op) {\n case '$set':\n case '$min':\n case '$max':\n case '$push':\n case '$addToSet':\n case '$pull':\n case '$setOnInsert':\n ensure(entry.op)[entry.path] = entry.value;\n break;\n case '$unset':\n ensure('$unset')[entry.path] = '';\n break;\n case '$rename':\n ensure('$rename')[entry.path] = entry.newName;\n break;\n case '$inc':\n ensure('$inc')[entry.path] = entry.amount;\n break;\n case '$mul':\n ensure('$mul')[entry.path] = entry.factor;\n break;\n case '$pop':\n ensure('$pop')[entry.path] = entry.direction;\n break;\n case '$pullAll':\n ensure('$pullAll')[entry.path] = entry.values;\n break;\n case '$currentDate':\n ensure('$currentDate')[entry.path] = true;\n break;\n }\n }\n\n return buckets;\n}\n\nexport type UpdaterItem = TypedUpdateOp | MongoUpdatePipelineStage;\n\n/**\n * The return type for updater callbacks. Typed as a union of homogeneous\n * arrays so mixed-shape updaters (operator + pipeline stage in the same\n * array) are a compile error. The runtime guard in `resolveUpdaterResult`\n * remains as defence-in-depth.\n */\nexport type UpdaterResult = ReadonlyArray<TypedUpdateOp> | ReadonlyArray<MongoUpdatePipelineStage>;\n\n/**\n * Classify an array of updater items and produce a `MongoUpdateSpec`.\n *\n * - All `TypedUpdateOp` → fold via `foldUpdateOps` (classic `{ $set, $inc, … }`)\n * - All `MongoUpdatePipelineStage` → return as-is (pipeline-style update)\n * - Mixed → throw (also a type error at the call site via the union shape)\n */\nexport function resolveUpdaterResult(items: ReadonlyArray<UpdaterItem>): MongoUpdateSpec {\n if (items.length === 0) {\n throw new Error(\n 'Updater returned no operations. Return at least one update from the callback (e.g. `[f.amount.set(0)]`).',\n );\n }\n\n const isOp = (item: UpdaterItem): item is TypedUpdateOp =>\n 'op' in item && typeof (item as TypedUpdateOp).op === 'string';\n\n const first = items[0];\n if (first === undefined) {\n throw new Error('Unreachable: items.length > 0 but first is undefined');\n }\n const firstIsOp = isOp(first);\n\n for (let i = 1; i < items.length; i++) {\n const item = items[i];\n if (item === undefined) continue;\n if (isOp(item) !== firstIsOp) {\n throw new Error(\n 'Cannot mix TypedUpdateOp values and pipeline stages in a single updater. ' +\n 'Use either `[f.amount.set(0)]` (operator form) or `[f.stage.set({...})]` (pipeline form), not both.',\n );\n }\n }\n\n if (firstIsOp) {\n return foldUpdateOps(items as ReadonlyArray<TypedUpdateOp>);\n }\n return items as ReadonlyArray<MongoUpdatePipelineStage>;\n}\n","import type {\n MongoAggExpr,\n MongoFilterExpr,\n MongoUpdatePipelineStage,\n} from '@prisma-next/mongo-query-ast/execution';\nimport {\n MongoAddFieldsStage,\n MongoAggFieldRef,\n MongoExistsExpr,\n MongoExprFilter,\n MongoFieldFilter,\n MongoProjectStage,\n MongoReplaceRootStage,\n} from '@prisma-next/mongo-query-ast/execution';\nimport type { MongoValue } from '@prisma-next/mongo-value';\nimport type { NestedDocShape, ObjectField, ResolvePath, ValidPaths } from './resolve-path';\nimport type { DocField, DocShape, TypedAggExpr } from './types';\nimport type { TypedUpdateOp } from './update-ops';\nimport {\n addToSetOp,\n currentDateOp,\n incOp,\n maxOp,\n minOp,\n mulOp,\n popOp,\n pullAllOp,\n pullOp,\n pushOp,\n renameOp,\n setOnInsertOp,\n setOp,\n unsetOp,\n} from './update-ops';\n\n/**\n * Operator surface for leaf (scalar) paths — today's full set: filter,\n * update, and aggregation operators. Returned by `Expression<F>` for any\n * `F extends DocField` that is not an `ObjectField<…>` sub-tree.\n *\n * Operator surfaces are intentionally not trait-gated by codec in this\n * revision — tracked on Linear as TML-2259 (scope extended to cover the\n * query-builder's `Expression<F>`). Calling, e.g. `.inc(1)` on a\n * string-typed expression compiles; the runtime relies on Mongo to\n * surface the error. Trait-gating can be tightened in a follow-up\n * without changing the accessor's public shape.\n */\nexport interface LeafExpression<F extends DocField> extends TypedAggExpr<F> {\n readonly _path: string;\n\n // Filter operators\n eq(value: MongoValue): MongoFilterExpr;\n ne(value: MongoValue): MongoFilterExpr;\n gt(value: MongoValue): MongoFilterExpr;\n gte(value: MongoValue): MongoFilterExpr;\n lt(value: MongoValue): MongoFilterExpr;\n lte(value: MongoValue): MongoFilterExpr;\n in(values: ReadonlyArray<MongoValue>): MongoFilterExpr;\n nin(values: ReadonlyArray<MongoValue>): MongoFilterExpr;\n exists(flag?: boolean): MongoFilterExpr;\n\n /**\n * `$type` filter: `{ field: { $type: bsonType } }`. Rides\n * `MongoFieldFilter`'s generic `op` string — no dedicated AST node. The\n * BSON type is expressed as Mongo's alias string (e.g. `'string'`) or\n * numeric type code; an array selects any of several types.\n */\n type(bsonType: MongoValue): MongoFilterExpr;\n\n // Update operators ($set family)\n set(value: MongoValue): TypedUpdateOp;\n unset(): TypedUpdateOp;\n rename(newName: string): TypedUpdateOp;\n\n // Numeric update operators\n inc(amount: number): TypedUpdateOp;\n mul(factor: number): TypedUpdateOp;\n min(value: MongoValue): TypedUpdateOp;\n max(value: MongoValue): TypedUpdateOp;\n\n // Array update operators\n push(value: MongoValue): TypedUpdateOp;\n addToSet(value: MongoValue): TypedUpdateOp;\n pop(direction?: 1 | -1): TypedUpdateOp;\n pull(value: MongoValue): TypedUpdateOp;\n pullAll(values: ReadonlyArray<MongoValue>): TypedUpdateOp;\n\n // Date / upsert helpers\n currentDate(): TypedUpdateOp;\n setOnInsert(value: MongoValue): TypedUpdateOp;\n}\n\n/**\n * Operator surface for non-leaf (value-object) paths — `f('address')`\n * when `address` is a `ContractValueObject`. Intentionally minimal: the\n * whole-value ops that make sense on a structured sub-document\n * (`set`/`unset`/`exists`, null presence via `eq(null)`/`ne(null)`). Field-\n * level ops belong on the constituent leaves (`f('address.city')`).\n *\n * The aggregation `node` is still present (`TypedAggExpr<ObjectField<N>>`)\n * so the value object can be piped through `$addFields` /\n * `$replaceRoot` / etc. as-is.\n */\nexport interface ObjectExpression<N extends NestedDocShape> extends TypedAggExpr<ObjectField<N>> {\n readonly _path: string;\n\n exists(flag?: boolean): MongoFilterExpr;\n eq(value: null): MongoFilterExpr;\n ne(value: null): MongoFilterExpr;\n\n set(value: MongoValue): TypedUpdateOp;\n unset(): TypedUpdateOp;\n}\n\n/**\n * The unified field accessor expression returned by `FieldAccessor` (per\n * [ADR 180](../../../../docs/architecture%20docs/adrs/ADR%20180%20-%20Dot-path%20field%20accessor.md)).\n *\n * Resolves to `ObjectExpression<Sub>` when `F` is an `ObjectField<Sub>`\n * (non-leaf path), otherwise to `LeafExpression<F>` (the full operator\n * surface). The conditional is driven off the `fields` marker that\n * `ObjectField` adds to `DocField`, so existing code that uses plain\n * `DocField` shapes continues to resolve to `LeafExpression`.\n */\nexport type Expression<F extends DocField> =\n F extends ObjectField<infer N> ? ObjectExpression<N> : LeafExpression<F>;\n\n/**\n * Emitters for MongoDB update-pipeline stages (`$addFields`/`$set`,\n * `$project`/`$unset`, `$replaceRoot`/`$replaceWith`). These return\n * `MongoUpdatePipelineStage` nodes and let an updater callback express\n * the pipeline-form update as an alternative to the typed-operator form.\n *\n * The two forms are mutually exclusive per updater call: `resolveUpdaterResult`\n * rejects arrays that mix `TypedUpdateOp` and `MongoUpdatePipelineStage`\n * entries with a clear error — an updater callback must return either all\n * typed ops or all pipeline stages. Pick the form that matches the update\n * you want and commit to it for that call site.\n *\n * Accessible via `f.stage` on the `FieldAccessor`.\n */\nexport interface StageEmitters {\n set(fields: Record<string, MongoAggExpr>): MongoUpdatePipelineStage;\n unset(...paths: ReadonlyArray<string>): MongoUpdatePipelineStage;\n replaceRoot(newRoot: MongoAggExpr): MongoUpdatePipelineStage;\n replaceWith(newRoot: MongoAggExpr): MongoUpdatePipelineStage;\n}\n\nfunction buildStageEmitters(): StageEmitters {\n return {\n set: (fields) => new MongoAddFieldsStage(fields),\n unset: (...paths) => {\n const spec: Record<string, 0> = {};\n for (const p of paths) {\n spec[p] = 0;\n }\n return new MongoProjectStage(spec);\n },\n replaceRoot: (newRoot) => new MongoReplaceRootStage(newRoot),\n replaceWith: (newRoot) => new MongoReplaceRootStage(newRoot),\n };\n}\n\n/**\n * The unified `FieldAccessor` per ADR 180.\n *\n * - Property access (`f.status`) returns an `Expression<F>` whose codec\n * comes from the current pipeline shape `S`.\n * - Callable form (`f('address.city')`) returns an `Expression<ResolvePath<N, P>>`\n * where `N` is the nested shape carrying value-object sub-shapes.\n * Paths that don't exist in `N` are rejected with a compile-time error\n * (via `P extends ValidPaths<N>`). Non-leaf paths like `f('address')`\n * resolve to an `ObjectExpression` whose reduced surface covers the\n * whole-value operations (`set`, `unset`, `exists`, `eq(null)`,\n * `ne(null)`).\n * - `f.rawPath('path')` is a deliberate escape hatch that skips path\n * validation and returns a `LeafExpression<F>` for the given string.\n * Intended for migration authoring where the target field is not yet\n * part of the typed contract (e.g. a backfill writing a newly-added\n * column before the contract hash rolls forward). The method name is\n * deliberately `rawPath` rather than `raw` so it does not shadow a\n * legitimate top-level `raw` field on a user model.\n * - `f.stage` exposes pipeline-style update emitters (`$set`, `$unset`,\n * `$replaceRoot`, `$replaceWith`).\n *\n * When `N` is `Record<string, never>` (the default — e.g. after a\n * replacement stage like `$group` / `$project` / `$replaceRoot`),\n * `ValidPaths<N>` is `never` and the callable form is effectively\n * disabled at the type level. This keeps the builder sound downstream of\n * stages that invalidate the original document's nested-path tree.\n * `f.rawPath(...)` remains available in that state for callers that need\n * an explicit unvalidated path.\n */\nexport type FieldAccessor<S extends DocShape, N extends NestedDocShape = Record<string, never>> = {\n readonly [K in keyof S & string]: Expression<S[K]>;\n} & (<P extends ValidPaths<N>>(path: P) => Expression<ResolvePath<N, P>>) & {\n readonly stage: StageEmitters;\n /**\n * Escape hatch: build a `LeafExpression<F>` for an unvalidated string\n * path. Use only when the path is intentionally outside the typed\n * model surface — data-migration authoring is the canonical case\n * (e.g. backfilling a field that is not yet in the contract). Default\n * `F` is the opaque `DocField`; callers can narrow via the explicit\n * generic: `f.rawPath<StringField>(\"status\").set(\"active\")`.\n *\n * The method is named `rawPath` (not `raw`) so a user model with a\n * top-level `raw` field still resolves `f.raw` to the field-expression\n * property, not to this escape hatch. Does not participate in\n * `ValidPaths<N>` / `ResolvePath<N, P>` — the path is passed through\n * verbatim and no IDE autocomplete is offered.\n */\n rawPath<F extends DocField = DocField>(path: string): LeafExpression<F>;\n };\n\n/**\n * Wrap a boolean aggregation expression as an `$expr` filter\n * (`MongoExprFilter`). Lets a `$match` express an aggregation-expression\n * predicate — e.g. comparing two field references via `fn.eq(a, b)` — in\n * the typed AST rather than via a raw escape hatch. Pairs with `.type()`\n * to express filters like `{ _id: { $type: 'string' }, $expr: { $eq: ['$_id', '$space'] } }`.\n */\nexport function expr(predicate: TypedAggExpr<DocField>): MongoFilterExpr {\n return MongoExprFilter.of(predicate.node);\n}\n\nfunction buildExpression<F extends DocField>(path: string): Expression<F> {\n // The runtime object carries the full operator surface unconditionally;\n // `ObjectExpression` is a strict subset of `LeafExpression`, so a single\n // implementation satisfies both type-level shapes. Compile-time gating\n // prevents misuse of leaf-only operators on object paths.\n return {\n _field: undefined as never,\n _path: path,\n node: MongoAggFieldRef.of(path),\n\n eq: (value: MongoValue) => MongoFieldFilter.eq(path, value),\n ne: (value: MongoValue) => MongoFieldFilter.neq(path, value),\n gt: (value: MongoValue) => MongoFieldFilter.gt(path, value),\n gte: (value: MongoValue) => MongoFieldFilter.gte(path, value),\n lt: (value: MongoValue) => MongoFieldFilter.lt(path, value),\n lte: (value: MongoValue) => MongoFieldFilter.lte(path, value),\n in: (values: ReadonlyArray<MongoValue>) => MongoFieldFilter.in(path, values),\n nin: (values: ReadonlyArray<MongoValue>) => MongoFieldFilter.nin(path, values),\n exists: (flag?: boolean) =>\n flag === false ? MongoExistsExpr.notExists(path) : MongoExistsExpr.exists(path),\n type: (bsonType: MongoValue) => MongoFieldFilter.of(path, '$type', bsonType),\n\n set: (value: MongoValue) => setOp(path, value),\n unset: () => unsetOp(path),\n rename: (newName: string) => renameOp(path, newName),\n\n inc: (amount: number) => incOp(path, amount),\n mul: (factor: number) => mulOp(path, factor),\n min: (value: MongoValue) => minOp(path, value),\n max: (value: MongoValue) => maxOp(path, value),\n\n push: (value: MongoValue) => pushOp(path, value),\n addToSet: (value: MongoValue) => addToSetOp(path, value),\n pop: (direction: 1 | -1 = 1) => popOp(path, direction),\n pull: (value: MongoValue) => pullOp(path, value),\n pullAll: (values: ReadonlyArray<MongoValue>) => pullAllOp(path, values),\n\n currentDate: () => currentDateOp(path),\n setOnInsert: (value: MongoValue) => setOnInsertOp(path, value),\n } as unknown as Expression<F>;\n}\n\n/**\n * Construct a unified `FieldAccessor<S, N>` proxy. Property access creates\n * an `Expression` using the property name as the field path; callable\n * form accepts a dot-path string validated against `N` at compile time.\n *\n * The proxy target is a function so the resulting object is both callable\n * and indexable. Symbol-keyed accesses (e.g. `Symbol.toPrimitive`) return\n * `undefined` to keep accidental coercion behaviour unsurprising —\n * matching the previous `FieldProxy` / `FilterProxy` semantics.\n */\nexport function createFieldAccessor<\n S extends DocShape,\n N extends NestedDocShape = Record<string, never>,\n>(): FieldAccessor<S, N> {\n const stageInstance = buildStageEmitters();\n const callable = ((path: string) => buildExpression<DocField>(path)) as unknown as FieldAccessor<\n S,\n N\n >;\n return new Proxy(callable, {\n get(target, prop, receiver) {\n if (typeof prop === 'symbol') {\n return Reflect.get(target, prop, receiver);\n }\n if (prop === 'stage') {\n return stageInstance;\n }\n if (prop === 'rawPath') {\n return (path: string) => buildExpression<DocField>(path);\n }\n return buildExpression(prop);\n },\n });\n}\n"],"mappings":"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"}
|