@web-ts-toolkit/moo 0.55.0 → 0.56.0

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.
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/plugins/derived-fields.ts
21
+ var derived_fields_exports = {};
22
+ __export(derived_fields_exports, {
23
+ DerivedFieldsDependencyError: () => DerivedFieldsDependencyError,
24
+ defineDerivedField: () => defineDerivedField,
25
+ derivedFieldsPlugin: () => derivedFieldsPlugin
26
+ });
27
+ module.exports = __toCommonJS(derived_fields_exports);
28
+
29
+ // src/plugins/derived-fields/definitions.ts
30
+ function defineDerivedField(definition) {
31
+ return definition;
32
+ }
33
+ var DerivedFieldsDependencyError = class extends Error {
34
+ constructor(issues) {
35
+ const snapshot = Object.freeze(
36
+ issues.map(
37
+ ({ targetPath, missingPaths }) => Object.freeze({ targetPath, missingPaths: Object.freeze([...missingPaths]) })
38
+ )
39
+ );
40
+ const details = snapshot.map(
41
+ ({ targetPath, missingPaths }) => `${JSON.stringify(targetPath)} requires ${missingPaths.map((path) => JSON.stringify(path)).join(", ")}`
42
+ ).join("; ");
43
+ super(`derivedFieldsPlugin missing dependencies: ${details}`);
44
+ /** Stable discriminator, including across separately bundled entrypoints. */
45
+ this.code = "MOO_DERIVED_FIELDS_MISSING_DEPENDENCIES";
46
+ this.name = "DerivedFieldsDependencyError";
47
+ this.issues = snapshot;
48
+ }
49
+ };
50
+
51
+ // src/plugins/derived-fields/lifecycle.ts
52
+ var import_node_util = require("util");
53
+ var import_mongoose2 = require("mongoose");
54
+
55
+ // src/plugins/derived-fields/planner.ts
56
+ var hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
57
+ var isFullySelected = (document, path) => {
58
+ const projection = document.$__?.selected;
59
+ if (projection) {
60
+ if (hasOwn(projection, path) && projection[path] !== 1 && projection[path] !== true) return false;
61
+ const keys = Object.keys(projection);
62
+ if (keys.some((key) => key.startsWith(`${path}.`))) return false;
63
+ if (path !== "_id" && keys.length === 1 && projection._id === false) return true;
64
+ }
65
+ return document.isDirectSelected(path);
66
+ };
67
+ function planDerivedFields(registration, document, options = {}) {
68
+ const replacedPaths = registration.inputPaths.filter(
69
+ (path) => options.knownReplacedPaths?.has(path) || document.isDirectModified(path) && !document.$__?.primitiveAtomics?.[path]
70
+ );
71
+ const replacements = new Set(replacedPaths);
72
+ const availablePaths = registration.inputPaths.filter(
73
+ (path) => document.isNew || replacements.has(path) || isFullySelected(document, path)
74
+ );
75
+ const available = new Set(availablePaths);
76
+ const fields = registration.fields.filter(
77
+ (field) => document.isNew || field.mode === "always" || document.isDirectModified(field.targetPath) || options.pendingWritePaths?.has(field.targetPath) || field.dependsOn.some((path) => document.isModified(path) || options.pendingWritePaths?.has(path))
78
+ );
79
+ const issues = [];
80
+ for (const field of fields) {
81
+ const dependencies = field.mode === "always" ? registration.inputPaths : field.dependsOn;
82
+ const missingPaths = dependencies.filter((path) => !available.has(path));
83
+ if (missingPaths.length) issues.push({ targetPath: field.targetPath, missingPaths });
84
+ }
85
+ if (issues.length) throw new DerivedFieldsDependencyError(issues);
86
+ return Object.freeze({
87
+ fields: Object.freeze(fields),
88
+ availablePaths: Object.freeze(availablePaths),
89
+ replacedPaths: Object.freeze(replacedPaths)
90
+ });
91
+ }
92
+
93
+ // src/plugins/derived-fields/registration.ts
94
+ var supportedDerivedFieldTypes = Object.freeze([
95
+ "String",
96
+ "Number",
97
+ "Boolean",
98
+ "Date",
99
+ "ObjectId",
100
+ "Decimal128",
101
+ "BigInt"
102
+ ]);
103
+ var registrationKey = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/moo/derived-fields/registration");
104
+ var hasOwn2 = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
105
+ var isRootPath = (path) => path.length > 0 && !/[.\s$\0]/.test(path);
106
+ var isSupportedPath = (path, type) => isRootPath(path) && supportedDerivedFieldTypes.includes(type.instance);
107
+ var configurationError = (targetPath, reason) => {
108
+ throw new TypeError(`derivedFieldsPlugin target ${JSON.stringify(targetPath)}: ${reason}`);
109
+ };
110
+ var parseDefinition = (targetPath, value) => {
111
+ if (typeof value === "function") {
112
+ return Object.freeze({
113
+ targetPath,
114
+ mode: "always",
115
+ dependsOn: Object.freeze([]),
116
+ get: value
117
+ });
118
+ }
119
+ if (value === null || typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
120
+ return configurationError(targetPath, "derived must be a getter function or a plain { dependsOn, get } descriptor");
121
+ }
122
+ if (Reflect.ownKeys(value).some((key) => key !== "dependsOn" && key !== "get") || !hasOwn2(value, "dependsOn") || !hasOwn2(value, "get")) {
123
+ return configurationError(targetPath, "derived descriptors require exactly dependsOn and get");
124
+ }
125
+ const descriptor = value;
126
+ if (!Array.isArray(descriptor.dependsOn) || typeof descriptor.get !== "function") {
127
+ return configurationError(targetPath, "dependsOn must be an array of root path names and get must be a function");
128
+ }
129
+ const dependencies = [];
130
+ for (const dependency of descriptor.dependsOn) {
131
+ if (typeof dependency !== "string" || !isRootPath(dependency)) {
132
+ return configurationError(
133
+ targetPath,
134
+ "dependsOn must contain only non-empty root path names (no dots, whitespace, or $)"
135
+ );
136
+ }
137
+ if (dependencies.includes(dependency)) {
138
+ return configurationError(targetPath, `duplicate dependency path ${JSON.stringify(dependency)}`);
139
+ }
140
+ dependencies.push(dependency);
141
+ }
142
+ return Object.freeze({
143
+ targetPath,
144
+ mode: "dependencies",
145
+ dependsOn: Object.freeze(dependencies),
146
+ get: descriptor.get
147
+ });
148
+ };
149
+ function registerDerivedFields(schema) {
150
+ const markedSchema = schema;
151
+ if (hasOwn2(markedSchema, registrationKey)) {
152
+ throw new TypeError("derivedFieldsPlugin is already registered on this schema");
153
+ }
154
+ const fields = [];
155
+ const versionKey = schema.options.versionKey;
156
+ const visitSchema = (current, prefix, ancestors) => {
157
+ current.eachPath((path, type) => {
158
+ const fullPath = `${prefix}${path}`;
159
+ if (!hasOwn2(type.options, "derived")) return;
160
+ if (prefix || !isSupportedPath(path, type)) {
161
+ configurationError(
162
+ fullPath,
163
+ `only root scalar persisted paths are supported (${supportedDerivedFieldTypes.join(", ")})`
164
+ );
165
+ }
166
+ if (path === versionKey) {
167
+ configurationError(
168
+ fullPath,
169
+ `Mongoose-managed version key ${JSON.stringify(versionKey)} cannot be a derived target`
170
+ );
171
+ }
172
+ if (path === "_id" || (type.$immutable ?? type.options.immutable)) {
173
+ configurationError(fullPath, "immutable targets are not supported");
174
+ }
175
+ fields.push(parseDefinition(fullPath, type.options.derived));
176
+ });
177
+ const virtuals = current.virtuals;
178
+ for (const [path, virtual] of Object.entries(virtuals)) {
179
+ if (hasOwn2(virtual.options, "derived")) {
180
+ configurationError(`${prefix}${path}`, "derived targets must be persisted schema paths, not virtuals");
181
+ }
182
+ }
183
+ if (ancestors.has(current)) return;
184
+ const nextAncestors = /* @__PURE__ */ new Set([...ancestors, current]);
185
+ const visitEmbedded = (type, path) => {
186
+ if (type.schema) {
187
+ visitSchema(type.schema, `${path}.`, nextAncestors);
188
+ return;
189
+ }
190
+ for (const member of type.schemaTypes ?? []) {
191
+ if (hasOwn2(member.options, "derived")) {
192
+ configurationError(path, "derived targets inside union containers are not supported");
193
+ }
194
+ visitEmbedded(member, path);
195
+ }
196
+ const embedded = type.getEmbeddedSchemaType?.() ?? type.caster ?? type.$embeddedSchemaType ?? type.$__schemaType;
197
+ if (!embedded || embedded === type) return;
198
+ const embeddedPath = `${path}.${type.instance === "Map" ? "$*" : "$"}`;
199
+ if (hasOwn2(embedded.options, "derived")) {
200
+ configurationError(embeddedPath, "derived targets inside containers are not supported");
201
+ }
202
+ visitEmbedded(embedded, embeddedPath);
203
+ };
204
+ current.eachPath((path, type) => visitEmbedded(type, `${prefix}${path}`));
205
+ };
206
+ visitSchema(schema, "", /* @__PURE__ */ new Set());
207
+ const targets = new Set(fields.map((field) => field.targetPath));
208
+ for (const field of fields) {
209
+ for (const dependency of field.dependsOn) {
210
+ if (dependency === versionKey) {
211
+ configurationError(
212
+ field.targetPath,
213
+ `Mongoose-managed version key ${JSON.stringify(versionKey)} cannot be a dependency`
214
+ );
215
+ }
216
+ if (targets.has(dependency)) {
217
+ configurationError(
218
+ field.targetPath,
219
+ `dependency path ${JSON.stringify(dependency)} is itself a derived target`
220
+ );
221
+ }
222
+ const type = schema.path(dependency);
223
+ if (!type) {
224
+ configurationError(field.targetPath, `unknown or non-persisted dependency path ${JSON.stringify(dependency)}`);
225
+ }
226
+ if (!isSupportedPath(dependency, type)) {
227
+ configurationError(
228
+ field.targetPath,
229
+ `dependency path ${JSON.stringify(dependency)} must be a supported root scalar persisted path`
230
+ );
231
+ }
232
+ }
233
+ }
234
+ const inputPaths = [];
235
+ schema.eachPath((path, type) => {
236
+ if (path !== versionKey && !targets.has(path) && isSupportedPath(path, type)) inputPaths.push(path);
237
+ });
238
+ const registration = Object.freeze({ fields: Object.freeze(fields), inputPaths: Object.freeze(inputPaths) });
239
+ Object.defineProperty(markedSchema, registrationKey, { value: registration });
240
+ return registration;
241
+ }
242
+
243
+ // src/plugins/derived-fields/snapshot.ts
244
+ var import_mongoose = require("mongoose");
245
+ function cloneDerivedScalar(value) {
246
+ if (value instanceof Date) return new Date(value.getTime());
247
+ if (value !== null && typeof value === "object") {
248
+ const bson = value;
249
+ if (bson._bsontype === "ObjectId") return new import_mongoose.Types.ObjectId(Buffer.from(bson.id));
250
+ if (bson._bsontype === "Decimal128") return new import_mongoose.Types.Decimal128(Buffer.from(bson.bytes));
251
+ }
252
+ return value;
253
+ }
254
+ function readDerivedScalar(document, path) {
255
+ const id = document.populated(path);
256
+ return id === void 0 ? document.get(path, void 0, { getters: false }) : id;
257
+ }
258
+ function snapshotDerivedInputs(document, paths) {
259
+ return copyDerivedSnapshot(Object.fromEntries(paths.map((path) => [path, readDerivedScalar(document, path)])));
260
+ }
261
+ function copyDerivedSnapshot(snapshot, paths = Object.keys(snapshot)) {
262
+ return Object.freeze(Object.fromEntries(paths.map((path) => [path, cloneDerivedScalar(snapshot[path])])));
263
+ }
264
+
265
+ // src/plugins/derived-fields/lifecycle.ts
266
+ var watchedPaths = (plan) => [
267
+ ...new Set(plan.fields.flatMap((field) => field.mode === "always" ? plan.availablePaths : field.dependsOn))
268
+ ];
269
+ function derivedFieldsPlugin(schema) {
270
+ const registration = registerDerivedFields(schema);
271
+ const trackedPaths = [...registration.inputPaths, ...registration.fields.map((field) => field.targetPath)];
272
+ const states = /* @__PURE__ */ new WeakMap();
273
+ const stateFor = (document) => {
274
+ let state = states.get(document);
275
+ if (!state) {
276
+ state = { replacedPaths: /* @__PURE__ */ new Set(), assignedPaths: /* @__PURE__ */ new Set(), applyingResults: false };
277
+ states.set(document, state);
278
+ }
279
+ return state;
280
+ };
281
+ for (const path of trackedPaths) {
282
+ schema.path(path).set(function derivedFieldsObserveAssignment(value, _priorValue, _schemaType, options) {
283
+ if (this instanceof import_mongoose2.Document && !this.isNew && !options?._skipMarkModified && !isFullySelected(this, path)) {
284
+ const state = stateFor(this);
285
+ if (!state.applyingResults) state.assignedPaths.add(path);
286
+ }
287
+ return value;
288
+ });
289
+ }
290
+ const planFor = (document, state) => {
291
+ for (const path of state.assignedPaths) {
292
+ if (!document.$__.validationError?.errors[path] && !document.$__.primitiveAtomics?.[path]) {
293
+ document.markModified(path);
294
+ }
295
+ }
296
+ state.assignedPaths.clear();
297
+ const pendingWritePaths = new Set(
298
+ document.isNew ? [] : trackedPaths.filter(
299
+ (path) => path !== "_id" && document.$isDefault(path) && document.$__.selected?.[path] !== 0 && document.get(path, void 0, { getters: false }) != null
300
+ )
301
+ );
302
+ const plan = planDerivedFields(registration, document, {
303
+ knownReplacedPaths: state.replacedPaths,
304
+ pendingWritePaths
305
+ });
306
+ state.replacedPaths = new Set(document.isNew ? plan.availablePaths : plan.replacedPaths);
307
+ return plan;
308
+ };
309
+ const matches = (document, plan, checkpoint) => checkpoint !== void 0 && checkpoint.saveOptions === document.$__.saveOptions && checkpoint.isNew === document.isNew && // Restoring a target to its original persisted value can clear its dirty
310
+ // flag. It was still computed; only newly scheduled fields invalidate reuse.
311
+ plan.fields.every((field) => checkpoint.targets.includes(field.targetPath)) && (0, import_node_util.isDeepStrictEqual)(checkpoint.inputs, snapshotDerivedInputs(document, Object.keys(checkpoint.inputs))) && (0, import_node_util.isDeepStrictEqual)(checkpoint.results, snapshotDerivedInputs(document, checkpoint.targets));
312
+ const compute = async (document) => {
313
+ const state = stateFor(document);
314
+ state.checkpoint = void 0;
315
+ const plan = planFor(document, state);
316
+ const inputs = snapshotDerivedInputs(document, plan.availablePaths);
317
+ const staged = [];
318
+ for (const { targetPath, get } of plan.fields) {
319
+ const result = await get(copyDerivedSnapshot(inputs));
320
+ staged.push({ path: targetPath, value: cloneDerivedScalar(result) });
321
+ }
322
+ state.applyingResults = true;
323
+ try {
324
+ for (const { path, value } of staged) document.set(path, value);
325
+ } finally {
326
+ state.applyingResults = false;
327
+ }
328
+ const validationError = document.$__.validationError;
329
+ if (staged.some(({ path }) => validationError?.errors[path]?.name === "CastError")) throw validationError;
330
+ const targets = plan.fields.map((field) => field.targetPath);
331
+ state.checkpoint = {
332
+ saveOptions: document.$__.saveOptions,
333
+ isNew: document.isNew,
334
+ targets,
335
+ inputs: copyDerivedSnapshot(inputs, watchedPaths(plan)),
336
+ results: snapshotDerivedInputs(document, targets)
337
+ };
338
+ };
339
+ schema.pre("validate", async function derivedFieldsPreValidate() {
340
+ await compute(this);
341
+ });
342
+ schema.pre("save", async function derivedFieldsPreSave() {
343
+ const state = stateFor(this);
344
+ const plan = planFor(this, state);
345
+ if (this.$__.saveOptions && matches(this, plan, state.checkpoint)) return;
346
+ const options = this.$__.saveOptions;
347
+ const validate = options && "validateBeforeSave" in options ? !!options.validateBeforeSave : schema.options.validateBeforeSave;
348
+ if (validate) {
349
+ await this.validate(
350
+ void 0,
351
+ options && "validateModifiedOnly" in options ? { validateModifiedOnly: options.validateModifiedOnly } : void 0
352
+ );
353
+ } else {
354
+ await compute(this);
355
+ }
356
+ if (!matches(this, planFor(this, state), state.checkpoint)) {
357
+ throw new Error(
358
+ "derivedFieldsPlugin inputs or targets changed after computation; register source-mutating validation hooks before the plugin and keep getters/validators free of document mutations"
359
+ );
360
+ }
361
+ });
362
+ }
363
+ // Annotate the CommonJS export names for ESM import in node:
364
+ 0 && (module.exports = {
365
+ DerivedFieldsDependencyError,
366
+ defineDerivedField,
367
+ derivedFieldsPlugin
368
+ });