@atscript/moost-db 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +631 -123
- package/dist/index.d.cts +237 -24
- package/dist/index.d.mts +237 -24
- package/dist/index.mjs +614 -124
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -4,70 +4,6 @@ let _moostjs_event_http = require("@moostjs/event-http");
|
|
|
4
4
|
let moost = require("moost");
|
|
5
5
|
let _uniqu_url = require("@uniqu/url");
|
|
6
6
|
let _atscript_db = require("@atscript/db");
|
|
7
|
-
//#region src/decorators.ts
|
|
8
|
-
/**
|
|
9
|
-
* DI token under which the {@link AtscriptDbReadable} instance
|
|
10
|
-
* is exposed to the readable controller's constructor via `@Inject`.
|
|
11
|
-
*/
|
|
12
|
-
const READABLE_DEF = "__atscript_db_readable_def";
|
|
13
|
-
/**
|
|
14
|
-
* DI token under which the {@link AtscriptDbTable} instance
|
|
15
|
-
* is exposed to the controller's constructor via `@Inject`.
|
|
16
|
-
* Points to the same token as READABLE_DEF for backward compatibility.
|
|
17
|
-
*/
|
|
18
|
-
const TABLE_DEF = READABLE_DEF;
|
|
19
|
-
/**
|
|
20
|
-
* Combines the boilerplate needed to turn an {@link AsDbController}
|
|
21
|
-
* subclass into a fully wired HTTP controller for a given `@db.table` model.
|
|
22
|
-
*
|
|
23
|
-
* Internally applies three decorators:
|
|
24
|
-
* 1. **Provide** — registers the table instance under {@link TABLE_DEF}.
|
|
25
|
-
* 2. **Controller** — registers the class as a Moost HTTP controller
|
|
26
|
-
* with an optional route prefix. Defaults to `table.tableName`.
|
|
27
|
-
* 3. **Inherit** — copies metadata (routes, guards, etc.) from the
|
|
28
|
-
* parent class so they stay active in the derived controller.
|
|
29
|
-
*
|
|
30
|
-
* @param table The {@link AtscriptDbTable} instance for this controller.
|
|
31
|
-
* @param prefix Optional route prefix. Defaults to `table.tableName`.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* @TableController(usersTable)
|
|
36
|
-
* export class UsersController extends AsDbController<typeof UserModel> {}
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
const TableController = (table, prefix) => {
|
|
40
|
-
const resolvedPath = prefix || table.type.metadata.get("db.http.path");
|
|
41
|
-
return (0, moost.ApplyDecorators)((0, moost.Provide)(TABLE_DEF, () => table), (0, moost.Controller)(resolvedPath || table.tableName), (0, moost.Inherit)());
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* Combines the boilerplate needed to turn an {@link AsDbReadableController}
|
|
45
|
-
* subclass into a fully wired HTTP controller for a given `@db.view` or `@db.table` model.
|
|
46
|
-
*
|
|
47
|
-
* @param readable The {@link AtscriptDbReadable} instance (table or view).
|
|
48
|
-
* @param prefix Optional route prefix. Defaults to `readable.tableName`.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```ts
|
|
52
|
-
* @ReadableController(activeTasksView)
|
|
53
|
-
* export class ActiveTasksController extends AsDbReadableController<typeof ActiveTasks> {}
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
const ReadableController = (readable, prefix) => {
|
|
57
|
-
const resolvedPath = prefix || readable.type.metadata.get("db.http.path");
|
|
58
|
-
return (0, moost.ApplyDecorators)((0, moost.Provide)(READABLE_DEF, () => readable), (0, moost.Controller)(resolvedPath || readable.tableName), (0, moost.Inherit)());
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* Alias for {@link ReadableController} — use with view-backed controllers.
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```ts
|
|
65
|
-
* @ViewController(activeTasksView)
|
|
66
|
-
* export class ActiveTasksController extends AsDbReadableController<typeof ActiveTasks> {}
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
const ViewController = ReadableController;
|
|
70
|
-
//#endregion
|
|
71
7
|
//#region src/validation-interceptor.ts
|
|
72
8
|
const dbErrorCodeToStatus = { CONFLICT: 409 };
|
|
73
9
|
function transformValidationError(error, reply) {
|
|
@@ -178,18 +114,77 @@ var SelectControlDto = class {
|
|
|
178
114
|
(0, _atscript_typescript_utils.defineAnnotatedType)("object", SortControlDto).propPattern(/./, (0, _atscript_typescript_utils.defineAnnotatedType)("union").item((0, _atscript_typescript_utils.defineAnnotatedType)().designType("number").value(1).$type).item((0, _atscript_typescript_utils.defineAnnotatedType)().designType("number").value(-1).$type).$type);
|
|
179
115
|
(0, _atscript_typescript_utils.defineAnnotatedType)("object", SelectControlDto).propPattern(/./, (0, _atscript_typescript_utils.defineAnnotatedType)("union").item((0, _atscript_typescript_utils.defineAnnotatedType)().designType("number").value(1).$type).item((0, _atscript_typescript_utils.defineAnnotatedType)().designType("number").value(0).$type).$type);
|
|
180
116
|
//#endregion
|
|
117
|
+
//#region src/gate-utils.ts
|
|
118
|
+
/**
|
|
119
|
+
* Walks a Uniquery filter expression and returns the first field name that
|
|
120
|
+
* fails the `isAllowed` predicate, or `undefined` if every leaf field is
|
|
121
|
+
* allowed. Logical combinators (`$and`, `$or`, `$nor`, `$not`) are traversed;
|
|
122
|
+
* other `$`-prefixed keys are skipped.
|
|
123
|
+
*
|
|
124
|
+
* Shared by the DB readable's `@db.column.filterable` gate and the value-help
|
|
125
|
+
* controller's `@ui.dict.filterable` gate — only the predicate differs.
|
|
126
|
+
*/
|
|
127
|
+
function findFilterOffender(filter, isAllowed) {
|
|
128
|
+
if (!filter || typeof filter !== "object") return;
|
|
129
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
130
|
+
if (key === "$and" || key === "$or" || key === "$nor") {
|
|
131
|
+
if (Array.isArray(value)) for (const sub of value) {
|
|
132
|
+
const inner = findFilterOffender(sub, isAllowed);
|
|
133
|
+
if (inner) return inner;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (key === "$not") {
|
|
138
|
+
const inner = findFilterOffender(value, isAllowed);
|
|
139
|
+
if (inner) return inner;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (key.startsWith("$")) continue;
|
|
143
|
+
if (!isAllowed(key)) return key;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Walks a Uniquery `$sort` control (accepts string, string[], object, or
|
|
148
|
+
* array-of-{field: dir}) and returns the first field name that fails the
|
|
149
|
+
* `isAllowed` predicate, or `undefined` if every sort key is allowed.
|
|
150
|
+
*
|
|
151
|
+
* Shared by the DB readable's `@db.column.sortable` gate and the value-help
|
|
152
|
+
* controller's `@ui.dict.sortable` gate.
|
|
153
|
+
*/
|
|
154
|
+
function findSortOffender(sort, isAllowed) {
|
|
155
|
+
if (!sort) return void 0;
|
|
156
|
+
const check = (name) => isAllowed(name) ? void 0 : name;
|
|
157
|
+
if (typeof sort === "string") {
|
|
158
|
+
for (const part of sort.split(",")) {
|
|
159
|
+
const name = part.trim().replace(/^[-+]/, "").split(":")[0];
|
|
160
|
+
if (name) {
|
|
161
|
+
const bad = check(name);
|
|
162
|
+
if (bad) return bad;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(sort)) {
|
|
168
|
+
for (const entry of sort) if (typeof entry === "string") {
|
|
169
|
+
const bad = check(entry.replace(/^[-+]/, ""));
|
|
170
|
+
if (bad) return bad;
|
|
171
|
+
} else if (entry && typeof entry === "object") for (const name of Object.keys(entry)) {
|
|
172
|
+
const bad = check(name);
|
|
173
|
+
if (bad) return bad;
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (typeof sort === "object") for (const name of Object.keys(sort)) {
|
|
178
|
+
const bad = check(name);
|
|
179
|
+
if (bad) return bad;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
181
183
|
//#region \0@oxc-project+runtime@0.120.0/helpers/decorateMetadata.js
|
|
182
184
|
function __decorateMetadata(k, v) {
|
|
183
185
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
184
186
|
}
|
|
185
187
|
//#endregion
|
|
186
|
-
//#region \0@oxc-project+runtime@0.120.0/helpers/decorateParam.js
|
|
187
|
-
function __decorateParam(paramIndex, decorator) {
|
|
188
|
-
return function(target, key) {
|
|
189
|
-
decorator(target, key, paramIndex);
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
//#endregion
|
|
193
188
|
//#region \0@oxc-project+runtime@0.120.0/helpers/decorate.js
|
|
194
189
|
function __decorate(decorators, target, key, desc) {
|
|
195
190
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -198,24 +193,27 @@ function __decorate(decorators, target, key, desc) {
|
|
|
198
193
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
199
194
|
}
|
|
200
195
|
//#endregion
|
|
201
|
-
//#region src/as-
|
|
202
|
-
var _ref$
|
|
203
|
-
let
|
|
204
|
-
/**
|
|
205
|
-
|
|
196
|
+
//#region src/as-readable.controller.ts
|
|
197
|
+
var _ref$4;
|
|
198
|
+
let AsReadableController = class AsReadableController {
|
|
199
|
+
/** The Atscript interface this controller serves. */
|
|
200
|
+
boundType;
|
|
201
|
+
/** Short human-readable name for logging (usually the table/source name). */
|
|
202
|
+
controllerName;
|
|
206
203
|
/** Application-scoped logger. */
|
|
207
204
|
logger;
|
|
208
|
-
/** Cached serialized type definition (lazy, computed on first access). */
|
|
209
|
-
_serializedType;
|
|
210
205
|
/** Moost application instance. */
|
|
211
206
|
app;
|
|
207
|
+
/** Cached serialized type definition (lazy, computed on first access). */
|
|
208
|
+
_serializedType;
|
|
212
209
|
/** Cached full meta response (computed lazily on first meta() call). */
|
|
213
210
|
_metaResponse;
|
|
214
|
-
constructor(
|
|
215
|
-
this.
|
|
211
|
+
constructor(boundType, controllerName, app, kindTag = "readable") {
|
|
212
|
+
this.boundType = boundType;
|
|
213
|
+
this.controllerName = controllerName;
|
|
216
214
|
this.app = app;
|
|
217
|
-
this.logger = app.getLogger(`db [${
|
|
218
|
-
this.logger.info(`Initializing ${
|
|
215
|
+
this.logger = app.getLogger(`db [${controllerName}]`);
|
|
216
|
+
this.logger.info(`Initializing ${kindTag} controller`);
|
|
219
217
|
this._resolveHttpPath();
|
|
220
218
|
try {
|
|
221
219
|
const p = this.init();
|
|
@@ -236,12 +234,12 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
236
234
|
if (!prefix) prefix = (this.app.getControllersOverview?.()?.find((o) => o.type === this.constructor))?.computedPrefix;
|
|
237
235
|
if (prefix) {
|
|
238
236
|
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
239
|
-
this.
|
|
237
|
+
this.boundType.metadata.set("db.http.path", prefix);
|
|
240
238
|
}
|
|
241
239
|
}
|
|
242
|
-
/** Lazily serializes the type (after all controllers have set
|
|
240
|
+
/** Lazily serializes the bound type (after all controllers have set @db.http.path). */
|
|
243
241
|
getSerializedType() {
|
|
244
|
-
if (!this._serializedType) this._serializedType = (0, _atscript_typescript_utils.serializeAnnotatedType)(this.
|
|
242
|
+
if (!this._serializedType) this._serializedType = (0, _atscript_typescript_utils.serializeAnnotatedType)(this.boundType, this.getSerializeOptions());
|
|
245
243
|
return this._serializedType;
|
|
246
244
|
}
|
|
247
245
|
/**
|
|
@@ -250,13 +248,23 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
250
248
|
init() {}
|
|
251
249
|
/**
|
|
252
250
|
* Returns serialization options for the `/meta` endpoint's type field.
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
251
|
+
*
|
|
252
|
+
* `refDepth: 0.5` is intentionally static — independent of `@db.depth.limit`
|
|
253
|
+
* (which is a security guard on nested writes, not a serialization policy).
|
|
254
|
+
* The shallow shape emits `{ field, type: { id, metadata } }` for every FK,
|
|
255
|
+
* which carries the target's `db.http.path` so clients can resolve value-help
|
|
256
|
+
* URLs and lazy-fetch target `/meta` when deeper structure is needed. Nav
|
|
257
|
+
* props (`@db.rel.from` / `@db.rel.to` / `@db.rel.via`) are not `.ref` nodes
|
|
258
|
+
* and always expand fully regardless of `refDepth` — the write-payload shape
|
|
259
|
+
* clients need is unaffected.
|
|
260
|
+
*
|
|
261
|
+
* Annotation whitelist: keeps `meta.*`, `expect.*`, and `db.rel.*`; strips
|
|
262
|
+
* other `db.*` (table, column, index, default, etc.). Override in subclass
|
|
263
|
+
* to customise.
|
|
256
264
|
*/
|
|
257
265
|
getSerializeOptions() {
|
|
258
266
|
return {
|
|
259
|
-
refDepth:
|
|
267
|
+
refDepth: .5,
|
|
260
268
|
processAnnotation: ({ key, value }) => {
|
|
261
269
|
if (key.startsWith("meta.") || key.startsWith("expect.") || key.startsWith("db.rel.")) return {
|
|
262
270
|
key,
|
|
@@ -276,7 +284,7 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
276
284
|
}
|
|
277
285
|
/**
|
|
278
286
|
* Whether this controller is read-only (no write endpoints).
|
|
279
|
-
* Returns `true`
|
|
287
|
+
* Returns `true` by default; {@link AsDbController} overrides to `false`.
|
|
280
288
|
*/
|
|
281
289
|
_isReadOnly() {
|
|
282
290
|
return true;
|
|
@@ -303,7 +311,7 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
303
311
|
validateInsights(insights) {
|
|
304
312
|
for (const [key] of insights) {
|
|
305
313
|
if (key === "*") continue;
|
|
306
|
-
if (!this.
|
|
314
|
+
if (!this.hasField(key)) return `Unknown field "${key}"`;
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
validateParsed(parsed, type) {
|
|
@@ -313,6 +321,190 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
313
321
|
const insightsError = this.validateInsights(parsed.insights);
|
|
314
322
|
if (insightsError) return new _moostjs_event_http.HttpError(400, insightsError);
|
|
315
323
|
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Shared filter/sort/search gate check. Subclasses assemble a {@link ReadableGates}
|
|
327
|
+
* config per request (or once in the constructor when static) and call this to
|
|
328
|
+
* get a uniform HTTP 400 response for any offending field/control.
|
|
329
|
+
*/
|
|
330
|
+
checkGates(filter, controls, gates) {
|
|
331
|
+
if (gates.filter) {
|
|
332
|
+
const bad = findFilterOffender(filter, gates.filter.predicate);
|
|
333
|
+
if (bad) return new _moostjs_event_http.HttpError(400, `Filtering on field "${bad}" is not permitted — add ${gates.filter.annotation} to enable.`);
|
|
334
|
+
}
|
|
335
|
+
if (gates.sort) {
|
|
336
|
+
const bad = findSortOffender(controls.$sort, gates.sort.predicate);
|
|
337
|
+
if (bad) return new _moostjs_event_http.HttpError(400, `Sorting on field "${bad}" is not permitted — add ${gates.sort.annotation} to enable.`);
|
|
338
|
+
}
|
|
339
|
+
if (gates.search && controls.$search && !gates.search.allowed) return new _moostjs_event_http.HttpError(400, gates.search.rejectionMessage);
|
|
340
|
+
}
|
|
341
|
+
parseQueryString(url) {
|
|
342
|
+
const idx = url.indexOf("?");
|
|
343
|
+
return (0, _uniqu_url.parseUrl)(idx >= 0 ? url.slice(idx + 1) : "");
|
|
344
|
+
}
|
|
345
|
+
async returnOne(result) {
|
|
346
|
+
const item = await result;
|
|
347
|
+
if (!item) return new _moostjs_event_http.HttpError(404);
|
|
348
|
+
return item;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* **GET /meta** — returns the bound interface's metadata envelope.
|
|
352
|
+
*
|
|
353
|
+
* Base implementation delegates to {@link buildMetaResponse}, which subclasses
|
|
354
|
+
* override to add source-specific fields (relations, searchable flags, etc.).
|
|
355
|
+
* The response is cached on the instance; async overrides must cache any
|
|
356
|
+
* extra enrichment themselves.
|
|
357
|
+
*/
|
|
358
|
+
async meta() {
|
|
359
|
+
if (this._metaResponse) return this._metaResponse;
|
|
360
|
+
const response = await this.buildMetaResponse();
|
|
361
|
+
this._metaResponse = response;
|
|
362
|
+
return response;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Builds the `/meta` payload. Override in subclasses to populate source-specific
|
|
366
|
+
* fields. Defaults return a minimal envelope with the serialized type and the
|
|
367
|
+
* read-only flag; value-help dicts populate their capability hints here.
|
|
368
|
+
*/
|
|
369
|
+
async buildMetaResponse() {
|
|
370
|
+
return {
|
|
371
|
+
searchable: false,
|
|
372
|
+
vectorSearchable: false,
|
|
373
|
+
searchIndexes: [],
|
|
374
|
+
primaryKeys: [],
|
|
375
|
+
readOnly: this._isReadOnly(),
|
|
376
|
+
relations: [],
|
|
377
|
+
fields: {},
|
|
378
|
+
type: this.getSerializedType()
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
__decorate([
|
|
383
|
+
(0, _moostjs_event_http.Get)("meta"),
|
|
384
|
+
__decorateMetadata("design:type", Function),
|
|
385
|
+
__decorateMetadata("design:paramtypes", []),
|
|
386
|
+
__decorateMetadata("design:returntype", Promise)
|
|
387
|
+
], AsReadableController.prototype, "meta", null);
|
|
388
|
+
AsReadableController = __decorate([UseValidationErrorTransform(), __decorateMetadata("design:paramtypes", [
|
|
389
|
+
Object,
|
|
390
|
+
String,
|
|
391
|
+
typeof (_ref$4 = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref$4 : Object,
|
|
392
|
+
Object
|
|
393
|
+
])], AsReadableController);
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/decorators.ts
|
|
396
|
+
/**
|
|
397
|
+
* DI token under which the {@link AtscriptDbReadable} instance
|
|
398
|
+
* is exposed to the readable controller's constructor via `@Inject`.
|
|
399
|
+
*/
|
|
400
|
+
const READABLE_DEF = "__atscript_db_readable_def";
|
|
401
|
+
/**
|
|
402
|
+
* DI token under which the {@link AtscriptDbTable} instance
|
|
403
|
+
* is exposed to the controller's constructor via `@Inject`.
|
|
404
|
+
* Points to the same token as READABLE_DEF for backward compatibility.
|
|
405
|
+
*/
|
|
406
|
+
const TABLE_DEF = READABLE_DEF;
|
|
407
|
+
/**
|
|
408
|
+
* Combines the boilerplate needed to turn an {@link AsDbController}
|
|
409
|
+
* subclass into a fully wired HTTP controller for a given `@db.table` model.
|
|
410
|
+
*
|
|
411
|
+
* Internally applies three decorators:
|
|
412
|
+
* 1. **Provide** — registers the table instance under {@link TABLE_DEF}.
|
|
413
|
+
* 2. **Controller** — registers the class as a Moost HTTP controller
|
|
414
|
+
* with an optional route prefix. Defaults to `table.tableName`.
|
|
415
|
+
* 3. **Inherit** — copies metadata (routes, guards, etc.) from the
|
|
416
|
+
* parent class so they stay active in the derived controller.
|
|
417
|
+
*
|
|
418
|
+
* @param table The {@link AtscriptDbTable} instance for this controller.
|
|
419
|
+
* @param prefix Optional route prefix. Defaults to `table.tableName`.
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```ts
|
|
423
|
+
* @TableController(usersTable)
|
|
424
|
+
* export class UsersController extends AsDbController<typeof UserModel> {}
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
const TableController = (table, prefix) => {
|
|
428
|
+
const resolvedPath = prefix || table.type.metadata.get("db.http.path");
|
|
429
|
+
return (0, moost.ApplyDecorators)((0, moost.Provide)(TABLE_DEF, () => table), (0, moost.Controller)(resolvedPath || table.tableName), (0, moost.Inherit)());
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Combines the boilerplate needed to turn an {@link AsDbReadableController}
|
|
433
|
+
* subclass into a fully wired HTTP controller for a given `@db.view` or `@db.table` model.
|
|
434
|
+
*
|
|
435
|
+
* @param readable The {@link AtscriptDbReadable} instance (table or view).
|
|
436
|
+
* @param prefix Optional route prefix. Defaults to `readable.tableName`.
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* @ReadableController(activeTasksView)
|
|
441
|
+
* export class ActiveTasksController extends AsDbReadableController<typeof ActiveTasks> {}
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
const ReadableController = (readable, prefix) => {
|
|
445
|
+
const resolvedPath = prefix || readable.type.metadata.get("db.http.path");
|
|
446
|
+
return (0, moost.ApplyDecorators)((0, moost.Provide)(READABLE_DEF, () => readable), (0, moost.Controller)(resolvedPath || readable.tableName), (0, moost.Inherit)());
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Alias for {@link ReadableController} — use with view-backed controllers.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* @ViewController(activeTasksView)
|
|
454
|
+
* export class ActiveTasksController extends AsDbReadableController<typeof ActiveTasks> {}
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
const ViewController = ReadableController;
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region \0@oxc-project+runtime@0.120.0/helpers/decorateParam.js
|
|
460
|
+
function __decorateParam(paramIndex, decorator) {
|
|
461
|
+
return function(target, key) {
|
|
462
|
+
decorator(target, key, paramIndex);
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/as-db-readable.controller.ts
|
|
467
|
+
var _ref$3, _ref2$2;
|
|
468
|
+
let AsDbReadableController = class AsDbReadableController extends AsReadableController {
|
|
469
|
+
/** Reference to the underlying readable (table or view). */
|
|
470
|
+
readable;
|
|
471
|
+
_gates;
|
|
472
|
+
constructor(readable, app) {
|
|
473
|
+
super(readable.type, readable.tableName, app, readable.isView ? "view" : "table");
|
|
474
|
+
this.readable = readable;
|
|
475
|
+
this._gates = this._buildGates();
|
|
476
|
+
}
|
|
477
|
+
_buildGates() {
|
|
478
|
+
const meta = this.readable.type.metadata;
|
|
479
|
+
const gates = {};
|
|
480
|
+
if (meta.get("db.table.filterable") === "manual") {
|
|
481
|
+
const allowed = this._collectAnnotated("db.column.filterable");
|
|
482
|
+
gates.filter = {
|
|
483
|
+
predicate: (f) => allowed.has(f),
|
|
484
|
+
annotation: "@db.column.filterable"
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (meta.get("db.table.sortable") === "manual") {
|
|
488
|
+
const allowed = this._collectAnnotated("db.column.sortable");
|
|
489
|
+
gates.sort = {
|
|
490
|
+
predicate: (f) => allowed.has(f),
|
|
491
|
+
annotation: "@db.column.sortable"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return gates;
|
|
495
|
+
}
|
|
496
|
+
_collectAnnotated(annotation) {
|
|
497
|
+
const out = /* @__PURE__ */ new Set();
|
|
498
|
+
for (const [path, entry] of this.readable.flatMap) if (entry.metadata.has(annotation)) out.add(path);
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
hasField(path) {
|
|
502
|
+
return this.readable.flatMap.has(path);
|
|
503
|
+
}
|
|
504
|
+
/** Validates $with relations against the readable. */
|
|
505
|
+
validateParsed(parsed, type) {
|
|
506
|
+
const baseError = super.validateParsed(parsed, type);
|
|
507
|
+
if (baseError) return baseError;
|
|
316
508
|
const withRelations = parsed.controls.$with;
|
|
317
509
|
if (withRelations?.length) {
|
|
318
510
|
const relations = this.readable.relations;
|
|
@@ -348,15 +540,6 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
348
540
|
transformProjection(projection) {
|
|
349
541
|
return projection;
|
|
350
542
|
}
|
|
351
|
-
parseQueryString(url) {
|
|
352
|
-
const idx = url.indexOf("?");
|
|
353
|
-
return (0, _uniqu_url.parseUrl)(idx >= 0 ? url.slice(idx + 1) : "");
|
|
354
|
-
}
|
|
355
|
-
async returnOne(result) {
|
|
356
|
-
const item = await result;
|
|
357
|
-
if (!item) return new _moostjs_event_http.HttpError(404);
|
|
358
|
-
return item;
|
|
359
|
-
}
|
|
360
543
|
/**
|
|
361
544
|
* Extracts a composite identifier object from query params.
|
|
362
545
|
* Tries composite primary key first, then compound unique indexes.
|
|
@@ -411,6 +594,8 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
411
594
|
}
|
|
412
595
|
const error = this.validateParsed(parsed, "query");
|
|
413
596
|
if (error) return error;
|
|
597
|
+
const gateError = this.checkGates(parsed.filter, controls, this._gates);
|
|
598
|
+
if (gateError) return gateError;
|
|
414
599
|
const [filter, select] = await Promise.all([this.transformFilter(parsed.filter), this.transformProjection(controls.$select)]);
|
|
415
600
|
if (controls.$count) return this.readable.count({
|
|
416
601
|
filter,
|
|
@@ -448,6 +633,8 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
448
633
|
const error = this.validateParsed(parsed, "pages");
|
|
449
634
|
if (error) return error;
|
|
450
635
|
const controls = parsed.controls;
|
|
636
|
+
const gateError = this.checkGates(parsed.filter, controls, this._gates);
|
|
637
|
+
if (gateError) return gateError;
|
|
451
638
|
const page = Math.max(Number(controls.$page || 1), 1);
|
|
452
639
|
const size = Math.max(Number(controls.$size || 10), 1);
|
|
453
640
|
const skip = (page - 1) * size;
|
|
@@ -514,28 +701,31 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
514
701
|
/**
|
|
515
702
|
* **GET /meta** — returns table/view metadata for UI.
|
|
516
703
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
* their own enrichment if needed.
|
|
704
|
+
* Overrides the base's minimal envelope to add relations, searchable flags,
|
|
705
|
+
* vector-searchable flags, field-descriptor-derived filter/sort hints, and
|
|
706
|
+
* the configured primary keys.
|
|
521
707
|
*/
|
|
522
|
-
|
|
523
|
-
if (this._metaResponse) return this._metaResponse;
|
|
708
|
+
async buildMetaResponse() {
|
|
524
709
|
const relations = [];
|
|
525
710
|
for (const [name, rel] of this.readable.relations) relations.push({
|
|
526
711
|
name,
|
|
527
712
|
direction: rel.direction,
|
|
528
713
|
isArray: rel.isArray
|
|
529
714
|
});
|
|
715
|
+
const filterableMode = this.readable.type.metadata.get("db.table.filterable") === "manual";
|
|
716
|
+
const sortableMode = this.readable.type.metadata.get("db.table.sortable") === "manual";
|
|
530
717
|
const fields = {};
|
|
531
718
|
for (const fd of this.readable.fieldDescriptors) {
|
|
532
719
|
if (fd.ignored) continue;
|
|
720
|
+
const annotations = fd.type?.metadata;
|
|
721
|
+
const annotatedFilterable = annotations?.has("db.column.filterable") ?? false;
|
|
722
|
+
const annotatedSortable = annotations?.has("db.column.sortable") ?? false;
|
|
533
723
|
fields[fd.path] = {
|
|
534
|
-
sortable: !!fd.isIndexed,
|
|
535
|
-
filterable: true
|
|
724
|
+
sortable: sortableMode ? annotatedSortable : !!fd.isIndexed,
|
|
725
|
+
filterable: filterableMode ? annotatedFilterable : true
|
|
536
726
|
};
|
|
537
727
|
}
|
|
538
|
-
|
|
728
|
+
return {
|
|
539
729
|
searchable: this.readable.isSearchable(),
|
|
540
730
|
vectorSearchable: this.readable.isVectorSearchable(),
|
|
541
731
|
searchIndexes: this.readable.getSearchIndexes(),
|
|
@@ -545,8 +735,6 @@ let AsDbReadableController = class AsDbReadableController {
|
|
|
545
735
|
fields,
|
|
546
736
|
type: this.getSerializedType()
|
|
547
737
|
};
|
|
548
|
-
this._metaResponse = response;
|
|
549
|
-
return response;
|
|
550
738
|
}
|
|
551
739
|
};
|
|
552
740
|
__decorate([
|
|
@@ -576,23 +764,17 @@ __decorate([
|
|
|
576
764
|
__decorateParam(0, (0, _moostjs_event_http.Query)()),
|
|
577
765
|
__decorateParam(1, (0, _moostjs_event_http.Url)()),
|
|
578
766
|
__decorateMetadata("design:type", Function),
|
|
579
|
-
__decorateMetadata("design:paramtypes", [typeof (_ref2$
|
|
767
|
+
__decorateMetadata("design:paramtypes", [typeof (_ref2$2 = typeof Record !== "undefined" && Record) === "function" ? _ref2$2 : Object, String]),
|
|
580
768
|
__decorateMetadata("design:returntype", Promise)
|
|
581
769
|
], AsDbReadableController.prototype, "getOneComposite", null);
|
|
582
|
-
__decorate([
|
|
583
|
-
(0, _moostjs_event_http.Get)("meta"),
|
|
584
|
-
__decorateMetadata("design:type", Function),
|
|
585
|
-
__decorateMetadata("design:paramtypes", []),
|
|
586
|
-
__decorateMetadata("design:returntype", Object)
|
|
587
|
-
], AsDbReadableController.prototype, "meta", null);
|
|
588
770
|
AsDbReadableController = __decorate([
|
|
589
|
-
|
|
771
|
+
(0, moost.Inherit)(),
|
|
590
772
|
__decorateParam(0, (0, moost.Inject)(READABLE_DEF)),
|
|
591
|
-
__decorateMetadata("design:paramtypes", [Object, typeof (_ref$
|
|
773
|
+
__decorateMetadata("design:paramtypes", [Object, typeof (_ref$3 = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref$3 : Object])
|
|
592
774
|
], AsDbReadableController);
|
|
593
775
|
//#endregion
|
|
594
776
|
//#region src/as-db.controller.ts
|
|
595
|
-
var _ref, _ref2;
|
|
777
|
+
var _ref$2, _ref2$1;
|
|
596
778
|
let AsDbController = class AsDbController extends AsDbReadableController {
|
|
597
779
|
/** Reference to the underlying table (typed for write access). */
|
|
598
780
|
get table() {
|
|
@@ -713,15 +895,323 @@ __decorate([
|
|
|
713
895
|
(0, _moostjs_event_http.Delete)(""),
|
|
714
896
|
__decorateParam(0, (0, _moostjs_event_http.Query)()),
|
|
715
897
|
__decorateMetadata("design:type", Function),
|
|
716
|
-
__decorateMetadata("design:paramtypes", [typeof (_ref2 = typeof Record !== "undefined" && Record) === "function" ? _ref2 : Object]),
|
|
898
|
+
__decorateMetadata("design:paramtypes", [typeof (_ref2$1 = typeof Record !== "undefined" && Record) === "function" ? _ref2$1 : Object]),
|
|
717
899
|
__decorateMetadata("design:returntype", Promise)
|
|
718
900
|
], AsDbController.prototype, "removeComposite", null);
|
|
719
901
|
AsDbController = __decorate([
|
|
720
902
|
(0, moost.Inherit)(),
|
|
721
903
|
__decorateParam(0, (0, moost.Inject)(TABLE_DEF)),
|
|
722
|
-
__decorateMetadata("design:paramtypes", [Object, typeof (_ref = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref : Object])
|
|
904
|
+
__decorateMetadata("design:paramtypes", [Object, typeof (_ref$2 = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref$2 : Object])
|
|
723
905
|
], AsDbController);
|
|
724
906
|
//#endregion
|
|
907
|
+
//#region src/as-value-help.controller.ts
|
|
908
|
+
var _ref$1, _ref2;
|
|
909
|
+
let AsValueHelpController = class AsValueHelpController extends AsReadableController {
|
|
910
|
+
/** Per-prop metadata map of the bound interface; eagerly built once. */
|
|
911
|
+
fieldMeta;
|
|
912
|
+
/**
|
|
913
|
+
* Fields that participate in `$search` by default. Populated from
|
|
914
|
+
* `@ui.dict.searchable`:
|
|
915
|
+
* - If any prop carries `@ui.dict.searchable`, only those props are here.
|
|
916
|
+
* - Else if the interface carries `@ui.dict.searchable`, every `string`-typed prop is here.
|
|
917
|
+
* - Else every `string`-typed prop is here (hint is absent — default to all strings).
|
|
918
|
+
*/
|
|
919
|
+
searchableFields;
|
|
920
|
+
/** The `@meta.id` field name on the bound interface, if any. */
|
|
921
|
+
primaryKey;
|
|
922
|
+
constructor(boundType, controllerName, app) {
|
|
923
|
+
super(boundType, controllerName, app, "value-help");
|
|
924
|
+
const fieldMeta = /* @__PURE__ */ new Map();
|
|
925
|
+
const explicitlySearchable = [];
|
|
926
|
+
const stringProps = [];
|
|
927
|
+
let primaryKey;
|
|
928
|
+
const interfaceSearchable = boundType.metadata.has("ui.dict.searchable");
|
|
929
|
+
const asObj = boundType.type;
|
|
930
|
+
if (asObj?.props) for (const [name, prop] of asObj.props) {
|
|
931
|
+
const meta = prop.metadata;
|
|
932
|
+
fieldMeta.set(name, meta);
|
|
933
|
+
if (!primaryKey && meta.has("meta.id")) primaryKey = name;
|
|
934
|
+
if (prop.type.designType === "string") stringProps.push(name);
|
|
935
|
+
if (meta.has("ui.dict.searchable")) explicitlySearchable.push(name);
|
|
936
|
+
}
|
|
937
|
+
this.fieldMeta = fieldMeta;
|
|
938
|
+
this.primaryKey = primaryKey;
|
|
939
|
+
this.searchableFields = explicitlySearchable.length > 0 ? explicitlySearchable : interfaceSearchable ? stringProps : stringProps;
|
|
940
|
+
}
|
|
941
|
+
hasField(path) {
|
|
942
|
+
return this.fieldMeta.has(path);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* **GET /query** — returns an array of matched rows (up to `$limit`).
|
|
946
|
+
*/
|
|
947
|
+
async runQuery(url) {
|
|
948
|
+
const parsed = this.parseQueryString(url);
|
|
949
|
+
const validateError = this.validateParsed(parsed, "query");
|
|
950
|
+
if (validateError) return validateError;
|
|
951
|
+
return (await this.query({
|
|
952
|
+
filter: parsed.filter,
|
|
953
|
+
controls: parsed.controls
|
|
954
|
+
})).data;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* **GET /pages** — paginated row window plus total count.
|
|
958
|
+
*/
|
|
959
|
+
async runPages(url) {
|
|
960
|
+
const parsed = this.parseQueryString(url);
|
|
961
|
+
const validateError = this.validateParsed(parsed, "pages");
|
|
962
|
+
if (validateError) return validateError;
|
|
963
|
+
const controls = parsed.controls;
|
|
964
|
+
const page = Math.max(Number(controls.$page || 1), 1);
|
|
965
|
+
const size = Math.max(Number(controls.$size || 10), 1);
|
|
966
|
+
const skip = (page - 1) * size;
|
|
967
|
+
const result = await this.query({
|
|
968
|
+
filter: parsed.filter,
|
|
969
|
+
controls: {
|
|
970
|
+
...controls,
|
|
971
|
+
$skip: skip,
|
|
972
|
+
$limit: size
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
data: result.data,
|
|
977
|
+
page,
|
|
978
|
+
itemsPerPage: size,
|
|
979
|
+
pages: Math.ceil(result.count / size),
|
|
980
|
+
count: result.count
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* **GET /one/:id** — retrieves a single row by primary key.
|
|
985
|
+
*/
|
|
986
|
+
async runGetOne(id) {
|
|
987
|
+
return this.returnOne(this.getOne(id));
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* **GET /one?<pk>=<val>** — retrieves a single row by PK query param (fallback).
|
|
991
|
+
*/
|
|
992
|
+
async runGetOneComposite(query) {
|
|
993
|
+
const pk = this.primaryKey;
|
|
994
|
+
if (!pk) return new _moostjs_event_http.HttpError(400, "No primary key (@meta.id) on value-help interface");
|
|
995
|
+
const id = query[pk];
|
|
996
|
+
if (id === void 0) return new _moostjs_event_http.HttpError(400, `Missing PK field "${pk}"`);
|
|
997
|
+
return this.returnOne(this.getOne(id));
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Meta response surfaces `@ui.dict.*` annotations as **hints** for the
|
|
1001
|
+
* client picker UI (which controls to render); the server does not enforce
|
|
1002
|
+
* these flags at request time.
|
|
1003
|
+
*/
|
|
1004
|
+
async buildMetaResponse() {
|
|
1005
|
+
const fields = {};
|
|
1006
|
+
for (const [path, meta] of this.fieldMeta) fields[path] = {
|
|
1007
|
+
sortable: meta.has("ui.dict.sortable"),
|
|
1008
|
+
filterable: meta.has("ui.dict.filterable")
|
|
1009
|
+
};
|
|
1010
|
+
return {
|
|
1011
|
+
searchable: this.searchableFields.length > 0,
|
|
1012
|
+
vectorSearchable: false,
|
|
1013
|
+
searchIndexes: [],
|
|
1014
|
+
primaryKeys: this.primaryKey ? [this.primaryKey] : [],
|
|
1015
|
+
readOnly: this._isReadOnly(),
|
|
1016
|
+
relations: [],
|
|
1017
|
+
fields,
|
|
1018
|
+
type: this.getSerializedType()
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
__decorate([
|
|
1023
|
+
(0, _moostjs_event_http.Get)("query"),
|
|
1024
|
+
__decorateParam(0, (0, _moostjs_event_http.Url)()),
|
|
1025
|
+
__decorateMetadata("design:type", Function),
|
|
1026
|
+
__decorateMetadata("design:paramtypes", [String]),
|
|
1027
|
+
__decorateMetadata("design:returntype", Promise)
|
|
1028
|
+
], AsValueHelpController.prototype, "runQuery", null);
|
|
1029
|
+
__decorate([
|
|
1030
|
+
(0, _moostjs_event_http.Get)("pages"),
|
|
1031
|
+
__decorateParam(0, (0, _moostjs_event_http.Url)()),
|
|
1032
|
+
__decorateMetadata("design:type", Function),
|
|
1033
|
+
__decorateMetadata("design:paramtypes", [String]),
|
|
1034
|
+
__decorateMetadata("design:returntype", Promise)
|
|
1035
|
+
], AsValueHelpController.prototype, "runPages", null);
|
|
1036
|
+
__decorate([
|
|
1037
|
+
(0, _moostjs_event_http.Get)("one/:id"),
|
|
1038
|
+
__decorateParam(0, (0, moost.Param)("id")),
|
|
1039
|
+
__decorateMetadata("design:type", Function),
|
|
1040
|
+
__decorateMetadata("design:paramtypes", [String]),
|
|
1041
|
+
__decorateMetadata("design:returntype", Promise)
|
|
1042
|
+
], AsValueHelpController.prototype, "runGetOne", null);
|
|
1043
|
+
__decorate([
|
|
1044
|
+
(0, _moostjs_event_http.Get)("one"),
|
|
1045
|
+
__decorateParam(0, (0, _moostjs_event_http.Query)()),
|
|
1046
|
+
__decorateMetadata("design:type", Function),
|
|
1047
|
+
__decorateMetadata("design:paramtypes", [typeof (_ref2 = typeof Record !== "undefined" && Record) === "function" ? _ref2 : Object]),
|
|
1048
|
+
__decorateMetadata("design:returntype", Promise)
|
|
1049
|
+
], AsValueHelpController.prototype, "runGetOneComposite", null);
|
|
1050
|
+
AsValueHelpController = __decorate([(0, moost.Inherit)(), __decorateMetadata("design:paramtypes", [
|
|
1051
|
+
Object,
|
|
1052
|
+
String,
|
|
1053
|
+
typeof (_ref$1 = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref$1 : Object
|
|
1054
|
+
])], AsValueHelpController);
|
|
1055
|
+
//#endregion
|
|
1056
|
+
//#region src/as-json-value-help.controller.ts
|
|
1057
|
+
var _ref;
|
|
1058
|
+
let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueHelpController {
|
|
1059
|
+
rows;
|
|
1060
|
+
_pkIndex;
|
|
1061
|
+
constructor(boundType, rows, app, controllerName) {
|
|
1062
|
+
const name = controllerName || boundType.metadata.get("db.table") || "value-help";
|
|
1063
|
+
super(boundType, name, app);
|
|
1064
|
+
this.rows = rows;
|
|
1065
|
+
if (this.primaryKey) {
|
|
1066
|
+
const pk = this.primaryKey;
|
|
1067
|
+
const index = /* @__PURE__ */ new Map();
|
|
1068
|
+
for (const row of rows) index.set(String(row[pk]), row);
|
|
1069
|
+
this._pkIndex = index;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async query(controls) {
|
|
1073
|
+
let rows = this.rows;
|
|
1074
|
+
if (controls.filter && Object.keys(controls.filter).length > 0) rows = rows.filter((row) => matchFilter(row, controls.filter));
|
|
1075
|
+
const search = controls.controls.$search;
|
|
1076
|
+
if (search) {
|
|
1077
|
+
const needle = search.toLowerCase();
|
|
1078
|
+
const fields = this.searchableFields;
|
|
1079
|
+
rows = rows.filter((row) => {
|
|
1080
|
+
for (const field of fields) {
|
|
1081
|
+
const v = row[field];
|
|
1082
|
+
if (typeof v === "string" && v.toLowerCase().includes(needle)) return true;
|
|
1083
|
+
}
|
|
1084
|
+
return false;
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (controls.controls.$sort) rows = sortRows(rows, controls.controls.$sort);
|
|
1088
|
+
const total = rows.length;
|
|
1089
|
+
const skip = Math.max(0, Number(controls.controls.$skip ?? 0));
|
|
1090
|
+
const limit = Math.max(0, Number(controls.controls.$limit ?? total - skip));
|
|
1091
|
+
return {
|
|
1092
|
+
data: applySelect(rows.slice(skip, skip + limit), controls.controls.$select),
|
|
1093
|
+
count: total
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
async getOne(id) {
|
|
1097
|
+
return this._pkIndex?.get(String(id)) ?? null;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
AsJsonValueHelpController = __decorate([(0, moost.Inherit)(), __decorateMetadata("design:paramtypes", [
|
|
1101
|
+
Object,
|
|
1102
|
+
Array,
|
|
1103
|
+
typeof (_ref = typeof moost.Moost !== "undefined" && moost.Moost) === "function" ? _ref : Object,
|
|
1104
|
+
String
|
|
1105
|
+
])], AsJsonValueHelpController);
|
|
1106
|
+
function matchFilter(row, filter) {
|
|
1107
|
+
if (!filter || typeof filter !== "object") return true;
|
|
1108
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
1109
|
+
if (key === "$and") {
|
|
1110
|
+
if (!Array.isArray(value)) continue;
|
|
1111
|
+
if (!value.every((clause) => matchFilter(row, clause))) return false;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (key === "$or") {
|
|
1115
|
+
if (!Array.isArray(value)) continue;
|
|
1116
|
+
if (!value.some((clause) => matchFilter(row, clause))) return false;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (key === "$nor") {
|
|
1120
|
+
if (!Array.isArray(value)) continue;
|
|
1121
|
+
if (value.some((clause) => matchFilter(row, clause))) return false;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (key === "$not") {
|
|
1125
|
+
if (matchFilter(row, value)) return false;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (key.startsWith("$")) continue;
|
|
1129
|
+
const fieldValue = row[key];
|
|
1130
|
+
if (!matchFieldPredicate(fieldValue, value)) return false;
|
|
1131
|
+
}
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
function matchFieldPredicate(fieldValue, predicate) {
|
|
1135
|
+
if (predicate === null || typeof predicate !== "object" || Array.isArray(predicate)) return fieldValue === predicate;
|
|
1136
|
+
for (const [op, operand] of Object.entries(predicate)) switch (op) {
|
|
1137
|
+
case "$eq":
|
|
1138
|
+
if (fieldValue !== operand) return false;
|
|
1139
|
+
break;
|
|
1140
|
+
case "$ne":
|
|
1141
|
+
if (fieldValue === operand) return false;
|
|
1142
|
+
break;
|
|
1143
|
+
case "$in":
|
|
1144
|
+
if (!Array.isArray(operand) || !operand.includes(fieldValue)) return false;
|
|
1145
|
+
break;
|
|
1146
|
+
case "$nin":
|
|
1147
|
+
if (!Array.isArray(operand) || operand.includes(fieldValue)) return false;
|
|
1148
|
+
break;
|
|
1149
|
+
case "$gt":
|
|
1150
|
+
if (!(fieldValue > operand)) return false;
|
|
1151
|
+
break;
|
|
1152
|
+
case "$gte":
|
|
1153
|
+
if (!(fieldValue >= operand)) return false;
|
|
1154
|
+
break;
|
|
1155
|
+
case "$lt":
|
|
1156
|
+
if (!(fieldValue < operand)) return false;
|
|
1157
|
+
break;
|
|
1158
|
+
case "$lte":
|
|
1159
|
+
if (!(fieldValue <= operand)) return false;
|
|
1160
|
+
break;
|
|
1161
|
+
case "$regex": {
|
|
1162
|
+
const re = operand instanceof RegExp ? operand : new RegExp(String(operand));
|
|
1163
|
+
if (typeof fieldValue !== "string" || !re.test(fieldValue)) return false;
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
default: if (fieldValue !== operand) return false;
|
|
1167
|
+
}
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
function sortRows(rows, sort) {
|
|
1171
|
+
const keys = [];
|
|
1172
|
+
const push = (name, explicit) => {
|
|
1173
|
+
const clean = name.replace(/^[-+]/, "");
|
|
1174
|
+
const dir = explicit ?? (name.startsWith("-") ? -1 : 1);
|
|
1175
|
+
if (clean) keys.push({
|
|
1176
|
+
name: clean,
|
|
1177
|
+
dir
|
|
1178
|
+
});
|
|
1179
|
+
};
|
|
1180
|
+
if (typeof sort === "string") for (const part of sort.split(",")) {
|
|
1181
|
+
const trimmed = part.trim();
|
|
1182
|
+
if (!trimmed) continue;
|
|
1183
|
+
const [name, dir] = trimmed.split(":");
|
|
1184
|
+
push(name, dir === "desc" ? -1 : dir === "asc" ? 1 : void 0);
|
|
1185
|
+
}
|
|
1186
|
+
else if (Array.isArray(sort)) {
|
|
1187
|
+
for (const entry of sort) if (typeof entry === "string") push(entry);
|
|
1188
|
+
else if (entry && typeof entry === "object") for (const [name, d] of Object.entries(entry)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
1189
|
+
} else if (sort && typeof sort === "object") for (const [name, d] of Object.entries(sort)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
1190
|
+
if (keys.length === 0) return rows;
|
|
1191
|
+
const out = rows.slice();
|
|
1192
|
+
out.sort((a, b) => {
|
|
1193
|
+
for (const { name, dir } of keys) {
|
|
1194
|
+
const av = a[name];
|
|
1195
|
+
const bv = b[name];
|
|
1196
|
+
if (av === bv) continue;
|
|
1197
|
+
if (av === void 0 || av === null) return -1 * dir;
|
|
1198
|
+
if (bv === void 0 || bv === null) return 1 * dir;
|
|
1199
|
+
if (av < bv) return -1 * dir;
|
|
1200
|
+
if (av > bv) return 1 * dir;
|
|
1201
|
+
}
|
|
1202
|
+
return 0;
|
|
1203
|
+
});
|
|
1204
|
+
return out;
|
|
1205
|
+
}
|
|
1206
|
+
function applySelect(rows, select) {
|
|
1207
|
+
if (!select?.length) return rows;
|
|
1208
|
+
return rows.map((row) => {
|
|
1209
|
+
const out = {};
|
|
1210
|
+
for (const key of select) out[key] = row[key];
|
|
1211
|
+
return out;
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
//#endregion
|
|
725
1215
|
Object.defineProperty(exports, "AsDbController", {
|
|
726
1216
|
enumerable: true,
|
|
727
1217
|
get: function() {
|
|
@@ -734,6 +1224,24 @@ Object.defineProperty(exports, "AsDbReadableController", {
|
|
|
734
1224
|
return AsDbReadableController;
|
|
735
1225
|
}
|
|
736
1226
|
});
|
|
1227
|
+
Object.defineProperty(exports, "AsJsonValueHelpController", {
|
|
1228
|
+
enumerable: true,
|
|
1229
|
+
get: function() {
|
|
1230
|
+
return AsJsonValueHelpController;
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
Object.defineProperty(exports, "AsReadableController", {
|
|
1234
|
+
enumerable: true,
|
|
1235
|
+
get: function() {
|
|
1236
|
+
return AsReadableController;
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
Object.defineProperty(exports, "AsValueHelpController", {
|
|
1240
|
+
enumerable: true,
|
|
1241
|
+
get: function() {
|
|
1242
|
+
return AsValueHelpController;
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
737
1245
|
exports.READABLE_DEF = READABLE_DEF;
|
|
738
1246
|
exports.ReadableController = ReadableController;
|
|
739
1247
|
exports.TABLE_DEF = TABLE_DEF;
|