@atscript/moost-db 0.1.111 → 0.1.113
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 +57 -111
- package/dist/index.d.cts +40 -9
- package/dist/index.d.mts +40 -9
- package/dist/index.mjs +57 -111
- package/package.json +12 -11
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ 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
|
+
let _atscript_db_memory = require("@atscript/db-memory");
|
|
7
8
|
let _wooksjs_event_core = require("@wooksjs/event-core");
|
|
8
9
|
let _wooksjs_http_body = require("@wooksjs/http-body");
|
|
9
10
|
//#region src/validation-interceptor.ts
|
|
@@ -2052,7 +2053,10 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
2052
2053
|
}
|
|
2053
2054
|
async query(controls) {
|
|
2054
2055
|
let rows = this.rows;
|
|
2055
|
-
if (controls.filter && Object.keys(controls.filter).length > 0)
|
|
2056
|
+
if (controls.filter && Object.keys(controls.filter).length > 0) {
|
|
2057
|
+
const predicate = (0, _atscript_db_memory.buildMemoryPredicate)(controls.filter);
|
|
2058
|
+
rows = rows.filter((row) => predicate(row));
|
|
2059
|
+
}
|
|
2056
2060
|
const search = controls.controls.$search;
|
|
2057
2061
|
if (search) {
|
|
2058
2062
|
const needle = search.toLowerCase();
|
|
@@ -2065,18 +2069,68 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
2065
2069
|
return false;
|
|
2066
2070
|
});
|
|
2067
2071
|
}
|
|
2068
|
-
|
|
2072
|
+
const sort = this.normalizeSort(controls.controls.$sort);
|
|
2073
|
+
if (sort) rows = (0, _atscript_db_memory.sortRows)(rows, sort);
|
|
2069
2074
|
const total = rows.length;
|
|
2070
2075
|
const skip = Math.max(0, Number(controls.controls.$skip ?? 0));
|
|
2071
2076
|
const limit = Math.max(0, Number(controls.controls.$limit ?? total - skip));
|
|
2077
|
+
const page = rows.slice(skip, skip + limit);
|
|
2078
|
+
const projection = this.normalizeSelect(controls.controls.$select);
|
|
2072
2079
|
return {
|
|
2073
|
-
data:
|
|
2080
|
+
data: projection ? page.map((row) => (0, _atscript_db_memory.projectRow)(row, projection, { clone: false })) : page,
|
|
2074
2081
|
count: total
|
|
2075
2082
|
};
|
|
2076
2083
|
}
|
|
2077
2084
|
async getOne(id) {
|
|
2078
2085
|
return this._pkIndex?.get(String(id)) ?? null;
|
|
2079
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Ports the value-help `$sort` grammar to the shared engine's `{ field: 1 |
|
|
2089
|
+
* -1 }` shape (order-preserving for multi-key sorts). Accepts:
|
|
2090
|
+
* - a string `"field:asc,-other"` (comma-separated; `-` prefix or `:desc` → descending),
|
|
2091
|
+
* - an array of such strings / `{ field: dir }` objects,
|
|
2092
|
+
* - a `{ field: 'asc' | 'desc' | 1 | -1 }` object.
|
|
2093
|
+
* Returns `undefined` when nothing sortable was parsed (engine skips sorting).
|
|
2094
|
+
*/
|
|
2095
|
+
normalizeSort(sort) {
|
|
2096
|
+
const out = {};
|
|
2097
|
+
const push = (name, explicit) => {
|
|
2098
|
+
const clean = name.replace(/^[-+]/, "");
|
|
2099
|
+
const dir = explicit ?? (name.startsWith("-") ? -1 : 1);
|
|
2100
|
+
if (clean) out[clean] = dir;
|
|
2101
|
+
};
|
|
2102
|
+
if (typeof sort === "string") for (const part of sort.split(",")) {
|
|
2103
|
+
const trimmed = part.trim();
|
|
2104
|
+
if (!trimmed) continue;
|
|
2105
|
+
const [name, dir] = trimmed.split(":");
|
|
2106
|
+
push(name, dir === "desc" ? -1 : dir === "asc" ? 1 : void 0);
|
|
2107
|
+
}
|
|
2108
|
+
else {
|
|
2109
|
+
const entries = Array.isArray(sort) ? sort : sort && typeof sort === "object" ? [sort] : [];
|
|
2110
|
+
for (const entry of entries) if (typeof entry === "string") push(entry);
|
|
2111
|
+
else if (entry && typeof entry === "object") for (const [name, d] of Object.entries(entry)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2112
|
+
}
|
|
2113
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
|
|
2117
|
+
* 1 }` projection map:
|
|
2118
|
+
* - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
|
|
2119
|
+
* - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
|
|
2120
|
+
* - anything else / empty → `undefined` (no projection; whole rows returned).
|
|
2121
|
+
*/
|
|
2122
|
+
normalizeSelect(select) {
|
|
2123
|
+
if (Array.isArray(select)) {
|
|
2124
|
+
const out = {};
|
|
2125
|
+
for (const field of select) if (typeof field === "string" && field) out[field] = 1;
|
|
2126
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2127
|
+
}
|
|
2128
|
+
if (select && typeof select === "object") {
|
|
2129
|
+
const out = {};
|
|
2130
|
+
for (const [path, v] of Object.entries(select)) out[path] = v === 0 || v === false ? 0 : 1;
|
|
2131
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2080
2134
|
};
|
|
2081
2135
|
AsJsonValueHelpController = __decorate([(0, moost.Inherit)(), __decorateMetadata("design:paramtypes", [
|
|
2082
2136
|
Object,
|
|
@@ -2084,114 +2138,6 @@ AsJsonValueHelpController = __decorate([(0, moost.Inherit)(), __decorateMetadata
|
|
|
2084
2138
|
typeof moost.Moost === "undefined" ? Object : moost.Moost,
|
|
2085
2139
|
String
|
|
2086
2140
|
])], AsJsonValueHelpController);
|
|
2087
|
-
function matchFilter(row, filter) {
|
|
2088
|
-
if (!filter || typeof filter !== "object") return true;
|
|
2089
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
2090
|
-
if (key === "$and") {
|
|
2091
|
-
if (!Array.isArray(value)) continue;
|
|
2092
|
-
if (!value.every((clause) => matchFilter(row, clause))) return false;
|
|
2093
|
-
continue;
|
|
2094
|
-
}
|
|
2095
|
-
if (key === "$or") {
|
|
2096
|
-
if (!Array.isArray(value)) continue;
|
|
2097
|
-
if (!value.some((clause) => matchFilter(row, clause))) return false;
|
|
2098
|
-
continue;
|
|
2099
|
-
}
|
|
2100
|
-
if (key === "$nor") {
|
|
2101
|
-
if (!Array.isArray(value)) continue;
|
|
2102
|
-
if (value.some((clause) => matchFilter(row, clause))) return false;
|
|
2103
|
-
continue;
|
|
2104
|
-
}
|
|
2105
|
-
if (key === "$not") {
|
|
2106
|
-
if (matchFilter(row, value)) return false;
|
|
2107
|
-
continue;
|
|
2108
|
-
}
|
|
2109
|
-
if (key.startsWith("$")) continue;
|
|
2110
|
-
const fieldValue = row[key];
|
|
2111
|
-
if (!matchFieldPredicate(fieldValue, value)) return false;
|
|
2112
|
-
}
|
|
2113
|
-
return true;
|
|
2114
|
-
}
|
|
2115
|
-
function matchFieldPredicate(fieldValue, predicate) {
|
|
2116
|
-
if (predicate === null || typeof predicate !== "object" || Array.isArray(predicate)) return fieldValue === predicate;
|
|
2117
|
-
for (const [op, operand] of Object.entries(predicate)) switch (op) {
|
|
2118
|
-
case "$eq":
|
|
2119
|
-
if (fieldValue !== operand) return false;
|
|
2120
|
-
break;
|
|
2121
|
-
case "$ne":
|
|
2122
|
-
if (fieldValue === operand) return false;
|
|
2123
|
-
break;
|
|
2124
|
-
case "$in":
|
|
2125
|
-
if (!Array.isArray(operand) || !operand.includes(fieldValue)) return false;
|
|
2126
|
-
break;
|
|
2127
|
-
case "$nin":
|
|
2128
|
-
if (!Array.isArray(operand) || operand.includes(fieldValue)) return false;
|
|
2129
|
-
break;
|
|
2130
|
-
case "$gt":
|
|
2131
|
-
if (!(fieldValue > operand)) return false;
|
|
2132
|
-
break;
|
|
2133
|
-
case "$gte":
|
|
2134
|
-
if (!(fieldValue >= operand)) return false;
|
|
2135
|
-
break;
|
|
2136
|
-
case "$lt":
|
|
2137
|
-
if (!(fieldValue < operand)) return false;
|
|
2138
|
-
break;
|
|
2139
|
-
case "$lte":
|
|
2140
|
-
if (!(fieldValue <= operand)) return false;
|
|
2141
|
-
break;
|
|
2142
|
-
case "$regex": {
|
|
2143
|
-
const re = operand instanceof RegExp ? operand : new RegExp(String(operand));
|
|
2144
|
-
if (typeof fieldValue !== "string" || !re.test(fieldValue)) return false;
|
|
2145
|
-
break;
|
|
2146
|
-
}
|
|
2147
|
-
default: if (fieldValue !== operand) return false;
|
|
2148
|
-
}
|
|
2149
|
-
return true;
|
|
2150
|
-
}
|
|
2151
|
-
function sortRows(rows, sort) {
|
|
2152
|
-
const keys = [];
|
|
2153
|
-
const push = (name, explicit) => {
|
|
2154
|
-
const clean = name.replace(/^[-+]/, "");
|
|
2155
|
-
const dir = explicit ?? (name.startsWith("-") ? -1 : 1);
|
|
2156
|
-
if (clean) keys.push({
|
|
2157
|
-
name: clean,
|
|
2158
|
-
dir
|
|
2159
|
-
});
|
|
2160
|
-
};
|
|
2161
|
-
if (typeof sort === "string") for (const part of sort.split(",")) {
|
|
2162
|
-
const trimmed = part.trim();
|
|
2163
|
-
if (!trimmed) continue;
|
|
2164
|
-
const [name, dir] = trimmed.split(":");
|
|
2165
|
-
push(name, dir === "desc" ? -1 : dir === "asc" ? 1 : void 0);
|
|
2166
|
-
}
|
|
2167
|
-
else if (Array.isArray(sort)) {
|
|
2168
|
-
for (const entry of sort) if (typeof entry === "string") push(entry);
|
|
2169
|
-
else if (entry && typeof entry === "object") for (const [name, d] of Object.entries(entry)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2170
|
-
} else if (sort && typeof sort === "object") for (const [name, d] of Object.entries(sort)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2171
|
-
if (keys.length === 0) return rows;
|
|
2172
|
-
const out = rows.slice();
|
|
2173
|
-
out.sort((a, b) => {
|
|
2174
|
-
for (const { name, dir } of keys) {
|
|
2175
|
-
const av = a[name];
|
|
2176
|
-
const bv = b[name];
|
|
2177
|
-
if (av === bv) continue;
|
|
2178
|
-
if (av === void 0 || av === null) return -1 * dir;
|
|
2179
|
-
if (bv === void 0 || bv === null) return 1 * dir;
|
|
2180
|
-
if (av < bv) return -1 * dir;
|
|
2181
|
-
if (av > bv) return 1 * dir;
|
|
2182
|
-
}
|
|
2183
|
-
return 0;
|
|
2184
|
-
});
|
|
2185
|
-
return out;
|
|
2186
|
-
}
|
|
2187
|
-
function applySelect(rows, select) {
|
|
2188
|
-
if (!select?.length) return rows;
|
|
2189
|
-
return rows.map((row) => {
|
|
2190
|
-
const out = {};
|
|
2191
|
-
for (const key of select) out[key] = row[key];
|
|
2192
|
-
return out;
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
2141
|
//#endregion
|
|
2196
2142
|
//#region src/actions/action-disabled-error.ts
|
|
2197
2143
|
function buildMessage(action, ids) {
|
package/dist/index.d.cts
CHANGED
|
@@ -470,18 +470,32 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
|
|
|
470
470
|
* filter/sort/search/paginate over the provided rows, respecting the
|
|
471
471
|
* `@ui.dict.*` capability annotations on the bound interface.
|
|
472
472
|
*
|
|
473
|
+
* Filter/sort/projection are delegated to the shared JS-native engine from
|
|
474
|
+
* `@atscript/db-memory` (the same one every in-memory table uses) so a static
|
|
475
|
+
* value-help source behaves identically to the SQL/Mongo adapters instead of
|
|
476
|
+
* carrying a second, weaker implementation.
|
|
477
|
+
*
|
|
473
478
|
* **Semantics:**
|
|
474
479
|
* - Filter is interpreted as a subset of MongoDB-style comparison operators
|
|
475
|
-
* (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex
|
|
476
|
-
* logical combinators (`$and`, `$or`, `$not
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
+
* (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex`,
|
|
481
|
+
* `$exists`) and logical combinators (`$and`, `$or`, `$not`). Field names may
|
|
482
|
+
* be dot-paths (`a.b.c`) that descend into nested objects. Any operator the
|
|
483
|
+
* engine does not implement raises `DbError('INVALID_QUERY')`, which the
|
|
484
|
+
* validation interceptor surfaces as HTTP 400 (it is NOT silently ignored).
|
|
485
|
+
* - Null model is Mongo-like: `{ field: null }` / `$eq: null` matches an
|
|
486
|
+
* explicit `null` OR a missing field; `$ne: null` matches only a concrete,
|
|
487
|
+
* present, non-null value.
|
|
488
|
+
* - `$regex` honours `/pattern/flags` literals, so `$regex: '/foo/i'` is a real
|
|
489
|
+
* case-insensitive match.
|
|
490
|
+
* - Sort is stable, multi-key, lexicographic (insertion order preserved among
|
|
491
|
+
* ties). Direction via `-` prefix on the field name or `{ [field]: 'asc' |
|
|
492
|
+
* 'desc' | 1 | -1 }`.
|
|
480
493
|
* - Search is case-insensitive substring matching across every field listed in
|
|
481
|
-
* {@link searchableFields}.
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
494
|
+
* {@link searchableFields}. This is value-help's own concern — the shared
|
|
495
|
+
* engine has no `$search`.
|
|
496
|
+
* - Projection (`$select`) supports inclusion (`[fields]` / `{ f: 1 }`) and
|
|
497
|
+
* exclusion (`{ f: 0 }`) with dot-path nesting; the primary key is NOT auto-
|
|
498
|
+
* added (a value-help projection returns exactly the selected fields).
|
|
485
499
|
*
|
|
486
500
|
* **Constructor:**
|
|
487
501
|
* ```ts
|
|
@@ -503,6 +517,23 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
|
|
|
503
517
|
count: number;
|
|
504
518
|
}>;
|
|
505
519
|
protected getOne(id: string | number): Promise<DataType | null>;
|
|
520
|
+
/**
|
|
521
|
+
* Ports the value-help `$sort` grammar to the shared engine's `{ field: 1 |
|
|
522
|
+
* -1 }` shape (order-preserving for multi-key sorts). Accepts:
|
|
523
|
+
* - a string `"field:asc,-other"` (comma-separated; `-` prefix or `:desc` → descending),
|
|
524
|
+
* - an array of such strings / `{ field: dir }` objects,
|
|
525
|
+
* - a `{ field: 'asc' | 'desc' | 1 | -1 }` object.
|
|
526
|
+
* Returns `undefined` when nothing sortable was parsed (engine skips sorting).
|
|
527
|
+
*/
|
|
528
|
+
private normalizeSort;
|
|
529
|
+
/**
|
|
530
|
+
* Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
|
|
531
|
+
* 1 }` projection map:
|
|
532
|
+
* - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
|
|
533
|
+
* - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
|
|
534
|
+
* - anything else / empty → `undefined` (no projection; whole rows returned).
|
|
535
|
+
*/
|
|
536
|
+
private normalizeSelect;
|
|
506
537
|
}
|
|
507
538
|
//#endregion
|
|
508
539
|
//#region src/decorators.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -470,18 +470,32 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
|
|
|
470
470
|
* filter/sort/search/paginate over the provided rows, respecting the
|
|
471
471
|
* `@ui.dict.*` capability annotations on the bound interface.
|
|
472
472
|
*
|
|
473
|
+
* Filter/sort/projection are delegated to the shared JS-native engine from
|
|
474
|
+
* `@atscript/db-memory` (the same one every in-memory table uses) so a static
|
|
475
|
+
* value-help source behaves identically to the SQL/Mongo adapters instead of
|
|
476
|
+
* carrying a second, weaker implementation.
|
|
477
|
+
*
|
|
473
478
|
* **Semantics:**
|
|
474
479
|
* - Filter is interpreted as a subset of MongoDB-style comparison operators
|
|
475
|
-
* (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex
|
|
476
|
-
* logical combinators (`$and`, `$or`, `$not
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
+
* (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex`,
|
|
481
|
+
* `$exists`) and logical combinators (`$and`, `$or`, `$not`). Field names may
|
|
482
|
+
* be dot-paths (`a.b.c`) that descend into nested objects. Any operator the
|
|
483
|
+
* engine does not implement raises `DbError('INVALID_QUERY')`, which the
|
|
484
|
+
* validation interceptor surfaces as HTTP 400 (it is NOT silently ignored).
|
|
485
|
+
* - Null model is Mongo-like: `{ field: null }` / `$eq: null` matches an
|
|
486
|
+
* explicit `null` OR a missing field; `$ne: null` matches only a concrete,
|
|
487
|
+
* present, non-null value.
|
|
488
|
+
* - `$regex` honours `/pattern/flags` literals, so `$regex: '/foo/i'` is a real
|
|
489
|
+
* case-insensitive match.
|
|
490
|
+
* - Sort is stable, multi-key, lexicographic (insertion order preserved among
|
|
491
|
+
* ties). Direction via `-` prefix on the field name or `{ [field]: 'asc' |
|
|
492
|
+
* 'desc' | 1 | -1 }`.
|
|
480
493
|
* - Search is case-insensitive substring matching across every field listed in
|
|
481
|
-
* {@link searchableFields}.
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
494
|
+
* {@link searchableFields}. This is value-help's own concern — the shared
|
|
495
|
+
* engine has no `$search`.
|
|
496
|
+
* - Projection (`$select`) supports inclusion (`[fields]` / `{ f: 1 }`) and
|
|
497
|
+
* exclusion (`{ f: 0 }`) with dot-path nesting; the primary key is NOT auto-
|
|
498
|
+
* added (a value-help projection returns exactly the selected fields).
|
|
485
499
|
*
|
|
486
500
|
* **Constructor:**
|
|
487
501
|
* ```ts
|
|
@@ -503,6 +517,23 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
|
|
|
503
517
|
count: number;
|
|
504
518
|
}>;
|
|
505
519
|
protected getOne(id: string | number): Promise<DataType | null>;
|
|
520
|
+
/**
|
|
521
|
+
* Ports the value-help `$sort` grammar to the shared engine's `{ field: 1 |
|
|
522
|
+
* -1 }` shape (order-preserving for multi-key sorts). Accepts:
|
|
523
|
+
* - a string `"field:asc,-other"` (comma-separated; `-` prefix or `:desc` → descending),
|
|
524
|
+
* - an array of such strings / `{ field: dir }` objects,
|
|
525
|
+
* - a `{ field: 'asc' | 'desc' | 1 | -1 }` object.
|
|
526
|
+
* Returns `undefined` when nothing sortable was parsed (engine skips sorting).
|
|
527
|
+
*/
|
|
528
|
+
private normalizeSort;
|
|
529
|
+
/**
|
|
530
|
+
* Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
|
|
531
|
+
* 1 }` projection map:
|
|
532
|
+
* - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
|
|
533
|
+
* - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
|
|
534
|
+
* - anything else / empty → `undefined` (no projection; whole rows returned).
|
|
535
|
+
*/
|
|
536
|
+
private normalizeSelect;
|
|
506
537
|
}
|
|
507
538
|
//#endregion
|
|
508
539
|
//#region src/decorators.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { Body, Delete, Get, HttpError, Patch, Post, Put, Query, Url } from "@moo
|
|
|
3
3
|
import { ApplyDecorators, Controller, Inherit, Inject, Intercept, Moost, Param, Provide, Resolve, TInterceptorPriority, defineBeforeInterceptor, defineInterceptor, getMoostMate, useControllerContext } from "moost";
|
|
4
4
|
import { parseUrl } from "@uniqu/url";
|
|
5
5
|
import { DbError } from "@atscript/db";
|
|
6
|
+
import { buildMemoryPredicate, projectRow, sortRows } from "@atscript/db-memory";
|
|
6
7
|
import { cached, current, defineWook, key } from "@wooksjs/event-core";
|
|
7
8
|
import { useBody } from "@wooksjs/http-body";
|
|
8
9
|
//#region src/validation-interceptor.ts
|
|
@@ -2051,7 +2052,10 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
2051
2052
|
}
|
|
2052
2053
|
async query(controls) {
|
|
2053
2054
|
let rows = this.rows;
|
|
2054
|
-
if (controls.filter && Object.keys(controls.filter).length > 0)
|
|
2055
|
+
if (controls.filter && Object.keys(controls.filter).length > 0) {
|
|
2056
|
+
const predicate = buildMemoryPredicate(controls.filter);
|
|
2057
|
+
rows = rows.filter((row) => predicate(row));
|
|
2058
|
+
}
|
|
2055
2059
|
const search = controls.controls.$search;
|
|
2056
2060
|
if (search) {
|
|
2057
2061
|
const needle = search.toLowerCase();
|
|
@@ -2064,18 +2068,68 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
2064
2068
|
return false;
|
|
2065
2069
|
});
|
|
2066
2070
|
}
|
|
2067
|
-
|
|
2071
|
+
const sort = this.normalizeSort(controls.controls.$sort);
|
|
2072
|
+
if (sort) rows = sortRows(rows, sort);
|
|
2068
2073
|
const total = rows.length;
|
|
2069
2074
|
const skip = Math.max(0, Number(controls.controls.$skip ?? 0));
|
|
2070
2075
|
const limit = Math.max(0, Number(controls.controls.$limit ?? total - skip));
|
|
2076
|
+
const page = rows.slice(skip, skip + limit);
|
|
2077
|
+
const projection = this.normalizeSelect(controls.controls.$select);
|
|
2071
2078
|
return {
|
|
2072
|
-
data:
|
|
2079
|
+
data: projection ? page.map((row) => projectRow(row, projection, { clone: false })) : page,
|
|
2073
2080
|
count: total
|
|
2074
2081
|
};
|
|
2075
2082
|
}
|
|
2076
2083
|
async getOne(id) {
|
|
2077
2084
|
return this._pkIndex?.get(String(id)) ?? null;
|
|
2078
2085
|
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Ports the value-help `$sort` grammar to the shared engine's `{ field: 1 |
|
|
2088
|
+
* -1 }` shape (order-preserving for multi-key sorts). Accepts:
|
|
2089
|
+
* - a string `"field:asc,-other"` (comma-separated; `-` prefix or `:desc` → descending),
|
|
2090
|
+
* - an array of such strings / `{ field: dir }` objects,
|
|
2091
|
+
* - a `{ field: 'asc' | 'desc' | 1 | -1 }` object.
|
|
2092
|
+
* Returns `undefined` when nothing sortable was parsed (engine skips sorting).
|
|
2093
|
+
*/
|
|
2094
|
+
normalizeSort(sort) {
|
|
2095
|
+
const out = {};
|
|
2096
|
+
const push = (name, explicit) => {
|
|
2097
|
+
const clean = name.replace(/^[-+]/, "");
|
|
2098
|
+
const dir = explicit ?? (name.startsWith("-") ? -1 : 1);
|
|
2099
|
+
if (clean) out[clean] = dir;
|
|
2100
|
+
};
|
|
2101
|
+
if (typeof sort === "string") for (const part of sort.split(",")) {
|
|
2102
|
+
const trimmed = part.trim();
|
|
2103
|
+
if (!trimmed) continue;
|
|
2104
|
+
const [name, dir] = trimmed.split(":");
|
|
2105
|
+
push(name, dir === "desc" ? -1 : dir === "asc" ? 1 : void 0);
|
|
2106
|
+
}
|
|
2107
|
+
else {
|
|
2108
|
+
const entries = Array.isArray(sort) ? sort : sort && typeof sort === "object" ? [sort] : [];
|
|
2109
|
+
for (const entry of entries) if (typeof entry === "string") push(entry);
|
|
2110
|
+
else if (entry && typeof entry === "object") for (const [name, d] of Object.entries(entry)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2111
|
+
}
|
|
2112
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
|
|
2116
|
+
* 1 }` projection map:
|
|
2117
|
+
* - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
|
|
2118
|
+
* - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
|
|
2119
|
+
* - anything else / empty → `undefined` (no projection; whole rows returned).
|
|
2120
|
+
*/
|
|
2121
|
+
normalizeSelect(select) {
|
|
2122
|
+
if (Array.isArray(select)) {
|
|
2123
|
+
const out = {};
|
|
2124
|
+
for (const field of select) if (typeof field === "string" && field) out[field] = 1;
|
|
2125
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2126
|
+
}
|
|
2127
|
+
if (select && typeof select === "object") {
|
|
2128
|
+
const out = {};
|
|
2129
|
+
for (const [path, v] of Object.entries(select)) out[path] = v === 0 || v === false ? 0 : 1;
|
|
2130
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2079
2133
|
};
|
|
2080
2134
|
AsJsonValueHelpController = __decorate([Inherit(), __decorateMetadata("design:paramtypes", [
|
|
2081
2135
|
Object,
|
|
@@ -2083,114 +2137,6 @@ AsJsonValueHelpController = __decorate([Inherit(), __decorateMetadata("design:pa
|
|
|
2083
2137
|
typeof Moost === "undefined" ? Object : Moost,
|
|
2084
2138
|
String
|
|
2085
2139
|
])], AsJsonValueHelpController);
|
|
2086
|
-
function matchFilter(row, filter) {
|
|
2087
|
-
if (!filter || typeof filter !== "object") return true;
|
|
2088
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
2089
|
-
if (key === "$and") {
|
|
2090
|
-
if (!Array.isArray(value)) continue;
|
|
2091
|
-
if (!value.every((clause) => matchFilter(row, clause))) return false;
|
|
2092
|
-
continue;
|
|
2093
|
-
}
|
|
2094
|
-
if (key === "$or") {
|
|
2095
|
-
if (!Array.isArray(value)) continue;
|
|
2096
|
-
if (!value.some((clause) => matchFilter(row, clause))) return false;
|
|
2097
|
-
continue;
|
|
2098
|
-
}
|
|
2099
|
-
if (key === "$nor") {
|
|
2100
|
-
if (!Array.isArray(value)) continue;
|
|
2101
|
-
if (value.some((clause) => matchFilter(row, clause))) return false;
|
|
2102
|
-
continue;
|
|
2103
|
-
}
|
|
2104
|
-
if (key === "$not") {
|
|
2105
|
-
if (matchFilter(row, value)) return false;
|
|
2106
|
-
continue;
|
|
2107
|
-
}
|
|
2108
|
-
if (key.startsWith("$")) continue;
|
|
2109
|
-
const fieldValue = row[key];
|
|
2110
|
-
if (!matchFieldPredicate(fieldValue, value)) return false;
|
|
2111
|
-
}
|
|
2112
|
-
return true;
|
|
2113
|
-
}
|
|
2114
|
-
function matchFieldPredicate(fieldValue, predicate) {
|
|
2115
|
-
if (predicate === null || typeof predicate !== "object" || Array.isArray(predicate)) return fieldValue === predicate;
|
|
2116
|
-
for (const [op, operand] of Object.entries(predicate)) switch (op) {
|
|
2117
|
-
case "$eq":
|
|
2118
|
-
if (fieldValue !== operand) return false;
|
|
2119
|
-
break;
|
|
2120
|
-
case "$ne":
|
|
2121
|
-
if (fieldValue === operand) return false;
|
|
2122
|
-
break;
|
|
2123
|
-
case "$in":
|
|
2124
|
-
if (!Array.isArray(operand) || !operand.includes(fieldValue)) return false;
|
|
2125
|
-
break;
|
|
2126
|
-
case "$nin":
|
|
2127
|
-
if (!Array.isArray(operand) || operand.includes(fieldValue)) return false;
|
|
2128
|
-
break;
|
|
2129
|
-
case "$gt":
|
|
2130
|
-
if (!(fieldValue > operand)) return false;
|
|
2131
|
-
break;
|
|
2132
|
-
case "$gte":
|
|
2133
|
-
if (!(fieldValue >= operand)) return false;
|
|
2134
|
-
break;
|
|
2135
|
-
case "$lt":
|
|
2136
|
-
if (!(fieldValue < operand)) return false;
|
|
2137
|
-
break;
|
|
2138
|
-
case "$lte":
|
|
2139
|
-
if (!(fieldValue <= operand)) return false;
|
|
2140
|
-
break;
|
|
2141
|
-
case "$regex": {
|
|
2142
|
-
const re = operand instanceof RegExp ? operand : new RegExp(String(operand));
|
|
2143
|
-
if (typeof fieldValue !== "string" || !re.test(fieldValue)) return false;
|
|
2144
|
-
break;
|
|
2145
|
-
}
|
|
2146
|
-
default: if (fieldValue !== operand) return false;
|
|
2147
|
-
}
|
|
2148
|
-
return true;
|
|
2149
|
-
}
|
|
2150
|
-
function sortRows(rows, sort) {
|
|
2151
|
-
const keys = [];
|
|
2152
|
-
const push = (name, explicit) => {
|
|
2153
|
-
const clean = name.replace(/^[-+]/, "");
|
|
2154
|
-
const dir = explicit ?? (name.startsWith("-") ? -1 : 1);
|
|
2155
|
-
if (clean) keys.push({
|
|
2156
|
-
name: clean,
|
|
2157
|
-
dir
|
|
2158
|
-
});
|
|
2159
|
-
};
|
|
2160
|
-
if (typeof sort === "string") for (const part of sort.split(",")) {
|
|
2161
|
-
const trimmed = part.trim();
|
|
2162
|
-
if (!trimmed) continue;
|
|
2163
|
-
const [name, dir] = trimmed.split(":");
|
|
2164
|
-
push(name, dir === "desc" ? -1 : dir === "asc" ? 1 : void 0);
|
|
2165
|
-
}
|
|
2166
|
-
else if (Array.isArray(sort)) {
|
|
2167
|
-
for (const entry of sort) if (typeof entry === "string") push(entry);
|
|
2168
|
-
else if (entry && typeof entry === "object") for (const [name, d] of Object.entries(entry)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2169
|
-
} else if (sort && typeof sort === "object") for (const [name, d] of Object.entries(sort)) push(name, d === "desc" || d === -1 ? -1 : 1);
|
|
2170
|
-
if (keys.length === 0) return rows;
|
|
2171
|
-
const out = rows.slice();
|
|
2172
|
-
out.sort((a, b) => {
|
|
2173
|
-
for (const { name, dir } of keys) {
|
|
2174
|
-
const av = a[name];
|
|
2175
|
-
const bv = b[name];
|
|
2176
|
-
if (av === bv) continue;
|
|
2177
|
-
if (av === void 0 || av === null) return -1 * dir;
|
|
2178
|
-
if (bv === void 0 || bv === null) return 1 * dir;
|
|
2179
|
-
if (av < bv) return -1 * dir;
|
|
2180
|
-
if (av > bv) return 1 * dir;
|
|
2181
|
-
}
|
|
2182
|
-
return 0;
|
|
2183
|
-
});
|
|
2184
|
-
return out;
|
|
2185
|
-
}
|
|
2186
|
-
function applySelect(rows, select) {
|
|
2187
|
-
if (!select?.length) return rows;
|
|
2188
|
-
return rows.map((row) => {
|
|
2189
|
-
const out = {};
|
|
2190
|
-
for (const key of select) out[key] = row[key];
|
|
2191
|
-
return out;
|
|
2192
|
-
});
|
|
2193
|
-
}
|
|
2194
2140
|
//#endregion
|
|
2195
2141
|
//#region src/actions/action-disabled-error.ts
|
|
2196
2142
|
function buildMessage(action, ids) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-db",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.113",
|
|
4
4
|
"description": "Generic database controller for Moost with Atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -38,27 +38,28 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@uniqu/url": "^0.1.6"
|
|
41
|
+
"@uniqu/url": "^0.1.6",
|
|
42
|
+
"@atscript/db-memory": "^0.1.113"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
|
-
"@atscript/core": "^0.1.
|
|
45
|
-
"@atscript/typescript": "^0.1.
|
|
46
|
-
"@moostjs/event-http": "^0.6.
|
|
45
|
+
"@atscript/core": "^0.1.80",
|
|
46
|
+
"@atscript/typescript": "^0.1.80",
|
|
47
|
+
"@moostjs/event-http": "^0.6.29",
|
|
47
48
|
"@uniqu/core": "^0.1.6",
|
|
48
49
|
"@wooksjs/event-core": "^0.7.19",
|
|
49
50
|
"@wooksjs/event-http": "^0.7.19",
|
|
50
51
|
"@wooksjs/http-body": "^0.7.19",
|
|
51
|
-
"moost": "^0.6.
|
|
52
|
-
"unplugin-atscript": "^0.1.
|
|
52
|
+
"moost": "^0.6.29",
|
|
53
|
+
"unplugin-atscript": "^0.1.80"
|
|
53
54
|
},
|
|
54
55
|
"peerDependencies": {
|
|
55
|
-
"@atscript/typescript": "^0.1.
|
|
56
|
-
"@moostjs/event-http": "^0.6.
|
|
56
|
+
"@atscript/typescript": "^0.1.80",
|
|
57
|
+
"@moostjs/event-http": "^0.6.29",
|
|
57
58
|
"@uniqu/core": "^0.1.6",
|
|
58
59
|
"@wooksjs/event-core": "^0.7.19",
|
|
59
60
|
"@wooksjs/http-body": "^0.7.19",
|
|
60
|
-
"moost": "^0.6.
|
|
61
|
-
"@atscript/db": "^0.1.
|
|
61
|
+
"moost": "^0.6.29",
|
|
62
|
+
"@atscript/db": "^0.1.113"
|
|
62
63
|
},
|
|
63
64
|
"scripts": {
|
|
64
65
|
"postinstall": "asc -f dts",
|