@entity-access/server-pages 1.1.620 → 1.1.623

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.
Files changed (40) hide show
  1. package/dist/cache/BaseDiskCache.d.ts +34 -0
  2. package/dist/cache/BaseDiskCache.d.ts.map +1 -0
  3. package/dist/cache/BaseDiskCache.js +325 -0
  4. package/dist/cache/BaseDiskCache.js.map +1 -0
  5. package/dist/cache/LockFile.d.ts +13 -0
  6. package/dist/cache/LockFile.d.ts.map +1 -0
  7. package/dist/cache/LockFile.js +81 -0
  8. package/dist/cache/LockFile.js.map +1 -0
  9. package/dist/cache/spawnPromise.d.ts +12 -0
  10. package/dist/cache/spawnPromise.d.ts.map +1 -0
  11. package/dist/cache/spawnPromise.js +63 -0
  12. package/dist/cache/spawnPromise.js.map +1 -0
  13. package/dist/core/FileApi.d.ts +1 -0
  14. package/dist/core/FileApi.d.ts.map +1 -1
  15. package/dist/core/FileApi.js +12 -0
  16. package/dist/core/FileApi.js.map +1 -1
  17. package/dist/core/NumberFormats.d.ts +15 -0
  18. package/dist/core/NumberFormats.d.ts.map +1 -0
  19. package/dist/core/NumberFormats.js +64 -0
  20. package/dist/core/NumberFormats.js.map +1 -0
  21. package/dist/core/RunOnce.d.ts +4 -0
  22. package/dist/core/RunOnce.d.ts.map +1 -0
  23. package/dist/core/RunOnce.js +25 -0
  24. package/dist/core/RunOnce.js.map +1 -0
  25. package/dist/core/TimeoutError.d.ts +3 -0
  26. package/dist/core/TimeoutError.d.ts.map +1 -0
  27. package/dist/core/TimeoutError.js +3 -0
  28. package/dist/core/TimeoutError.js.map +1 -0
  29. package/dist/services/EntityAccessServer.js +3 -3
  30. package/dist/services/EntityAccessServer.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +2 -1
  33. package/src/cache/BaseDiskCache.ts +330 -0
  34. package/src/cache/LockFile.ts +101 -0
  35. package/src/cache/spawnPromise.ts +69 -0
  36. package/src/core/FileApi.ts +12 -0
  37. package/src/core/NumberFormats.ts +78 -0
  38. package/src/core/RunOnce.ts +30 -0
  39. package/src/core/TimeoutError.ts +3 -0
  40. package/src/services/EntityAccessServer.ts +3 -3
