@web-ts-toolkit/access-router 0.2.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.
package/index.mjs ADDED
@@ -0,0 +1,3979 @@
1
+ // src/index.ts
2
+ import { isPlainObject as isPlainObject10, isString as isString11, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
3
+
4
+ // src/middleware.ts
5
+ import JsonRouter2 from "@web-ts-toolkit/express-json-router";
6
+ import { isArray as isArray9, isFunction as isFunction6, isPlainObject as isPlainObject7, isString as isString8 } from "@web-ts-toolkit/utils";
7
+
8
+ // src/core.ts
9
+ import {
10
+ castArray as castArray5,
11
+ compact as compact3,
12
+ difference,
13
+ forEach as forEach6,
14
+ intersection,
15
+ isArray as isArray8,
16
+ isBoolean as isBoolean5,
17
+ isFunction as isFunction5,
18
+ isString as isString7,
19
+ isUndefined,
20
+ reduce as reduce2
21
+ } from "@web-ts-toolkit/utils";
22
+
23
+ // src/options/manager.ts
24
+ import { assign, get, set } from "@web-ts-toolkit/utils";
25
+ var getNestedOption = (manager, key, defaultValue) => {
26
+ const keys2 = String(key).split(".");
27
+ if (keys2.length === 1) {
28
+ return manager.get(key, defaultValue);
29
+ }
30
+ let option = manager.get(key, void 0);
31
+ if (option !== void 0) {
32
+ return option;
33
+ }
34
+ const parentKey = keys2.slice(0, -1).join(".");
35
+ option = manager.get(`${parentKey}.default`, void 0);
36
+ if (option === void 0) {
37
+ option = manager.get(parentKey, defaultValue);
38
+ }
39
+ return option;
40
+ };
41
+ var OptionsManager = class {
42
+ constructor(defaultOptions) {
43
+ this.defaultOptions = defaultOptions;
44
+ this.listeners = {};
45
+ const _this = this;
46
+ this.currentOptions = new Proxy({}, {
47
+ set(target, key, value) {
48
+ const keystr = String(key);
49
+ const oldvalue = target[key];
50
+ target[key] = value;
51
+ _this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
52
+ return true;
53
+ }
54
+ });
55
+ }
56
+ build() {
57
+ this.assign(this.defaultOptions);
58
+ return this;
59
+ }
60
+ get(key, defaultValue) {
61
+ return get(this.currentOptions, key, defaultValue);
62
+ }
63
+ set(key, value) {
64
+ set(this.currentOptions, key, value);
65
+ }
66
+ fetch() {
67
+ return { ...this.currentOptions };
68
+ }
69
+ assign(options) {
70
+ assign(this.currentOptions, options);
71
+ }
72
+ onchange(key, func) {
73
+ set(this.listeners, key, func);
74
+ return this;
75
+ }
76
+ };
77
+
78
+ // src/logger.ts
79
+ import util from "util";
80
+ import { createLogger, format, transports } from "winston";
81
+ var colorizer = format.colorize();
82
+ var defaultLogLevel = process.env.WTT_LOG_LEVEL || "silent";
83
+ var inspectValue = (value) => {
84
+ return typeof value === "string" ? value : util.inspect(value, { depth: 5, colors: false });
85
+ };
86
+ var defaultLogger = createLogger({
87
+ level: defaultLogLevel,
88
+ format: format.combine(
89
+ format.errors({ stack: true }),
90
+ format.splat(),
91
+ format.label({ label: "[WTT]" }),
92
+ format.timestamp(),
93
+ format.printf(({ level, label, message, timestamp, stack, ...meta }) => {
94
+ const formattedLabel = colorizer.colorize(level, label);
95
+ const formattedLevel = colorizer.colorize(level, `[${level.toUpperCase()}]`);
96
+ const formattedMessage = colorizer.colorize(level, inspectValue(stack ?? message));
97
+ const formattedMeta = Object.keys(meta).length > 0 ? ` ${inspectValue(meta)}` : "";
98
+ return `${formattedLabel} ${timestamp} ${formattedLevel} ${formattedMessage}${formattedMeta}`;
99
+ })
100
+ ),
101
+ transports: [new transports.Console()]
102
+ });
103
+ var passthrough = (level) => (...args) => {
104
+ const globalLogger = getGlobalOption("logger");
105
+ const target = globalLogger?.[level] ?? defaultLogger[level];
106
+ return target.apply(globalLogger ?? defaultLogger, args);
107
+ };
108
+ var logger = {
109
+ debug: passthrough("debug"),
110
+ info: passthrough("info"),
111
+ warn: passthrough("warn"),
112
+ error: passthrough("error")
113
+ };
114
+
115
+ // src/options/global-options.ts
116
+ var globalOptions = new OptionsManager({
117
+ requestPermissionField: "_permissions",
118
+ globalPermissions: () => ({}),
119
+ logger: defaultLogger
120
+ }).build();
121
+ var setGlobalOptions = (options) => {
122
+ globalOptions.assign(options);
123
+ };
124
+ var setGlobalOption = (key, value) => {
125
+ globalOptions.set(key, value);
126
+ };
127
+ var getGlobalOptions = () => {
128
+ return globalOptions.fetch();
129
+ };
130
+ var getGlobalOption = (key, defaultValue) => {
131
+ return globalOptions.get(key, defaultValue);
132
+ };
133
+
134
+ // src/options/model-options.ts
135
+ import mongoose from "mongoose";
136
+ import mschema2Jsonschema from "mongoose-schema-jsonschema";
137
+ import { addLeadingSlash, forEach, isArray, isBoolean, isFunction, isNil, isString } from "@web-ts-toolkit/utils";
138
+
139
+ // src/options/default-model-options.ts
140
+ var DEFAULT_QUERY_PATH = "__query";
141
+ var DEFAULT_MUTATION_PATH = "__mutation";
142
+ var defaultModelOptions = new OptionsManager({
143
+ listHardLimit: 1e3,
144
+ documentPermissionField: "_permissions",
145
+ idParam: "id",
146
+ identifier: "_id",
147
+ parentPath: "/",
148
+ queryPath: DEFAULT_QUERY_PATH,
149
+ mutationPath: DEFAULT_MUTATION_PATH,
150
+ routeGuard: false,
151
+ modelPermissionPrefix: ""
152
+ }).build();
153
+ var setDefaultModelOptions = (options) => {
154
+ defaultModelOptions.assign(options);
155
+ };
156
+ var setDefaultModelOption = (key, value) => {
157
+ defaultModelOptions.set(key, value);
158
+ };
159
+ var getDefaultModelOptions = () => {
160
+ return defaultModelOptions.fetch();
161
+ };
162
+ var getDefaultModelOption = (key, defaultValue) => {
163
+ return defaultModelOptions.get(key, defaultValue);
164
+ };
165
+
166
+ // src/options/model-options.ts
167
+ mschema2Jsonschema(mongoose);
168
+ var pluralize = mongoose.pluralize();
169
+ var defaultModelOptions2 = {
170
+ basePath: null,
171
+ mandatoryFields: []
172
+ };
173
+ var modelOptions = {};
174
+ var modelJsonSchemas = {};
175
+ var normalizeBasePath = (name, value) => {
176
+ if (isNil(value)) {
177
+ return `/${pluralize(name)}`;
178
+ }
179
+ return isString(value) ? addLeadingSlash(value) : "";
180
+ };
181
+ var hasModelPermission = (value, modelPermissionPrefix) => {
182
+ if (!modelPermissionPrefix) {
183
+ return true;
184
+ }
185
+ if (isString(value)) {
186
+ return value.trim().split(" ").some((item) => item.startsWith(modelPermissionPrefix));
187
+ }
188
+ if (isArray(value)) {
189
+ return value.some((item) => {
190
+ if (isString(item) || isArray(item)) {
191
+ return hasModelPermission(item, modelPermissionPrefix);
192
+ }
193
+ return true;
194
+ });
195
+ }
196
+ return isFunction(value);
197
+ };
198
+ var classifyPermissionSchema = (permissionSchema, modelPermissionPrefix) => {
199
+ const schemaKeys = Object.keys(permissionSchema);
200
+ const globalPermissionKeys = {};
201
+ const modelPermissionKeys = {};
202
+ forEach(schemaKeys, (schemaKey) => {
203
+ forEach(permissionSchema[schemaKey], (value, accessKey) => {
204
+ if (!isArray(globalPermissionKeys[accessKey])) {
205
+ globalPermissionKeys[accessKey] = [];
206
+ }
207
+ if (!isArray(modelPermissionKeys[accessKey])) {
208
+ modelPermissionKeys[accessKey] = [];
209
+ }
210
+ if (isBoolean(value)) {
211
+ globalPermissionKeys[accessKey].push(schemaKey);
212
+ return;
213
+ }
214
+ if (hasModelPermission(value, modelPermissionPrefix)) {
215
+ modelPermissionKeys[accessKey].push(schemaKey);
216
+ return;
217
+ }
218
+ globalPermissionKeys[accessKey].push(schemaKey);
219
+ });
220
+ });
221
+ return {
222
+ schemaKeys,
223
+ globalPermissionKeys,
224
+ modelPermissionKeys
225
+ };
226
+ };
227
+ var createModelOptions = (modelName) => {
228
+ const model = mongoose.model(modelName);
229
+ const manager = new OptionsManager({
230
+ ...defaultModelOptions2,
231
+ modelName
232
+ });
233
+ manager.onchange("permissionSchema", function(newval, key, target, oldval) {
234
+ const permissionSchema = newval ?? {};
235
+ const { schemaKeys, globalPermissionKeys, modelPermissionKeys } = classifyPermissionSchema(
236
+ permissionSchema,
237
+ target.modelPermissionPrefix ?? ""
238
+ );
239
+ target._permissionSchemaKeys = schemaKeys;
240
+ target._globalPermissionKeys = globalPermissionKeys;
241
+ target._modelPermissionKeys = modelPermissionKeys;
242
+ }).onchange("basePath", function(newval, key, target, oldval) {
243
+ target[key] = normalizeBasePath(
244
+ modelName,
245
+ isString(newval) || isNil(newval) ? newval : void 0
246
+ );
247
+ }).build();
248
+ modelJsonSchemas[modelName] = model.jsonSchema();
249
+ return manager;
250
+ };
251
+ var getOrCreateModelOptions = (modelName) => {
252
+ let manager = modelOptions[modelName];
253
+ if (!manager) {
254
+ manager = createModelOptions(modelName);
255
+ modelOptions[modelName] = manager;
256
+ }
257
+ return manager;
258
+ };
259
+ var setModelOptions = (modelName, options) => {
260
+ const manager = getOrCreateModelOptions(modelName);
261
+ const defaultOptions = getDefaultModelOptions();
262
+ const modelOptions2 = manager.fetch();
263
+ manager.assign({ ...defaultOptions, ...modelOptions2, ...options });
264
+ };
265
+ var setModelOption = (modelName, key, value) => {
266
+ const manager = getOrCreateModelOptions(modelName);
267
+ manager.set(key, value);
268
+ };
269
+ var getModelOptions = (modelName) => {
270
+ const manager = getOrCreateModelOptions(modelName);
271
+ return manager.fetch();
272
+ };
273
+ var getModelOption = (modelName, key, defaultValue) => {
274
+ const manager = getOrCreateModelOptions(modelName);
275
+ const defaultModelValue = getDefaultModelOption(
276
+ key,
277
+ defaultValue
278
+ );
279
+ return getNestedOption(manager, key, defaultModelValue);
280
+ };
281
+ var getExactModelOption = (modelName, key) => {
282
+ const manager = getOrCreateModelOptions(modelName);
283
+ const defaultModelValue = getDefaultModelOption(key);
284
+ return manager.get(key, defaultModelValue);
285
+ };
286
+ var getModelNames = () => {
287
+ return Object.keys(modelOptions);
288
+ };
289
+ var getModelJsonSchema = (modelName) => {
290
+ return modelJsonSchemas[modelName];
291
+ };
292
+
293
+ // src/options/data-options.ts
294
+ import mongoose2 from "mongoose";
295
+ import { addLeadingSlash as addLeadingSlash2, isNil as isNil2, isString as isString2 } from "@web-ts-toolkit/utils";
296
+ var pluralize2 = mongoose2.pluralize();
297
+ var dataOptions = {};
298
+ var defaultDataOptions = {
299
+ basePath: null,
300
+ queryPath: "__query"
301
+ };
302
+ var normalizeBasePath2 = (name, value) => {
303
+ if (isNil2(value)) {
304
+ return `/${pluralize2(name)}`;
305
+ }
306
+ return isString2(value) ? addLeadingSlash2(value) : "";
307
+ };
308
+ var createDataOptions = (dataName) => {
309
+ const manager = new OptionsManager({
310
+ ...defaultDataOptions,
311
+ dataName
312
+ });
313
+ manager.onchange("basePath", function(newval, key, target, oldval) {
314
+ target[key] = normalizeBasePath2(
315
+ dataName,
316
+ isString2(newval) || isNil2(newval) ? newval : void 0
317
+ );
318
+ }).build();
319
+ return manager;
320
+ };
321
+ var getOrCreateDataOptions = (dataName) => {
322
+ let manager = dataOptions[dataName];
323
+ if (!manager) {
324
+ manager = createDataOptions(dataName);
325
+ dataOptions[dataName] = manager;
326
+ }
327
+ return manager;
328
+ };
329
+ var setDataOptions = (dataName, options) => {
330
+ const manager = getOrCreateDataOptions(dataName);
331
+ const dataOptions2 = manager.fetch();
332
+ manager.assign({ ...dataOptions2, ...options });
333
+ };
334
+ var setDataOption = (dataName, key, value) => {
335
+ const manager = getOrCreateDataOptions(dataName);
336
+ manager.set(key, value);
337
+ };
338
+ var getDataOptions = (dataName) => {
339
+ const manager = getOrCreateDataOptions(dataName);
340
+ return manager.fetch();
341
+ };
342
+ var getDataOption = (dataName, key, defaultValue) => {
343
+ const manager = getOrCreateDataOptions(dataName);
344
+ return getNestedOption(manager, key, defaultValue);
345
+ };
346
+ var getExactDataOption = (dataName, key) => {
347
+ const manager = getOrCreateDataOptions(dataName);
348
+ return manager.get(key);
349
+ };
350
+
351
+ // src/meta.ts
352
+ import mongoose3 from "mongoose";
353
+ import { get as get3, keys } from "@web-ts-toolkit/utils";
354
+
355
+ // src/helpers/collection.ts
356
+ import sift from "sift";
357
+ import { filter, find } from "@web-ts-toolkit/utils";
358
+ var filterCollection = (collection, predicate) => {
359
+ return filter(collection, sift(predicate));
360
+ };
361
+ var findElement = (collection, predicate) => {
362
+ return find(collection, sift(predicate));
363
+ };
364
+ var matchElement = (element, predicate) => {
365
+ return sift(predicate)(element);
366
+ };
367
+ var findElementById = (collection, id) => {
368
+ return findElement(collection, { _id: id });
369
+ };
370
+
371
+ // src/helpers/document.ts
372
+ import { get as get2, isArray as isArray2, isPlainObject as isPlainObject3, isPromise as isPromise2, isString as isString4, pick, set as set2 } from "@web-ts-toolkit/utils";
373
+
374
+ // src/lib.ts
375
+ import { Document, Schema } from "mongoose";
376
+ import { addLeadingSlash as addLeadingSlash3, isPlainObject, isPromise } from "@web-ts-toolkit/utils";
377
+ var isSchema = (val) => val instanceof Schema;
378
+ var isObjectIdType = (val) => val === "ObjectId" || val === Schema.Types.ObjectId;
379
+ var isReference = (val) => isPlainObject(val) && !!val.ref && isObjectIdType(val.type);
380
+ var isDocument = function isDocument2(doc) {
381
+ return doc instanceof Document;
382
+ };
383
+ var mapValuesAsync = async function mapValuesAsync2(object, asyncFn) {
384
+ return Object.fromEntries(
385
+ await Promise.all(Object.entries(object).map(async ([key, value]) => [key, await asyncFn(value, key, object)]))
386
+ );
387
+ };
388
+ var arrToObj = (arr) => {
389
+ const obj = {};
390
+ for (let x = 0; x < arr.length; x++) {
391
+ obj[arr[x]] = true;
392
+ }
393
+ return obj;
394
+ };
395
+ var removeConsecutiveSlashesFromUrl = (url) => url.replace(/\/{2,}/g, "/");
396
+ var processUrl = (url) => addLeadingSlash3(removeConsecutiveSlashesFromUrl(url));
397
+
398
+ // src/helpers/query.ts
399
+ import { flattenDeep, isNaN, isNil as isNil3, isPlainObject as isPlainObject2, isString as isString3, reduce } from "@web-ts-toolkit/utils";
400
+ function genPagination({
401
+ skip,
402
+ limit,
403
+ page,
404
+ pageSize
405
+ }, hardLimit) {
406
+ let _skip = 0;
407
+ let _limit = Number(limit ?? pageSize);
408
+ if (isNaN(_limit) || _limit > hardLimit) _limit = hardLimit;
409
+ if (!isNil3(skip)) {
410
+ _skip = Number(skip);
411
+ } else if (!isNil3(page)) {
412
+ const npage = Number(page);
413
+ if (npage > 1) _skip = (npage - 1) * _limit;
414
+ }
415
+ return { skip: _skip, limit: _limit };
416
+ }
417
+ function parseSortString(sortString) {
418
+ if (!sortString) return { sortKey: "", sortOrder: "asc" };
419
+ if (sortString.startsWith("-")) {
420
+ return { sortKey: sortString.substring(1), sortOrder: "desc" };
421
+ } else {
422
+ return { sortKey: sortString, sortOrder: "asc" };
423
+ }
424
+ }
425
+ function normalizeSelect(select) {
426
+ if (Array.isArray(select)) return flattenDeep(select.map(normalizeSelect));
427
+ if (isPlainObject2(select)) {
428
+ return reduce(
429
+ select,
430
+ (ret, val, key) => {
431
+ if (val === 1) ret.push(key);
432
+ else if (val === -1) ret.push(`-${key}`);
433
+ return ret;
434
+ },
435
+ []
436
+ );
437
+ }
438
+ if (isString3(select)) return select.split(" ").map((v) => v.trim());
439
+ return [];
440
+ }
441
+
442
+ // src/helpers/document.ts
443
+ function getDocValue(doc, path, defalutValue) {
444
+ if (isDocument(doc)) {
445
+ return get2(doc._doc, path, defalutValue);
446
+ } else if (isPlainObject3(doc)) {
447
+ return get2(doc, path, defalutValue);
448
+ }
449
+ }
450
+ function setDocValue(doc, path, value) {
451
+ if (isDocument(doc)) {
452
+ set2(doc._doc, path, value);
453
+ } else if (isPlainObject3(doc)) {
454
+ set2(doc, path, value);
455
+ }
456
+ }
457
+ function getDocPermissions(modelName, doc) {
458
+ const docPermissionField = getModelOption(modelName, "documentPermissionField");
459
+ return getDocValue(doc, docPermissionField, {});
460
+ }
461
+ function toObject(doc) {
462
+ return isDocument(doc) ? doc.toObject() : doc;
463
+ }
464
+ function pickDocFields(doc, fields = []) {
465
+ if (isDocument(doc)) {
466
+ doc._doc = pick(doc._doc, fields);
467
+ return doc;
468
+ } else {
469
+ return pick(doc, fields);
470
+ }
471
+ }
472
+ async function populateDoc(doc, target) {
473
+ let p = doc.populate(target);
474
+ if (isPromise2(p)) return p;
475
+ return "execPopulate" in p && p.execPopulate?.();
476
+ }
477
+ var genSubPopulate = (sub, popul) => {
478
+ if (!popul) return [];
479
+ let populate = isArray2(popul) ? popul : [popul];
480
+ populate = populate.map((p) => {
481
+ const ret = isString4(p) ? { path: `${sub}.${p}` } : {
482
+ path: `${sub}.${p.path}`,
483
+ select: normalizeSelect(p.select)
484
+ };
485
+ return ret;
486
+ });
487
+ return populate;
488
+ };
489
+
490
+ // src/helpers/errors.ts
491
+ import JsonRouter from "@web-ts-toolkit/express-json-router";
492
+
493
+ // src/enums.ts
494
+ var StatusCodes = /* @__PURE__ */ ((StatusCodes3) => {
495
+ StatusCodes3[StatusCodes3["OK"] = 200] = "OK";
496
+ StatusCodes3[StatusCodes3["Created"] = 201] = "Created";
497
+ StatusCodes3[StatusCodes3["BadRequest"] = 400] = "BadRequest";
498
+ StatusCodes3[StatusCodes3["Unauthorized"] = 401] = "Unauthorized";
499
+ StatusCodes3[StatusCodes3["Forbidden"] = 403] = "Forbidden";
500
+ StatusCodes3[StatusCodes3["NotFound"] = 404] = "NotFound";
501
+ StatusCodes3[StatusCodes3["UnprocessableContent"] = 422] = "UnprocessableContent";
502
+ return StatusCodes3;
503
+ })(StatusCodes || {});
504
+ var Codes = /* @__PURE__ */ ((Codes2) => {
505
+ Codes2["Success"] = "success";
506
+ Codes2["Created"] = "created";
507
+ Codes2["BadRequest"] = "bad_request";
508
+ Codes2["Unauthorized"] = "unauthorized";
509
+ Codes2["Forbidden"] = "forbidden";
510
+ Codes2["NotFound"] = "not_found";
511
+ return Codes2;
512
+ })(Codes || {});
513
+ var CustomHeaders = /* @__PURE__ */ ((CustomHeaders2) => {
514
+ CustomHeaders2["TotalCount"] = "wtt-total-count";
515
+ CustomHeaders2["ReturnedCount"] = "wtt-returned-count";
516
+ CustomHeaders2["Page"] = "wtt-page";
517
+ CustomHeaders2["PageSize"] = "wtt-page-size";
518
+ CustomHeaders2["TotalPages"] = "wtt-total-pages";
519
+ CustomHeaders2["HasNextPage"] = "wtt-has-next-page";
520
+ CustomHeaders2["HasPreviousPage"] = "wtt-has-previous-page";
521
+ return CustomHeaders2;
522
+ })(CustomHeaders || {});
523
+ var FilterOperator = /* @__PURE__ */ ((FilterOperator2) => {
524
+ FilterOperator2[FilterOperator2["SubQuery"] = 0] = "SubQuery";
525
+ FilterOperator2[FilterOperator2["Date"] = 1] = "Date";
526
+ return FilterOperator2;
527
+ })(FilterOperator || {});
528
+
529
+ // src/helpers/errors.ts
530
+ var { BadRequestError, ForbiddenError, NotFoundError, UnprocessableEntityError } = JsonRouter.clientErrors;
531
+ function mapCodeToMessage(code) {
532
+ switch (code) {
533
+ case "success" /* Success */:
534
+ return "OK";
535
+ case "created" /* Created */:
536
+ return "Created";
537
+ case "bad_request" /* BadRequest */:
538
+ return "Bad Request";
539
+ case "forbidden" /* Forbidden */:
540
+ return "Forbidden";
541
+ case "not_found" /* NotFound */:
542
+ return "Not Found";
543
+ default:
544
+ return;
545
+ }
546
+ }
547
+ function mapCodeToStatusCode(code) {
548
+ switch (code) {
549
+ case "success" /* Success */:
550
+ return 200 /* OK */;
551
+ case "created" /* Created */:
552
+ return 201 /* Created */;
553
+ case "bad_request" /* BadRequest */:
554
+ return 400 /* BadRequest */;
555
+ case "forbidden" /* Forbidden */:
556
+ return 403 /* Forbidden */;
557
+ case "not_found" /* NotFound */:
558
+ return 404 /* NotFound */;
559
+ default:
560
+ return 422 /* UnprocessableContent */;
561
+ }
562
+ }
563
+ function handleResultError(result) {
564
+ if (result.success) return;
565
+ const { code, errors = [] } = result;
566
+ const errorOptions = { errors };
567
+ switch (code) {
568
+ case "bad_request" /* BadRequest */:
569
+ throw new BadRequestError("Bad Request", errorOptions);
570
+ case "forbidden" /* Forbidden */:
571
+ throw new ForbiddenError("Forbidden", errorOptions);
572
+ case "not_found" /* NotFound */:
573
+ throw new NotFoundError("Not Found", errorOptions);
574
+ default:
575
+ throw new UnprocessableEntityError("Unprocessable Content", errorOptions);
576
+ }
577
+ }
578
+
579
+ // src/helpers/index.ts
580
+ import { forEach as forEach2, isArray as isArray3, isEmpty, isObject, isPlainObject as isPlainObject4, isString as isString5, noop } from "@web-ts-toolkit/utils";
581
+ function recurseObject(obj) {
582
+ if (isSchema(obj)) {
583
+ return buildRefs(obj.tree);
584
+ }
585
+ if (!isObject(obj)) return null;
586
+ if (isReference(obj)) {
587
+ return obj.ref;
588
+ }
589
+ let ret = null;
590
+ forEach2(obj, (val) => {
591
+ ret = recurseObject(val);
592
+ if (!isEmpty(ret)) {
593
+ return false;
594
+ }
595
+ });
596
+ return ret;
597
+ }
598
+ function buildRefs(schema) {
599
+ if (!isObject(schema)) return {};
600
+ const references = {};
601
+ forEach2(schema, (val, key) => {
602
+ const paths = recurseObject(val);
603
+ if (!isEmpty(paths)) {
604
+ references[key] = paths;
605
+ }
606
+ const target = isObject(val) && "type" in val ? val.type ?? val : val;
607
+ if (isArray3(target) && target.length > 0) {
608
+ if (isSchema(target[0]) || isPlainObject4(target[0])) {
609
+ return;
610
+ }
611
+ }
612
+ });
613
+ return references;
614
+ }
615
+ function buildSubPaths(schema) {
616
+ if (!isObject(schema)) return [];
617
+ const subPaths = [];
618
+ forEach2(schema, (val, key) => {
619
+ const target = isObject(val) && "type" in val ? val.type ?? val : val;
620
+ if (isArray3(target) && target.length > 0) {
621
+ if (isSchema(target[0]) || isPlainObject4(target[0]) && !isReference(target[0])) {
622
+ subPaths.push(key);
623
+ }
624
+ }
625
+ });
626
+ return subPaths;
627
+ }
628
+ async function iterateQuery(query, handler) {
629
+ if (!isPlainObject4(query)) return query;
630
+ if (!handler) return noop;
631
+ const queryObject = query;
632
+ return mapValuesAsync(queryObject, async (val, key) => {
633
+ if (isPlainObject4(val)) {
634
+ const plainValue = val;
635
+ if (plainValue.$$sq) {
636
+ return handler(0 /* SubQuery */, plainValue.$$sq, key);
637
+ } else if (plainValue.$$date) {
638
+ return handler(1 /* Date */, plainValue.$$date, key);
639
+ } else {
640
+ return iterateQuery(val, handler);
641
+ }
642
+ }
643
+ if (isArray3(val)) {
644
+ return Promise.all(val.map((v) => iterateQuery(v, handler)));
645
+ }
646
+ return val;
647
+ });
648
+ }
649
+ var createValidator = (fn) => {
650
+ const stringHandler = (key) => key.trim().split(" ").every((v) => fn(v));
651
+ const arrayHandler = (arr) => arr.some((item) => {
652
+ if (isString5(item)) return stringHandler(item);
653
+ else if (isArray3(item)) return arrayHandler(item);
654
+ else return false;
655
+ });
656
+ return { stringHandler, arrayHandler };
657
+ };
658
+
659
+ // src/meta.ts
660
+ var modelRefs = {};
661
+ var modelSubs = {};
662
+ var modelAtts = {};
663
+ var ensureModelMeta = (modelName) => {
664
+ if (modelName in modelRefs && modelName in modelSubs && modelName in modelAtts) {
665
+ return;
666
+ }
667
+ const model = mongoose3.models[modelName];
668
+ if (!model) {
669
+ modelRefs[modelName] = modelRefs[modelName] ?? {};
670
+ modelSubs[modelName] = modelSubs[modelName] ?? [];
671
+ modelAtts[modelName] = modelAtts[modelName] ?? [];
672
+ return;
673
+ }
674
+ const schema = model.schema;
675
+ modelRefs[modelName] = buildRefs(schema.tree);
676
+ modelSubs[modelName] = buildSubPaths(schema.tree);
677
+ modelAtts[modelName] = keys(schema.obj);
678
+ };
679
+ var getModelRef = (modelName, refPath) => {
680
+ ensureModelMeta(modelName);
681
+ return get3(modelRefs, `${modelName}.${refPath}`, null);
682
+ };
683
+ var getModelSub = (modelName) => {
684
+ ensureModelMeta(modelName);
685
+ return get3(modelSubs, modelName, []);
686
+ };
687
+
688
+ // src/services/base.ts
689
+ import {
690
+ castArray,
691
+ compact,
692
+ flatten,
693
+ forEach as forEach3,
694
+ get as get4,
695
+ intersectionBy,
696
+ isArray as isArray4,
697
+ isPlainObject as isPlainObject5,
698
+ uniq
699
+ } from "@web-ts-toolkit/utils";
700
+ function validateClientFilter(filter2) {
701
+ const errors = [];
702
+ const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
703
+ const visit = (value, path) => {
704
+ if (isArray4(value)) {
705
+ value.forEach((item, index) => visit(item, `${path}[${index}]`));
706
+ return;
707
+ }
708
+ if (!isPlainObject5(value)) return;
709
+ Object.entries(value).forEach(([key, child]) => {
710
+ const nextPath = path ? `${path}.${key}` : key;
711
+ if (blockedOperators.has(key)) {
712
+ errors.push(`Unsupported filter operator: ${nextPath}`);
713
+ return;
714
+ }
715
+ visit(child, nextPath);
716
+ });
717
+ };
718
+ visit(filter2, "filter");
719
+ return errors;
720
+ }
721
+ var Base = class {
722
+ constructor(req, modelName) {
723
+ this.req = req;
724
+ this.modelName = modelName;
725
+ }
726
+ decorate(doc, access, context) {
727
+ return this.req.macl.decorate(this.modelName, doc, access, context);
728
+ }
729
+ decorateAll(docs, access, context) {
730
+ return this.req.macl.decorateAll(this.modelName, docs, access, context);
731
+ }
732
+ genAllowedFields(doc, access, baseFields) {
733
+ return this.req.macl.genAllowedFields(this.modelName, doc, access, baseFields);
734
+ }
735
+ genDocPermissions(doc, access, context) {
736
+ return this.req.macl.genDocPermissions(this.modelName, doc, access, context);
737
+ }
738
+ genFilter(access, filter2) {
739
+ return this.req.macl.genFilter(this.modelName, access, filter2);
740
+ }
741
+ getIdentifier() {
742
+ return this.req.macl.getIdentifier(this.modelName);
743
+ }
744
+ genIDFilter(id) {
745
+ return this.req.macl.genIDFilter(this.modelName, id);
746
+ }
747
+ genPopulate(access, populate) {
748
+ return this.req.macl.genPopulate(this.modelName, access, populate);
749
+ }
750
+ genSelect(access, targetFields, skipChecks, subPaths) {
751
+ return this.req.macl.genSelect(this.modelName, access, targetFields, skipChecks, subPaths);
752
+ }
753
+ genQuerySelect(access, targetFields, skipChecks, subPaths) {
754
+ return this.genSelect(access, targetFields, skipChecks, subPaths);
755
+ }
756
+ addEmptyPermissions(doc) {
757
+ return this.req.macl.addEmptyPermissions(this.modelName, doc);
758
+ }
759
+ addDocPermissions(doc, access, context) {
760
+ return this.req.macl.addDocPermissions(this.modelName, doc, access, context);
761
+ }
762
+ addFieldPermissions(doc, access, context) {
763
+ return this.req.macl.addFieldPermissions(this.modelName, doc, access, context);
764
+ }
765
+ pickAllowedFields(doc, access, baseFields) {
766
+ return this.req.macl.pickAllowedFields(this.modelName, doc, access, baseFields);
767
+ }
768
+ trimOutputFields(doc, access, baseFields) {
769
+ return this.pickAllowedFields(doc, access, baseFields);
770
+ }
771
+ prepare(allowedData, access, context) {
772
+ return this.req.macl.prepare(this.modelName, allowedData, access, context);
773
+ }
774
+ runTasks(docObject, tasks) {
775
+ return this.req.macl.runTasks(this.modelName, docObject, tasks);
776
+ }
777
+ transform(doc, access, context) {
778
+ return this.req.macl.transform(this.modelName, doc, access, context);
779
+ }
780
+ finalize(doc, access, context) {
781
+ return this.req.macl.finalize(this.modelName, doc, access, context);
782
+ }
783
+ changes(doc, context) {
784
+ return this.req.macl.changes(this.modelName, doc, context);
785
+ }
786
+ validate(allowedData, access, context) {
787
+ return this.req.macl.validate(this.modelName, allowedData, access, context);
788
+ }
789
+ checkIfModelPermissionExists(accesses) {
790
+ const modelPermissionKeys = getModelOption(this.modelName, "_modelPermissionKeys");
791
+ return accesses.some((access) => modelPermissionKeys[access]?.length > 0);
792
+ }
793
+ validateClientFilter(filter2) {
794
+ return validateClientFilter(filter2);
795
+ }
796
+ processInclude(include) {
797
+ const includes = compact(castArray(include)).filter(({ model, op, path, localField, foreignField }) => {
798
+ return model && op && path && localField && foreignField;
799
+ });
800
+ let includeLocalFields = [];
801
+ let includePaths = [];
802
+ forEach3(includes, (inc) => {
803
+ includeLocalFields.push(inc.localField);
804
+ includePaths.push(inc.path);
805
+ });
806
+ includeLocalFields = uniq(compact(includeLocalFields));
807
+ includePaths = uniq(compact(includePaths));
808
+ return {
809
+ includes,
810
+ includeLocalFields,
811
+ includePaths
812
+ };
813
+ }
814
+ async includeDocs(docs, include) {
815
+ if (!include) return docs;
816
+ const includes = compact(castArray(include));
817
+ if (includes.length === 0) return docs;
818
+ const isSingle = !isArray4(docs);
819
+ if (isSingle) docs = [docs];
820
+ for (let x = 0; x < includes.length; x++) {
821
+ const include2 = includes[x];
822
+ if (include2.op === "count") {
823
+ docs = await this.includeDocsCount(docs, include2);
824
+ } else {
825
+ docs = await this.includeDocsList(docs, include2);
826
+ }
827
+ }
828
+ return isSingle ? docs[0] : docs;
829
+ }
830
+ async includeDocsList(docs, include) {
831
+ const { model, op, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
832
+ const svc = this.req.macl.getPublicService(model);
833
+ if (!svc) return docs;
834
+ const includeLocalValues = [];
835
+ forEach3(docs, (doc, i) => {
836
+ includeLocalValues.push(get4(doc, localField));
837
+ });
838
+ const filter2 = { ..._filters ?? {}, [foreignField]: { $in: flatten(includeLocalValues) } };
839
+ const result = await svc.find(filter2, args, {
840
+ ...options,
841
+ lean: true,
842
+ includePermissions: false,
843
+ includeCount: false
844
+ });
845
+ if (!result.success) return docs;
846
+ for (let y = 0; y < docs.length; y++) {
847
+ const doc = docs[y];
848
+ const localValue = get4(doc, localField);
849
+ const filterFn = (row) => intersectionBy(castArray(localValue), castArray(get4(row, foreignField)), String).length > 0;
850
+ const matches = result.data.filter(filterFn);
851
+ setDocValue(doc, path, op === "list" ? matches : matches[0]);
852
+ }
853
+ return docs;
854
+ }
855
+ async includeDocsCount(docs, include) {
856
+ const { model, path, localField, foreignField, filter: _filters } = include;
857
+ const svc = this.req.macl.getPublicService(model);
858
+ if (!svc) return docs;
859
+ for (let y = 0; y < docs.length; y++) {
860
+ const doc = docs[y];
861
+ const localValue = get4(doc, localField);
862
+ const filter2 = { ..._filters ?? {}, [foreignField]: localValue };
863
+ const result = await svc.count(filter2);
864
+ if (!result.success) continue;
865
+ setDocValue(doc, path, result.data);
866
+ }
867
+ return docs;
868
+ }
869
+ async parseClientData(filter2) {
870
+ const result = await iterateQuery(filter2, async (fo, val, key) => {
871
+ switch (fo) {
872
+ case 0 /* SubQuery */:
873
+ return this.handleSubQuery(val, key);
874
+ case 1 /* Date */:
875
+ return this.handleDate(val, key);
876
+ default:
877
+ return null;
878
+ }
879
+ });
880
+ return result;
881
+ }
882
+ async handleSubQuery(sq, key) {
883
+ const { model, op, id, filter: filter2, args, options, sqOptions = {} } = sq;
884
+ const svc = this.req.macl.getPublicService(model);
885
+ if (!svc) return null;
886
+ let result;
887
+ if (op === "list") {
888
+ result = await svc.find(filter2, args, options);
889
+ } else if (op === "read") {
890
+ if (id) {
891
+ result = await svc.findById(id, args, options);
892
+ } else if (filter2) {
893
+ result = await svc.findOne(filter2, args, options);
894
+ } else {
895
+ return null;
896
+ }
897
+ } else {
898
+ return null;
899
+ }
900
+ if (!result.success) return null;
901
+ let ret = result.data;
902
+ if (sqOptions.path) {
903
+ ret = isArray4(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
904
+ }
905
+ if (sqOptions.compact) {
906
+ ret = compact(castArray(ret));
907
+ }
908
+ return ret;
909
+ }
910
+ async handleDate(val, key) {
911
+ return /* @__PURE__ */ new Date();
912
+ }
913
+ };
914
+
915
+ // src/services/service.ts
916
+ import {
917
+ castArray as castArray2,
918
+ forEach as forEach4,
919
+ get as get5,
920
+ isArray as isArray5,
921
+ isBoolean as isBoolean3,
922
+ isFunction as isFunction3,
923
+ omit,
924
+ pick as pick3,
925
+ uniq as uniq2
926
+ } from "@web-ts-toolkit/utils";
927
+ import diff from "deep-diff";
928
+
929
+ // src/model.ts
930
+ import mongoose4 from "mongoose";
931
+ var Model = class {
932
+ constructor(modelName) {
933
+ this.modelName = modelName;
934
+ this.model = mongoose4.model(modelName);
935
+ if (!this.model) return;
936
+ this.model.schema.set("optimisticConcurrency", true);
937
+ const currVersionKey = this.model.schema.get("versionKey");
938
+ if (!currVersionKey) this.model.schema.set("versionKey", "__v");
939
+ }
940
+ new() {
941
+ const doc = new this.model();
942
+ return doc;
943
+ }
944
+ create(data) {
945
+ return this.model.create(data);
946
+ }
947
+ find({ filter: filter2, select, sort, populate, limit, skip, lean }) {
948
+ if (!this.validateSort(sort)) {
949
+ sort = null;
950
+ }
951
+ const builder = this.model.find(filter2);
952
+ if (select) builder.select(select);
953
+ if (skip) builder.skip(Number(skip));
954
+ if (limit) builder.limit(Number(limit));
955
+ if (sort) builder.sort(sort);
956
+ if (populate) builder.populate(populate);
957
+ if (lean) builder.lean();
958
+ return builder;
959
+ }
960
+ // See https://github.com/Automattic/mongoose/blob/65b2d12a8f85f86136cfaf32947f338ba0c5f451/lib/query.js#L3011
961
+ validateSort(sort, logError = console.error) {
962
+ const validSortOrders = [1, -1, "asc", "ascending", "desc", "descending"];
963
+ if (sort === null || sort === void 0) return true;
964
+ if (typeof sort === "string") {
965
+ if (!/^[a-zA-Z0-9\s-]+/.test(sort)) {
966
+ logError("Invalid sort string:", sort);
967
+ return false;
968
+ }
969
+ return true;
970
+ }
971
+ if (Array.isArray(sort)) {
972
+ const isValid = sort.every((pair) => {
973
+ if (!Array.isArray(pair) || pair.length !== 2) {
974
+ logError("Invalid sort array element: must be [key, order]", pair);
975
+ return false;
976
+ }
977
+ const [key, order] = pair;
978
+ if (typeof key !== "string") {
979
+ logError("Invalid sort array key: must be string", key);
980
+ return false;
981
+ }
982
+ if (!validSortOrders.includes(order)) {
983
+ logError('Invalid sort array order: must be 1, -1, "asc", or "desc"', order);
984
+ return false;
985
+ }
986
+ return true;
987
+ });
988
+ return isValid;
989
+ }
990
+ if (typeof sort === "object" && !(sort instanceof Map)) {
991
+ const isValid = Object.values(sort).every((order) => {
992
+ if (!validSortOrders.includes(order)) {
993
+ logError('Invalid sort object value: must be 1, -1, "asc", or "desc"', order);
994
+ return false;
995
+ }
996
+ return true;
997
+ });
998
+ return isValid;
999
+ }
1000
+ if (sort instanceof Map) {
1001
+ const isValid = Array.from(sort.values()).every((order) => {
1002
+ if (!validSortOrders.includes(order)) {
1003
+ logError('Invalid sort Map value: must be 1, -1, "asc", or "desc"', order);
1004
+ return false;
1005
+ }
1006
+ return true;
1007
+ });
1008
+ return isValid;
1009
+ }
1010
+ logError("Invalid sort type: must be string, array, object, or Map", sort);
1011
+ return false;
1012
+ }
1013
+ findOne({ filter: filter2, select, sort, populate, lean }) {
1014
+ if (!this.validateSort(sort)) {
1015
+ sort = null;
1016
+ }
1017
+ const builder = this.model.findOne(filter2);
1018
+ if (select) builder.select(select);
1019
+ if (sort) builder.sort(sort);
1020
+ if (populate) builder.populate(populate);
1021
+ if (lean) builder.lean();
1022
+ return builder;
1023
+ }
1024
+ findOneAndDelete(filter2) {
1025
+ return this.model.findOneAndDelete(filter2);
1026
+ }
1027
+ exists(filter2) {
1028
+ if (!filter2) return null;
1029
+ return this.findOne(filter2).select("_id").lean();
1030
+ }
1031
+ // see https://mongoosejs.com/docs/api.html#query_Query-countDocuments
1032
+ countDocuments(filter2 = {}) {
1033
+ return this.model.countDocuments(filter2);
1034
+ }
1035
+ // see https://mongoosejs.com/docs/api.html#model_Model.estimatedDocumentCount
1036
+ estimatedDocumentCount() {
1037
+ return this.model.estimatedDocumentCount();
1038
+ }
1039
+ // see https://mongoosejs.com/docs/api.html#model_Model.distinct
1040
+ distinct(field, conditions = {}) {
1041
+ return this.model.distinct(field, conditions);
1042
+ }
1043
+ };
1044
+ var model_default = Model;
1045
+
1046
+ // src/services/service.ts
1047
+ var Service = class extends Base {
1048
+ constructor(req, modelName) {
1049
+ super(req, modelName);
1050
+ this.model = new model_default(modelName);
1051
+ this.options = getModelOptions(modelName);
1052
+ this.defaults = this.options.defaults || {};
1053
+ this.baseFields = ["_id"];
1054
+ this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
1055
+ }
1056
+ async findOne(filter2, args, options) {
1057
+ const filterErrors = this.validateClientFilter(filter2);
1058
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1059
+ const {
1060
+ select = this.defaults.findOneArgs?.select,
1061
+ sort = this.defaults.findOneArgs?.sort,
1062
+ populate = this.defaults.findOneArgs?.populate,
1063
+ include = this.defaults.findOneArgs?.include,
1064
+ overrides = {}
1065
+ } = args ?? {};
1066
+ const {
1067
+ skim = this.defaults.findOneOptions?.skim ?? false,
1068
+ includePermissions = this.defaults.findOneOptions?.includePermissions ?? true,
1069
+ access = this.defaults.findOneOptions?.access ?? "read",
1070
+ populateAccess = this.defaults.findOneOptions?.populateAccess,
1071
+ lean = this.defaults.findOneOptions?.lean ?? false
1072
+ } = options ?? {};
1073
+ const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
1074
+ let [_filter, _select, _populate] = await Promise.all([
1075
+ overrideFilter || this.genFilter(access, await this.parseClientData(filter2)),
1076
+ overrideSelect || this.genQuerySelect(access, select),
1077
+ overridePopulate || this.genPopulate(populateAccess || access, populate)
1078
+ ]);
1079
+ const { includes, includeLocalFields, includePaths } = this.processInclude(include);
1080
+ const finalSelect = normalizeSelect(_select).concat(includeLocalFields);
1081
+ const query = {
1082
+ filter: _filter,
1083
+ select: finalSelect,
1084
+ sort,
1085
+ populate: _populate
1086
+ };
1087
+ logger.debug(JSON.stringify({ op: "findOne", query }));
1088
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1089
+ let doc = await this.model.findOne({ ...query, lean });
1090
+ if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
1091
+ const context = {
1092
+ model: this.model.model,
1093
+ modelName: this.modelName,
1094
+ originalDocObject: toObject(doc)
1095
+ };
1096
+ doc = await this.includeDocs(doc, includes);
1097
+ let includeDocPermissions = includePermissions;
1098
+ if (!includeDocPermissions && !skim) {
1099
+ includeDocPermissions = this.checkIfModelPermissionExists([access, "read", "update"]);
1100
+ }
1101
+ if (includeDocPermissions) doc = await this.addDocPermissions(doc, access, context);
1102
+ if (includePermissions) doc = await this.addFieldPermissions(doc, access, context);
1103
+ doc = await this.trimOutputFields(
1104
+ doc,
1105
+ access,
1106
+ this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
1107
+ );
1108
+ if (!includePermissions) doc = this.addEmptyPermissions(doc);
1109
+ return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
1110
+ }
1111
+ async findById(id, args, options) {
1112
+ const {
1113
+ select = this.defaults.findByIdArgs?.select,
1114
+ populate = this.defaults.findByIdArgs?.populate,
1115
+ include = this.defaults.findByIdArgs?.include,
1116
+ overrides = {}
1117
+ } = args ?? {};
1118
+ const {
1119
+ skim = this.defaults.findOneOptions?.skim ?? false,
1120
+ includePermissions = this.defaults.findOneOptions?.includePermissions ?? true,
1121
+ access = this.defaults.findOneOptions?.access ?? "read",
1122
+ populateAccess = this.defaults.findOneOptions?.populateAccess,
1123
+ lean = this.defaults.findOneOptions?.lean ?? false
1124
+ } = options ?? {};
1125
+ const { select: overrideSelect, populate: overridePopulate, idFilter: overrideIdFilter } = overrides ?? {};
1126
+ const filter2 = overrideIdFilter || await this.genIDFilter(id);
1127
+ return this.findOne(
1128
+ filter2,
1129
+ {
1130
+ select,
1131
+ populate,
1132
+ include,
1133
+ overrides: {
1134
+ select: overrideSelect,
1135
+ populate: overridePopulate
1136
+ }
1137
+ },
1138
+ { skim, includePermissions, access, populateAccess, lean }
1139
+ );
1140
+ }
1141
+ async find(filter2, args, options, decorate) {
1142
+ const filterErrors = this.validateClientFilter(filter2);
1143
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1144
+ const {
1145
+ select = this.defaults.findArgs?.select,
1146
+ populate = this.defaults.findArgs?.populate,
1147
+ include = this.defaults.findArgs?.include,
1148
+ sort = this.defaults.findArgs?.sort,
1149
+ skip = this.defaults.findArgs?.skip,
1150
+ limit = this.defaults.findArgs?.limit,
1151
+ page = this.defaults.findArgs?.page,
1152
+ pageSize = this.defaults.findArgs?.pageSize,
1153
+ overrides = {}
1154
+ } = args ?? {};
1155
+ const {
1156
+ skim = this.defaults.findOptions?.skim ?? false,
1157
+ includePermissions = this.defaults.findOptions?.includePermissions ?? true,
1158
+ includeCount = this.defaults.findOptions?.includeCount ?? false,
1159
+ populateAccess = this.defaults.findOptions?.populateAccess ?? "read",
1160
+ lean = this.defaults.findOptions?.lean ?? false
1161
+ } = options ?? {};
1162
+ const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
1163
+ const [_filter, _select, _populate, pagination] = await Promise.all([
1164
+ overrideFilter || this.genFilter("list", await this.parseClientData(filter2)),
1165
+ overrideSelect || this.genQuerySelect("list", select),
1166
+ overridePopulate || this.genPopulate(populateAccess, populate),
1167
+ genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
1168
+ ]);
1169
+ const finalSelect = normalizeSelect(_select);
1170
+ const filteredPopulate = isArray5(finalSelect) && isArray5(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
1171
+ const { includes, includeLocalFields, includePaths } = this.processInclude(include);
1172
+ const query = {
1173
+ filter: _filter,
1174
+ select: finalSelect.concat(includeLocalFields),
1175
+ populate: filteredPopulate,
1176
+ sort,
1177
+ ...pagination
1178
+ };
1179
+ logger.debug(JSON.stringify({ op: "find", query }));
1180
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1181
+ let docs = await this.model.find({
1182
+ ...query,
1183
+ lean
1184
+ });
1185
+ const contexts = docs.map((doc) => ({
1186
+ model: this.model.model,
1187
+ modelName: this.modelName,
1188
+ originalDocObject: toObject(doc)
1189
+ }));
1190
+ const _decorate = isFunction3(decorate) ? decorate : (v) => v;
1191
+ docs = await this.includeDocs(docs, includes);
1192
+ const fieldPermissionAccess = includePermissions ? await this.getFieldPermissionAccess(docs.map((doc) => doc._id)) : void 0;
1193
+ docs = await Promise.all(
1194
+ docs.map(async (doc, i) => {
1195
+ contexts[i].fieldPermissionAccess = fieldPermissionAccess;
1196
+ let includeDocPermissions = includePermissions;
1197
+ if (!includeDocPermissions && !skim) {
1198
+ includeDocPermissions = this.checkIfModelPermissionExists(["list", "read", "update"]);
1199
+ }
1200
+ if (includeDocPermissions) doc = await this.addDocPermissions(doc, "list", contexts[i]);
1201
+ if (includePermissions) doc = await this.addFieldPermissions(doc, "list", contexts[i]);
1202
+ doc = await this.trimOutputFields(
1203
+ doc,
1204
+ "list",
1205
+ this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
1206
+ );
1207
+ doc = await _decorate(doc, contexts[i]);
1208
+ if (!includePermissions) doc = this.addEmptyPermissions(doc);
1209
+ return doc;
1210
+ })
1211
+ );
1212
+ return {
1213
+ success: true,
1214
+ kind: "list",
1215
+ code: "success" /* Success */,
1216
+ data: docs,
1217
+ count: docs.length,
1218
+ totalCount: includeCount ? await this.model.countDocuments(_filter) : null,
1219
+ query,
1220
+ contexts
1221
+ };
1222
+ }
1223
+ async create(data, args, options, decorate) {
1224
+ const { populate = this.defaults.createArgs?.populate } = args ?? {};
1225
+ const {
1226
+ skim = this.defaults.createOptions?.skim ?? false,
1227
+ includePermissions = this.defaults.createOptions?.includePermissions ?? true,
1228
+ populateAccess = this.defaults.createOptions?.populateAccess ?? "read"
1229
+ } = options ?? {};
1230
+ const isArr = Array.isArray(data);
1231
+ let dataArr = isArr ? data : [data];
1232
+ dataArr = await Promise.all(dataArr.map((d) => this.parseClientData(d)));
1233
+ const contexts = [];
1234
+ let validationError = null;
1235
+ const items = await Promise.all(
1236
+ dataArr.map(async (item, index) => {
1237
+ const context = { model: this.model.model, modelName: this.modelName, originalData: item };
1238
+ const allowedFields = await this.genAllowedFields(item, "create");
1239
+ const allowedData = pick3(item, allowedFields);
1240
+ const validated = await this.validate(allowedData, "create", context);
1241
+ if (isBoolean3(validated)) {
1242
+ if (!validated) {
1243
+ validationError = { success: false, code: "bad_request" /* BadRequest */ };
1244
+ return;
1245
+ }
1246
+ } else if (isArray5(validated)) {
1247
+ if (validated.length > 0) {
1248
+ validationError = { success: false, code: "bad_request" /* BadRequest */, errors: validated };
1249
+ return;
1250
+ }
1251
+ }
1252
+ const preparedData = await this.prepare(allowedData, "create", context);
1253
+ context.preparedData = preparedData;
1254
+ contexts.push(context);
1255
+ return preparedData;
1256
+ })
1257
+ );
1258
+ if (validationError) return validationError;
1259
+ const _decorate = isFunction3(decorate) ? decorate : (v) => v;
1260
+ let docs = await this.model.create(items);
1261
+ docs = await Promise.all(
1262
+ docs.map(async (doc, index) => {
1263
+ doc = await this.finalize(doc, "create", contexts[index]);
1264
+ contexts[index].finalDocObject = doc.toObject({ virtuals: false });
1265
+ let includeDocPermissions = includePermissions;
1266
+ if (!includeDocPermissions && !skim) {
1267
+ includeDocPermissions = this.checkIfModelPermissionExists(["create", "read", "update"]);
1268
+ }
1269
+ if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
1270
+ if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
1271
+ if (populate) await populateDoc(doc, await this.genPopulate(populateAccess, populate));
1272
+ doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
1273
+ doc = await _decorate(doc, contexts[index]);
1274
+ if (!includePermissions) doc = this.addEmptyPermissions(doc);
1275
+ return doc;
1276
+ })
1277
+ );
1278
+ return {
1279
+ success: true,
1280
+ kind: "list",
1281
+ code: "created" /* Created */,
1282
+ data: docs,
1283
+ input: items,
1284
+ count: docs.length
1285
+ };
1286
+ }
1287
+ async new() {
1288
+ const data = await this.model.new();
1289
+ return {
1290
+ success: true,
1291
+ kind: "single",
1292
+ code: "success" /* Success */,
1293
+ data
1294
+ };
1295
+ }
1296
+ async updateOne(filter2, data, args, options, decorate) {
1297
+ const filterErrors = this.validateClientFilter(filter2);
1298
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1299
+ const { populate = this.defaults.updateOneArgs?.populate, overrides = {} } = args ?? {};
1300
+ const {
1301
+ skim = this.defaults.updateOneOptions?.skim ?? false,
1302
+ includePermissions = this.defaults.updateOneOptions?.includePermissions ?? true,
1303
+ populateAccess = this.defaults.updateOneOptions?.populateAccess ?? "read"
1304
+ } = options ?? {};
1305
+ const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
1306
+ const [_filter, _populate] = await Promise.all([
1307
+ overrideFilter || this.genFilter("update", filter2),
1308
+ overridePopulate || this.genPopulate(populateAccess, populate)
1309
+ ]);
1310
+ const query = { filter: _filter, populate: _populate };
1311
+ logger.debug(JSON.stringify({ op: "updateOne", query }));
1312
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1313
+ let doc = await this.model.findOne({ filter: _filter });
1314
+ if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
1315
+ const context = { model: this.model.model, modelName: this.modelName };
1316
+ data = await this.parseClientData(data);
1317
+ context.originalDocObject = doc.toObject({ virtuals: false });
1318
+ context.originalData = data;
1319
+ doc = await this.addDocPermissions(doc, "update", context);
1320
+ context.docPermissions = this.getDocPermissions(doc);
1321
+ context.currentDoc = doc;
1322
+ const allowedFields = await this.genAllowedFields(doc, "update");
1323
+ const allowedData = pick3(data, allowedFields);
1324
+ const validated = await this.validate(allowedData, "update", context);
1325
+ if (isBoolean3(validated)) {
1326
+ if (!validated) return { success: false, code: "bad_request" /* BadRequest */ };
1327
+ } else if (isArray5(validated)) {
1328
+ if (validated.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: validated };
1329
+ }
1330
+ const prepared = await this.prepare(allowedData, "update", context);
1331
+ context.preparedData = prepared;
1332
+ Object.assign(doc, prepared);
1333
+ context.modifiedPaths = doc.modifiedPaths();
1334
+ doc = await this.transform(doc, "update", context);
1335
+ doc = await doc.save();
1336
+ const diffExcludeFields = [this.options.documentPermissionField, "__v"];
1337
+ context.diff = (d) => {
1338
+ context.changes = diff(
1339
+ omit(context.originalDocObject, diffExcludeFields),
1340
+ omit(d.toObject({ virtuals: false }), diffExcludeFields)
1341
+ ) || [];
1342
+ context.modifiedPaths = uniq2(context.changes.map((di) => di.path.length > 0 ? di.path[0] : ""));
1343
+ };
1344
+ doc = await this.finalize(doc, "update", context);
1345
+ context.finalDocObject = doc.toObject({ virtuals: false });
1346
+ context.diff(doc);
1347
+ await this.changes(doc, context);
1348
+ let includeDocPermissions = includePermissions;
1349
+ if (!includeDocPermissions && !skim) {
1350
+ includeDocPermissions = this.checkIfModelPermissionExists(["read", "update"]);
1351
+ }
1352
+ if (includeDocPermissions) doc = await this.addDocPermissions(doc, "update", context);
1353
+ if (includePermissions) doc = await this.addFieldPermissions(doc, "update", context);
1354
+ if (_populate) await populateDoc(doc, _populate);
1355
+ doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
1356
+ if (isFunction3(decorate)) doc = await decorate(doc, context);
1357
+ if (!includePermissions) doc = this.addEmptyPermissions(doc);
1358
+ return { success: true, kind: "single", code: "success" /* Success */, data: doc, input: prepared };
1359
+ }
1360
+ async updateById(id, data, { populate = this.defaults.updateByIdArgs?.populate, overrides = {} } = {}, {
1361
+ skim = this.defaults.updateByIdOptions?.skim ?? false,
1362
+ includePermissions = this.defaults.updateByIdOptions?.includePermissions ?? true,
1363
+ populateAccess = this.defaults.updateByIdOptions?.populateAccess ?? "read"
1364
+ } = {}, decorate) {
1365
+ const { populate: overridePopulate, idFilter: overrideIdFilter } = overrides;
1366
+ const filter2 = overrideIdFilter || await this.genIDFilter(id);
1367
+ return this.updateOne(
1368
+ filter2,
1369
+ data,
1370
+ {
1371
+ populate,
1372
+ overrides: {
1373
+ populate: overridePopulate
1374
+ }
1375
+ },
1376
+ { skim, includePermissions, populateAccess },
1377
+ decorate
1378
+ );
1379
+ }
1380
+ async upsert(filter2, data, args, options, decorate) {
1381
+ const filterErrors = this.validateClientFilter(filter2);
1382
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1383
+ const { populate = this.defaults.upsertArgs?.populate, overrides = {} } = args ?? {};
1384
+ const {
1385
+ skim = this.defaults.upsertOptions?.skim ?? false,
1386
+ includePermissions = this.defaults.upsertOptions?.includePermissions ?? true,
1387
+ populateAccess = this.defaults.upsertOptions?.populateAccess ?? "read"
1388
+ } = options ?? {};
1389
+ const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
1390
+ const theone = await this.model.findOne({ filter: filter2 });
1391
+ if (theone) {
1392
+ const _filter = await (overrideFilter || this.genFilter("update", filter2));
1393
+ const query = { filter: _filter };
1394
+ logger.debug(JSON.stringify({ op: "upsert", query }));
1395
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1396
+ const matched = matchElement(theone, _filter);
1397
+ if (!matched) return { success: false, code: "forbidden" /* Forbidden */, query };
1398
+ return this.updateOne(
1399
+ null,
1400
+ data,
1401
+ {
1402
+ populate,
1403
+ overrides: {
1404
+ filter: _filter,
1405
+ populate: overridePopulate
1406
+ }
1407
+ },
1408
+ { skim, includePermissions, populateAccess },
1409
+ decorate
1410
+ );
1411
+ } else {
1412
+ return this.create(
1413
+ data,
1414
+ { populate },
1415
+ {
1416
+ skim,
1417
+ includePermissions,
1418
+ populateAccess
1419
+ },
1420
+ decorate
1421
+ );
1422
+ }
1423
+ }
1424
+ async delete(id) {
1425
+ const filter2 = await this.genFilter("delete", await this.genIDFilter(id));
1426
+ const query = { filter: filter2 };
1427
+ logger.debug(JSON.stringify({ op: "delete", query }));
1428
+ if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1429
+ let doc = await this.model.findOne({ filter: filter2 });
1430
+ if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
1431
+ await ("deleteOne" in doc ? doc.deleteOne() : doc.remove());
1432
+ return { success: true, kind: "single", code: "success" /* Success */, data: doc._id, query };
1433
+ }
1434
+ async exists(filter2, options) {
1435
+ const filterErrors = this.validateClientFilter(filter2);
1436
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1437
+ const {
1438
+ access = this.defaults.existsOptions?.access ?? "read",
1439
+ includeId = this.defaults.existsOptions?.includeId ?? false
1440
+ } = options ?? {};
1441
+ filter2 = await this.genFilter(access, filter2);
1442
+ const result = await this.model.exists(filter2);
1443
+ return {
1444
+ success: true,
1445
+ kind: "single",
1446
+ code: "success" /* Success */,
1447
+ data: includeId ? result : !!result,
1448
+ query: { filter: filter2 }
1449
+ };
1450
+ }
1451
+ async distinct(field, args) {
1452
+ let { filter: filter2 } = args ?? {};
1453
+ const filterErrors = this.validateClientFilter(filter2);
1454
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1455
+ filter2 = await this.genFilter("read", filter2);
1456
+ const query = { filter: filter2 };
1457
+ if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1458
+ const result = await this.model.distinct(field, filter2);
1459
+ return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length, query };
1460
+ }
1461
+ async count(filter2, access = "list") {
1462
+ const filterErrors = this.validateClientFilter(filter2);
1463
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1464
+ filter2 = await this.genFilter(access, filter2);
1465
+ const query = { filter: filter2 };
1466
+ if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1467
+ return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
1468
+ }
1469
+ getDocPermissions(doc) {
1470
+ return getDocPermissions(this.modelName, doc);
1471
+ }
1472
+ async getFieldPermissionAccess(ids) {
1473
+ const uniqueIds = uniq2(ids.map((id) => String(id)).filter(Boolean));
1474
+ if (uniqueIds.length === 0) {
1475
+ return {
1476
+ readIds: /* @__PURE__ */ new Set(),
1477
+ updateIds: /* @__PURE__ */ new Set()
1478
+ };
1479
+ }
1480
+ const [readIds, updateIds] = await Promise.all([
1481
+ this.getAccessibleIdSet(uniqueIds, "read"),
1482
+ this.getAccessibleIdSet(uniqueIds, "update")
1483
+ ]);
1484
+ return { readIds, updateIds };
1485
+ }
1486
+ async getAccessibleIdSet(ids, access) {
1487
+ const filter2 = await this.genFilter(access, { _id: { $in: ids } });
1488
+ if (filter2 === false) return /* @__PURE__ */ new Set();
1489
+ const docs = await this.model.find({ filter: filter2, select: "_id", lean: true });
1490
+ return new Set(docs.map((doc) => String(doc._id)));
1491
+ }
1492
+ async listSub(id, sub, options) {
1493
+ let { filter: ft, fields } = options ?? {};
1494
+ const parentDoc = await this.getParentDoc(id, sub, null, { access: "read" });
1495
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1496
+ let result = get5(parentDoc, sub);
1497
+ const [subFilter, subSelect] = await Promise.all([
1498
+ this.genFilter(`subs.${sub}.list`, ft),
1499
+ this.genQuerySelect("list", fields, false, [sub, "sub"])
1500
+ ]);
1501
+ if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
1502
+ result = filterCollection(result, subFilter);
1503
+ if (subSelect) result = result.map((v) => pick3(toObject(v), subSelect.concat("_id")));
1504
+ return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
1505
+ }
1506
+ async readSub(id, sub, subId, options) {
1507
+ let { fields, populate } = options ?? {};
1508
+ const parentDoc = await this.getParentDoc(id, sub, { populate }, { access: "read" });
1509
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1510
+ const result = get5(parentDoc, sub);
1511
+ const [subFilter, subSelect] = await Promise.all([
1512
+ this.genFilter(`subs.${sub}.read`, { _id: subId }),
1513
+ this.genQuerySelect("read", fields, false, [sub, "sub"])
1514
+ ]);
1515
+ if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
1516
+ let subdoc = findElement(result, subFilter);
1517
+ if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
1518
+ if (subSelect) subdoc = pick3(toObject(subdoc), subSelect.concat(["_id"]));
1519
+ return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
1520
+ }
1521
+ async updateSub(id, sub, subId, data) {
1522
+ const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
1523
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1524
+ const result = get5(parentDoc, sub);
1525
+ const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
1526
+ this.genFilter(`subs.${sub}.update`, { _id: subId }),
1527
+ this.genQuerySelect("read", null, false, [sub, "sub"]),
1528
+ this.genQuerySelect("update", null, false, [sub, "sub"])
1529
+ ]);
1530
+ if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
1531
+ let subdoc = findElement(result, subFilter);
1532
+ if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
1533
+ const allowedData = pick3(data, subUpdateSelect);
1534
+ Object.assign(subdoc, allowedData);
1535
+ await parentDoc.save();
1536
+ if (subReadSelect) subdoc = pick3(toObject(subdoc), subReadSelect.concat(["_id"]));
1537
+ return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
1538
+ }
1539
+ async bulkUpdateSub(id, sub, data) {
1540
+ const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
1541
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1542
+ let result = get5(parentDoc, sub);
1543
+ data = castArray2(data);
1544
+ const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
1545
+ this.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
1546
+ this.genQuerySelect("read", null, false, [sub, "sub"]),
1547
+ this.genQuerySelect("update", null, false, [sub, "sub"])
1548
+ ]);
1549
+ if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
1550
+ result = filterCollection(result, subFilter);
1551
+ forEach4(result, (subdoc) => {
1552
+ const tdata = findElementById(data, subdoc._id);
1553
+ if (!tdata) return;
1554
+ const allowedData = pick3(tdata, subUpdateSelect);
1555
+ Object.assign(subdoc, allowedData);
1556
+ });
1557
+ await parentDoc.save();
1558
+ if (subReadSelect) result = result.map((v) => pick3(toObject(v), subReadSelect.concat(["_id"])));
1559
+ return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
1560
+ }
1561
+ async createSub(id, sub, data, options) {
1562
+ const { addFirst } = options ?? {};
1563
+ const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
1564
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1565
+ let result = get5(parentDoc, sub);
1566
+ const [subCreateSelect, subReadSelect] = await Promise.all([
1567
+ this.genQuerySelect("create", null, false, [sub, "sub"]),
1568
+ this.genQuerySelect("read", null, false, [sub, "sub"])
1569
+ ]);
1570
+ const allowedData = pick3(data, subCreateSelect);
1571
+ addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
1572
+ await parentDoc.save();
1573
+ if (subReadSelect) result = result.map((v) => pick3(toObject(v), subReadSelect.concat(["_id"])));
1574
+ return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
1575
+ }
1576
+ async deleteSub(id, sub, subId) {
1577
+ const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
1578
+ if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
1579
+ const result = get5(parentDoc, sub);
1580
+ const subFilter = await this.genFilter(`subs.${sub}.delete`, { _id: subId });
1581
+ if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
1582
+ const subdoc = findElement(result, subFilter);
1583
+ if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
1584
+ await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
1585
+ await parentDoc.save();
1586
+ return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
1587
+ }
1588
+ async getParentDoc(id, sub, args, options) {
1589
+ const { populate } = args ?? {};
1590
+ const { access = "read", lean = false } = options ?? {};
1591
+ const parentFilter = await this.genFilter(access, await this.genIDFilter(id));
1592
+ if (parentFilter === false) return null;
1593
+ return this.model.findOne({ filter: parentFilter, select: sub, populate: genSubPopulate(sub, populate), lean });
1594
+ }
1595
+ };
1596
+
1597
+ // src/services/public-service.ts
1598
+ import { pick as pick4 } from "@web-ts-toolkit/utils";
1599
+ var PublicService = class extends Service {
1600
+ async _list(filter2, args, options) {
1601
+ const {
1602
+ select = this.defaults.publicListArgs?.select,
1603
+ populate = this.defaults.publicListArgs?.populate,
1604
+ include = this.defaults.publicListArgs?.include,
1605
+ sort = this.defaults.publicListArgs?.sort,
1606
+ skip = this.defaults.publicListArgs?.skip,
1607
+ limit = this.defaults.publicListArgs?.limit,
1608
+ page = this.defaults.publicListArgs?.page,
1609
+ pageSize = this.defaults.publicListArgs?.pageSize,
1610
+ tasks = this.defaults.publicListArgs?.tasks ?? []
1611
+ } = args ?? {};
1612
+ const {
1613
+ skim = this.defaults.publicListOptions?.skim ?? true,
1614
+ includePermissions = this.defaults.publicListOptions?.includePermissions ?? false,
1615
+ includeCount = this.defaults.publicListOptions?.includeCount ?? false,
1616
+ populateAccess = this.defaults.publicListOptions?.populateAccess ?? "read",
1617
+ lean = this.defaults.publicListOptions?.lean ?? true
1618
+ } = options ?? {};
1619
+ const result = await this.find(
1620
+ filter2,
1621
+ { select, populate, include, sort, skip, limit, page, pageSize },
1622
+ { skim, includePermissions, includeCount, populateAccess, lean },
1623
+ async (doc, context) => {
1624
+ doc = toObject(doc);
1625
+ return this.decorate(doc, "list", context);
1626
+ }
1627
+ );
1628
+ if (!result.success) {
1629
+ return result;
1630
+ }
1631
+ let docs = result.data;
1632
+ docs = await this.decorateAll(docs, "list", { model: this.model.model, modelName: this.modelName });
1633
+ docs = docs.map((row) => this.runTasks(row, tasks));
1634
+ result.data = docs;
1635
+ return result;
1636
+ }
1637
+ async _create(data, args, options) {
1638
+ const {
1639
+ select = this.defaults.publicCreateArgs?.select,
1640
+ populate = this.defaults.publicCreateArgs?.populate,
1641
+ tasks = this.defaults.publicCreateArgs?.tasks ?? []
1642
+ } = args ?? {};
1643
+ const {
1644
+ skim = this.defaults.publicCreateOptions?.skim ?? false,
1645
+ includePermissions = this.defaults.publicCreateOptions?.includePermissions ?? true,
1646
+ populateAccess = this.defaults.publicCreateOptions?.populateAccess ?? "read"
1647
+ } = options ?? {};
1648
+ const result = await this.create(
1649
+ data,
1650
+ { populate },
1651
+ { skim, includePermissions, populateAccess },
1652
+ async (doc, context) => {
1653
+ doc = toObject(doc);
1654
+ doc = await this.decorate(doc, "create", context);
1655
+ doc = this.runTasks(doc, tasks);
1656
+ if (select) doc = pick4(doc, [...normalizeSelect(select), ...this.baseFieldsExt]);
1657
+ return doc;
1658
+ }
1659
+ );
1660
+ return result;
1661
+ }
1662
+ async _new() {
1663
+ return this.new();
1664
+ }
1665
+ async _read(id, args, options) {
1666
+ const {
1667
+ select = this.defaults.publicReadArgs?.select,
1668
+ populate = this.defaults.publicReadArgs?.populate,
1669
+ include = this.defaults.publicReadArgs?.include,
1670
+ tasks = this.defaults.publicReadArgs?.tasks ?? []
1671
+ } = args ?? {};
1672
+ const {
1673
+ skim = this.defaults.publicReadOptions?.skim ?? false,
1674
+ includePermissions = this.defaults.publicReadOptions?.includePermissions ?? true,
1675
+ tryList = this.defaults.publicReadOptions?.tryList ?? true,
1676
+ populateAccess = this.defaults.publicReadOptions?.populateAccess,
1677
+ lean = this.defaults.publicReadOptions?.lean ?? false
1678
+ } = options ?? {};
1679
+ let access = "read";
1680
+ const idFilter = await this.genIDFilter(id);
1681
+ let result = await this.findById(
1682
+ id,
1683
+ {
1684
+ select,
1685
+ populate,
1686
+ include,
1687
+ overrides: { idFilter }
1688
+ },
1689
+ { skim, includePermissions, access, populateAccess, lean }
1690
+ );
1691
+ if (tryList && (!result.success || !result.data)) {
1692
+ access = "list";
1693
+ result = await this.findById(
1694
+ id,
1695
+ {
1696
+ select,
1697
+ populate,
1698
+ overrides: { idFilter }
1699
+ },
1700
+ { skim, includePermissions, access, populateAccess, lean }
1701
+ );
1702
+ }
1703
+ if (!result.success) {
1704
+ return result;
1705
+ }
1706
+ let doc = toObject(result.data);
1707
+ doc = await this.decorate(doc, access, result.context);
1708
+ doc = this.runTasks(doc, tasks);
1709
+ result.data = doc;
1710
+ return result;
1711
+ }
1712
+ async _readFilter(filter2, args, options) {
1713
+ const {
1714
+ select = this.defaults.publicReadArgs?.select,
1715
+ sort = this.defaults.publicListArgs?.sort,
1716
+ populate = this.defaults.publicReadArgs?.populate,
1717
+ include = this.defaults.publicReadArgs?.include,
1718
+ tasks = this.defaults.publicReadArgs?.tasks ?? []
1719
+ } = args ?? {};
1720
+ const {
1721
+ skim = this.defaults.publicReadOptions?.skim ?? false,
1722
+ includePermissions = this.defaults.publicReadOptions?.includePermissions ?? true,
1723
+ tryList = this.defaults.publicReadOptions?.tryList ?? true,
1724
+ populateAccess = this.defaults.publicReadOptions?.populateAccess,
1725
+ lean = this.defaults.publicReadOptions?.lean ?? false
1726
+ } = options ?? {};
1727
+ let access = "read";
1728
+ let result = await this.findOne(
1729
+ filter2,
1730
+ {
1731
+ select,
1732
+ sort,
1733
+ populate,
1734
+ include,
1735
+ overrides: {}
1736
+ },
1737
+ { skim, includePermissions, access, populateAccess, lean }
1738
+ );
1739
+ if (tryList && (!result.success || !result.data)) {
1740
+ access = "list";
1741
+ result = await this.findOne(
1742
+ filter2,
1743
+ {
1744
+ select,
1745
+ sort,
1746
+ populate,
1747
+ overrides: {}
1748
+ },
1749
+ { skim, includePermissions, access, populateAccess, lean }
1750
+ );
1751
+ }
1752
+ if (!result.success) {
1753
+ return result;
1754
+ }
1755
+ let doc = toObject(result.data);
1756
+ doc = await this.decorate(doc, access, result.context);
1757
+ doc = this.runTasks(doc, tasks);
1758
+ result.data = doc;
1759
+ return result;
1760
+ }
1761
+ async _update(id, data, args, options) {
1762
+ const {
1763
+ select = this.defaults.publicUpdateArgs?.select,
1764
+ populate = this.defaults.publicUpdateArgs?.populate,
1765
+ tasks = this.defaults.publicUpdateArgs?.tasks ?? []
1766
+ } = args ?? {};
1767
+ const {
1768
+ skim = this.defaults.publicUpdateOptions?.skim ?? false,
1769
+ returningAll = this.defaults.publicUpdateOptions?.returningAll ?? true,
1770
+ includePermissions = this.defaults.publicUpdateOptions?.includePermissions ?? true,
1771
+ populateAccess = this.defaults.publicUpdateOptions?.populateAccess ?? "read"
1772
+ } = options ?? {};
1773
+ const result = await this.updateById(
1774
+ id,
1775
+ data,
1776
+ { populate },
1777
+ { skim, includePermissions, populateAccess },
1778
+ async (doc, context) => {
1779
+ doc = toObject(doc);
1780
+ doc = await this.decorate(doc, "update", context);
1781
+ doc = this.runTasks(doc, tasks);
1782
+ if (select) doc = pick4(doc, [...normalizeSelect(select), ...this.baseFieldsExt]);
1783
+ else if (!returningAll) doc = pick4(doc, [...Object.keys(data), "_id"]);
1784
+ return doc;
1785
+ }
1786
+ );
1787
+ return result;
1788
+ }
1789
+ async _delete(id) {
1790
+ const result = await this.delete(id);
1791
+ return result;
1792
+ }
1793
+ async _distinct(field, options = {}) {
1794
+ const result = await this.distinct(field, options);
1795
+ return result;
1796
+ }
1797
+ async _count(filter2, access = "list") {
1798
+ const result = await this.count(filter2, access);
1799
+ return result;
1800
+ }
1801
+ };
1802
+
1803
+ // src/services/data-service.ts
1804
+ import { orderBy, pick as pick5 } from "@web-ts-toolkit/utils";
1805
+ var DataService = class {
1806
+ constructor(req, dataName) {
1807
+ this.req = req;
1808
+ this.dataName = dataName;
1809
+ this.options = getDataOptions(dataName);
1810
+ this.data = this.options.data ?? [];
1811
+ }
1812
+ async findOne(filter2, args, options) {
1813
+ const filterErrors = validateClientFilter(filter2);
1814
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1815
+ const { select } = args ?? {};
1816
+ const { access = "read" } = options ?? {};
1817
+ let [_filter, _select] = await Promise.all([this.genFilter(access, filter2), this.genQuerySelect(access, select)]);
1818
+ const query = {
1819
+ filter: _filter,
1820
+ select: _select
1821
+ };
1822
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1823
+ let doc = await findElement(this.data, _filter);
1824
+ if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
1825
+ doc = await this.trimOutputFields(doc, access);
1826
+ if (_select.length > 0) doc = pick5(doc, _select);
1827
+ return { success: true, kind: "single", code: "success" /* Success */, data: doc, query };
1828
+ }
1829
+ async findById(id, args, options) {
1830
+ const { select } = args ?? {};
1831
+ const { access = "read" } = options ?? {};
1832
+ const filter2 = await this.genIDFilter(id);
1833
+ return this.findOne(
1834
+ filter2,
1835
+ {
1836
+ select
1837
+ },
1838
+ { access }
1839
+ );
1840
+ }
1841
+ async find(filter2, args, options) {
1842
+ const filterErrors = validateClientFilter(filter2);
1843
+ if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
1844
+ const { select, sort, skip, limit, page, pageSize } = args ?? {};
1845
+ const [_filter, _select, pagination] = await Promise.all([
1846
+ this.genFilter("list", filter2),
1847
+ this.genQuerySelect("list", select),
1848
+ genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
1849
+ ]);
1850
+ const query = {
1851
+ filter: _filter,
1852
+ select: _select,
1853
+ sort,
1854
+ ...pagination
1855
+ };
1856
+ if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
1857
+ let docs = await filterCollection(this.data, _filter);
1858
+ const totalCount = docs.length;
1859
+ docs = await Promise.all(
1860
+ docs.map(async (doc) => {
1861
+ doc = await this.trimOutputFields(doc, "list");
1862
+ if (_select.length > 0) doc = pick5(doc, _select);
1863
+ return doc;
1864
+ })
1865
+ );
1866
+ if (sort) {
1867
+ const { sortKey, sortOrder } = parseSortString(sort);
1868
+ docs = orderBy(docs, [sortKey], [sortOrder]);
1869
+ }
1870
+ docs = docs.slice(query.skip, query.limit && query.skip + query.limit);
1871
+ return {
1872
+ success: true,
1873
+ kind: "list",
1874
+ code: "success" /* Success */,
1875
+ data: docs,
1876
+ count: docs.length,
1877
+ totalCount,
1878
+ query
1879
+ };
1880
+ }
1881
+ decorate(doc, access, context) {
1882
+ return this.req.dacl.decorate(this.dataName, doc, access, context);
1883
+ }
1884
+ decorateAll(docs, access) {
1885
+ return this.req.dacl.decorateAll(this.dataName, docs, access);
1886
+ }
1887
+ genAllowedFields(doc, access, baseFields) {
1888
+ return this.req.dacl.genAllowedFields(this.dataName, doc, access, baseFields);
1889
+ }
1890
+ genFilter(access, filter2) {
1891
+ return this.req.dacl.genFilter(this.dataName, access, filter2);
1892
+ }
1893
+ genIDFilter(id) {
1894
+ return this.req.dacl.genIDFilter(this.dataName, id);
1895
+ }
1896
+ genSelect(access, targetFields, skipChecks, subPaths) {
1897
+ return this.req.dacl.genSelect(this.dataName, access, targetFields, skipChecks, subPaths);
1898
+ }
1899
+ genQuerySelect(access, targetFields, skipChecks, subPaths) {
1900
+ return this.genSelect(access, targetFields, skipChecks, subPaths);
1901
+ }
1902
+ pickAllowedFields(doc, access, baseFields) {
1903
+ return this.req.dacl.pickAllowedFields(this.dataName, doc, access, baseFields);
1904
+ }
1905
+ trimOutputFields(doc, access, baseFields) {
1906
+ return this.pickAllowedFields(doc, access, baseFields);
1907
+ }
1908
+ };
1909
+
1910
+ // src/processors.ts
1911
+ import { castArray as castArray3, cloneDeep, forEach as forEach5, get as get6, isArray as isArray6, map as map3, set as set5 } from "@web-ts-toolkit/utils";
1912
+ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
1913
+ const obj = get6(options, "mutable", true) ? docObject : cloneDeep(docObject);
1914
+ const idField = get6(options, "idField", "_id");
1915
+ forEach5(castArray3(operations), (op) => {
1916
+ if (!op.src || !op.dest) return;
1917
+ let targets = [obj];
1918
+ const segs = op.src.split(".");
1919
+ forEach5(segs, (seg, ind) => {
1920
+ if (segs.length === ind + 1) {
1921
+ forEach5(targets, (target) => {
1922
+ const targetObject = target;
1923
+ set5(targetObject, op.dest, get6(targetObject, seg));
1924
+ set5(
1925
+ targetObject,
1926
+ seg,
1927
+ isArray6(targetObject[seg]) ? map3(targetObject[seg], idField) : get6(targetObject, `${seg}.${idField}`)
1928
+ );
1929
+ });
1930
+ } else {
1931
+ targets = targets.reduce((ret, target) => {
1932
+ const targetObject = target;
1933
+ if (isArray6(targetObject[seg])) ret.push(...targetObject[seg]);
1934
+ else ret.push(targetObject[seg]);
1935
+ return ret;
1936
+ }, []);
1937
+ }
1938
+ });
1939
+ });
1940
+ return obj;
1941
+ };
1942
+
1943
+ // src/symbols.ts
1944
+ var MIDDLEWARE = /* @__PURE__ */ Symbol("middleware");
1945
+ var DATA_MIDDLEWARE = /* @__PURE__ */ Symbol("data-middleware");
1946
+ var CORE = /* @__PURE__ */ Symbol("core");
1947
+ var PERMISSIONS = /* @__PURE__ */ Symbol("permissions");
1948
+ var PERMISSION_KEYS = /* @__PURE__ */ Symbol("permission-keys");
1949
+
1950
+ // src/cache.ts
1951
+ var Cache = class {
1952
+ constructor() {
1953
+ this.cache = /* @__PURE__ */ new Map();
1954
+ }
1955
+ set(key, value) {
1956
+ this.cache.set(key, value);
1957
+ }
1958
+ get(key) {
1959
+ return this.cache.get(key);
1960
+ }
1961
+ delete(key) {
1962
+ this.cache.delete(key);
1963
+ }
1964
+ clear() {
1965
+ this.cache.clear();
1966
+ }
1967
+ has(key) {
1968
+ return this.cache.has(key);
1969
+ }
1970
+ keys() {
1971
+ return Array.from(this.cache.keys());
1972
+ }
1973
+ values() {
1974
+ return Array.from(this.cache.values());
1975
+ }
1976
+ size() {
1977
+ return this.cache.size;
1978
+ }
1979
+ };
1980
+
1981
+ // src/core-shared.ts
1982
+ import {
1983
+ castArray as castArray4,
1984
+ isArray as isArray7,
1985
+ isBoolean as isBoolean4,
1986
+ isEmpty as isEmpty2,
1987
+ isEqual,
1988
+ isFunction as isFunction4,
1989
+ isPlainObject as isPlainObject6,
1990
+ isString as isString6
1991
+ } from "@web-ts-toolkit/utils";
1992
+
1993
+ // src/permission.ts
1994
+ var Permission = class {
1995
+ constructor(permissions) {
1996
+ this.$_permissions = permissions;
1997
+ this.$_permissionKeys = Object.keys(permissions);
1998
+ for (let x = 0; x < this.$_permissionKeys.length; x++) {
1999
+ const key = this.$_permissionKeys[x];
2000
+ Object.defineProperty(this, key, {
2001
+ enumerable: true,
2002
+ get: function() {
2003
+ return this.has(key);
2004
+ }
2005
+ });
2006
+ }
2007
+ }
2008
+ prop(permission) {
2009
+ return this.$_permissions.hasOwnProperty(permission);
2010
+ }
2011
+ has(permission) {
2012
+ return this.$_permissions[permission] || false;
2013
+ }
2014
+ hasAny(permissions) {
2015
+ return permissions.some((permission) => {
2016
+ return this.has(permission);
2017
+ });
2018
+ }
2019
+ hasAll(permissions) {
2020
+ return permissions.every((permission) => {
2021
+ return this.has(permission);
2022
+ });
2023
+ }
2024
+ any(permissions) {
2025
+ return this.hasAny(permissions);
2026
+ }
2027
+ all(permissions) {
2028
+ return this.hasAll(permissions);
2029
+ }
2030
+ };
2031
+ var permission_default = Permission;
2032
+
2033
+ // src/core-shared.ts
2034
+ function isAndFilter(filter2) {
2035
+ return !!filter2 && isPlainObject6(filter2) && Object.keys(filter2).length === 1 && isArray7(filter2.$and);
2036
+ }
2037
+ function isMergeableClause(filter2) {
2038
+ return !!filter2 && isPlainObject6(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
2039
+ }
2040
+ function optimizeAndFilter(clausesInput) {
2041
+ const clauses = [];
2042
+ for (let x = 0; x < clausesInput.length; x++) {
2043
+ const clause = normalizeFilter(clausesInput[x]);
2044
+ if (clause === false) return false;
2045
+ if (!clause) continue;
2046
+ if (isAndFilter(clause)) {
2047
+ clauses.push(...clause.$and);
2048
+ } else {
2049
+ clauses.push(clause);
2050
+ }
2051
+ }
2052
+ const dedupedClauses = clauses.filter(
2053
+ (clause, index) => clauses.findIndex((item) => isEqual(item, clause)) === index
2054
+ );
2055
+ const mergedClause = {};
2056
+ const remainingClauses = [];
2057
+ for (let x = 0; x < dedupedClauses.length; x++) {
2058
+ const clause = dedupedClauses[x];
2059
+ if (!isMergeableClause(clause)) {
2060
+ remainingClauses.push(clause);
2061
+ continue;
2062
+ }
2063
+ let canMerge = true;
2064
+ for (const [key, value] of Object.entries(clause)) {
2065
+ if (Object.prototype.hasOwnProperty.call(mergedClause, key) && !isEqual(mergedClause[key], value)) {
2066
+ canMerge = false;
2067
+ break;
2068
+ }
2069
+ }
2070
+ if (!canMerge) {
2071
+ remainingClauses.push(clause);
2072
+ continue;
2073
+ }
2074
+ Object.assign(mergedClause, clause);
2075
+ }
2076
+ const finalClauses = isEmpty2(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
2077
+ if (finalClauses.length === 0) return null;
2078
+ if (finalClauses.length === 1) return finalClauses[0];
2079
+ return { $and: finalClauses };
2080
+ }
2081
+ function normalizeFilter(filter2) {
2082
+ if (filter2 === false) return false;
2083
+ if (!filter2 || !isPlainObject6(filter2) || isEmpty2(filter2)) return null;
2084
+ if (!isAndFilter(filter2)) {
2085
+ return filter2;
2086
+ }
2087
+ return optimizeAndFilter(filter2.$and);
2088
+ }
2089
+ async function resolveIdentifierFilter(req, identifier, id) {
2090
+ if (isString6(identifier)) {
2091
+ return { [identifier]: id };
2092
+ }
2093
+ if (isFunction4(identifier)) {
2094
+ return identifier.call(req, id);
2095
+ }
2096
+ return { _id: id };
2097
+ }
2098
+ async function resolveAccessFilter({
2099
+ req,
2100
+ permissions,
2101
+ cache,
2102
+ cacheKey,
2103
+ access = "read",
2104
+ filter: filter2 = null,
2105
+ getOption
2106
+ }) {
2107
+ let nextFilter = normalizeFilter(filter2);
2108
+ const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
2109
+ if (isFunction4(overrideFilterFn)) {
2110
+ nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
2111
+ }
2112
+ const baseFilterFn = getOption(`baseFilter.${access}`, null);
2113
+ if (!isFunction4(baseFilterFn)) return nextFilter || {};
2114
+ const baseFilter = normalizeFilter(
2115
+ cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
2116
+ );
2117
+ if (!cache.has(cacheKey)) {
2118
+ cache.set(cacheKey, baseFilter);
2119
+ }
2120
+ if (baseFilter === false) return false;
2121
+ if (!baseFilter) return nextFilter || {};
2122
+ if (!nextFilter) return baseFilter;
2123
+ return optimizeAndFilter([baseFilter, nextFilter]);
2124
+ }
2125
+ function getRequestPermissions(req) {
2126
+ const requestPermissionField = getGlobalOption("requestPermissionField");
2127
+ return new permission_default(req[requestPermissionField] || {});
2128
+ }
2129
+ async function setRequestPermissions(req) {
2130
+ const requestPermissionField = getGlobalOption("requestPermissionField");
2131
+ if (req[requestPermissionField]) return;
2132
+ const globalPermissions = getGlobalOption("globalPermissions");
2133
+ if (!isFunction4(globalPermissions)) return;
2134
+ const permissions = await globalPermissions.call(req, req);
2135
+ if (isPlainObject6(permissions)) req[requestPermissionField] = permissions;
2136
+ else if (isArray7(permissions)) req[requestPermissionField] = arrToObj(permissions);
2137
+ else if (isString6(permissions)) req[requestPermissionField] = { [permissions]: true };
2138
+ }
2139
+ async function evaluateRouteGuard(req, permissions, routeGuard) {
2140
+ const phas = (key) => permissions.has(key);
2141
+ const { stringHandler, arrayHandler } = createValidator(phas);
2142
+ if (isBoolean4(routeGuard)) {
2143
+ return routeGuard === true;
2144
+ }
2145
+ if (isString6(routeGuard)) {
2146
+ return stringHandler(routeGuard);
2147
+ }
2148
+ if (isArray7(routeGuard)) {
2149
+ return arrayHandler(routeGuard);
2150
+ }
2151
+ if (isFunction4(routeGuard)) {
2152
+ return routeGuard.call(req, permissions);
2153
+ }
2154
+ return false;
2155
+ }
2156
+ async function callMiddlewareChain(req, middleware, doc, permissions, context) {
2157
+ const middlewares = castArray4(middleware);
2158
+ for (let x = 0; x < middlewares.length; x++) {
2159
+ if (isFunction4(middlewares[x])) {
2160
+ doc = await middlewares[x].call(req, doc, permissions, context);
2161
+ }
2162
+ }
2163
+ return doc;
2164
+ }
2165
+ async function collectSchemaFields({
2166
+ req,
2167
+ permissionSchema,
2168
+ access,
2169
+ baseFields = [],
2170
+ hasPermission,
2171
+ functionArgs = []
2172
+ }) {
2173
+ const fields = [...baseFields ?? []];
2174
+ if (!permissionSchema) return fields;
2175
+ const { stringHandler, arrayHandler } = createValidator(hasPermission);
2176
+ const keys2 = Object.keys(permissionSchema);
2177
+ for (let x = 0; x < keys2.length; x++) {
2178
+ const key = keys2[x];
2179
+ if (baseFields.includes(key)) continue;
2180
+ const val = permissionSchema[key];
2181
+ const value = val && val[access] || val;
2182
+ if (isBoolean4(value)) {
2183
+ if (value) fields.push(key);
2184
+ } else if (isString6(value)) {
2185
+ if (stringHandler(value)) fields.push(key);
2186
+ } else if (isArray7(value)) {
2187
+ if (arrayHandler(value)) fields.push(key);
2188
+ } else if (isFunction4(value)) {
2189
+ if (await value.call(req, ...functionArgs)) fields.push(key);
2190
+ }
2191
+ }
2192
+ return fields;
2193
+ }
2194
+
2195
+ // src/core.ts
2196
+ var Core = class {
2197
+ constructor(req) {
2198
+ this.req = req;
2199
+ this.caches = {
2200
+ baseFilter: new Cache()
2201
+ };
2202
+ }
2203
+ getIdentifier(modelName) {
2204
+ const identifier = getModelOption(modelName, "identifier");
2205
+ if (isFunction5(identifier)) {
2206
+ return null;
2207
+ }
2208
+ if (isString7(identifier)) {
2209
+ return identifier;
2210
+ }
2211
+ return "_id";
2212
+ }
2213
+ async genIDFilter(modelName, id) {
2214
+ const identifier = getModelOption(modelName, "identifier");
2215
+ return resolveIdentifierFilter(this.req, identifier, id);
2216
+ }
2217
+ async genFilter(modelName, access = "read", _filter = null) {
2218
+ const permissions = this.getGlobalPermissions();
2219
+ const cacheKey = `${modelName}_baseFilter_${access}`;
2220
+ return resolveAccessFilter({
2221
+ req: this.req,
2222
+ permissions,
2223
+ cache: this.caches.baseFilter,
2224
+ cacheKey,
2225
+ access,
2226
+ filter: _filter,
2227
+ getOption: (key, defaultValue) => getModelOption(modelName, key, defaultValue)
2228
+ });
2229
+ }
2230
+ removePrefix(str, prefix) {
2231
+ if (!prefix) return str;
2232
+ if (str.startsWith(prefix)) {
2233
+ return str.substring(prefix.length);
2234
+ }
2235
+ return str;
2236
+ }
2237
+ async genAllowedFields(modelName, doc, access, baseFields = []) {
2238
+ const permissionSchema = getModelOption(modelName, "permissionSchema");
2239
+ const modelPermissionPrefix = getModelOption(modelName, "modelPermissionPrefix", "");
2240
+ const permissions = this.getGlobalPermissions();
2241
+ const docPermissions = getDocPermissions(modelName, doc);
2242
+ return collectSchemaFields({
2243
+ req: this.req,
2244
+ permissionSchema,
2245
+ access,
2246
+ baseFields,
2247
+ hasPermission: (key) => permissions.has(key) || docPermissions[this.removePrefix(key, modelPermissionPrefix)],
2248
+ functionArgs: [permissions, docPermissions]
2249
+ });
2250
+ }
2251
+ async pickAllowedFields(modelName, doc, access, baseFields = []) {
2252
+ const allowed = await this.genAllowedFields(modelName, doc, access, baseFields);
2253
+ return pickDocFields(doc, allowed);
2254
+ }
2255
+ async genSelect(modelName, access, targetFields = null, skipChecks = true, subPaths = []) {
2256
+ let normalizedSelect = normalizeSelect(targetFields);
2257
+ const permissionSchema = getModelOption(modelName, ["permissionSchema"].concat(subPaths).join("."));
2258
+ if (!permissionSchema) return [];
2259
+ const permissions = this.getGlobalPermissions();
2260
+ let fields = await collectSchemaFields({
2261
+ req: this.req,
2262
+ permissionSchema,
2263
+ access,
2264
+ hasPermission: (key) => {
2265
+ if (permissions.prop(key)) {
2266
+ return permissions.has(key);
2267
+ }
2268
+ return !!skipChecks;
2269
+ },
2270
+ functionArgs: [permissions]
2271
+ });
2272
+ if (normalizedSelect.length > 0) {
2273
+ const excludeid = normalizedSelect.includes("-_id");
2274
+ const excludeall = normalizedSelect.every((v) => v.startsWith("-"));
2275
+ if (excludeall) {
2276
+ normalizedSelect = normalizedSelect.map((v) => v.substring(1));
2277
+ fields = difference(fields, normalizedSelect);
2278
+ if (excludeid) fields.push("-_id");
2279
+ } else {
2280
+ fields = intersection(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
2281
+ }
2282
+ }
2283
+ const mandatoryFields = subPaths.length > 0 ? [] : getModelOption(modelName, `mandatoryFields.${access}`, []);
2284
+ return fields.concat(mandatoryFields);
2285
+ }
2286
+ async genPopulate(modelName, access = "read", _populate = null) {
2287
+ if (!_populate) return [];
2288
+ let populate = Array.isArray(_populate) ? _populate : [_populate];
2289
+ populate = compact3(
2290
+ await Promise.all(
2291
+ populate.map(async (p) => {
2292
+ const populateAccess = !isString7(p) && p.access ? p.access : access;
2293
+ const ret = isString7(p) ? { path: p } : {
2294
+ path: p.path,
2295
+ select: normalizeSelect(p.select)
2296
+ };
2297
+ const allowedParentPaths = await this.genSelect(modelName, populateAccess, [ret.path], false);
2298
+ if (!allowedParentPaths.includes(ret.path)) return null;
2299
+ const refModelName = getModelRef(modelName, ret.path);
2300
+ if (!refModelName) return null;
2301
+ ret.select = await this.genSelect(refModelName, populateAccess, ret.select, false);
2302
+ const filter2 = await this.genFilter(refModelName, populateAccess, null);
2303
+ if (filter2 === false) return null;
2304
+ ret.match = filter2;
2305
+ return ret;
2306
+ })
2307
+ )
2308
+ );
2309
+ return populate;
2310
+ }
2311
+ async validate(modelName, allowedData, access, context) {
2312
+ const validate = getModelOption(modelName, `validate.${access}`, null);
2313
+ if (isFunction5(validate)) {
2314
+ const permissions = this.getGlobalPermissions();
2315
+ return validate.call(this.req, allowedData, permissions, context);
2316
+ } else if (isBoolean5(validate) || isArray8(validate)) {
2317
+ return validate;
2318
+ } else {
2319
+ return true;
2320
+ }
2321
+ }
2322
+ async prepare(modelName, allowedData, access, context) {
2323
+ const prepare = getModelOption(modelName, `prepare.${access}`, null);
2324
+ const permissions = this.getGlobalPermissions();
2325
+ return callMiddlewareChain(this.req, prepare, allowedData, permissions, context);
2326
+ }
2327
+ async transform(modelName, doc, access, context) {
2328
+ const transform = getModelOption(modelName, `transform.${access}`, null);
2329
+ const permissions = this.getGlobalPermissions();
2330
+ return callMiddlewareChain(this.req, transform, doc, permissions, context);
2331
+ }
2332
+ async finalize(modelName, doc, access, context) {
2333
+ const finalize = getModelOption(modelName, `finalize.${access}`, null);
2334
+ const permissions = this.getGlobalPermissions();
2335
+ return callMiddlewareChain(this.req, finalize, doc, permissions, context);
2336
+ }
2337
+ async changes(modelName, doc, context) {
2338
+ const changeOptions = getModelOption(modelName, `change`, {});
2339
+ for (let x = 0; x < context.modifiedPaths.length; x++) {
2340
+ const mpath = context.modifiedPaths[x];
2341
+ if (isFunction5(changeOptions[mpath])) {
2342
+ await changeOptions[mpath].call(
2343
+ this.req,
2344
+ context.originalDocObject[mpath],
2345
+ doc[mpath],
2346
+ context.changes.filter((di) => di.path.length > 0 && di.path[0] === mpath),
2347
+ context
2348
+ );
2349
+ }
2350
+ }
2351
+ }
2352
+ async genDocPermissions(modelName, doc, access, context) {
2353
+ const docPermissionsFn = getModelOption(modelName, `docPermissions.${access}`, null);
2354
+ let docPermissions = {};
2355
+ if (isFunction5(docPermissionsFn)) {
2356
+ const permissions = this.getGlobalPermissions();
2357
+ try {
2358
+ docPermissions = await docPermissionsFn.call(this.req, doc, permissions, context);
2359
+ } catch (error) {
2360
+ logger.warn(
2361
+ `docPermissions hook failed for model=${modelName} access=${access}: ${error instanceof Error ? error.message : String(error)}`
2362
+ );
2363
+ }
2364
+ }
2365
+ return docPermissions;
2366
+ }
2367
+ addEmptyPermissions(modelName, doc) {
2368
+ const docPermissionField = getModelOption(modelName, "documentPermissionField");
2369
+ setDocValue(doc, docPermissionField, { _view: { $: "_" }, _edit: { $: "_" } });
2370
+ return doc;
2371
+ }
2372
+ async addDocPermissions(modelName, doc, access, context) {
2373
+ const docPermissionField = getModelOption(modelName, "documentPermissionField");
2374
+ const docPermissions = await this.genDocPermissions(modelName, doc, access, context);
2375
+ setDocValue(doc, docPermissionField, docPermissions);
2376
+ return doc;
2377
+ }
2378
+ async addFieldPermissions(modelName, doc, access, context) {
2379
+ const docPermissionField = getModelOption(modelName, "documentPermissionField");
2380
+ const docId = String(doc._id);
2381
+ let readExists = true;
2382
+ let updateExists = true;
2383
+ if (access !== "read") {
2384
+ if (context.fieldPermissionAccess?.readIds) {
2385
+ readExists = context.fieldPermissionAccess.readIds.has(docId);
2386
+ } else {
2387
+ const existsResult = await this.req.macl.getService(modelName).exists({ _id: doc._id }, { access: "read" });
2388
+ readExists = existsResult.success ? !!existsResult.data : false;
2389
+ }
2390
+ }
2391
+ if (access !== "update") {
2392
+ if (context.fieldPermissionAccess?.updateIds) {
2393
+ updateExists = context.fieldPermissionAccess.updateIds.has(docId);
2394
+ } else {
2395
+ const existsResult = await this.req.macl.getService(modelName).exists({ _id: doc._id }, { access: "update" });
2396
+ updateExists = existsResult.success ? !!existsResult.data : false;
2397
+ }
2398
+ }
2399
+ const [views, edits] = await Promise.all([
2400
+ readExists ? this.genAllowedFields(modelName, doc, "read") : [],
2401
+ updateExists ? this.genAllowedFields(modelName, doc, "update") : []
2402
+ ]);
2403
+ const viewObj = reduce2(
2404
+ views,
2405
+ (ret, view) => {
2406
+ ret[view] = true;
2407
+ return ret;
2408
+ },
2409
+ {}
2410
+ );
2411
+ const editObj = reduce2(
2412
+ edits,
2413
+ (ret, view) => {
2414
+ ret[view] = true;
2415
+ return ret;
2416
+ },
2417
+ {}
2418
+ );
2419
+ setDocValue(doc, `${docPermissionField}._view`, viewObj);
2420
+ setDocValue(doc, `${docPermissionField}._edit`, editObj);
2421
+ return doc;
2422
+ }
2423
+ async decorate(modelName, doc, access, context) {
2424
+ const decorate = getModelOption(modelName, `decorate.${access}`, null);
2425
+ const permissions = this.getGlobalPermissions();
2426
+ context.docPermissions = getDocPermissions(modelName, doc);
2427
+ return callMiddlewareChain(this.req, decorate, doc, permissions, context);
2428
+ }
2429
+ async decorateAll(modelName, docs, access, context) {
2430
+ const decorateAll = getModelOption(modelName, `decorateAll.${access}`, null);
2431
+ const permissions = this.getGlobalPermissions();
2432
+ return callMiddlewareChain(this.req, decorateAll, docs, permissions, context);
2433
+ }
2434
+ runTasks(modelName, docObject, task) {
2435
+ const tasks = compact3(castArray5(task));
2436
+ if (tasks.length === 0) return docObject;
2437
+ forEach6(tasks, (task2) => {
2438
+ const { type, args, options } = task2;
2439
+ switch (type) {
2440
+ case "COPY_AND_DEPOPULATE":
2441
+ docObject = copyAndDepopulate(
2442
+ docObject,
2443
+ args,
2444
+ options
2445
+ );
2446
+ break;
2447
+ }
2448
+ });
2449
+ return docObject;
2450
+ }
2451
+ getPermissions() {
2452
+ return getRequestPermissions(this.req);
2453
+ }
2454
+ async setPermissions() {
2455
+ await setRequestPermissions(this.req);
2456
+ }
2457
+ async canActivate(routeGuard) {
2458
+ const permissions = this.getGlobalPermissions();
2459
+ return evaluateRouteGuard(this.req, permissions, routeGuard);
2460
+ }
2461
+ async isAllowed(modelName, access) {
2462
+ if (access.startsWith("subs")) {
2463
+ const keys2 = access.split(".");
2464
+ if (keys2.length < 3) {
2465
+ return false;
2466
+ }
2467
+ const [, field, op] = keys2;
2468
+ const subOption = getExactModelOption(modelName, `routeGuard.${access}`);
2469
+ if (isUndefined(subOption)) {
2470
+ const subFieldOption = getExactModelOption(modelName, `routeGuard.subs.${field}`);
2471
+ if (isUndefined(subFieldOption)) {
2472
+ const opOption = getModelOption(modelName, `routeGuard.${op}`);
2473
+ return this.canActivate(opOption);
2474
+ }
2475
+ return this.canActivate(subFieldOption);
2476
+ }
2477
+ return this.canActivate(subOption);
2478
+ }
2479
+ const routeGuard = getModelOption(modelName, `routeGuard.${access}`);
2480
+ return this.canActivate(routeGuard);
2481
+ }
2482
+ getService(modelName) {
2483
+ return new Service(this.req, modelName);
2484
+ }
2485
+ getPublicService(modelName) {
2486
+ return new PublicService(this.req, modelName);
2487
+ }
2488
+ service(modelName) {
2489
+ return this.getPublicService(modelName);
2490
+ }
2491
+ svc(modelName) {
2492
+ return this.getPublicService(modelName);
2493
+ }
2494
+ getGlobalPermissions() {
2495
+ return this.req[PERMISSIONS];
2496
+ }
2497
+ };
2498
+ async function setCore(req, res, next) {
2499
+ if (req[MIDDLEWARE]) return next();
2500
+ const core = new Core(req);
2501
+ await core.setPermissions();
2502
+ req.macl = core;
2503
+ req[PERMISSIONS] = core.getPermissions();
2504
+ req[PERMISSION_KEYS] = req[PERMISSIONS].$_permissionKeys;
2505
+ req[MIDDLEWARE] = true;
2506
+ next();
2507
+ }
2508
+
2509
+ // src/middleware.ts
2510
+ function macl() {
2511
+ return async function(req, res, next) {
2512
+ await setCore(req, res, next);
2513
+ };
2514
+ }
2515
+ function guard(condition) {
2516
+ return async (req, _res, next) => {
2517
+ const permissions = req[PERMISSIONS];
2518
+ let cond = condition;
2519
+ let phas = (key) => permissions.has(key);
2520
+ if (isPlainObject7(condition)) {
2521
+ const { modelName, id, condition: _cond } = condition;
2522
+ const svc = req.macl.getPublicService(modelName);
2523
+ const select = getModelOption(modelName, `mandatoryFields.read`, void 0);
2524
+ let _id = isString8(id) ? id : null;
2525
+ if (isPlainObject7(id)) {
2526
+ const { type = "param", key } = id;
2527
+ if (type === "param") {
2528
+ const paramValue = req.params[key];
2529
+ _id = isArray9(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
2530
+ } else if (type === "query") {
2531
+ const _qval = req.query[key];
2532
+ if (isArray9(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
2533
+ else _id = _qval;
2534
+ } else {
2535
+ _id = null;
2536
+ }
2537
+ }
2538
+ if (!isString8(_id) || !_id) {
2539
+ return next(new JsonRouter2.clientErrors.BadRequestError());
2540
+ }
2541
+ const result = await svc._read(_id, { select });
2542
+ if (!result.success) {
2543
+ return next(new JsonRouter2.clientErrors.UnauthorizedError());
2544
+ }
2545
+ const docPermissions = getDocPermissions(modelName, result.data);
2546
+ phas = (key) => permissions.has(key) || docPermissions[key];
2547
+ cond = _cond;
2548
+ }
2549
+ const { stringHandler, arrayHandler } = createValidator(phas);
2550
+ if (isString8(cond)) {
2551
+ if (stringHandler(cond)) return next();
2552
+ } else if (isArray9(cond)) {
2553
+ if (arrayHandler(cond)) return next();
2554
+ } else if (isFunction6(cond)) {
2555
+ const result = await cond.call(req, permissions);
2556
+ if (!!result) return next();
2557
+ }
2558
+ next(new JsonRouter2.clientErrors.UnauthorizedError());
2559
+ };
2560
+ }
2561
+
2562
+ // src/routers/model-router.ts
2563
+ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
2564
+ import { forEach as forEach7, isPlainObject as isPlainObject8, isString as isString9, isUndefined as isUndefined2, padEnd } from "@web-ts-toolkit/utils";
2565
+
2566
+ // src/routers/shared.ts
2567
+ var parseBooleanString = (str, defaultValue) => str ? str === "true" : defaultValue;
2568
+ var formatListResponse = (req, result, includeCount, includeExtraHeaders) => {
2569
+ const { data, count: returnedCount, totalCount } = result;
2570
+ const query = result.query ?? {};
2571
+ const rawLimit = Number(query.limit ?? 0);
2572
+ const skip = Number(query.skip ?? 0);
2573
+ const limit = rawLimit > 0 ? rawLimit : returnedCount;
2574
+ const pageSize = limit;
2575
+ const page = rawLimit > 0 ? Math.floor(skip / limit) + 1 : 1;
2576
+ const hasPreviousPage = skip > 0;
2577
+ const meta = {
2578
+ returnedCount,
2579
+ skip,
2580
+ limit,
2581
+ page,
2582
+ pageSize,
2583
+ hasPreviousPage,
2584
+ ...includeCount && totalCount != null ? {
2585
+ totalCount,
2586
+ totalPages: limit > 0 ? Math.ceil(totalCount / limit) : 1,
2587
+ hasNextPage: skip + returnedCount < totalCount
2588
+ } : {}
2589
+ };
2590
+ if (includeExtraHeaders) {
2591
+ req.res.setHeader("wtt-returned-count" /* ReturnedCount */, returnedCount);
2592
+ req.res.setHeader("wtt-page" /* Page */, page);
2593
+ req.res.setHeader("wtt-page-size" /* PageSize */, pageSize);
2594
+ req.res.setHeader("wtt-has-previous-page" /* HasPreviousPage */, String(hasPreviousPage));
2595
+ if (includeCount && totalCount != null) {
2596
+ req.res.setHeader("wtt-total-count" /* TotalCount */, totalCount);
2597
+ req.res.setHeader("wtt-total-pages" /* TotalPages */, meta.totalPages);
2598
+ req.res.setHeader("wtt-has-next-page" /* HasNextPage */, String(meta.hasNextPage));
2599
+ }
2600
+ }
2601
+ return { data, meta };
2602
+ };
2603
+ var formatCreatedData = (result) => {
2604
+ return result.count === 1 ? result.data[0] : result.data;
2605
+ };
2606
+ var formatUpsertCreatedData = (result) => {
2607
+ return result.data.length > 0 ? result.data[0] : null;
2608
+ };
2609
+
2610
+ // src/routers/validation.ts
2611
+ import JsonRouter3 from "@web-ts-toolkit/express-json-router";
2612
+ import { z } from "zod";
2613
+ var clientErrors = JsonRouter3.clientErrors;
2614
+ var stringOrStringArray = z.union([z.string(), z.array(z.string())]);
2615
+ var queryBooleanString = z.enum(["true", "false"]);
2616
+ var positiveIntegerString = z.string().regex(/^\d+$/, "Expected a non-negative integer");
2617
+ var unknownRecord = z.record(z.string(), z.unknown());
2618
+ var projectionObjectSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));
2619
+ var projectionSchema = z.union([z.string(), z.array(z.string()), projectionObjectSchema]);
2620
+ var sortOrderSchema = z.union([
2621
+ z.literal(-1),
2622
+ z.literal(1),
2623
+ z.literal("asc"),
2624
+ z.literal("ascending"),
2625
+ z.literal("desc"),
2626
+ z.literal("descending")
2627
+ ]);
2628
+ var sortSchema = z.union([
2629
+ z.string(),
2630
+ z.record(z.string(), sortOrderSchema),
2631
+ z.array(z.tuple([z.string(), sortOrderSchema])),
2632
+ z.null()
2633
+ ]);
2634
+ var populateSchema = z.union([
2635
+ z.string(),
2636
+ z.array(
2637
+ z.union([
2638
+ z.string(),
2639
+ z.object({
2640
+ path: z.string().min(1),
2641
+ select: projectionSchema.optional(),
2642
+ match: z.unknown().optional(),
2643
+ access: z.enum(["list", "read"]).optional()
2644
+ }).passthrough()
2645
+ ])
2646
+ ),
2647
+ z.object({
2648
+ path: z.string().min(1),
2649
+ select: projectionSchema.optional(),
2650
+ match: z.unknown().optional(),
2651
+ access: z.enum(["list", "read"]).optional()
2652
+ }).passthrough()
2653
+ ]);
2654
+ var subPopulateSchema = z.union([
2655
+ z.string(),
2656
+ z.array(
2657
+ z.union([
2658
+ z.string(),
2659
+ z.object({
2660
+ path: z.string().min(1),
2661
+ select: projectionSchema.optional()
2662
+ }).passthrough()
2663
+ ])
2664
+ ),
2665
+ z.object({
2666
+ path: z.string().min(1),
2667
+ select: projectionSchema.optional()
2668
+ }).passthrough()
2669
+ ]);
2670
+ var includeItemSchema = z.object({
2671
+ model: z.string().min(1),
2672
+ op: z.enum(["list", "read", "count"]),
2673
+ path: z.string().min(1),
2674
+ filter: z.record(z.string(), z.unknown()).optional(),
2675
+ localField: z.string().min(1),
2676
+ foreignField: z.string().min(1),
2677
+ args: z.unknown().optional(),
2678
+ options: z.unknown().optional()
2679
+ }).passthrough();
2680
+ var includeSchema = z.union([includeItemSchema, z.array(includeItemSchema)]);
2681
+ var fieldsSchema = z.array(z.string().min(1));
2682
+ var taskSchema = z.object({
2683
+ type: z.string().min(1),
2684
+ args: z.unknown(),
2685
+ options: unknownRecord.optional()
2686
+ });
2687
+ var tasksSchema = z.union([taskSchema, z.array(taskSchema)]);
2688
+ var objectOrArraySchema = z.union([z.record(z.string(), z.unknown()), z.array(z.unknown())]);
2689
+ var listQuerySchema = z.object({
2690
+ skip: positiveIntegerString.optional(),
2691
+ limit: positiveIntegerString.optional(),
2692
+ page: positiveIntegerString.optional(),
2693
+ page_size: positiveIntegerString.optional(),
2694
+ skim: queryBooleanString.optional(),
2695
+ include_permissions: queryBooleanString.optional(),
2696
+ include_count: queryBooleanString.optional(),
2697
+ include_extra_headers: queryBooleanString.optional()
2698
+ }).passthrough();
2699
+ var createQuerySchema = z.object({
2700
+ include_permissions: queryBooleanString.optional()
2701
+ }).passthrough();
2702
+ var readQuerySchema = z.object({
2703
+ include_permissions: queryBooleanString.optional(),
2704
+ try_list: queryBooleanString.optional()
2705
+ }).passthrough();
2706
+ var updateQuerySchema = z.object({
2707
+ returning_all: queryBooleanString.optional()
2708
+ }).passthrough();
2709
+ var upsertQuerySchema = z.object({
2710
+ returning_all: queryBooleanString.optional(),
2711
+ include_permissions: queryBooleanString.optional()
2712
+ }).passthrough();
2713
+ var rootQueryEntrySchema = z.object({
2714
+ model: z.string().min(1),
2715
+ op: z.string().min(1),
2716
+ id: z.string().min(1).optional(),
2717
+ field: z.string().min(1).optional(),
2718
+ filter: objectOrArraySchema.optional(),
2719
+ data: z.unknown().optional(),
2720
+ args: z.unknown().optional(),
2721
+ options: z.unknown().optional(),
2722
+ order: z.number().int().optional()
2723
+ }).passthrough();
2724
+ var rootQuerySchema = z.array(rootQueryEntrySchema);
2725
+ var listBodySchema = z.object({
2726
+ query: objectOrArraySchema.optional(),
2727
+ filter: objectOrArraySchema.optional(),
2728
+ select: projectionSchema.optional(),
2729
+ sort: sortSchema.optional(),
2730
+ populate: populateSchema.optional(),
2731
+ include: includeSchema.optional(),
2732
+ tasks: tasksSchema.optional(),
2733
+ skip: z.union([z.number(), positiveIntegerString]).optional(),
2734
+ limit: z.union([z.number(), positiveIntegerString]).optional(),
2735
+ page: z.union([z.number(), positiveIntegerString]).optional(),
2736
+ pageSize: z.union([z.number(), positiveIntegerString]).optional(),
2737
+ options: z.object({
2738
+ skim: z.boolean().optional(),
2739
+ includePermissions: z.boolean().optional(),
2740
+ includeCount: z.boolean().optional(),
2741
+ includeExtraHeaders: z.boolean().optional(),
2742
+ populateAccess: z.unknown().optional()
2743
+ }).passthrough().optional()
2744
+ }).passthrough();
2745
+ var dataListBodySchema = z.object({
2746
+ filter: objectOrArraySchema.optional(),
2747
+ select: projectionSchema.optional(),
2748
+ sort: sortSchema.optional(),
2749
+ skip: z.union([z.number(), positiveIntegerString]).optional(),
2750
+ limit: z.union([z.number(), positiveIntegerString]).optional(),
2751
+ page: z.union([z.number(), positiveIntegerString]).optional(),
2752
+ pageSize: z.union([z.number(), positiveIntegerString]).optional(),
2753
+ options: z.object({
2754
+ includeCount: z.boolean().optional(),
2755
+ includeExtraHeaders: z.boolean().optional()
2756
+ }).passthrough().optional()
2757
+ }).passthrough();
2758
+ var countBodySchema = z.object({
2759
+ query: objectOrArraySchema.optional(),
2760
+ filter: objectOrArraySchema.optional(),
2761
+ access: z.unknown().optional()
2762
+ }).passthrough();
2763
+ var readFilterBodySchema = z.object({
2764
+ filter: objectOrArraySchema.optional(),
2765
+ select: projectionSchema.optional(),
2766
+ sort: sortSchema.optional(),
2767
+ populate: populateSchema.optional(),
2768
+ include: includeSchema.optional(),
2769
+ tasks: tasksSchema.optional(),
2770
+ options: z.object({
2771
+ skim: z.boolean().optional(),
2772
+ includePermissions: z.boolean().optional(),
2773
+ tryList: z.boolean().optional(),
2774
+ populateAccess: z.unknown().optional()
2775
+ }).passthrough().optional()
2776
+ }).passthrough();
2777
+ var dataReadFilterBodySchema = z.object({
2778
+ filter: objectOrArraySchema.optional(),
2779
+ select: projectionSchema.optional(),
2780
+ options: z.object({}).passthrough().optional()
2781
+ }).passthrough();
2782
+ var readByIdBodySchema = z.object({
2783
+ select: projectionSchema.optional(),
2784
+ populate: populateSchema.optional(),
2785
+ include: includeSchema.optional(),
2786
+ tasks: tasksSchema.optional(),
2787
+ options: z.object({
2788
+ skim: z.boolean().optional(),
2789
+ includePermissions: z.boolean().optional(),
2790
+ tryList: z.boolean().optional(),
2791
+ populateAccess: z.unknown().optional()
2792
+ }).passthrough().optional()
2793
+ }).passthrough();
2794
+ var dataReadByIdBodySchema = z.object({
2795
+ select: projectionSchema.optional(),
2796
+ options: z.object({}).passthrough().optional()
2797
+ }).passthrough();
2798
+ var advancedCreateBodySchema = z.object({
2799
+ data: z.unknown(),
2800
+ select: projectionSchema.optional(),
2801
+ populate: populateSchema.optional(),
2802
+ tasks: tasksSchema.optional(),
2803
+ options: z.object({
2804
+ includePermissions: z.boolean().optional(),
2805
+ populateAccess: z.unknown().optional()
2806
+ }).passthrough().optional()
2807
+ }).passthrough();
2808
+ var createBodySchema = z.union([
2809
+ z.record(z.string(), z.unknown()),
2810
+ z.array(z.record(z.string(), z.unknown()))
2811
+ ]);
2812
+ var updateBodySchema = z.record(z.string(), z.unknown());
2813
+ var advancedUpdateBodySchema = z.object({
2814
+ data: z.unknown(),
2815
+ select: projectionSchema.optional(),
2816
+ populate: populateSchema.optional(),
2817
+ tasks: tasksSchema.optional(),
2818
+ options: z.object({
2819
+ returningAll: z.boolean().optional(),
2820
+ includePermissions: z.boolean().optional(),
2821
+ populateAccess: z.unknown().optional()
2822
+ }).passthrough().optional()
2823
+ }).passthrough();
2824
+ var upsertBodySchema = z.record(z.string(), z.unknown());
2825
+ var advancedUpsertBodySchema = z.object({
2826
+ data: z.record(z.string(), z.unknown()),
2827
+ select: projectionSchema.optional(),
2828
+ populate: populateSchema.optional(),
2829
+ tasks: tasksSchema.optional(),
2830
+ options: z.object({
2831
+ returningAll: z.boolean().optional(),
2832
+ includePermissions: z.boolean().optional(),
2833
+ populateAccess: z.unknown().optional()
2834
+ }).passthrough().optional()
2835
+ }).passthrough();
2836
+ var distinctBodySchema = z.object({
2837
+ query: objectOrArraySchema.optional(),
2838
+ filter: objectOrArraySchema.optional()
2839
+ }).passthrough();
2840
+ var subListBodySchema = z.object({
2841
+ filter: z.record(z.string(), z.unknown()).optional(),
2842
+ fields: fieldsSchema.optional()
2843
+ }).passthrough();
2844
+ var subMutationBodySchema = z.union([
2845
+ z.record(z.string(), z.unknown()),
2846
+ z.array(z.record(z.string(), z.unknown()))
2847
+ ]);
2848
+ var subReadBodySchema = z.object({
2849
+ fields: fieldsSchema.optional(),
2850
+ populate: subPopulateSchema.optional()
2851
+ }).passthrough();
2852
+ function parsePathParam(value, parameter) {
2853
+ const result = stringOrStringArray.safeParse(value);
2854
+ if (!result.success) {
2855
+ throwValidationError(result.error.issues, parameter, "parameter");
2856
+ }
2857
+ const param = Array.isArray(result.data) ? result.data[0] : result.data;
2858
+ if (!param) {
2859
+ throw new clientErrors.BadRequestError("Bad Request", {
2860
+ errors: [{ detail: "Required", parameter }]
2861
+ });
2862
+ }
2863
+ return param;
2864
+ }
2865
+ function parseQuery(schema, value) {
2866
+ const result = schema.safeParse(value);
2867
+ if (!result.success) {
2868
+ throwValidationError(result.error.issues, void 0, "parameter");
2869
+ }
2870
+ return result.data;
2871
+ }
2872
+ function parseBody(schema, value) {
2873
+ const result = schema.safeParse(value ?? {});
2874
+ if (!result.success) {
2875
+ throwValidationError(result.error.issues, void 0, "pointer");
2876
+ }
2877
+ return result.data;
2878
+ }
2879
+ function parseBodyWithSchema(schema, value, userSchema) {
2880
+ const body = parseBody(schema, value);
2881
+ return isZodSchema(userSchema) ? parseUserSchema(userSchema, body) : body;
2882
+ }
2883
+ function parseNestedBodyWithSchema(schema, value, nestedKey, userSchema) {
2884
+ const body = parseBody(schema, value);
2885
+ if (!isZodSchema(userSchema)) return body;
2886
+ return {
2887
+ ...body,
2888
+ [nestedKey]: parseUserSchema(userSchema, body?.[nestedKey], [nestedKey])
2889
+ };
2890
+ }
2891
+ function throwValidationError(issues, key, location = "pointer") {
2892
+ const errors = issues.map((issue) => formatIssue(issue, key, location));
2893
+ throw new clientErrors.BadRequestError("Bad Request", { errors });
2894
+ }
2895
+ function parseUserSchema(schema, value, prefix = []) {
2896
+ const result = schema.safeParse(value);
2897
+ if (!result.success) {
2898
+ const issues = result.error.issues.map((issue) => ({
2899
+ ...issue,
2900
+ path: prefix.concat(issue.path.map(String))
2901
+ }));
2902
+ throwValidationError(issues, void 0, "pointer");
2903
+ }
2904
+ return result.data;
2905
+ }
2906
+ function isZodSchema(schema) {
2907
+ return typeof schema === "object" && schema !== null && "safeParse" in schema && typeof schema.safeParse === "function";
2908
+ }
2909
+ function formatIssue(issue, key, location = "pointer") {
2910
+ const path = issue.path.map(String);
2911
+ const joinedPath = path.join(".");
2912
+ if (location === "parameter") {
2913
+ return {
2914
+ detail: issue.message,
2915
+ parameter: (key ?? joinedPath) || void 0
2916
+ };
2917
+ }
2918
+ return {
2919
+ detail: issue.message,
2920
+ pointer: buildPointer(path)
2921
+ };
2922
+ }
2923
+ function buildPointer(path) {
2924
+ return path.length === 0 ? "#" : `#/${path.join("/")}`;
2925
+ }
2926
+ var requestSchemas = {
2927
+ listQuery: listQuerySchema,
2928
+ createQuery: createQuerySchema,
2929
+ readQuery: readQuerySchema,
2930
+ updateQuery: updateQuerySchema,
2931
+ upsertQuery: upsertQuerySchema
2932
+ };
2933
+
2934
+ // src/routers/model-router.ts
2935
+ var clientErrors2 = JsonRouter4.clientErrors;
2936
+ var success = JsonRouter4.success;
2937
+ function setOption(parentKey, optionKey, option) {
2938
+ const key = isUndefined2(option) ? parentKey : `${parentKey}.${optionKey}`;
2939
+ const value = isUndefined2(option) ? optionKey : option;
2940
+ setModelOption(this.modelName, key, value);
2941
+ return this;
2942
+ }
2943
+ var ModelRouter = class {
2944
+ constructor(modelName, initialOptions) {
2945
+ /**
2946
+ * The maximum limit of the number of documents returned from the `list` operation.
2947
+ */
2948
+ this.listHardLimit = setOption.bind(this, "listHardLimit");
2949
+ /**
2950
+ * The object schema to define the access control policy for each model field.
2951
+ */
2952
+ this.permissionSchema = setOption.bind(this, "permissionSchema");
2953
+ /**
2954
+ * The object field to store the document permissions.
2955
+ */
2956
+ this.documentPermissionField = setOption.bind(this, "documentPermissionField");
2957
+ /**
2958
+ * The essential model fields involved in generating document permissions.
2959
+ */
2960
+ this.mandatoryFields = setOption.bind(this, "mandatoryFields");
2961
+ /**
2962
+ * The function called in the process of generating document permissions.
2963
+ */
2964
+ this.docPermissions = setOption.bind(this, "docPermissions");
2965
+ /**
2966
+ * The access control policy for CRUDL endpoints.
2967
+ * @operation `create`, `list`, `read`, `update`, `delete`
2968
+ */
2969
+ this.routeGuard = setOption.bind(this, "routeGuard");
2970
+ /**
2971
+ * The base filter definitions applied in every query transaction.
2972
+ * @operation `list`, `read`, `update`, `delete`
2973
+ */
2974
+ this.baseFilter = setOption.bind(this, "baseFilter");
2975
+ /**
2976
+ * The override filter definitions applied in every query transaction.
2977
+ * @operation `list`, `read`, `update`, `delete`
2978
+ */
2979
+ this.overrideFilter = setOption.bind(this, "overrideFilter");
2980
+ /**
2981
+ * Middleware
2982
+ *
2983
+ * The function called before a new/update document data is processed in `prepare` hooks. This method is used to validate `write data` and throw an error if not valid.
2984
+ * @operation `create`, `update`
2985
+ */
2986
+ this.validate = setOption.bind(this, "validate");
2987
+ /**
2988
+ * Middleware
2989
+ *
2990
+ * The function called before a new document is created or an existing document is updated. This method is used to process raw data passed into the API endpoints.
2991
+ * @operation `create`, `update`
2992
+ */
2993
+ this.prepare = setOption.bind(this, "prepare");
2994
+ /**
2995
+ * Middleware
2996
+ *
2997
+ * The function called before an updated document is saved.
2998
+ * @operation `update`
2999
+ */
3000
+ this.transform = setOption.bind(this, "transform");
3001
+ /**
3002
+ * Middleware
3003
+ *
3004
+ * The function called after a new document is created or an updated document is saved.
3005
+ * @operation `create`, `update`
3006
+ */
3007
+ this.finalize = setOption.bind(this, "finalize");
3008
+ /**
3009
+ * Middleware
3010
+ *
3011
+ * The function called after a updated document finalized
3012
+ * @operation `update`
3013
+ */
3014
+ this.change = setOption.bind(this, "change");
3015
+ /**
3016
+ * Middleware
3017
+ *
3018
+ * The function called before response data is sent. This method is used to process raw data to apply custom logic before sending the result.
3019
+ * @operation `list`, `read`, `create`, `update`
3020
+ */
3021
+ this.decorate = setOption.bind(this, "decorate");
3022
+ /**
3023
+ * Middleware
3024
+ *
3025
+ * The function are called before response data is sent and after `decorate` middleware runs. This method is used to process and filter multiple document objects before sending the result.
3026
+ * @operation `list`
3027
+ */
3028
+ this.decorateAll = setOption.bind(this, "decorateAll");
3029
+ /**
3030
+ * The document selector definition with the `id` param.
3031
+ * @option `string` | `Function`
3032
+ * @operation `read`, `update`, `delete`
3033
+ */
3034
+ this.identifier = setOption.bind(this, "identifier");
3035
+ /**
3036
+ * The default values used when missing in the operations.
3037
+ */
3038
+ this.defaults = setOption.bind(this, "defaults");
3039
+ setModelOptions(modelName, initialOptions);
3040
+ this.options = getModelOptions(modelName);
3041
+ this.fullBasePath = processUrl(this.options.parentPath + this.options.basePath);
3042
+ this.modelName = modelName;
3043
+ this.router = new JsonRouter4(this.options.basePath, setCore, accessRouterResponseHandler);
3044
+ this.model = new model_default(modelName);
3045
+ this.setCollectionRoutes();
3046
+ this.setDocumentRoutes();
3047
+ this.setSubDocumentRoutes();
3048
+ this.logEndpoints();
3049
+ }
3050
+ getRequestSchema(key) {
3051
+ return getExactModelOption(this.modelName, key);
3052
+ }
3053
+ async assertAllowed(req, access) {
3054
+ const allowed = await req.macl.isAllowed(this.modelName, access);
3055
+ if (!allowed) throw new clientErrors2.UnauthorizedError();
3056
+ }
3057
+ ///////////////////////
3058
+ // Collection Routes //
3059
+ ///////////////////////
3060
+ setCollectionRoutes() {
3061
+ this.router.get("", async (req) => {
3062
+ await this.assertAllowed(req, "list");
3063
+ const { skip, limit, page, page_size, skim, include_permissions, include_count, include_extra_headers } = parseQuery(requestSchemas.listQuery, req.query);
3064
+ const svc = req.macl.getPublicService(this.modelName);
3065
+ const includeCount = parseBooleanString(include_count);
3066
+ const includeExtraHeaders = parseBooleanString(include_extra_headers);
3067
+ const result = await svc._list(
3068
+ {},
3069
+ { skip, limit, page, pageSize: page_size },
3070
+ {
3071
+ skim: parseBooleanString(skim),
3072
+ includePermissions: parseBooleanString(include_permissions),
3073
+ includeCount
3074
+ }
3075
+ );
3076
+ handleResultError(result);
3077
+ return formatListResponse(req, result, includeCount, includeExtraHeaders);
3078
+ });
3079
+ this.router.post(`/${this.options.queryPath}`, async (req) => {
3080
+ await this.assertAllowed(req, "list");
3081
+ const body = parseBodyWithSchema(
3082
+ listBodySchema,
3083
+ req.body,
3084
+ this.getRequestSchema("requestSchemas.advancedList")
3085
+ );
3086
+ let { query, filter: filter2, select, sort, populate, include, tasks, skip, limit, page, pageSize } = body;
3087
+ const options = body.options ?? {};
3088
+ const { skim, includePermissions, includeCount, includeExtraHeaders, populateAccess } = options;
3089
+ const svc = req.macl.getPublicService(this.modelName);
3090
+ const listFilter = filter2 ?? query ?? {};
3091
+ const result = await svc._list(
3092
+ listFilter,
3093
+ { select, sort, populate, include, tasks, skip, limit, page, pageSize },
3094
+ {
3095
+ skim,
3096
+ includePermissions,
3097
+ includeCount,
3098
+ populateAccess
3099
+ }
3100
+ );
3101
+ handleResultError(result);
3102
+ return formatListResponse(req, result, includeCount, includeExtraHeaders);
3103
+ });
3104
+ this.router.post("", async (req, res) => {
3105
+ await this.assertAllowed(req, "create");
3106
+ const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
3107
+ const data = parseBodyWithSchema(createBodySchema, req.body, this.getRequestSchema("requestSchemas.create"));
3108
+ const svc = req.macl.getPublicService(this.modelName);
3109
+ const result = await svc._create(data, {}, { includePermissions: parseBooleanString(include_permissions) });
3110
+ handleResultError(result);
3111
+ return new success.Created(formatCreatedData(result));
3112
+ });
3113
+ this.router.post(`/${this.options.mutationPath}`, async (req, res) => {
3114
+ await this.assertAllowed(req, "create");
3115
+ const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
3116
+ const body = parseNestedBodyWithSchema(
3117
+ advancedCreateBodySchema,
3118
+ req.body,
3119
+ "data",
3120
+ this.getRequestSchema("requestSchemas.advancedCreate.data")
3121
+ );
3122
+ const { data, select, populate, tasks } = body;
3123
+ const options = body.options ?? {};
3124
+ parseBodyWithSchema(
3125
+ advancedCreateBodySchema,
3126
+ { data, select, populate, tasks, options },
3127
+ this.getRequestSchema("requestSchemas.advancedCreate.default") ?? this.getRequestSchema("requestSchemas.advancedCreate")
3128
+ );
3129
+ const { includePermissions, populateAccess } = options;
3130
+ const svc = req.macl.getPublicService(this.modelName);
3131
+ const result = await svc._create(
3132
+ data,
3133
+ { select, populate, tasks },
3134
+ {
3135
+ includePermissions: includePermissions ?? parseBooleanString(include_permissions),
3136
+ populateAccess
3137
+ }
3138
+ );
3139
+ handleResultError(result);
3140
+ return new success.Created(formatCreatedData(result));
3141
+ });
3142
+ this.router.get("/new", async (req) => {
3143
+ const svc = req.macl.getPublicService(this.modelName);
3144
+ const result = await svc._new();
3145
+ handleResultError(result);
3146
+ return result.data;
3147
+ });
3148
+ }
3149
+ /////////////////////
3150
+ // Document Routes //
3151
+ /////////////////////
3152
+ setDocumentRoutes() {
3153
+ this.router.get("/count", async (req) => {
3154
+ await this.assertAllowed(req, "count");
3155
+ const svc = req.macl.getPublicService(this.modelName);
3156
+ const result = await svc._count({});
3157
+ handleResultError(result);
3158
+ return result.data;
3159
+ });
3160
+ this.router.post("/count", async (req) => {
3161
+ await this.assertAllowed(req, "count");
3162
+ const { query, filter: filter2, access } = parseBodyWithSchema(
3163
+ countBodySchema,
3164
+ req.body,
3165
+ this.getRequestSchema("requestSchemas.count")
3166
+ );
3167
+ const svc = req.macl.getPublicService(this.modelName);
3168
+ const countFilter = filter2 ?? query ?? {};
3169
+ const result = await svc._count(countFilter, access);
3170
+ handleResultError(result);
3171
+ return result.data;
3172
+ });
3173
+ this.router.get(`/:${this.options.idParam}`, async (req) => {
3174
+ await this.assertAllowed(req, "read");
3175
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3176
+ const { include_permissions, try_list } = parseQuery(requestSchemas.readQuery, req.query);
3177
+ const svc = req.macl.getPublicService(this.modelName);
3178
+ const result = await svc._read(
3179
+ id,
3180
+ {},
3181
+ {
3182
+ includePermissions: parseBooleanString(include_permissions),
3183
+ tryList: parseBooleanString(try_list)
3184
+ }
3185
+ );
3186
+ handleResultError(result);
3187
+ return result.data;
3188
+ });
3189
+ this.router.post(`/${this.options.queryPath}/__filter`, async (req) => {
3190
+ await this.assertAllowed(req, "read");
3191
+ const body = parseBodyWithSchema(
3192
+ readFilterBodySchema,
3193
+ req.body,
3194
+ this.getRequestSchema("requestSchemas.advancedReadFilter")
3195
+ );
3196
+ let { filter: filter2, select, sort, populate, include, tasks } = body;
3197
+ const options = body.options ?? {};
3198
+ const { skim, includePermissions, tryList, populateAccess } = options;
3199
+ const svc = req.macl.getPublicService(this.modelName);
3200
+ const result = await svc._readFilter(
3201
+ filter2 ?? {},
3202
+ {
3203
+ select,
3204
+ sort,
3205
+ populate,
3206
+ include,
3207
+ tasks
3208
+ },
3209
+ { skim, includePermissions, tryList, populateAccess }
3210
+ );
3211
+ handleResultError(result);
3212
+ return result.data;
3213
+ });
3214
+ this.router.post(`/${this.options.queryPath}/:${this.options.idParam}`, async (req) => {
3215
+ await this.assertAllowed(req, "read");
3216
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3217
+ const body = parseBodyWithSchema(
3218
+ readByIdBodySchema,
3219
+ req.body,
3220
+ this.getRequestSchema("requestSchemas.advancedRead")
3221
+ );
3222
+ let { select, populate, include, tasks } = body;
3223
+ const options = body.options ?? {};
3224
+ const { skim, includePermissions, tryList, populateAccess } = options;
3225
+ const svc = req.macl.getPublicService(this.modelName);
3226
+ const result = await svc._read(
3227
+ id,
3228
+ {
3229
+ select,
3230
+ populate,
3231
+ include,
3232
+ tasks
3233
+ },
3234
+ { skim, includePermissions, tryList, populateAccess }
3235
+ );
3236
+ handleResultError(result);
3237
+ return result.data;
3238
+ });
3239
+ this.router.patch(`/:${this.options.idParam}`, async (req) => {
3240
+ await this.assertAllowed(req, "update");
3241
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3242
+ const { returning_all } = parseQuery(requestSchemas.updateQuery, req.query);
3243
+ const data = parseBodyWithSchema(updateBodySchema, req.body, this.getRequestSchema("requestSchemas.update"));
3244
+ const svc = req.macl.getPublicService(this.modelName);
3245
+ const result = await svc._update(id, data, {}, { returningAll: parseBooleanString(returning_all) });
3246
+ handleResultError(result);
3247
+ return result.data;
3248
+ });
3249
+ this.router.patch(`/${this.options.mutationPath}/:${this.options.idParam}`, async (req) => {
3250
+ await this.assertAllowed(req, "update");
3251
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3252
+ const { returning_all } = parseQuery(requestSchemas.updateQuery, req.query);
3253
+ const body = parseNestedBodyWithSchema(
3254
+ advancedUpdateBodySchema,
3255
+ req.body,
3256
+ "data",
3257
+ this.getRequestSchema("requestSchemas.advancedUpdate.data")
3258
+ );
3259
+ const { data, select, populate, tasks } = body;
3260
+ const options = body.options ?? {};
3261
+ parseBodyWithSchema(
3262
+ advancedUpdateBodySchema,
3263
+ { data, select, populate, tasks, options },
3264
+ this.getRequestSchema("requestSchemas.advancedUpdate.default") ?? this.getRequestSchema("requestSchemas.advancedUpdate")
3265
+ );
3266
+ const { returningAll, includePermissions, populateAccess } = options;
3267
+ const svc = req.macl.getPublicService(this.modelName);
3268
+ const result = await svc._update(
3269
+ id,
3270
+ data,
3271
+ { select, populate, tasks },
3272
+ {
3273
+ returningAll: returningAll ?? parseBooleanString(returning_all),
3274
+ includePermissions,
3275
+ populateAccess
3276
+ }
3277
+ );
3278
+ handleResultError(result);
3279
+ return result.data;
3280
+ });
3281
+ this.router.put(`/`, async (req) => {
3282
+ await this.assertAllowed(req, "upsert");
3283
+ const svc = req.macl.getPublicService(this.modelName);
3284
+ const idKey = svc.getIdentifier();
3285
+ if (idKey !== "_id") throw new clientErrors2.BadRequestError("not supported identifier");
3286
+ const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
3287
+ const body = parseBodyWithSchema(upsertBodySchema, req.body, this.getRequestSchema("requestSchemas.upsert"));
3288
+ const { [idKey]: idVal, ...data } = body;
3289
+ const upsertId = idVal;
3290
+ if (upsertId) {
3291
+ const existing = await svc.exists({ [idKey]: upsertId }, { access: "update" });
3292
+ handleResultError(existing);
3293
+ if (!existing.data) throw new clientErrors2.UnauthorizedError();
3294
+ const result = await svc._update(upsertId, data, {}, { returningAll: parseBooleanString(returning_all) });
3295
+ handleResultError(result);
3296
+ return result.data;
3297
+ } else {
3298
+ const result = await svc._create(data, {}, { includePermissions: parseBooleanString(include_permissions) });
3299
+ handleResultError(result);
3300
+ return new success.Created(formatUpsertCreatedData(result));
3301
+ }
3302
+ });
3303
+ this.router.put(`/${this.options.mutationPath}`, async (req) => {
3304
+ await this.assertAllowed(req, "upsert");
3305
+ const svc = req.macl.getPublicService(this.modelName);
3306
+ const idKey = svc.getIdentifier();
3307
+ if (idKey !== "_id") throw new clientErrors2.BadRequestError("not supported identifier");
3308
+ const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
3309
+ const body = parseNestedBodyWithSchema(
3310
+ advancedUpsertBodySchema,
3311
+ req.body,
3312
+ "data",
3313
+ this.getRequestSchema("requestSchemas.advancedUpsert.data")
3314
+ );
3315
+ const { data, select, populate, tasks } = body;
3316
+ const options = body.options ?? {};
3317
+ parseBodyWithSchema(
3318
+ advancedUpsertBodySchema,
3319
+ { data, select, populate, tasks, options },
3320
+ this.getRequestSchema("requestSchemas.advancedUpsert.default") ?? this.getRequestSchema("requestSchemas.advancedUpsert")
3321
+ );
3322
+ const { returningAll, includePermissions, populateAccess } = options;
3323
+ const { [idKey]: idVal, ...otherData } = data;
3324
+ const upsertId = idVal;
3325
+ if (upsertId) {
3326
+ const existing = await svc.exists({ [idKey]: upsertId }, { access: "update" });
3327
+ handleResultError(existing);
3328
+ if (!existing.data) throw new clientErrors2.UnauthorizedError();
3329
+ const result = await svc._update(
3330
+ upsertId,
3331
+ otherData,
3332
+ { select, populate, tasks },
3333
+ {
3334
+ returningAll: returningAll ?? parseBooleanString(returning_all),
3335
+ includePermissions: includePermissions ?? parseBooleanString(include_permissions),
3336
+ populateAccess
3337
+ }
3338
+ );
3339
+ handleResultError(result);
3340
+ return result.data;
3341
+ } else {
3342
+ const result = await svc._create(
3343
+ otherData,
3344
+ { select, populate, tasks },
3345
+ {
3346
+ includePermissions: includePermissions ?? parseBooleanString(include_permissions),
3347
+ populateAccess
3348
+ }
3349
+ );
3350
+ handleResultError(result);
3351
+ return new success.Created(formatUpsertCreatedData(result));
3352
+ }
3353
+ });
3354
+ this.router.delete(`/:${this.options.idParam}`, async (req) => {
3355
+ await this.assertAllowed(req, "delete");
3356
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3357
+ const svc = req.macl.getPublicService(this.modelName);
3358
+ const result = await svc._delete(id);
3359
+ handleResultError(result);
3360
+ return result.data;
3361
+ });
3362
+ this.router.get("/distinct/:field", async (req) => {
3363
+ await this.assertAllowed(req, "distinct");
3364
+ const field = parsePathParam(req.params.field, "field");
3365
+ const svc = req.macl.getPublicService(this.modelName);
3366
+ const result = await svc._distinct(field);
3367
+ handleResultError(result);
3368
+ return result.data;
3369
+ });
3370
+ this.router.post("/distinct/:field", async (req) => {
3371
+ await this.assertAllowed(req, "distinct");
3372
+ const field = parsePathParam(req.params.field, "field");
3373
+ const { query, filter: filter2 } = parseBodyWithSchema(
3374
+ distinctBodySchema,
3375
+ req.body,
3376
+ this.getRequestSchema("requestSchemas.distinct")
3377
+ );
3378
+ const svc = req.macl.getPublicService(this.modelName);
3379
+ const result = await svc._distinct(field, { filter: filter2 ?? query ?? {} });
3380
+ handleResultError(result);
3381
+ return result.data;
3382
+ });
3383
+ }
3384
+ /////////////////////////
3385
+ // Sub-Document Routes //
3386
+ /////////////////////////
3387
+ setSubDocumentRoutes() {
3388
+ const subs = getModelSub(this.modelName);
3389
+ for (let x = 0; x < subs.length; x++) {
3390
+ const sub = subs[x];
3391
+ this.router.get(`/:${this.options.idParam}/${sub}`, async (req) => {
3392
+ await this.assertAllowed(req, `subs.${sub}.list`);
3393
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3394
+ const svc = req.macl.getPublicService(this.modelName);
3395
+ const result = await svc.listSub(id, sub);
3396
+ handleResultError(result);
3397
+ return result.data;
3398
+ });
3399
+ this.router.post(`/:${this.options.idParam}/${sub}/${this.options.queryPath}`, async (req) => {
3400
+ await this.assertAllowed(req, `subs.${sub}.list`);
3401
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3402
+ const body = parseBodyWithSchema(
3403
+ subListBodySchema,
3404
+ req.body,
3405
+ this.getRequestSchema("requestSchemas.subList")
3406
+ );
3407
+ const svc = req.macl.getPublicService(this.modelName);
3408
+ const result = await svc.listSub(id, sub, { filter: body.filter ?? {}, fields: body.fields ?? [] });
3409
+ handleResultError(result);
3410
+ return result.data;
3411
+ });
3412
+ this.router.patch(`/:${this.options.idParam}/${sub}`, async (req) => {
3413
+ await this.assertAllowed(req, `subs.${sub}.update`);
3414
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3415
+ const data = parseBodyWithSchema(
3416
+ subMutationBodySchema,
3417
+ req.body,
3418
+ this.getRequestSchema("requestSchemas.subBulkUpdate")
3419
+ );
3420
+ const svc = req.macl.getPublicService(this.modelName);
3421
+ const result = await svc.bulkUpdateSub(id, sub, data);
3422
+ handleResultError(result);
3423
+ return result.data;
3424
+ });
3425
+ this.router.get(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
3426
+ await this.assertAllowed(req, `subs.${sub}.read`);
3427
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3428
+ const subId = parsePathParam(req.params.subId, "subId");
3429
+ const svc = req.macl.getPublicService(this.modelName);
3430
+ const result = await svc.readSub(id, sub, subId);
3431
+ handleResultError(result);
3432
+ return result.data;
3433
+ });
3434
+ this.router.post(`/:${this.options.idParam}/${sub}/:subId/${this.options.queryPath}`, async (req) => {
3435
+ await this.assertAllowed(req, `subs.${sub}.read`);
3436
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3437
+ const subId = parsePathParam(req.params.subId, "subId");
3438
+ const body = parseBodyWithSchema(
3439
+ subReadBodySchema,
3440
+ req.body,
3441
+ this.getRequestSchema("requestSchemas.subRead")
3442
+ );
3443
+ const populate = body.populate;
3444
+ const normalizedPopulate = Array.isArray(populate) ? populate.map((item) => isString9(item) ? { path: item } : item) : isString9(populate) ? { path: populate } : populate ?? [];
3445
+ const svc = req.macl.getPublicService(this.modelName);
3446
+ const result = await svc.readSub(id, sub, subId, { fields: body.fields ?? [], populate: normalizedPopulate });
3447
+ handleResultError(result);
3448
+ return result.data;
3449
+ });
3450
+ this.router.patch(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
3451
+ await this.assertAllowed(req, `subs.${sub}.update`);
3452
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3453
+ const subId = parsePathParam(req.params.subId, "subId");
3454
+ const data = parseBodyWithSchema(
3455
+ subMutationBodySchema,
3456
+ req.body,
3457
+ this.getRequestSchema("requestSchemas.subUpdate")
3458
+ );
3459
+ const svc = req.macl.getPublicService(this.modelName);
3460
+ const result = await svc.updateSub(id, sub, subId, data);
3461
+ handleResultError(result);
3462
+ return result.data;
3463
+ });
3464
+ this.router.post(`/:${this.options.idParam}/${sub}`, async (req) => {
3465
+ await this.assertAllowed(req, `subs.${sub}.create`);
3466
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3467
+ const data = parseBodyWithSchema(
3468
+ subMutationBodySchema,
3469
+ req.body,
3470
+ this.getRequestSchema("requestSchemas.subCreate")
3471
+ );
3472
+ const svc = req.macl.getPublicService(this.modelName);
3473
+ const result = await svc.createSub(id, sub, data);
3474
+ handleResultError(result);
3475
+ return new success.Created(result.data);
3476
+ });
3477
+ this.router.delete(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
3478
+ await this.assertAllowed(req, `subs.${sub}.delete`);
3479
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3480
+ const subId = parsePathParam(req.params.subId, "subId");
3481
+ const svc = req.macl.getPublicService(this.modelName);
3482
+ const result = await svc.deleteSub(id, sub, subId);
3483
+ handleResultError(result);
3484
+ return result.data;
3485
+ });
3486
+ }
3487
+ }
3488
+ logEndpoints() {
3489
+ forEach7(this.router.endpoints, ({ method, path }) => {
3490
+ logger.info(`${padEnd(method, 6)} ${processUrl(this.options.parentPath + path)}`);
3491
+ });
3492
+ }
3493
+ set(keyOrOptions, value) {
3494
+ if (arguments.length === 2 && isString9(keyOrOptions)) {
3495
+ setModelOption(this.modelName, keyOrOptions, value);
3496
+ }
3497
+ if (arguments.length === 1 && isPlainObject8(keyOrOptions)) {
3498
+ setModelOptions(this.modelName, keyOrOptions);
3499
+ }
3500
+ return this;
3501
+ }
3502
+ setOption(key, option) {
3503
+ setModelOption(this.modelName, key, option);
3504
+ return this;
3505
+ }
3506
+ setOptions(options) {
3507
+ setModelOptions(this.modelName, options);
3508
+ return this;
3509
+ }
3510
+ get routes() {
3511
+ return this.router.original;
3512
+ }
3513
+ };
3514
+
3515
+ // src/routers/root-router.ts
3516
+ import JsonRouter5 from "@web-ts-toolkit/express-json-router";
3517
+ import { isNumber as _isNumber, orderBy as _orderBy } from "@web-ts-toolkit/utils";
3518
+ var clientErrors3 = JsonRouter5.clientErrors;
3519
+ var ALL_ROUTES = ["new", "list", "read", "update", "delete", "create", "distinct", "count"];
3520
+ var RootRouter = class {
3521
+ constructor(options = { basePath: "", routeGuard: true }) {
3522
+ const { basePath, routeGuard } = options;
3523
+ this.basename = basePath || "";
3524
+ this.routeGuard = routeGuard;
3525
+ this.router = new JsonRouter5(this.basename, setCore, accessRouterResponseHandler);
3526
+ this.setRoutes();
3527
+ }
3528
+ processResult(op, result) {
3529
+ const message = mapCodeToMessage(result.code);
3530
+ const statusCode = mapCodeToStatusCode(result.code);
3531
+ if (!result.success) {
3532
+ const { success: success3, code: code2, errors } = result;
3533
+ return { success: success3, code: code2, errors, message, statusCode, op };
3534
+ }
3535
+ if (result.kind === "list") {
3536
+ const { success: success3, code: code2, data: data2, count, totalCount } = result;
3537
+ return { success: success3, code: code2, data: data2, count, totalCount, message, statusCode, op };
3538
+ }
3539
+ const { success: success2, code, data } = result;
3540
+ return { success: success2, code, data, message, statusCode, op };
3541
+ }
3542
+ async processOp(req, item) {
3543
+ const svc = req.macl.getPublicService(item.model);
3544
+ if (!svc) return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Model ${item.model} not found` };
3545
+ if (!ALL_ROUTES.includes(item.op))
3546
+ return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Operation ${item.op} not found` };
3547
+ const routeGuard = getModelOption(item.model, `routeGuard.${item.op}`);
3548
+ const allowed = await req.macl.canActivate(routeGuard);
3549
+ if (!allowed) return { success: false, code: "unauthorized" /* Unauthorized */, data: null, message: "Unauthorized" };
3550
+ if (item.op === "list") {
3551
+ return this.processResult(item.op, await svc._list(item.filter, item.args, item.options));
3552
+ } else if (item.op === "create") {
3553
+ return this.processResult(item.op, await svc._create(item.data, item.args));
3554
+ } else if (item.op === "new") {
3555
+ return this.processResult(item.op, await svc._new());
3556
+ } else if (item.op === "read") {
3557
+ if (item.id) {
3558
+ return this.processResult(item.op, await svc._read(item.id, item.args, item.options));
3559
+ }
3560
+ if (item.filter) {
3561
+ return this.processResult(item.op, await svc._readFilter(item.filter, item.args, item.options));
3562
+ }
3563
+ return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Operation ${item.op} invalid` };
3564
+ } else if (item.op === "update") {
3565
+ return this.processResult(item.op, await svc._update(item.id, item.data, item.args, item.options));
3566
+ } else if (item.op === "delete") {
3567
+ return this.processResult(item.op, await svc._delete(item.id));
3568
+ } else if (item.op === "distinct") {
3569
+ return this.processResult(item.op, await svc._distinct(item.field, { filter: item.filter }));
3570
+ } else if (item.op === "count") {
3571
+ return this.processResult(item.op, await svc._count(item.filter));
3572
+ } else {
3573
+ return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `operation ${item.op} not found` };
3574
+ }
3575
+ }
3576
+ groupItemsByOrder(items) {
3577
+ return items.reduce((acc, item, index) => {
3578
+ let order = 0;
3579
+ if (_isNumber(item.order)) {
3580
+ order = item.order < 0 ? 0 : item.order;
3581
+ }
3582
+ if (!acc[order]) {
3583
+ acc[order] = [];
3584
+ }
3585
+ acc[order].push({ index, item });
3586
+ return acc;
3587
+ }, []);
3588
+ }
3589
+ async assertAllowed(req) {
3590
+ const allowed = await req.macl.canActivate(this.routeGuard);
3591
+ if (!allowed) throw new clientErrors3.UnauthorizedError();
3592
+ }
3593
+ setRoutes() {
3594
+ this.router.post("", async (req) => {
3595
+ await this.assertAllowed(req);
3596
+ const items = parseBody(rootQuerySchema, req.body).map((item) => ({
3597
+ ...item,
3598
+ filter: item.filter ?? {}
3599
+ }));
3600
+ const groupedItems = this.groupItemsByOrder(items);
3601
+ const results = [];
3602
+ for (let x = 0; x < groupedItems.length; x++) {
3603
+ const arrResult = await Promise.all(
3604
+ groupedItems[x].map(async ({ item, index }) => {
3605
+ const ret = await this.processOp(req, item);
3606
+ return { ret, index };
3607
+ })
3608
+ );
3609
+ results.push(...arrResult);
3610
+ }
3611
+ return _orderBy(results, ["index"], ["asc"]).map(({ ret }) => ret);
3612
+ });
3613
+ }
3614
+ get routes() {
3615
+ return this.router.original;
3616
+ }
3617
+ };
3618
+
3619
+ // src/routers/data-router.ts
3620
+ import JsonRouter6 from "@web-ts-toolkit/express-json-router";
3621
+ import { isPlainObject as isPlainObject9, isString as isString10, isUndefined as isUndefined3 } from "@web-ts-toolkit/utils";
3622
+
3623
+ // src/core-data.ts
3624
+ import { intersection as intersection2 } from "@web-ts-toolkit/utils";
3625
+ var DataCore = class {
3626
+ constructor(req) {
3627
+ this.req = req;
3628
+ this.caches = {
3629
+ baseFilter: new Cache()
3630
+ };
3631
+ }
3632
+ async genIDFilter(dataName, id) {
3633
+ const identifier = getDataOption(dataName, "identifier");
3634
+ return resolveIdentifierFilter(this.req, identifier, id);
3635
+ }
3636
+ async genFilter(dataName, access = "read", _filter = null) {
3637
+ const permissions = this.getGlobalPermissions();
3638
+ const cacheKey = `${dataName}_baseFilter_${access}`;
3639
+ return resolveAccessFilter({
3640
+ req: this.req,
3641
+ permissions,
3642
+ cache: this.caches.baseFilter,
3643
+ cacheKey,
3644
+ access,
3645
+ filter: _filter,
3646
+ getOption: (key, defaultValue) => getDataOption(dataName, key, defaultValue)
3647
+ });
3648
+ }
3649
+ async genAllowedFields(dataName, _doc, access, baseFields = []) {
3650
+ const permissionSchema = getDataOption(dataName, "permissionSchema");
3651
+ const permissions = this.getGlobalPermissions();
3652
+ return collectSchemaFields({
3653
+ req: this.req,
3654
+ permissionSchema,
3655
+ access,
3656
+ baseFields,
3657
+ hasPermission: (key) => permissions.has(key),
3658
+ functionArgs: [permissions]
3659
+ });
3660
+ }
3661
+ async pickAllowedFields(dataName, doc, access, baseFields = []) {
3662
+ const allowed = await this.genAllowedFields(dataName, doc, access, baseFields);
3663
+ return pickDocFields(doc, allowed);
3664
+ }
3665
+ async genSelect(dataName, access, targetFields = null, skipChecks = true, subPaths = []) {
3666
+ let normalizedSelect = normalizeSelect(targetFields);
3667
+ const permissionSchema = getDataOption(dataName, ["permissionSchema"].concat(subPaths).join("."));
3668
+ if (!permissionSchema) return [];
3669
+ const permissions = this.getGlobalPermissions();
3670
+ let fields = await collectSchemaFields({
3671
+ req: this.req,
3672
+ permissionSchema,
3673
+ access,
3674
+ hasPermission: (key) => {
3675
+ if (permissions.prop(key)) {
3676
+ return permissions.has(key);
3677
+ }
3678
+ return !!skipChecks;
3679
+ },
3680
+ functionArgs: [permissions]
3681
+ });
3682
+ fields = intersection2(normalizedSelect, fields);
3683
+ return fields;
3684
+ }
3685
+ async decorate(dataName, doc, access, context = {}) {
3686
+ const decorate = getDataOption(dataName, `decorate.${access}`, null);
3687
+ const permissions = this.getGlobalPermissions();
3688
+ return callMiddlewareChain(this.req, decorate, doc, permissions, context);
3689
+ }
3690
+ async decorateAll(dataName, docs, access) {
3691
+ const decorateAll = getDataOption(dataName, `decorateAll.${access}`, null);
3692
+ const permissions = this.getGlobalPermissions();
3693
+ return callMiddlewareChain(this.req, decorateAll, docs, permissions, {});
3694
+ }
3695
+ getPermissions() {
3696
+ return getRequestPermissions(this.req);
3697
+ }
3698
+ async setPermissions() {
3699
+ await setRequestPermissions(this.req);
3700
+ }
3701
+ async canActivate(routeGuard) {
3702
+ const permissions = this.getGlobalPermissions();
3703
+ return evaluateRouteGuard(this.req, permissions, routeGuard);
3704
+ }
3705
+ async isAllowed(dataName, access) {
3706
+ const routeGuard = getDataOption(dataName, `routeGuard.${access}`);
3707
+ return this.canActivate(routeGuard);
3708
+ }
3709
+ getService(dataName) {
3710
+ return new DataService(this.req, dataName);
3711
+ }
3712
+ service(dataName) {
3713
+ return this.getService(dataName);
3714
+ }
3715
+ svc(dataName) {
3716
+ return this.getService(dataName);
3717
+ }
3718
+ getGlobalPermissions() {
3719
+ return this.req[PERMISSIONS];
3720
+ }
3721
+ };
3722
+ async function setDataCore(req, _res, next) {
3723
+ if (req[DATA_MIDDLEWARE]) return next();
3724
+ const core = new DataCore(req);
3725
+ await core.setPermissions();
3726
+ req.dacl = core;
3727
+ req[PERMISSIONS] = core.getPermissions();
3728
+ req[PERMISSION_KEYS] = req[PERMISSIONS].$_permissionKeys;
3729
+ req[DATA_MIDDLEWARE] = true;
3730
+ next();
3731
+ }
3732
+
3733
+ // src/routers/data-router.ts
3734
+ var clientErrors4 = JsonRouter6.clientErrors;
3735
+ function setOption2(parentKey, optionKey, option) {
3736
+ const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
3737
+ const value = isUndefined3(option) ? optionKey : option;
3738
+ setDataOption(this.dataName, key, value);
3739
+ return this;
3740
+ }
3741
+ var DataRouter = class {
3742
+ constructor(dataName, initialOptions) {
3743
+ this.data = setOption2.bind(this, "data");
3744
+ this.listHardLimit = setOption2.bind(this, "listHardLimit");
3745
+ this.permissionSchema = setOption2.bind(this, "permissionSchema");
3746
+ this.routeGuard = setOption2.bind(this, "routeGuard");
3747
+ this.baseFilter = setOption2.bind(this, "baseFilter");
3748
+ this.overrideFilter = setOption2.bind(this, "overrideFilter");
3749
+ this.decorate = setOption2.bind(this, "decorate");
3750
+ this.decorateAll = setOption2.bind(this, "decorateAll");
3751
+ this.identifier = setOption2.bind(this, "identifier");
3752
+ setDataOptions(dataName, initialOptions);
3753
+ this.options = getDataOptions(dataName);
3754
+ this.fullBasePath = processUrl(this.options.parentPath + this.options.basePath);
3755
+ this.dataName = dataName;
3756
+ this.router = new JsonRouter6(this.options.basePath, setDataCore, accessRouterResponseHandler);
3757
+ this.setCollectionRoutes();
3758
+ this.setDocumentRoutes();
3759
+ }
3760
+ getRequestSchema(key) {
3761
+ return getExactDataOption(this.dataName, key);
3762
+ }
3763
+ async assertAllowed(req, access) {
3764
+ const allowed = await req.dacl.isAllowed(this.dataName, access);
3765
+ if (!allowed) throw new clientErrors4.UnauthorizedError();
3766
+ }
3767
+ ///////////////////////
3768
+ // Collection Routes //
3769
+ ///////////////////////
3770
+ setCollectionRoutes() {
3771
+ this.router.get("", async (req) => {
3772
+ await this.assertAllowed(req, "list");
3773
+ const { skip, limit, page, page_size, include_count, include_extra_headers } = parseQuery(
3774
+ requestSchemas.listQuery,
3775
+ req.query
3776
+ );
3777
+ const svc = req.dacl.getService(this.dataName);
3778
+ const includeCount = parseBooleanString(include_count);
3779
+ const includeExtraHeaders = parseBooleanString(include_extra_headers);
3780
+ const result = await svc.find(
3781
+ {},
3782
+ { skip, limit, page, pageSize: page_size },
3783
+ {
3784
+ includeCount
3785
+ }
3786
+ );
3787
+ handleResultError(result);
3788
+ return formatListResponse(req, result, includeCount, includeExtraHeaders);
3789
+ });
3790
+ this.router.post(`/${this.options.queryPath}`, async (req) => {
3791
+ await this.assertAllowed(req, "list");
3792
+ let {
3793
+ filter: filter2,
3794
+ select,
3795
+ sort,
3796
+ skip,
3797
+ limit,
3798
+ page,
3799
+ pageSize,
3800
+ options = {}
3801
+ } = parseBodyWithSchema(dataListBodySchema, req.body, this.getRequestSchema("requestSchemas.advancedList"));
3802
+ const { includeCount, includeExtraHeaders } = options;
3803
+ const svc = req.dacl.getService(this.dataName);
3804
+ const result = await svc.find(
3805
+ filter2 ?? {},
3806
+ { select, sort: typeof sort === "string" ? sort : void 0, skip, limit, page, pageSize },
3807
+ { includeCount }
3808
+ );
3809
+ handleResultError(result);
3810
+ return formatListResponse(req, result, includeCount, includeExtraHeaders);
3811
+ });
3812
+ }
3813
+ /////////////////////
3814
+ // Document Routes //
3815
+ /////////////////////
3816
+ setDocumentRoutes() {
3817
+ this.router.get(`/:${this.options.idParam}`, async (req) => {
3818
+ await this.assertAllowed(req, "read");
3819
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3820
+ const svc = req.dacl.getService(this.dataName);
3821
+ const result = await svc.findById(id, {}, {});
3822
+ handleResultError(result);
3823
+ return result.data;
3824
+ });
3825
+ this.router.post(`/${this.options.queryPath}/__filter`, async (req) => {
3826
+ await this.assertAllowed(req, "read");
3827
+ let { filter: filter2, select } = parseBodyWithSchema(
3828
+ dataReadFilterBodySchema,
3829
+ req.body,
3830
+ this.getRequestSchema("requestSchemas.advancedReadFilter")
3831
+ );
3832
+ const svc = req.dacl.getService(this.dataName);
3833
+ const result = await svc.findOne(filter2 ?? {}, { select }, {});
3834
+ handleResultError(result);
3835
+ return result.data;
3836
+ });
3837
+ this.router.post(`/${this.options.queryPath}/:${this.options.idParam}`, async (req) => {
3838
+ await this.assertAllowed(req, "read");
3839
+ const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
3840
+ let { select } = parseBodyWithSchema(
3841
+ dataReadByIdBodySchema,
3842
+ req.body,
3843
+ this.getRequestSchema("requestSchemas.advancedRead")
3844
+ );
3845
+ const svc = req.dacl.getService(this.dataName);
3846
+ const result = await svc.findById(id, { select }, {});
3847
+ handleResultError(result);
3848
+ return result.data;
3849
+ });
3850
+ }
3851
+ set(keyOrOptions, value) {
3852
+ if (arguments.length === 2 && isString10(keyOrOptions)) {
3853
+ setDataOption(this.dataName, keyOrOptions, value);
3854
+ }
3855
+ if (arguments.length === 1 && isPlainObject9(keyOrOptions)) {
3856
+ setDataOptions(this.dataName, keyOrOptions);
3857
+ }
3858
+ return this;
3859
+ }
3860
+ setOption(key, option) {
3861
+ setDataOption(this.dataName, key, option);
3862
+ return this;
3863
+ }
3864
+ setOptions(options) {
3865
+ setDataOptions(this.dataName, options);
3866
+ return this;
3867
+ }
3868
+ get routes() {
3869
+ return this.router.original;
3870
+ }
3871
+ };
3872
+
3873
+ // src/routers/index.ts
3874
+ import JsonRouter7 from "@web-ts-toolkit/express-json-router";
3875
+ var accessRouterResponseHandler = JsonRouter7.createHandler({
3876
+ errorFormat: JsonRouter7.ErrorFormats.rfc9457,
3877
+ rfc9457ContentType: "application/problem+json"
3878
+ });
3879
+ accessRouterResponseHandler.errorMessageProvider = function(error) {
3880
+ const errorLike = error;
3881
+ console.error(error);
3882
+ return errorLike.message || errorLike._message || String(error);
3883
+ };
3884
+
3885
+ // src/plugins.ts
3886
+ function permissionsPlugin(schema, options) {
3887
+ if (!options?.modelName) return;
3888
+ schema.virtual(options?.virtualPermissionField || "permissions").get(function() {
3889
+ const docPermissionField = getModelOption(options.modelName, "documentPermissionField");
3890
+ return this._doc[docPermissionField];
3891
+ });
3892
+ }
3893
+
3894
+ // src/index.ts
3895
+ var wtt = macl;
3896
+ wtt.createRouter = function(modelName, options) {
3897
+ return isUndefined4(options) ? new RootRouter(modelName) : new ModelRouter(modelName, options);
3898
+ };
3899
+ wtt.createDataRouter = function(dataName, options) {
3900
+ return new DataRouter(dataName, options);
3901
+ };
3902
+ wtt.set = function(keyOrOptions, value) {
3903
+ if (arguments.length === 2 && isString11(keyOrOptions)) {
3904
+ return setGlobalOption(keyOrOptions, value);
3905
+ }
3906
+ if (arguments.length === 1 && isPlainObject10(keyOrOptions)) {
3907
+ return setGlobalOptions(keyOrOptions);
3908
+ }
3909
+ };
3910
+ wtt.setGlobalOptions = setGlobalOptions;
3911
+ wtt.setGlobalOption = setGlobalOption;
3912
+ wtt.getGlobalOptions = getGlobalOptions;
3913
+ wtt.getGlobalOption = getGlobalOption;
3914
+ wtt.setModelOptions = setModelOptions;
3915
+ wtt.setModelOption = setModelOption;
3916
+ wtt.getModelOptions = getModelOptions;
3917
+ wtt.getModelOption = getModelOption;
3918
+ wtt.setDefaultModelOptions = setDefaultModelOptions;
3919
+ wtt.setDefaultModelOption = setDefaultModelOption;
3920
+ wtt.getDefaultModelOptions = getDefaultModelOptions;
3921
+ wtt.getDefaultModelOption = getDefaultModelOption;
3922
+ wtt.RootRouter = RootRouter;
3923
+ wtt.ModelRouter = ModelRouter;
3924
+ wtt.DataRouter = DataRouter;
3925
+ var index_default = wtt;
3926
+ export {
3927
+ CORE,
3928
+ Codes,
3929
+ CustomHeaders,
3930
+ DATA_MIDDLEWARE,
3931
+ FilterOperator,
3932
+ MIDDLEWARE,
3933
+ ModelRouter,
3934
+ PERMISSIONS,
3935
+ PERMISSION_KEYS,
3936
+ RootRouter,
3937
+ StatusCodes,
3938
+ advancedCreateBodySchema,
3939
+ advancedUpdateBodySchema,
3940
+ advancedUpsertBodySchema,
3941
+ countBodySchema,
3942
+ createBodySchema,
3943
+ dataListBodySchema,
3944
+ dataReadByIdBodySchema,
3945
+ dataReadFilterBodySchema,
3946
+ index_default as default,
3947
+ distinctBodySchema,
3948
+ getDefaultModelOption,
3949
+ getDefaultModelOptions,
3950
+ getGlobalOption,
3951
+ getGlobalOptions,
3952
+ getModelJsonSchema,
3953
+ getModelNames,
3954
+ getModelOption,
3955
+ getModelOptions,
3956
+ guard,
3957
+ listBodySchema,
3958
+ parseBody,
3959
+ parseBodyWithSchema,
3960
+ parseNestedBodyWithSchema,
3961
+ parsePathParam,
3962
+ parseQuery,
3963
+ permissionsPlugin,
3964
+ readByIdBodySchema,
3965
+ readFilterBodySchema,
3966
+ requestSchemas,
3967
+ rootQuerySchema,
3968
+ setDefaultModelOption,
3969
+ setDefaultModelOptions,
3970
+ setGlobalOption,
3971
+ setGlobalOptions,
3972
+ setModelOption,
3973
+ setModelOptions,
3974
+ subListBodySchema,
3975
+ subMutationBodySchema,
3976
+ subReadBodySchema,
3977
+ updateBodySchema,
3978
+ upsertBodySchema
3979
+ };