@e-mc/core 0.13.6 → 0.13.7

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 (3) hide show
  1. package/README.md +13 -11
  2. package/index.js +394 -275
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## Interface
11
11
 
12
- * [View Source](https://www.unpkg.com/@e-mc/types@0.13.6/lib/index.d.ts)
12
+ * [View Source](https://www.unpkg.com/@e-mc/types@0.13.7/lib/index.d.ts)
13
13
 
14
14
  ```typescript
15
15
  import type { DataSource, LogStatus, WorkerAction } from "./squared";
@@ -74,6 +74,7 @@ interface HostConstructor extends ModuleConstructor {
74
74
  getThreadCount(full: true): ThreadCountStat;
75
75
  getThreadCount(username: string, iv?: BinaryLike): ThreadCountStat;
76
76
  getThreadCount(username?: string | boolean, iv?: BinaryLike): number;
77
+ parseIp(value: unknown, kind?: "ipv4" | "ipv6"): string;
77
78
  getPermissionFromSettings(freeze?: boolean): IPermission;
78
79
  readonly prototype: IHost;
79
80
  new(config?: HostInitConfig): IHost;
@@ -102,11 +103,11 @@ interface IClientDb extends IClient<IHost, ClientModule<ClientDbSettings>> {
102
103
  hasCache(source: string, sessionKey?: string): boolean;
103
104
  hasCoerce(source: string, component: keyof DbCoerceSettings, uuidKey: string | undefined): boolean;
104
105
  hasCoerce(source: string, component: keyof DbCoerceSettings, credential?: unknown): boolean;
106
+ getQueryResult(source: string, credential: unknown, queryString: string, options: CacheOptions): QueryResult | undefined;
105
107
  getQueryResult(source: string, credential: unknown, queryString: string, renewCache: boolean): QueryResult | undefined;
106
108
  getQueryResult(source: string, credential: unknown, queryString: string, sessionKey?: string, renewCache?: boolean): QueryResult | undefined;
107
- getQueryResult(source: string, credential: unknown, queryString: string, options?: CacheOptions, renewCache?: boolean): QueryResult | undefined;
109
+ setQueryResult(source: string, credential: unknown, queryString: string, result: unknown, options: CacheOptions): QueryResult;
108
110
  setQueryResult(source: string, credential: unknown, queryString: string, result: unknown, sessionKey?: string): QueryResult;
109
- setQueryResult(source: string, credential: unknown, queryString: string, result: unknown, options?: CacheOptions): QueryResult;
110
111
  getCacheResult(source: string, credential: unknown, queryString: string, cacheValue: CacheOptions, ignoreCache?: unknown): QueryResult | undefined;
111
112
  applyState(items: DataSource | DataSource[], value: number, as?: boolean): void;
112
113
  commit(items?: DataSource[]): Promise<boolean>;
@@ -130,7 +131,8 @@ interface ClientDbConstructor extends ClientConstructor<IHost, ClientModule> {
130
131
  loadSettings(settings: Pick<Settings, "process" | "memory">, password?: string): boolean;
131
132
  getTimeout(value: number | string | TimeoutAction | undefined): number;
132
133
  convertTime(value: number | string): number;
133
- findResult(source: string, credential: unknown, queryString: string, timeout: number, sessionKey?: string | boolean, renewCache?: boolean): QueryResult | undefined;
134
+ findResult(source: string, credential: unknown, queryString: string, timeout: number, renewCache: boolean): QueryResult | undefined;
135
+ findResult(source: string, credential: unknown, queryString: string, timeout: number, sessionKey?: string, renewCache?: boolean): QueryResult | undefined;
134
136
  storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, options: StoreResultOptions): QueryResult;
135
137
  /** @deprecated */
136
138
  storeResult(source: string, credential: unknown, queryString: string, result: QueryResult, sessionKey: string, sessionExpires: number): QueryResult;
@@ -322,13 +324,13 @@ NOTE: **@e-mc/core** is mostly a collection of abstract base classes which canno
322
324
 
323
325
  ## References
324
326
 
325
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/squared.d.ts
326
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/core.d.ts
327
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/db.d.ts
328
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/dom.d.ts
329
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/logger.d.ts
330
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/node.d.ts
331
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/settings.d.ts
327
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/squared.d.ts
328
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/core.d.ts
329
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/db.d.ts
330
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/dom.d.ts
331
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/logger.d.ts
332
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/node.d.ts
333
+ - https://www.unpkg.com/@e-mc/types@0.13.7/lib/settings.d.ts
332
334
 
333
335
  * https://www.npmjs.com/package/@types/node
334
336
 
package/index.js CHANGED
@@ -19,15 +19,8 @@ const CACHE_SOURCE = Object.create(null);
19
19
  const CACHE_SESSION = Object.create(null);
20
20
  const ABORT_LISTENER = new WeakMap();
21
21
  const HOST = {
22
- DONE: new Map(),
23
- QUEUE: [],
24
22
  QUEUE_LIMIT: Infinity,
25
23
  QUEUE_EXPIRES: 86400000,
26
- PID: 1,
27
- CLOSED: 0,
28
- QUEUED: 0,
29
- REJECTED: 0,
30
- KILLED: 0,
31
24
  CIPHER: null,
32
25
  ADMIN_USERS: [],
33
26
  ADMIN_PRIVATE: false,
@@ -35,11 +28,7 @@ const HOST = {
35
28
  THREAD_EXPIRES: 86400000,
36
29
  PRIORITY_MIN: 0,
37
30
  PRIORITY_MAX: 100,
38
- PRIORITY_BYPASS: undefined,
39
- LOG_PROGRESS: null,
40
- LOG_DELAYED: [],
41
- LOG_PROGRESS_QUEUE: new WeakSet(),
42
- LOG_PROGRESS_PAUSED: new Set()
31
+ PRIORITY_BYPASS: undefined
43
32
  };
44
33
  const WORKER_GROUP = {
45
34
  MAX: module_1.availableParallelism(),
@@ -49,119 +38,23 @@ const WORKER_GROUP = {
49
38
  const WORKER_CHANNEL = {
50
39
  MIN: 0,
51
40
  MAX: Math.max(WORKER_GROUP.MAX / 2, 2),
52
- TIMEOUT: 300 * 1000,
41
+ TIMEOUT: 5 * 60000,
53
42
  VERBOSE: true
54
43
  };
44
+ const GC = {
45
+ EXPIRES: 600,
46
+ EXPIRES_LIMIT: 2592000,
47
+ EXPIRES_DEFAULT: false,
48
+ INTERVAL: 60000
49
+ };
55
50
  let STORE_RESULT_COUNT = 0;
56
51
  let PICOMATCH_OPTIONS = { nocase: module_1.PLATFORM_WIN32 };
57
52
  let MINIMATCH_OPTIONS = {};
58
53
  let MINIMATCH;
59
54
  let PERMISSION;
60
- function closeHostThread(host) {
61
- HOST.DONE.delete(host);
62
- resumeHostLog(host);
63
- ++HOST.CLOSED;
64
- const current = Date.now();
65
- for (let i = 0; i < HOST.QUEUE.length; ++i) {
66
- const item = HOST.QUEUE[i];
67
- const { instance, startTime } = item;
68
- if (startTime + HOST.QUEUE_EXPIRES <= current) {
69
- cancelHostThread(instance, 'expired: ' + (0, types_1.formatTime)(startTime, true));
70
- HOST.QUEUE.splice(i--, 1);
71
- resumeHostQueue(item, true);
72
- }
73
- }
74
- if (HOST.QUEUE.length > 0 && HOST.DONE.size < HOST.THREAD_LIMIT) {
75
- const queue = HOST.QUEUE.shift();
76
- HOST.DONE.set(queue.instance, HOST.PID++);
77
- resumeHostQueue(queue);
78
- HOST.QUEUE.forEach((item, index) => {
79
- notifyHostQueue(item.instance, index, item.priority, true);
80
- });
81
- }
82
- }
83
- function cancelHostThread(host, hint, rejected) {
84
- host.writeFail(["Transaction was cancelled", host.username], (0, types_1.errorValue)("Thread was killed", hint), { type: 2, fatal: true });
85
- host.abort();
86
- resetHostLog(host);
87
- if (rejected) {
88
- ++HOST.REJECTED;
89
- }
90
- else {
91
- ++HOST.KILLED;
92
- }
93
- }
94
- function resetHostLog(host) {
95
- if (HOST.LOG_PROGRESS === host) {
96
- HOST.LOG_PROGRESS = null;
97
- HOST.LOG_DELAYED.length = 0;
98
- }
99
- HOST.LOG_PROGRESS_QUEUE.delete(host);
100
- HOST.LOG_PROGRESS_PAUSED.delete(host);
101
- }
102
- function resumeHostLog(host) {
103
- resetHostLog(host);
104
- if (!HOST.LOG_PROGRESS) {
105
- for (const item of HOST.LOG_PROGRESS_PAUSED) {
106
- item.resumeLog('progress');
107
- }
108
- }
109
- }
110
- function notifyHostQueue(host, position, priority, broadcast) {
111
- if (!broadcast || host.broadcastId) {
112
- host.formatMessage(2, 'QUEUE', ["Thread limit exceeded" + ` (position #${position + 1})`, host.username], 'Priority: ' + priority, { type: 2, ...module_1.LOG_STYLE_WARN });
113
- }
114
- }
115
- function addHostQueue(item) {
116
- HOST.QUEUE.push(item);
117
- HOST.QUEUE.sort((a, b) => {
118
- const au = a.instance.username;
119
- const bu = b.instance.username;
120
- if (au && !bu) {
121
- return -1;
122
- }
123
- if (!au && bu) {
124
- return 1;
125
- }
126
- return a.priority - b.priority;
127
- });
128
- }
129
- function resumeHostQueue(item, aborted) {
130
- if (!item.joined) {
131
- return;
132
- }
133
- const { instance, args = [], startTime } = item;
134
- if (!aborted) {
135
- if (HOST.LOG_PROGRESS) {
136
- instance.pauseLog('progress');
137
- }
138
- else if (HOST.LOG_PROGRESS_QUEUE.has(instance)) {
139
- HOST.LOG_PROGRESS = instance;
140
- instance.resumeLog('progress');
141
- for (const host of HOST.DONE.keys()) {
142
- if (host !== instance) {
143
- host.pauseLog('progress');
144
- }
145
- }
146
- }
147
- HOST.LOG_PROGRESS_QUEUE.delete(instance);
148
- instance.writeTimeElapsed('JOIN', ['Thread restarting...', HOST.THREAD_LIMIT > 1 ? 'Availability: ' + HOST.DONE.size + ' / ' + HOST.THREAD_LIMIT : ''], startTime, { type: 2, ...module_1.LOG_STYLE_WARN });
149
- }
150
- queueMicrotask(() => {
151
- try {
152
- instance.resumeThread({ args, startTime, aborted });
153
- }
154
- catch {
155
- }
156
- });
157
- }
158
55
  function encryptText(data, iv) {
159
56
  return HOST.CIPHER && (0, types_1.isString)(data) && (0, types_1.encryptUTF8)(HOST.CIPHER.algorithm, HOST.CIPHER.key, iv, data) || '';
160
57
  }
161
- function getSettingsValue(options, name, component) {
162
- const result = options[name];
163
- return component ? (0, types_1.isObject)(result) ? result[component] : undefined : result;
164
- }
165
58
  function isInvalidRange(result, range) {
166
59
  const [lower, upper = 0] = range;
167
60
  return Array.isArray(result) && (result.length <= lower || upper > 0 && result.length > upper);
@@ -173,30 +66,8 @@ function asPosix(value) {
173
66
  }
174
67
  return path.normalize(value);
175
68
  }
176
- function parseIp(value, kind) {
177
- if (value) {
178
- try {
179
- let ip = ipaddr.parse(value);
180
- if (ip.kind() === 'ipv4') {
181
- if (kind === 6) {
182
- ip = ip.toIPv4MappedAddress();
183
- }
184
- }
185
- else if (kind === 4) {
186
- if (!ip.isIPv4MappedAddress()) {
187
- return '';
188
- }
189
- ip = ip.toIPv4Address();
190
- }
191
- return ip.toNormalizedString();
192
- }
193
- catch {
194
- }
195
- }
196
- return '';
197
- }
198
69
  const readable = (value) => (0, types_1.isString)(value) || (0, types_1.isArray)(value);
199
- const expireTime = (value) => Date.now() + value * 1000;
70
+ const expireTime = (value) => Date.now() + Math.min(Math.trunc(value * 1000), GC.EXPIRES_LIMIT);
200
71
  const convertSeconds = (value) => typeof value === 'string' && Math.ceil((0, types_1.parseTime)(value) / 1000) || 0;
201
72
  class DbLRU {
202
73
  static get(source) {
@@ -215,28 +86,38 @@ class DbLRU {
215
86
  }
216
87
  return false;
217
88
  }
218
- items = [];
219
89
  config = null;
90
+ items = [];
220
91
  refresh(percent) {
221
92
  const { items, config } = this;
222
93
  let result = 0;
223
94
  if (config && items.length >= config.limit || percent) {
224
95
  percent = Math.min(1, percent ?? config.percent);
96
+ const length = items.length;
225
97
  if (percent === 1) {
226
- result = items.length;
227
- for (let i = 0; i < result; ++i) {
98
+ for (let i = 0; i < length; ++i) {
228
99
  items[i].delete(false);
229
100
  }
101
+ result = length;
230
102
  items.length = 0;
231
103
  }
232
104
  else {
105
+ const data = items.slice(0);
233
106
  const min = config?.min || 0;
234
- const data = items.slice(0).sort((a, b) => a.lastAccessed - b.lastAccessed);
235
- for (let i = 0, length = Math.floor(data.length * Math.min(percent, 1)); i < length; ++i) {
107
+ let lfu = config?.type === 'lfu';
108
+ if (lfu) {
109
+ data.sort((a, b) => (a.accessCount - b.accessCount) || (a.lastAccessed - b.lastAccessed));
110
+ }
111
+ else {
112
+ data.sort((a, b) => a.lastAccessed - b.lastAccessed);
113
+ lfu = min === 0;
114
+ }
115
+ for (let i = 0, j = Math.floor(length * Math.min(percent, 1)); i < length && j > 0; ++i) {
236
116
  const item = data[i];
237
- if (min === 0 || item.accessCount < min) {
117
+ if (lfu || item.accessCount < min) {
238
118
  item.delete(true);
239
119
  ++result;
120
+ --j;
240
121
  }
241
122
  }
242
123
  }
@@ -249,10 +130,10 @@ class DbCache {
249
130
  user;
250
131
  query;
251
132
  value;
252
- expires;
253
133
  accessCount = 0;
254
134
  lastAccessed = Date.now();
255
135
  disposed = false;
136
+ expires;
256
137
  userData;
257
138
  queryData;
258
139
  sourceList;
@@ -261,13 +142,18 @@ class DbCache {
261
142
  this.user = user;
262
143
  this.query = query;
263
144
  this.value = value;
264
- this.expires = expires;
265
- this.userData = CACHE_USER[source] ||= Object.create(null);
266
- this.queryData = this.userData[user] ||= Object.create(null);
267
- this.queryData[query] = this;
145
+ this.userData = CACHE_USER[source] ||= new Map();
146
+ let data = this.userData.get(user);
147
+ if (!data) {
148
+ data = {};
149
+ this.userData.set(user, data);
150
+ }
151
+ data[query] = this;
152
+ this.queryData = data;
268
153
  this.sourceList = DbLRU.get(source);
269
154
  this.sourceList.refresh();
270
155
  this.sourceList.items.push(this);
156
+ this.expires = expireTime(expires);
271
157
  }
272
158
  get(seconds) {
273
159
  const current = Date.now();
@@ -329,7 +215,7 @@ class DbCache {
329
215
  }
330
216
  }
331
217
  if (!found) {
332
- delete this.userData[this.user];
218
+ this.userData.delete(this.user);
333
219
  }
334
220
  if (typeof value === 'string') {
335
221
  try {
@@ -349,41 +235,254 @@ class DbCache {
349
235
  }
350
236
  class DbSession {
351
237
  source;
352
- sourceData = Object.create(null);
238
+ timeout;
239
+ sourceData = new Map();
353
240
  constructor(source) {
354
241
  this.source = source;
355
- const timer = setInterval(() => {
356
- this.clear();
357
- if (this.users.length === 0) {
358
- CACHE_SESSION[source] = undefined;
359
- clearInterval(timer);
360
- }
361
- }, 60000);
242
+ this.timeout = setInterval(() => {
243
+ this.cleanup();
244
+ this.refresh();
245
+ }, GC.INTERVAL);
362
246
  }
363
247
  set(user, key, result, seconds) {
364
- let data = this.sourceData[user];
248
+ let data = this.sourceData.get(user);
365
249
  if (!data) {
366
- data = [Object.create(null), Date.now() + Math.trunc(seconds * 1000)];
367
- this.sourceData[user] = data;
250
+ data = [Object.create(null), expireTime(seconds)];
251
+ this.sourceData.set(user, data);
368
252
  }
369
253
  data[0][key] = result;
370
254
  }
371
255
  get(user, key) {
372
- return this.sourceData[user]?.[0][key];
256
+ return this.sourceData.get(user)?.[0][key];
373
257
  }
374
- clear() {
258
+ refresh() {
259
+ if (this.done) {
260
+ CACHE_SESSION[this.source] = undefined;
261
+ if (this.timeout) {
262
+ clearInterval(this.timeout);
263
+ this.timeout = null;
264
+ }
265
+ }
266
+ }
267
+ cleanup() {
375
268
  const current = Date.now();
376
- for (const user of this.users) {
377
- const expires = this.sourceData[user][1];
378
- if (expires >= current) {
379
- delete this.sourceData[user];
269
+ for (const [user, item] of this.sourceData) {
270
+ if (item[1] >= current) {
271
+ this.sourceData.delete(user);
272
+ }
273
+ }
274
+ }
275
+ destroy() {
276
+ this.sourceData.clear();
277
+ this.refresh();
278
+ }
279
+ get done() {
280
+ return this.sourceData.size === 0;
281
+ }
282
+ }
283
+ class HostQueue {
284
+ thread;
285
+ host;
286
+ priority;
287
+ joined = false;
288
+ args = [];
289
+ startTime = Date.now();
290
+ constructor(thread, host, priority) {
291
+ this.thread = thread;
292
+ this.host = host;
293
+ this.priority = priority;
294
+ }
295
+ join(args) {
296
+ if (!this.joined) {
297
+ if (Array.isArray(args)) {
298
+ this.args = args;
299
+ }
300
+ this.joined = true;
301
+ this.thread.queued++;
302
+ return true;
303
+ }
304
+ return false;
305
+ }
306
+ resume(aborted) {
307
+ if (!this.joined) {
308
+ return;
309
+ }
310
+ const { thread, host, args, startTime } = this;
311
+ if (!aborted) {
312
+ if (thread.progress) {
313
+ host.pauseLog('progress');
314
+ }
315
+ else if (thread.foreground.has(host)) {
316
+ thread.progress = host;
317
+ host.resumeLog('progress');
318
+ for (const pended of thread.items.keys()) {
319
+ if (pended !== host) {
320
+ host.pauseLog('progress');
321
+ }
322
+ }
323
+ }
324
+ host.writeTimeElapsed('JOIN', ['Thread restarting...', HOST.THREAD_LIMIT > 1 ? `Availability: ${thread.size} / ${HOST.THREAD_LIMIT}` : ''], startTime, { type: 2, ...module_1.LOG_STYLE_WARN });
325
+ }
326
+ thread.foreground.delete(host);
327
+ if (typeof host.resumeThread === 'function') {
328
+ queueMicrotask(() => host.resumeThread({ args, startTime, aborted }));
329
+ }
330
+ }
331
+ notify(broadcast, position = this.position) {
332
+ const host = this.host;
333
+ if (!broadcast || host.broadcastId) {
334
+ host.formatMessage(2, 'QUEUE', ["Thread limit exceeded" + ` (position #${position + 1})`, host.username], 'Priority: ' + this.priority, { type: 2, ...module_1.LOG_STYLE_WARN });
335
+ }
336
+ }
337
+ get position() {
338
+ return this.thread.position(this.host);
339
+ }
340
+ }
341
+ class HostThread {
342
+ pid = 1;
343
+ queued = 0;
344
+ closed = 0;
345
+ rejected = 0;
346
+ killed = 0;
347
+ progress = null;
348
+ items = new Map();
349
+ queue = [];
350
+ paused = new Set();
351
+ foreground = new WeakSet();
352
+ messages = [];
353
+ *[Symbol.iterator]() {
354
+ for (const item of this.items) {
355
+ yield item;
356
+ }
357
+ }
358
+ add(host, progress) {
359
+ this.items.set(host, this.pid++);
360
+ if (this.progress) {
361
+ host.pauseLog('progress');
362
+ if (progress) {
363
+ this.foreground.add(host);
364
+ }
365
+ }
366
+ else if (progress) {
367
+ this.progress = host;
368
+ host['_logDelayed'] = [];
369
+ }
370
+ }
371
+ pause(host) {
372
+ this.paused.add(host);
373
+ }
374
+ cancel(host, hint, rejected = false) {
375
+ host.writeFail(["Transaction was cancelled", host.username], (0, types_1.errorValue)("Thread was killed", hint), { type: 2, fatal: true });
376
+ host.abort();
377
+ this.resetLog(host);
378
+ if (rejected) {
379
+ this.rejected++;
380
+ }
381
+ else {
382
+ this.killed++;
383
+ }
384
+ }
385
+ close(host) {
386
+ this.items.delete(host);
387
+ this.resumeLog(host);
388
+ this.closed++;
389
+ const current = Date.now();
390
+ const queue = this.queue;
391
+ for (let i = 0; i < queue.length; ++i) {
392
+ const item = queue[i];
393
+ if (current >= item.startTime + HOST.QUEUE_EXPIRES) {
394
+ this.cancel(item.host, 'expired: ' + (0, types_1.formatTime)(item.startTime, true));
395
+ item.resume(true);
396
+ queue.splice(i--, 1);
397
+ }
398
+ }
399
+ if (queue.length > 0 && this.size < HOST.THREAD_LIMIT) {
400
+ for (let i = 0; i < queue.length; ++i) {
401
+ const item = queue[i];
402
+ if (item.joined) {
403
+ this.add(item.host);
404
+ item.resume(false);
405
+ queue.splice(i, 1);
406
+ break;
407
+ }
408
+ }
409
+ queue.forEach((item, index) => item.notify(true, index));
410
+ }
411
+ }
412
+ kill() {
413
+ const result = new Map();
414
+ const current = Date.now();
415
+ for (const [host, pid] of this.items) {
416
+ if (host.done) {
417
+ this.close(host);
380
418
  }
419
+ else if (host.startTime + HOST.THREAD_EXPIRES >= current) {
420
+ host.writeFail(["Transaction was cancelled", host.username], (0, types_1.errorValue)("Timeout was exceeded", (0, types_1.formatTime)(HOST.THREAD_EXPIRES)), { fatal: true });
421
+ host.abort();
422
+ this.close(host);
423
+ }
424
+ else {
425
+ result.set(host, pid);
426
+ }
427
+ }
428
+ return result;
429
+ }
430
+ resumeLog(host) {
431
+ this.resetLog(host);
432
+ if (!this.progress) {
433
+ for (const item of this.paused) {
434
+ item.resumeLog('progress');
435
+ }
436
+ }
437
+ }
438
+ resetLog(host) {
439
+ if (this.progress === host) {
440
+ this.progress = null;
441
+ this.messages.length = 0;
381
442
  }
443
+ this.foreground.delete(host);
444
+ this.paused.delete(host);
382
445
  }
383
- get users() {
384
- return Object.keys(this.sourceData);
446
+ schedule(host, priority) {
447
+ const item = new HostQueue(this, host, priority);
448
+ this.queue.push(item);
449
+ this.queue.sort((a, b) => {
450
+ const au = a.host.username;
451
+ const bu = b.host.username;
452
+ if (au && !bu) {
453
+ return -1;
454
+ }
455
+ if (!au && bu) {
456
+ return 1;
457
+ }
458
+ return a.priority - b.priority;
459
+ });
460
+ item.notify(false);
461
+ }
462
+ join(host, options = {}) {
463
+ const index = this.position(host);
464
+ if (index !== -1) {
465
+ const queue = this.queue[index];
466
+ if (options.reject) {
467
+ this.queue.splice(index, 1);
468
+ return false;
469
+ }
470
+ queue.join(options.args);
471
+ return true;
472
+ }
473
+ return false;
474
+ }
475
+ position(host) {
476
+ return this.queue.findIndex(item => item.host === host);
477
+ }
478
+ get pending() {
479
+ return this.queue.length;
480
+ }
481
+ get size() {
482
+ return this.items.size;
385
483
  }
386
484
  }
485
+ const HOST_MAIN = new HostThread();
387
486
  class Host extends module_1 {
388
487
  static [kHost] = true;
389
488
  static async purgeMemory(percent = 1, limit = 0, parent) {
@@ -539,10 +638,10 @@ class Host extends module_1 {
539
638
  }
540
639
  let result = 0;
541
640
  if (HOST.ADMIN_USERS.includes(username) || iv && HOST.ADMIN_USERS.includes(encryptText(username, iv))) {
542
- for (const [host, id] of HOST.DONE) {
641
+ for (const [host, id] of HOST_MAIN) {
543
642
  if (pid === true || id === pid || Array.isArray(pid) && pid.includes(id)) {
544
- cancelHostThread(host, 'pid: ' + id.toString());
545
- closeHostThread(host);
643
+ HOST_MAIN.cancel(host, `pid: ${id}`);
644
+ HOST_MAIN.close(host);
546
645
  ++result;
547
646
  }
548
647
  }
@@ -569,24 +668,10 @@ class Host extends module_1 {
569
668
  if (HOST.ADMIN_PRIVATE && !username) {
570
669
  return full ? { count: -1 } : -1;
571
670
  }
572
- const current = Date.now();
573
- const items = [];
574
- for (const [host, pid] of HOST.DONE) {
575
- if (host.done) {
576
- closeHostThread(host);
577
- }
578
- else if (host.startTime + HOST.THREAD_EXPIRES >= current) {
579
- host.writeFail(["Transaction was cancelled", host.username], (0, types_1.errorValue)("Timeout was exceeded", (0, types_1.formatTime)(HOST.THREAD_EXPIRES)), { fatal: true });
580
- host.abort();
581
- closeHostThread(host);
582
- }
583
- else {
584
- items.push([host, pid]);
585
- }
586
- }
671
+ const items = Array.from(HOST_MAIN.kill());
587
672
  const count = items.length;
588
673
  if (full) {
589
- const result = { count, pending: HOST.QUEUE.length, opened: HOST.DONE.size, closed: HOST.CLOSED, queued: HOST.QUEUED, rejected: HOST.REJECTED, killed: HOST.KILLED };
674
+ const result = { count, pending: HOST_MAIN.pending, opened: HOST_MAIN.size, closed: HOST_MAIN.closed, queued: HOST_MAIN.queued, rejected: HOST_MAIN.rejected, killed: HOST_MAIN.killed };
590
675
  if (count) {
591
676
  if (count > 1) {
592
677
  items.sort((a, b) => a[0].startTime - b[0].startTime);
@@ -617,8 +702,34 @@ class Host extends module_1 {
617
702
  }
618
703
  return count;
619
704
  }
705
+ static parseIp(value, kind) {
706
+ if ((0, types_1.isString)(value)) {
707
+ try {
708
+ let ip = ipaddr.parse(value);
709
+ if (kind) {
710
+ if (ip.kind() === 'ipv4') {
711
+ if (kind === 'ipv6') {
712
+ ip = ip.toIPv4MappedAddress();
713
+ }
714
+ }
715
+ else if (kind === 'ipv4') {
716
+ if (ip.isIPv4MappedAddress()) {
717
+ ip = ip.toIPv4Address();
718
+ }
719
+ else {
720
+ return '';
721
+ }
722
+ }
723
+ }
724
+ return ip.toNormalizedString();
725
+ }
726
+ catch {
727
+ }
728
+ }
729
+ return '';
730
+ }
620
731
  static getLogDelayed() {
621
- return HOST.LOG_DELAYED;
732
+ return HOST_MAIN.messages;
622
733
  }
623
734
  static getPermissionFromSettings(freeze = true) {
624
735
  return freeze ? PERMISSION : Permission.clone(PERMISSION);
@@ -670,22 +781,17 @@ class Host extends module_1 {
670
781
  this.ignoreLog(typeof log === 'boolean' ? !log : log);
671
782
  }
672
783
  this.#config = Object.freeze({ ...config });
673
- if (HOST.DONE.size < HOST.THREAD_LIMIT || this.username && typeof priority === 'number' && priority === HOST.PRIORITY_BYPASS) {
674
- HOST.DONE.set(this, HOST.PID++);
675
- if (HOST.LOG_PROGRESS) {
676
- this.pauseLog('progress');
677
- }
678
- else if (showProgress) {
679
- HOST.LOG_PROGRESS = this;
680
- this._logDelayed = [];
681
- }
784
+ if (HOST_MAIN.size >= HOST.THREAD_LIMIT) {
785
+ HOST_MAIN.kill();
786
+ }
787
+ if (HOST_MAIN.size < HOST.THREAD_LIMIT || this.username && typeof priority === 'number' && priority === HOST.PRIORITY_BYPASS) {
788
+ HOST_MAIN.add(this, showProgress);
682
789
  }
683
- else if (typeof priority === 'number' && priority >= HOST.PRIORITY_MIN && priority <= HOST.PRIORITY_MAX && HOST.QUEUE.length < HOST.QUEUE_LIMIT && typeof this.resumeThread === 'function') {
684
- addHostQueue({ instance: this, priority, startTime: Date.now() });
685
- notifyHostQueue(this, HOST.QUEUE.findIndex(item => item.instance === this), priority);
790
+ else if (typeof priority === 'number' && priority >= HOST.PRIORITY_MIN && priority <= HOST.PRIORITY_MAX && HOST_MAIN.pending < HOST.QUEUE_LIMIT && typeof this.resumeThread === 'function') {
791
+ HOST_MAIN.schedule(this, priority);
686
792
  }
687
793
  else {
688
- cancelHostThread(this, 'limit: ' + HOST.THREAD_LIMIT.toString(), true);
794
+ HOST_MAIN.cancel(this, `limit: ${HOST.THREAD_LIMIT}`, true);
689
795
  }
690
796
  }
691
797
  restart(...args) { }
@@ -793,7 +899,7 @@ class Host extends module_1 {
793
899
  hasLog(type) {
794
900
  switch (type) {
795
901
  case 'progress':
796
- return this === HOST.LOG_PROGRESS;
902
+ return this === HOST_MAIN.progress;
797
903
  default:
798
904
  return false;
799
905
  }
@@ -801,12 +907,12 @@ class Host extends module_1 {
801
907
  pauseLog(type) {
802
908
  if (type === 'progress') {
803
909
  this._logDelayed ||= [];
804
- HOST.LOG_PROGRESS_PAUSED.add(this);
910
+ HOST_MAIN.pause(this);
805
911
  }
806
912
  this.#logState = 0;
807
913
  }
808
914
  resumeLog(type) {
809
- if (!type || this.hasLog(type) || type === 'progress' && !HOST.LOG_PROGRESS) {
915
+ if (!type || this.hasLog(type) || type === 'progress' && !HOST_MAIN.progress) {
810
916
  this.#logState = 1;
811
917
  const log = this._logDelayed;
812
918
  if (log?.length) {
@@ -827,12 +933,12 @@ class Host extends module_1 {
827
933
  }
828
934
  log.length = 0;
829
935
  }
830
- resumeHostLog(this);
936
+ HOST_MAIN.resumeLog(this);
831
937
  }
832
938
  }
833
939
  delayMessage(args) {
834
940
  this._logDelayed?.push(args);
835
- HOST.LOG_DELAYED.push(args);
941
+ HOST_MAIN.messages.push(args);
836
942
  }
837
943
  retain(process) {
838
944
  this.subProcesses.add(process);
@@ -862,27 +968,7 @@ class Host extends module_1 {
862
968
  return null;
863
969
  }
864
970
  joinQueue(options) {
865
- const index = HOST.QUEUE.findIndex(item => item.instance === this);
866
- if (index !== -1) {
867
- const queue = HOST.QUEUE[index];
868
- let args, reject;
869
- if (options) {
870
- ({ args, reject } = options);
871
- }
872
- if (reject) {
873
- HOST.QUEUE.splice(index, 1);
874
- return false;
875
- }
876
- if (Array.isArray(args)) {
877
- queue.args = args;
878
- }
879
- if (!queue.joined) {
880
- queue.joined = true;
881
- ++HOST.QUEUED;
882
- }
883
- return true;
884
- }
885
- return false;
971
+ return HOST_MAIN.join(this, options);
886
972
  }
887
973
  set host(value) { }
888
974
  get host() {
@@ -895,22 +981,22 @@ class Host extends module_1 {
895
981
  return this.#username;
896
982
  }
897
983
  get ipV4() {
898
- return parseIp(this.config.remoteIp, 4);
984
+ return Host.parseIp(this.config.remoteIp, 'ipv4');
899
985
  }
900
986
  get ipV6() {
901
- return parseIp(this.config.remoteIp, 6);
987
+ return Host.parseIp(this.config.remoteIp, 'ipv6');
902
988
  }
903
989
  set done(value) {
904
990
  if (value) {
905
991
  this.#done = true;
906
- closeHostThread(this);
992
+ HOST_MAIN.close(this);
907
993
  }
908
994
  }
909
995
  get done() {
910
996
  return this.#done || this.aborted;
911
997
  }
912
998
  get queued() {
913
- return !!HOST.QUEUE.find(item => item.instance === this);
999
+ return HOST_MAIN.position(this) !== -1;
914
1000
  }
915
1001
  get logState() {
916
1002
  return this.#logState;
@@ -974,7 +1060,7 @@ class Client extends module_1 {
974
1060
  return result;
975
1061
  }
976
1062
  set cacheDir(value) {
977
- if (path.isAbsolute(value) && module_1.isDir(value)) {
1063
+ if (path.isAbsolute(value) && module_1.isDir(value) && this.supportsProperty('cacheDir')) {
978
1064
  this.#cacheDir = value;
979
1065
  }
980
1066
  }
@@ -1022,15 +1108,31 @@ class ClientDb extends Client {
1022
1108
  return false;
1023
1109
  }
1024
1110
  const memory = settings.memory;
1025
- if ((0, types_1.isPlainObject)(memory)) {
1026
- for (const name in memory) {
1111
+ for (const name in memory) {
1112
+ if (name === 'settings') {
1113
+ const gc = memory.settings?.gc;
1114
+ if (gc) {
1115
+ let { expires, interval, expires_limit } = gc;
1116
+ if (expires_limit !== undefined && (expires_limit = (0, types_1.parseExpires)(expires_limit) / 1000) >= 1) {
1117
+ GC.EXPIRES_LIMIT = expires_limit;
1118
+ }
1119
+ if (expires !== undefined && (expires = (0, types_1.parseExpires)(expires) / 1000) > 0) {
1120
+ GC.EXPIRES = Math.min(expires, GC.EXPIRES_LIMIT);
1121
+ GC.EXPIRES_DEFAULT = true;
1122
+ }
1123
+ if (interval !== undefined && (interval = (0, types_1.parseExpires)(interval)) > 0) {
1124
+ GC.INTERVAL = interval;
1125
+ }
1126
+ }
1127
+ }
1128
+ else {
1027
1129
  const client = memory[name];
1028
1130
  if ((0, types_1.isPlainObject)(client)) {
1029
1131
  for (const source in client) {
1030
1132
  const item = client[source];
1031
- if ((0, types_1.isPlainObject)(item) && 'enabled' in item) {
1133
+ if ((0, types_1.isObject)(item) && 'enabled' in item) {
1032
1134
  const key = name + '_' + source;
1033
- const { enabled, limit = 0, min = 0, max = 0 } = item;
1135
+ const { enabled, limit = 0, min = 0, max = 0, type = 'lru' } = item;
1034
1136
  if (enabled && +limit > 0) {
1035
1137
  let percent = item.percent || 0;
1036
1138
  if ((0, types_1.isString)(percent)) {
@@ -1041,6 +1143,7 @@ class ClientDb extends Client {
1041
1143
  }
1042
1144
  if (percent > 0) {
1043
1145
  DbLRU.get(key).config = {
1146
+ type,
1044
1147
  limit: +limit,
1045
1148
  percent: Math.min(percent, 1),
1046
1149
  min: min > 0 ? min : 0,
@@ -1062,7 +1165,7 @@ class ClientDb extends Client {
1062
1165
  return parent ? super.purgeMemory(percent, limit, parent) : 0;
1063
1166
  }
1064
1167
  static getTimeout(value) {
1065
- if (value === undefined) {
1168
+ if (value == null) {
1066
1169
  return 0;
1067
1170
  }
1068
1171
  let result = 0;
@@ -1074,7 +1177,7 @@ class ClientDb extends Client {
1074
1177
  result = convertSeconds(value);
1075
1178
  break;
1076
1179
  case 'object':
1077
- if (value !== null && typeof (result = value.timeout) === 'string') {
1180
+ if (typeof (result = value.timeout) === 'string') {
1078
1181
  result = convertSeconds(value.timeout);
1079
1182
  value.timeout = result;
1080
1183
  }
@@ -1097,15 +1200,14 @@ class ClientDb extends Client {
1097
1200
  static findResult(source, credential, queryString, timeout, sessionKey, renewCache) {
1098
1201
  const userKey = credential ? this.extractUUID(credential) || (0, types_1.hashKey)(this.asString(credential)) : undefined;
1099
1202
  if (userKey) {
1100
- queryString = (0, types_1.hashKey)(queryString);
1101
1203
  if (timeout > 0) {
1102
- const stored = CACHE_USER[source]?.[userKey]?.[queryString];
1204
+ const stored = CACHE_USER[source]?.get(userKey)?.[(0, types_1.hashKey)(queryString)];
1103
1205
  if (stored) {
1104
1206
  return stored.get(renewCache ? timeout : 0);
1105
1207
  }
1106
1208
  }
1107
1209
  else if (sessionKey) {
1108
- return this.findSession(source, userKey + sessionKey, queryString);
1210
+ return this.findSession(source, userKey + sessionKey, (0, types_1.hashKey)(queryString));
1109
1211
  }
1110
1212
  }
1111
1213
  }
@@ -1122,21 +1224,22 @@ class ClientDb extends Client {
1122
1224
  }
1123
1225
  let timeout = 0, userKey, whenEmpty, partition = false;
1124
1226
  switch (typeof cache) {
1125
- case 'object': {
1126
- let seconds, dir;
1127
- ({ timeout: seconds, when_empty: whenEmpty, dir } = cache);
1128
- if (dir && (!cacheDir || module_1.isDir(dir))) {
1129
- cacheDir = dir;
1130
- partition = true;
1131
- }
1132
- if (typeof seconds === 'number') {
1133
- timeout = seconds;
1134
- }
1135
- else {
1136
- cache.timeout = seconds ? timeout = convertSeconds(seconds) : 0;
1227
+ case 'object':
1228
+ if (cache) {
1229
+ let seconds, dir;
1230
+ ({ timeout: seconds, when_empty: whenEmpty, dir } = cache);
1231
+ if (dir && (!cacheDir || module_1.isDir(dir))) {
1232
+ cacheDir = dir;
1233
+ partition = true;
1234
+ }
1235
+ if (typeof seconds === 'number') {
1236
+ timeout = seconds;
1237
+ }
1238
+ else {
1239
+ cache.timeout = seconds ? timeout = convertSeconds(seconds) : 0;
1240
+ }
1137
1241
  }
1138
1242
  break;
1139
- }
1140
1243
  case 'number':
1141
1244
  timeout = cache;
1142
1245
  break;
@@ -1150,9 +1253,9 @@ class ClientDb extends Client {
1150
1253
  break;
1151
1254
  }
1152
1255
  if ((result.length > 0 || whenEmpty) && (userKey = this.extractUUID(credential) || (0, types_1.hashKey)(this.asString(credential)))) {
1153
- queryString = (0, types_1.hashKey)(queryString);
1154
1256
  if (timeout > 0) {
1155
- const item = new DbCache(source, userKey, queryString, result, expireTime(timeout));
1257
+ queryString = (0, types_1.hashKey)(queryString);
1258
+ const item = new DbCache(source, userKey, queryString, result, timeout);
1156
1259
  if (cacheDir) {
1157
1260
  if (partition) {
1158
1261
  if (STORE_RESULT_COUNT === this.STORE_RESULT_PARTITION_SIZE) {
@@ -1171,8 +1274,8 @@ class ClientDb extends Client {
1171
1274
  });
1172
1275
  }
1173
1276
  }
1174
- else if (typeof sessionKey === 'string' && typeof sessionExpires === 'number' && sessionExpires > 0) {
1175
- this.storeSession(source, userKey + sessionKey, queryString, result, sessionExpires);
1277
+ else if (typeof sessionKey === 'string') {
1278
+ this.storeSession(source, userKey + sessionKey, (0, types_1.hashKey)(queryString), result, sessionExpires);
1176
1279
  }
1177
1280
  }
1178
1281
  return result;
@@ -1180,8 +1283,10 @@ class ClientDb extends Client {
1180
1283
  static findSession(source, user, key) {
1181
1284
  return CACHE_SESSION[source]?.get(user, key);
1182
1285
  }
1183
- static storeSession(source, user, key, result, expires = 600) {
1184
- (CACHE_SESSION[source] ||= new DbSession(source)).set(user, key, result, expires);
1286
+ static storeSession(source, user, key, result, expires = 0) {
1287
+ if (expires > 0 || GC.EXPIRES_DEFAULT) {
1288
+ (CACHE_SESSION[source] ||= new DbSession(source)).set(user, key, result, expires > 0 ? expires : GC.EXPIRES);
1289
+ }
1185
1290
  }
1186
1291
  static async purgeResult(prefix, lru) {
1187
1292
  const cache = lru ? CACHE_SOURCE : CACHE_USER;
@@ -1194,15 +1299,26 @@ class ClientDb extends Client {
1194
1299
  items.push(cache[key]);
1195
1300
  }
1196
1301
  }
1302
+ for (const key in CACHE_SESSION) {
1303
+ if (key.startsWith(prefix)) {
1304
+ CACHE_SESSION[key]?.destroy();
1305
+ }
1306
+ }
1197
1307
  }
1198
- else if (cache[prefix]) {
1199
- items.push(cache[prefix]);
1308
+ else {
1309
+ if (cache[prefix]) {
1310
+ items.push(cache[prefix]);
1311
+ }
1312
+ CACHE_SESSION[prefix]?.destroy();
1200
1313
  }
1201
1314
  }
1202
1315
  else {
1203
1316
  for (const key in cache) {
1204
1317
  items.push(cache[key]);
1205
1318
  }
1319
+ for (const key in CACHE_SESSION) {
1320
+ CACHE_SESSION[key]?.destroy();
1321
+ }
1206
1322
  }
1207
1323
  let result = 0;
1208
1324
  if (lru) {
@@ -1213,8 +1329,7 @@ class ClientDb extends Client {
1213
1329
  else {
1214
1330
  const current = Date.now();
1215
1331
  for (const source of items) {
1216
- for (const session in source) {
1217
- const data = source[session];
1332
+ for (const data of source.values()) {
1218
1333
  for (const query in data) {
1219
1334
  const item = data[query];
1220
1335
  if (item.expired(current)) {
@@ -1253,13 +1368,8 @@ class ClientDb extends Client {
1253
1368
  this.cacheDir = cache_dir;
1254
1369
  }
1255
1370
  if (expires !== undefined) {
1256
- if (typeof expires === 'string') {
1257
- if (!isNaN(expires = (0, types_1.parseTime)(expires))) {
1258
- this.settings.session_expires = expires;
1259
- }
1260
- else {
1261
- delete this.settings.cache_session;
1262
- }
1371
+ if (typeof expires === 'string' && !isNaN(expires = (0, types_1.parseTime)(expires))) {
1372
+ this.settings.session_expires = expires;
1263
1373
  }
1264
1374
  if (expires >= 0) {
1265
1375
  this.cacheExpires = expires;
@@ -1306,7 +1416,7 @@ class ClientDb extends Client {
1306
1416
  sessionKey = options;
1307
1417
  break;
1308
1418
  case 'object':
1309
- if (options !== null) {
1419
+ if (options) {
1310
1420
  ({ value, sessionKey, exclusiveOf, renewCache } = options);
1311
1421
  if (Array.isArray(exclusiveOf)) {
1312
1422
  const ignoreCache = exclusiveOf[2];
@@ -1411,18 +1521,27 @@ class ClientDb extends Client {
1411
1521
  settingsOf(source, name, component) {
1412
1522
  const data = this.settings[source];
1413
1523
  if ((0, types_1.isObject)(data)) {
1414
- return getSettingsValue(data, name, component);
1524
+ return this.#settingsValue(data, name, component);
1415
1525
  }
1416
1526
  }
1417
1527
  settingsKey(uuidKey, name, component) {
1418
1528
  const data = this.settings.user_key?.[uuidKey];
1419
1529
  if ((0, types_1.isObject)(data)) {
1420
- return getSettingsValue(data, name, component);
1530
+ return this.#settingsValue(data, name, component);
1421
1531
  }
1422
1532
  }
1423
1533
  getSourceName(value) {
1424
1534
  return this.moduleName + '_' + value;
1425
1535
  }
1536
+ #settingsValue(options, name, component) {
1537
+ const result = options[name];
1538
+ if (!component) {
1539
+ return result;
1540
+ }
1541
+ if ((0, types_1.isObject)(result)) {
1542
+ return result[component];
1543
+ }
1544
+ }
1426
1545
  get pending() {
1427
1546
  return this.database.filter(item => {
1428
1547
  const state = item.transactionState || 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/core",
3
- "version": "0.13.6",
3
+ "version": "0.13.7",
4
4
  "description": "Core modules for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "license": "BSD-3-Clause",
20
20
  "homepage": "https://github.com/anpham6/e-mc#readme",
21
21
  "dependencies": {
22
- "@e-mc/module": "0.13.6",
23
- "@e-mc/types": "0.13.6",
22
+ "@e-mc/module": "0.13.7",
23
+ "@e-mc/types": "0.13.7",
24
24
  "ipaddr.js": "^2.3.0",
25
25
  "picomatch": "^4.0.3"
26
26
  }