@@ -0,0 +1,330 @@
1
+ /* eslint-disable no-console */
2
+ import fsp, { opendir, rm, rmdir, stat, unlink } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { join, parse } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import EntityAccessError from "@entity-access/entity-access/dist/common/EntityAccessError.js";
7
+ import ensureDir from "../core/FileApi.js";
8
+ import TempFolder from "../core/TempFolder.js";
9
+ import { LocalFile } from "../core/LocalFile.js";
10
+ import { spawnPromise } from "./spawnPromise.js";
11
+ import LockFile from "./LockFile.js";
12
+ import sleep from "../sleep.js";
13
+ import RunOnce from "../core/RunOnce.js";
14
+ import { toKMBString } from "../core/NumberFormats.js";
15
+
16
+ const doNothing = () => void 0;
17
+
18
+ export interface IDiskCacheContainer {
19
+ cache: BaseDiskCache;
20
+ }
21
+
22
+ export default class BaseDiskCache {
23
+
24
+ protected readonly root: string;
25
+ protected readonly keepTTLSeconds: number;
26
+ protected readonly minSize: number;
27
+ protected readonly updateAccessTime: boolean;
28
+ protected readonly maxAge: number;
29
+ protected readonly minAge: number;
30
+ constructor(
31
+ {
32
+ root,
33
+ keepTTLSeconds = 3600,
34
+ minSize = Number.MAX_SAFE_INTEGER,
35
+ updateAccessTime = true,
36
+ maxAge = 1,
37
+ minAge = 1
38
+ }: {
39
+ root: string;
40
+ keepTTLSeconds?: number;
41
+ minSize?: number;
42
+ updateAccessTime?: boolean;
43
+ maxAge?: number;
44
+ minAge?: number;
45
+ }
46
+ ) {
47
+ this.root = root;
48
+ this.keepTTLSeconds = keepTTLSeconds;
49
+ this.minSize = minSize;
50
+ this.updateAccessTime = updateAccessTime;
51
+ this.maxAge = maxAge;
52
+ this.minAge = minAge;
53
+ ensureDir(root);
54
+ // eslint-disable-next-line no-console
55
+ setTimeout(() => this.clean().catch(console.error), 1000);
56
+ }
57
+
58
+ newFolder(suffix = "") {
59
+ return new TempFolder(suffix, this.root);
60
+ }
61
+
62
+ async get(path: string) {
63
+ path = join(this.root, path);
64
+ if (existsSync(path)) {
65
+ if(this.updateAccessTime) {
66
+ const now = new Date();
67
+ await fsp.utimes(path, now, now);
68
+ }
69
+ return new LocalFile(path, void 0, void 0, doNothing);
70
+ }
71
+ }
72
+
73
+ async clear() {
74
+ try {
75
+ const path = this.root;
76
+ using _lock = await LockFile.lock(`df-clear:${path}`);
77
+ if (!existsSync(path)) {
78
+ return;
79
+ }
80
+ const stalePath = `${path}.old.${Date.now()}`;
81
+ await spawnPromise("mv",[path, stalePath]);
82
+ ensureDir(this.root);
83
+ rm(stalePath, { recursive: true, force: true}).catch(console.error);
84
+ } catch (error) {
85
+ console.error(error);
86
+ }
87
+ }
88
+
89
+ async clearFolder(path: string) {
90
+ try {
91
+ using _lock = await LockFile.lock(`df-clear:${path}`);
92
+ path = join(this.root, path);
93
+ if (!existsSync(path)) {
94
+ return;
95
+ }
96
+ const stalePath = `${path}.old.${Date.now()}`;
97
+ await spawnPromise("mv",[path, stalePath]);
98
+ rm(stalePath, { recursive: true, force: true}).catch(console.error);
99
+ } catch (error) {
100
+ console.error(error);
101
+ }
102
+ }
103
+
104
+ async getOrCreateJsonAsync<T>(path: string, factory: () => Promise<T>) {
105
+ const localFile = await this.getOrCreateAsync(path, async (lf) => {
106
+ const data = (await factory()) ?? null;
107
+ await lf.writeAllText(JSON.stringify(data));
108
+ });
109
+ const text = await localFile.readAsText();
110
+ return JSON.parse(text) as T;
111
+ }
112
+
113
+ deleteAt(path: string) {
114
+ path = join(this.root, path);
115
+ return unlink(path);
116
+ }
117
+
118
+ async getOrCreateAsync(path: string, factory: (fx: LocalFile) => Promise<void>, ext = ".dat") {
119
+ path = join(this.root, path);
120
+
121
+ const parsedPath = parse(path);
122
+ ensureDir(parsedPath.dir);
123
+
124
+ let error: Error;
125
+
126
+ for (let index = 0; index < 5; index++) {
127
+ if (existsSync(path)) {
128
+ if(this.updateAccessTime) {
129
+ const now = new Date();
130
+ await fsp.utimes(path, now, now);
131
+ }
132
+ return new LocalFile(path, void 0, void 0, doNothing);
133
+ }
134
+
135
+ using _lock = await LockFile.lock(`df:${path}`);
136
+
137
+ if (existsSync(path)) {
138
+ return new LocalFile(path, void 0, void 0, doNothing);
139
+ }
140
+
141
+ const tmpPath = join(this.root, randomUUID() + (parsedPath.ext || ext));
142
+
143
+ await factory(new LocalFile(tmpPath, void 0, void 0, doNothing));
144
+
145
+ try {
146
+ ensureDir(parsedPath.dir);
147
+ await fsp.rename(tmpPath, path);
148
+ } catch (e) {
149
+
150
+ if (existsSync(tmpPath)) {
151
+ unlink(tmpPath).catch(doNothing);
152
+ }
153
+
154
+ error = e;
155
+ await sleep(1000);
156
+ continue;
157
+ }
158
+ return new LocalFile(path, void 0, void 0, doNothing);
159
+ }
160
+
161
+ throw new EntityAccessError(`Failed to write file due to error ${error.stack ?? error}`);
162
+ }
163
+
164
+ createTempFileDeleteOnExit(pathFragments: string[], name: string, contentType: string) {
165
+ const fileName = pathFragments.pop();
166
+ let folder = void 0;
167
+ if (pathFragments.length) {
168
+ folder = join(this.root, ... pathFragments);
169
+ ensureDir(folder);
170
+ }
171
+ const path = join(this.root, ... pathFragments, fileName);
172
+ return new LocalFile(path, name, contentType, () => unlink(path).then(() => folder ? rmdir(folder).catch(console.error) : void 0 , console.error));
173
+ }
174
+
175
+ protected async deleteFile(path: string) {
176
+ if (!path.startsWith(this.root)) {
177
+ return;
178
+ }
179
+ if (existsSync(path)) {
180
+ await unlink(path);
181
+ }
182
+ for(;;) {
183
+ const parsed = parse(path);
184
+ if (parsed.dir === this.root) {
185
+ break;
186
+ }
187
+ path = parsed.dir;
188
+ try {
189
+ if (existsSync(path)) {
190
+ // check if folder is empty...
191
+ if(await this.isEmptyDir(path)) {
192
+ await rmdir(path);
193
+ }
194
+ }
195
+ } catch (error) {
196
+ console.error(error);
197
+ return;
198
+ }
199
+ }
200
+ }
201
+
202
+ protected async isEmptyDir(path) {
203
+ try {
204
+ const directory = await opendir(path);
205
+ const entry = await directory.read();
206
+ await directory.close();
207
+ return entry === null; // It's empty if the first entry read is null
208
+ } catch (error) {
209
+ // Catches errors like 'ENOENT' (directory doesn't exist)
210
+ // and treats the path as effectively "empty" for the purpose of the check.
211
+ // Adjust error handling as needed for your specific use case.
212
+ return true;
213
+ }
214
+ }
215
+
216
+ protected async clean() {
217
+
218
+ if (!await RunOnce.canRun(this.root)) {
219
+ setTimeout(() => this.clean().catch(console.error), 60000);
220
+ return;
221
+ }
222
+
223
+ const start = Date.now();
224
+ let total = 0;
225
+
226
+ const min = this.minAge;
227
+
228
+ let all = null as { time, path, size }[];
229
+ let freeSize = 0;
230
+ let deleted = 0;
231
+
232
+ for(let i=this.maxAge;i>= min;i--) {
233
+ const s = await fsp.statfs(this.root);
234
+ freeSize = s.bavail * s.bsize;
235
+
236
+ if (freeSize >= this.minSize) {
237
+ break;
238
+ }
239
+ all ??= await this.getFileStats();
240
+ try {
241
+ const keep = Date.now() - this.keepTTLSeconds * 1000 * i;
242
+ const pending = [];
243
+ for (const file of all) {
244
+ if (file.time < keep) {
245
+ await this.deleteFile(file.path);
246
+ deleted++;
247
+ total += file.size;
248
+ continue;
249
+ }
250
+ pending.push(file);
251
+ }
252
+ all = pending;
253
+ if(!all.length) {
254
+ break;
255
+ }
256
+ } catch (error) {
257
+ console.error(error);
258
+ }
259
+ }
260
+
261
+ if (total) {
262
+ console.log(`${this.root} (${deleted}/${all.length + deleted}) cleaned, ${toKMBString(total)} freed in ${Date.now()-start}ms.`);
263
+ } else {
264
+ if (all?.length) {
265
+ if (this.minSize === Number.MAX_SAFE_INTEGER) {
266
+ console.log(`Cleaning ${this.root} with entries (${all.length}) for ${Date.now()-start}ms.`);
267
+ } else {
268
+ console.log(`Cleaning ${this.root} with entries (${all.length}) for ${Date.now()-start}ms as ${toKMBString(freeSize)} < ${toKMBString(this.minSize)}.`);
269
+ }
270
+ }
271
+ }
272
+
273
+ setTimeout(() => this.clean().catch(console.error), 60000);
274
+
275
+ }
276
+
277
+ private async getFileStats() {
278
+ const min = Date.now() - this.minAge * this.keepTTLSeconds * 1000;
279
+ const files = [] as { path: string, size: number, time: number }[];
280
+ const dir = await fsp.opendir(this.root, { recursive: true });
281
+ for await (const entry of dir) {
282
+ if (!entry.isFile()) {
283
+ continue;
284
+ }
285
+ const f = join(entry.parentPath, entry.name);
286
+ const s = await stat(f);
287
+ const time = s.ctimeMs;
288
+ if (time > min) {
289
+ continue;
290
+ }
291
+ files.push({ path: f, size: s.size, time });
292
+ }
293
+ return files;
294
+ }
295
+
296
+ // private async getFilesToDelete(oldest: number) {
297
+ // const dir = await fsp.opendir(this.root, { recursive: true });
298
+ // const filesToDelete = [] as { path: string, statInfo: Stats }[];
299
+ // try {
300
+ // for await (const entry of dir) {
301
+ // if (!entry.isFile()) {
302
+ // continue;
303
+ // }
304
+ // const path = join(entry.parentPath, entry.name);
305
+ // try {
306
+ // const statInfo = await stat(path);
307
+ // if (statInfo.ctimeMs < oldest) {
308
+ // filesToDelete.push({ path, statInfo });
309
+ // if (filesToDelete.length === 1000) {
310
+ // break;
311
+ // }
312
+ // }
313
+ // } catch (error) {
314
+ // // file may not exist anymore...
315
+ // }
316
+ // }
317
+ // } catch (error) {
318
+ // console.error(error);
319
+ // } finally {
320
+ // try {
321
+ // await dir.close();
322
+ // } catch {
323
+ // // do nothing
324
+ // }
325
+ // }
326
+ // return filesToDelete;
327
+ // }
328
+
329
+
330
+ }
@@ -0,0 +1,101 @@
1
+ /* eslint-disable no-console */
2
+ import { join } from "node:path";
3
+ import { existsSync, mkdirSync, unlinkSync, statSync, utimesSync, writeFileSync } from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+ import sleep from "../sleep.js";
6
+ import TimeoutError from "../core/TimeoutError.js";
7
+
8
+ const lockFolder = join("/tmp", "locks");
9
+
10
+ if (!existsSync(lockFolder)) {
11
+ mkdirSync(lockFolder, { recursive: true });
12
+ }
13
+
14
+ const seconds = 1000;
15
+ const minutes = 60*seconds;
16
+ const hours = 60*minutes;
17
+
18
+ export default class LockFile implements Disposable {
19
+
20
+
21
+ static async lock(someKey: string, timeout = 2 * hours, interval = 3000, throwOnFail = true) {
22
+ const sha256 = createHash("sha256");
23
+ const hash = sha256.update(someKey).digest("hex");
24
+
25
+ const lockFile = join(lockFolder, hash + ".lock");
26
+
27
+ let now = Date.now();
28
+ const till = now + timeout;
29
+
30
+ let lastError = null as Error;
31
+
32
+ do {
33
+
34
+ try {
35
+ // check if it exists..
36
+ if (!existsSync(lockFile)) {
37
+ // create and return..
38
+ return new LockFile(lockFile);
39
+ }
40
+
41
+ const stat = statSync(lockFile);
42
+ const past = now - 15000;
43
+ if(stat.mtimeMs < past) {
44
+ // it is dead...
45
+ unlinkSync(lockFile);
46
+ }
47
+ } catch (error){
48
+ // do nothing
49
+ lastError = error;
50
+ // console.error(error);
51
+ }
52
+ await sleep(interval);
53
+ now = Date.now();
54
+ }while(now < till);
55
+
56
+ if (throwOnFail) {
57
+ lastError ??= new TimeoutError("lock timed out");
58
+ throw new Error(`Could not acquire lock ${lockFile}, Reason: ${lastError?.stack ?? lastError?.toString()}`);
59
+ }
60
+
61
+ return {
62
+
63
+ locked: false,
64
+ timer: void 0,
65
+
66
+ [Symbol.dispose]() {
67
+ // do nothing..
68
+ }
69
+ };
70
+ }
71
+
72
+ timer: any;
73
+ public locked: any;
74
+
75
+ constructor(private readonly lockFile) {
76
+
77
+ this.locked = true;
78
+
79
+ writeFileSync(lockFile, "locked", { flag :"wx", flush: true });
80
+
81
+ this.timer = setInterval(() => {
82
+ try {
83
+ const now = new Date();
84
+ utimesSync(lockFile, now, now);
85
+ } catch {
86
+ // do nothing
87
+ }
88
+ }, 1000);
89
+ }
90
+
91
+ [Symbol.dispose]() {
92
+ try {
93
+ clearInterval(this.timer);
94
+ unlinkSync(this.lockFile);
95
+ } catch (error) {
96
+ // ignore error...
97
+ console.error(error);
98
+ }
99
+ }
100
+
101
+ }
@@ -0,0 +1,69 @@
1
+ /* eslint-disable no-console */
2
+ import { SpawnOptionsWithoutStdio, spawn } from "node:child_process";
3
+
4
+ import { color } from "console-log-colors";
5
+
6
+ export const spawnPromise = (path, args?: string[], options?: SpawnOptionsWithoutStdio & { logCommand?: boolean, logData?: boolean, logError?: boolean, throwOnFail?: boolean }) => new Promise<{
7
+ get all(): string;
8
+ pid: number;
9
+ status: number;
10
+ }>((resolve, reject) => {
11
+ const all = [];
12
+ const { logCommand = true, throwOnFail = false, logData = true, logError = true } = options ??= {};
13
+ options.timeout ??= 5*60*1000;
14
+ const cd = spawn(path, args, options);
15
+ const pid = cd.pid;
16
+
17
+ const clear = () => {
18
+ try {
19
+ cd.stderr.removeAllListeners();
20
+ cd.stdout.removeAllListeners();
21
+ cd.removeAllListeners();
22
+ } catch {
23
+ // do nothing...
24
+ }
25
+ };
26
+
27
+ cd.stdout.on("data", (data) => {
28
+ data = data.toString("utf-8");
29
+ all.push(data);
30
+ });
31
+ cd.stderr.on("data", (data) => {
32
+ data = data.toString("utf-8");
33
+ all.push(color.red(data));
34
+ });
35
+
36
+ cd.on("error", (error) => {
37
+ const errorText = color.red(error.stack ?? error.toString());
38
+ all.push(error.stack ?? error.toString());
39
+ if (logData || logError) {
40
+ console.error(errorText);
41
+ }
42
+ reject(error);
43
+ });
44
+ cd.on("close", (status) => {
45
+ if (status>0) {
46
+ if (logError) {
47
+ console.error(all);
48
+ }
49
+ if (throwOnFail) {
50
+ clear();
51
+ reject(new Error(all.join("\n")));
52
+ return;
53
+ }
54
+ }
55
+ if (logCommand) {
56
+ console.log(`Spawn: ${path} ${JSON.stringify(args, undefined, 2)}`);
57
+ if (logData) {
58
+ console.log(all.join("\n"));
59
+ }
60
+ }
61
+ clear();
62
+ resolve({
63
+ get all() {
64
+ return all.join("\n");
65
+ },
66
+ pid,
67
+ status });
68
+ });
69
+ });
@@ -2,6 +2,7 @@ import { existsSync, unlinkSync, mkdirSync } from "fs";
2
2
 
3
3
  import { dirname } from "path";
4
4
  import { AsyncFileStream } from "./AsyncStream.js";
5
+ import { stat } from "fs/promises";
5
6
 
6
7
  export function ensureParentFolder(filePath: string) {
7
8
  ensureDir(dirname(filePath));
@@ -23,6 +24,17 @@ export default function ensureDir(folder: string) {
23
24
 
24
25
  }
25
26
 
27
+ export const fileInfo = async (filePath: string) => {
28
+ try {
29
+ return await stat(filePath);
30
+ } catch (error) {
31
+ if (error.code === "ENOENT") {
32
+ return null;
33
+ }
34
+ throw error;
35
+ }
36
+ };
37
+
26
38
  export const deleteIfExists = (path) => existsSync(path) ? unlinkSync(path) : void 0;
27
39
 
28
40
  export const areFilesEqual = async (file1: string, file2: string) => {
@@ -0,0 +1,78 @@
1
+ const k = 1000;
2
+ const m = k*k;
3
+ const b = m*k;
4
+
5
+ const kb = 1024;
6
+ const mb = kb*kb;
7
+ const gb = mb*kb;
8
+ const tb = gb*kb;
9
+ const pb = tb*kb;
10
+
11
+ export const toCompactFixed = (n: number, decimals: number) => {
12
+ if (decimals > 0) {
13
+ if (n>0) {
14
+ return (+(n).toFixed(decimals)).toString();
15
+ }
16
+ }
17
+ return n.toFixed();
18
+ };
19
+
20
+ export const toKMBString = (n: number, decimals = 0, def = "") => {
21
+ if (!n) {
22
+ return def + "";
23
+ }
24
+ if (n >= b) {
25
+ return toCompactFixed((n/b), decimals) + "B";
26
+ }
27
+ if (n >= m) {
28
+ return toCompactFixed((n / m), decimals) + "M";
29
+ }
30
+ if (n >= k) {
31
+ return toCompactFixed((n / k), decimals) + "K";
32
+ }
33
+ return toCompactFixed(n, 0);
34
+ };
35
+
36
+ export const toFileSize = (n: number, decimals = 0, def = "") => {
37
+ n = Number(n);
38
+ if (n === 0) {
39
+ return "0 B";
40
+ }
41
+ if (!n) {
42
+ return def + "";
43
+ }
44
+ if (n >= pb) {
45
+ return toCompactFixed((n/pb), decimals) + " PB";
46
+ }
47
+ if (n >= tb) {
48
+ return toCompactFixed((n/tb), decimals) + " TB";
49
+ }
50
+ if (n >= gb) {
51
+ return toCompactFixed((n/gb), decimals) + " GB";
52
+ }
53
+ if (n >= mb) {
54
+ return toCompactFixed((n / mb), decimals) + " MB";
55
+ }
56
+ if (n >= kb) {
57
+ return toCompactFixed((n / kb), decimals) + " KB";
58
+ }
59
+ return toCompactFixed(n, 0);
60
+ };
61
+
62
+ export interface IFormat {
63
+ prefix: string;
64
+ suffix: string;
65
+ numberToText?: (n: number) => string;
66
+ }
67
+
68
+ export const formatNumber = (n: number, {
69
+ prefix = "",
70
+ suffix = "",
71
+ def = "",
72
+ numberToText = toKMBString} = {}) => {
73
+ if (!n) {
74
+ return def;
75
+ }
76
+ const text = numberToText(n);
77
+ return prefix + text + suffix;
78
+ };
@@ -0,0 +1,30 @@
1
+ import { createHash } from "node:crypto";
2
+ import { utimes, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileInfo } from "./FileApi.js";
5
+
6
+ export default class RunOnce {
7
+
8
+ static async canRun(key: string, maxAge = 60*1000) {
9
+
10
+ const sha256 = createHash("sha256");
11
+ const hash = sha256.update(`run-once-file-${key}`).digest("hex");
12
+ const lockFile = join("/tmp", hash + ".last-run");
13
+
14
+ const info = await fileInfo(lockFile);
15
+ if (!info) {
16
+ await writeFile(lockFile, "running");
17
+ return true;
18
+ }
19
+
20
+ const past = Date.now() - maxAge;
21
+ if (info.mtimeMs > past) {
22
+ return false;
23
+ }
24
+ await writeFile(lockFile, "running");
25
+ const now = new Date();
26
+ await utimes(lockFile, now, now);
27
+ return true;
28
+ }
29
+
30
+ }
@@ -0,0 +1,3 @@
1
+ export default class TimeoutError extends Error {
2
+
3
+ }
@@ -163,6 +163,9 @@ export default class EntityAccessServer {
163
163
 
164
164
  let items;
165
165
 
166
+ if (trace) {
167
+ q = q.trace(console.log);
168
+ }
166
169
 
167
170
  if (count) {
168
171
  const total = await oq.slice().count();
@@ -182,9 +185,6 @@ export default class EntityAccessServer {
182
185
  .some();
183
186
  }
184
187
 
185
- if (trace) {
186
- q = q.trace(console.log);
187
- }
188
188
  items = await q.toArray();
189
189
  return DbJsonService.toJson(db, {
190
190
  total: 0,