jopi-toolkit 3.1.25 → 3.1.34
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/jk_app/common.d.ts +1 -0
- package/dist/jk_app/common.js +9 -2
- package/dist/jk_app/common.js.map +1 -1
- package/dist/jk_data/browserActions.d.ts +8 -0
- package/dist/jk_data/browserActions.js +9 -0
- package/dist/jk_data/browserActions.js.map +1 -0
- package/dist/jk_data/bundler_ifServer.d.ts +2 -0
- package/dist/jk_data/bundler_ifServer.js +2 -0
- package/dist/jk_data/bundler_ifServer.js.map +1 -0
- package/dist/jk_data/common.d.ts +8 -0
- package/dist/jk_data/common.js +2 -0
- package/dist/jk_data/common.js.map +1 -0
- package/dist/jk_data/core.d.ts +13 -0
- package/dist/jk_data/core.js +85 -0
- package/dist/jk_data/core.js.map +1 -0
- package/dist/jk_data/dataTableProxy.d.ts +21 -0
- package/dist/jk_data/dataTableProxy.js +49 -0
- package/dist/jk_data/dataTableProxy.js.map +1 -0
- package/dist/jk_data/ifServerSide.d.ts +0 -0
- package/dist/jk_data/ifServerSide.js +2 -0
- package/dist/jk_data/ifServerSide.js.map +1 -0
- package/dist/jk_data/index.d.ts +3 -55
- package/dist/jk_data/index.js +3 -105
- package/dist/jk_data/index.js.map +1 -1
- package/dist/jk_data/interfaces.d.ts +107 -0
- package/dist/jk_data/interfaces.js +2 -0
- package/dist/jk_data/interfaces.js.map +1 -0
- package/dist/jk_data/jBundler_common.d.ts +12 -0
- package/dist/jk_data/jBundler_common.js +2 -0
- package/dist/jk_data/jBundler_common.js.map +1 -0
- package/dist/jk_data/jBundler_ifBrowser.d.ts +2 -0
- package/dist/jk_data/jBundler_ifBrowser.js +2 -0
- package/dist/jk_data/jBundler_ifBrowser.js.map +1 -0
- package/dist/jk_data/jBundler_ifServer.1.d.ts +2 -0
- package/dist/jk_data/jBundler_ifServer.1.js +2 -0
- package/dist/jk_data/jBundler_ifServer.1.js.map +1 -0
- package/dist/jk_data/jBundler_ifServer.d.ts +2 -0
- package/dist/jk_data/jBundler_ifServer.js +2 -0
- package/dist/jk_data/jBundler_ifServer.js.map +1 -0
- package/dist/jk_data/proxy.d.ts +24 -0
- package/dist/jk_data/proxy.js +82 -0
- package/dist/jk_data/proxy.js.map +1 -0
- package/dist/jk_fs/jBundler_ifServer.d.ts +2 -1
- package/dist/jk_fs/jBundler_ifServer.js +1 -0
- package/dist/jk_fs/jBundler_ifServer.js.map +1 -1
- package/dist/jk_logs/index.js +2 -2
- package/dist/jk_logs/index.js.map +1 -1
- package/dist/jk_memcache/_logs.d.ts +1 -0
- package/dist/jk_memcache/_logs.js +3 -0
- package/dist/jk_memcache/_logs.js.map +1 -0
- package/dist/jk_memcache/index.d.ts +128 -0
- package/dist/jk_memcache/index.js +411 -0
- package/dist/jk_memcache/index.js.map +1 -0
- package/dist/jk_schemas/index.d.ts +20 -16
- package/dist/jk_schemas/index.js +13 -15
- package/dist/jk_schemas/index.js.map +1 -1
- package/dist/jk_tools/common.d.ts +6 -0
- package/dist/jk_tools/common.js +6 -0
- package/dist/jk_tools/common.js.map +1 -1
- package/dist/jk_tools/index.d.ts +1 -0
- package/package.json +8 -4
- package/src/jk_app/common.ts +12 -2
- package/src/jk_data/core.ts +92 -0
- package/src/jk_data/index.ts +3 -166
- package/src/jk_data/interfaces.ts +140 -0
- package/src/jk_data/proxy.ts +102 -0
- package/src/jk_fs/jBundler_ifServer.ts +1 -0
- package/src/jk_logs/index.ts +2 -2
- package/src/jk_memcache/README.md +66 -0
- package/src/jk_memcache/_logs.ts +3 -0
- package/src/jk_memcache/index.ts +494 -0
- package/src/jk_schemas/index.ts +29 -27
- package/src/jk_tools/common.ts +6 -0
- package/src/jk_tools/index.ts +3 -1
- package/src/jk_compress/index.js +0 -1
- package/src/jk_compress/jBundler_ifServer.js +0 -10
- package/src/jk_data/index.js +0 -155
- package/src/jk_schemas/index.js +0 -330
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { logMemCache } from "./_logs.ts";
|
|
2
|
+
|
|
3
|
+
export interface CacheEntryOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Importance level. Higher value means less likely to be evicted by GC.
|
|
6
|
+
* Default: 1
|
|
7
|
+
*/
|
|
8
|
+
importance?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Time to live in milliseconds.
|
|
11
|
+
*/
|
|
12
|
+
ttl?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Absolute expiration date.
|
|
15
|
+
*/
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Arbitrary metadata (optional).
|
|
19
|
+
*/
|
|
20
|
+
meta?: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface InternalCacheEntry {
|
|
24
|
+
key: string;
|
|
25
|
+
value: string | ArrayBuffer | Uint8Array;
|
|
26
|
+
type: 'string' | 'buffer' | 'json';
|
|
27
|
+
size: number;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
expiresAt: number | null;
|
|
30
|
+
accessCount: number;
|
|
31
|
+
importance: number;
|
|
32
|
+
meta: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface JkMemCacheOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Name of the cache instance (used for logs).
|
|
38
|
+
*/
|
|
39
|
+
name: string;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of items in cache.
|
|
42
|
+
* Default: Infinity
|
|
43
|
+
*/
|
|
44
|
+
maxCount?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Maximum size in bytes.
|
|
47
|
+
* Default: 50MB (50 * 1024 * 1024)
|
|
48
|
+
*/
|
|
49
|
+
maxSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Interval in milliseconds for the recurrent GC.
|
|
52
|
+
* Default: 60000 (1 minute)
|
|
53
|
+
*/
|
|
54
|
+
cleanupInterval?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class JkMemCache {
|
|
58
|
+
private _storage = new Map<string, InternalCacheEntry>();
|
|
59
|
+
private _currentSize = 0;
|
|
60
|
+
private _options: Required<JkMemCacheOptions>;
|
|
61
|
+
private _intervalId: any = null;
|
|
62
|
+
private _name: string;
|
|
63
|
+
|
|
64
|
+
constructor(options: JkMemCacheOptions) {
|
|
65
|
+
this._options = {
|
|
66
|
+
name: options.name,
|
|
67
|
+
maxCount: options.maxCount ?? Infinity,
|
|
68
|
+
maxSize: options.maxSize ?? 50 * 1024 * 1024,
|
|
69
|
+
cleanupInterval: options.cleanupInterval ?? 60000,
|
|
70
|
+
};
|
|
71
|
+
this._name = options.name;
|
|
72
|
+
|
|
73
|
+
logMemCache.info(`Cache [${this._name}] initialized`);
|
|
74
|
+
|
|
75
|
+
if (this._options.cleanupInterval > 0) {
|
|
76
|
+
this.startAutoCleanup();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Start the automatic cleanup interval.
|
|
82
|
+
*/
|
|
83
|
+
public startAutoCleanup() {
|
|
84
|
+
if (this._intervalId) clearInterval(this._intervalId);
|
|
85
|
+
|
|
86
|
+
this._intervalId = setInterval(() => {
|
|
87
|
+
this.performCleanup();
|
|
88
|
+
}, this._options.cleanupInterval);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stop the automatic cleanup interval.
|
|
93
|
+
*/
|
|
94
|
+
public stopAutoCleanup() {
|
|
95
|
+
if (this._intervalId) {
|
|
96
|
+
clearInterval(this._intervalId);
|
|
97
|
+
this._intervalId = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Add or update an item in the cache.
|
|
103
|
+
*/
|
|
104
|
+
public set(key: string, value: string | ArrayBuffer | Uint8Array | object, options: CacheEntryOptions = {}) {
|
|
105
|
+
// 1. Prepare new entry details
|
|
106
|
+
let storedValue: string | ArrayBuffer | Uint8Array;
|
|
107
|
+
let type: 'string' | 'buffer' | 'json';
|
|
108
|
+
let size = 0;
|
|
109
|
+
|
|
110
|
+
// Meta size calculation
|
|
111
|
+
const metaSize = options.meta ? JSON.stringify(options.meta).length * 2 : 0;
|
|
112
|
+
|
|
113
|
+
if (value instanceof ArrayBuffer) {
|
|
114
|
+
storedValue = value;
|
|
115
|
+
type = 'buffer';
|
|
116
|
+
size = value.byteLength;
|
|
117
|
+
} else if (value instanceof Uint8Array) {
|
|
118
|
+
storedValue = value;
|
|
119
|
+
type = 'buffer';
|
|
120
|
+
size = value.byteLength;
|
|
121
|
+
} else if (typeof value === 'string') {
|
|
122
|
+
storedValue = value;
|
|
123
|
+
type = 'string';
|
|
124
|
+
size = value.length * 2; // Approximation for JS string memory
|
|
125
|
+
} else {
|
|
126
|
+
storedValue = JSON.stringify(value);
|
|
127
|
+
type = 'json';
|
|
128
|
+
size = (storedValue as string).length * 2;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Overhead for object structure (approximate) + Meta size
|
|
132
|
+
size += 100 + metaSize;
|
|
133
|
+
|
|
134
|
+
// 2. Check overlap
|
|
135
|
+
if (this._storage.has(key)) {
|
|
136
|
+
this.delete(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 3. Calculate Expiration
|
|
140
|
+
|
|
141
|
+
let expiresAt: number | null = null;
|
|
142
|
+
|
|
143
|
+
if (options.expiresAt) {
|
|
144
|
+
expiresAt = options.expiresAt;
|
|
145
|
+
} else if (options.ttl) {
|
|
146
|
+
expiresAt = Date.now() + options.ttl;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const entry: InternalCacheEntry = {
|
|
150
|
+
key,
|
|
151
|
+
value: storedValue,
|
|
152
|
+
type,
|
|
153
|
+
size,
|
|
154
|
+
createdAt: Date.now(),
|
|
155
|
+
expiresAt,
|
|
156
|
+
accessCount: 0,
|
|
157
|
+
importance: options.importance ?? 1,
|
|
158
|
+
meta: options.meta,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// 4. Check if item is too big for the cache entirely
|
|
162
|
+
if (entry.size > this._options.maxSize) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 5. Check if we need to make space
|
|
167
|
+
if (this.needsEviction(entry.size)) {
|
|
168
|
+
this.evictFor(entry.size);
|
|
169
|
+
|
|
170
|
+
if (this.needsEviction(entry.size)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this._storage.set(key, entry);
|
|
176
|
+
this._currentSize += size;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Retrieve an item from the cache.
|
|
181
|
+
*/
|
|
182
|
+
public get<T = any>(key: string, peek = false): T | null {
|
|
183
|
+
const entry = this._storage.get(key);
|
|
184
|
+
if (!entry) return null;
|
|
185
|
+
|
|
186
|
+
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
187
|
+
this.delete(key);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!peek) entry.accessCount++;
|
|
192
|
+
|
|
193
|
+
if (entry.type === 'buffer') {
|
|
194
|
+
return entry.value as T;
|
|
195
|
+
} else if (entry.type === 'json') {
|
|
196
|
+
return JSON.parse(entry.value as string) as T;
|
|
197
|
+
} else {
|
|
198
|
+
return entry.value as T;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Retrieve an item from the cache with its metadata.
|
|
204
|
+
*/
|
|
205
|
+
public getWithMeta<T = any>(key: string, peek = false): { value: T; meta: any; size: number } | null {
|
|
206
|
+
const entry = this._storage.get(key);
|
|
207
|
+
|
|
208
|
+
if (!entry) return null;
|
|
209
|
+
if (!peek) entry.accessCount++;
|
|
210
|
+
|
|
211
|
+
let value: T;
|
|
212
|
+
|
|
213
|
+
if (entry.type === 'buffer') {
|
|
214
|
+
value = entry.value as T;
|
|
215
|
+
} else if (entry.type === 'json') {
|
|
216
|
+
value = JSON.parse(entry.value as string) as T;
|
|
217
|
+
} else {
|
|
218
|
+
value = entry.value as T;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { value, meta: entry.meta, size: entry.size };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if an item exists in the cache and is not expired.
|
|
226
|
+
* Does not increment accessCount.
|
|
227
|
+
*/
|
|
228
|
+
public has(key: string): boolean {
|
|
229
|
+
const entry = this._storage.get(key);
|
|
230
|
+
if (!entry) return false;
|
|
231
|
+
|
|
232
|
+
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
233
|
+
this.delete(key);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Manually delete an item.
|
|
242
|
+
*/
|
|
243
|
+
public delete(key: string) {
|
|
244
|
+
const entry = this._storage.get(key);
|
|
245
|
+
|
|
246
|
+
if (entry) {
|
|
247
|
+
this._currentSize -= entry.size;
|
|
248
|
+
this._storage.delete(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clear all items.
|
|
254
|
+
*/
|
|
255
|
+
public clear() {
|
|
256
|
+
this._storage.clear();
|
|
257
|
+
this._currentSize = 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get current cache stats.
|
|
262
|
+
*/
|
|
263
|
+
public getStats() {
|
|
264
|
+
return {
|
|
265
|
+
count: this._storage.size,
|
|
266
|
+
size: this._currentSize,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Iterate over all valid keys.
|
|
272
|
+
*/
|
|
273
|
+
public *keys(): Generator<string> {
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
for (const [key, entry] of this._storage) {
|
|
276
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
277
|
+
this.delete(key);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
yield key;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Iterate over keys starting with a prefix.
|
|
286
|
+
*/
|
|
287
|
+
public *keysStartingWith(prefix: string): Generator<string> {
|
|
288
|
+
for (const key of this.keys()) {
|
|
289
|
+
if (key.startsWith(prefix)) yield key;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Iterate over keys ending with a suffix.
|
|
295
|
+
*/
|
|
296
|
+
public *keysEndingWith(suffix: string): Generator<string> {
|
|
297
|
+
for (const key of this.keys()) {
|
|
298
|
+
if (key.endsWith(suffix)) yield key;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Iterate over keys containing a specific text.
|
|
304
|
+
*/
|
|
305
|
+
public *keysContaining(text: string): Generator<string> {
|
|
306
|
+
for (const key of this.keys()) {
|
|
307
|
+
if (key.includes(text)) yield key;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if we need to evict entries to fit a new one (or if limits are exceeded).
|
|
313
|
+
*/
|
|
314
|
+
private needsEviction(incomingSize: number): boolean {
|
|
315
|
+
return (
|
|
316
|
+
(this._storage.size + 1 > this._options.maxCount) ||
|
|
317
|
+
(this._currentSize + incomingSize > this._options.maxSize)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Perform recurrent cleanup (expiration only).
|
|
323
|
+
*/
|
|
324
|
+
private performCleanup() {
|
|
325
|
+
const now = Date.now();
|
|
326
|
+
let removedCount = 0;
|
|
327
|
+
|
|
328
|
+
logMemCache.info(`Cache [${this._name}] Recurrent GC started - ${JSON.stringify({
|
|
329
|
+
count: this._storage.size,
|
|
330
|
+
sizeMB: (this._currentSize / 1024 / 1024).toFixed(2)
|
|
331
|
+
})}`);
|
|
332
|
+
|
|
333
|
+
for (const [key, entry] of this._storage) {
|
|
334
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
335
|
+
this.delete(key);
|
|
336
|
+
removedCount++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (removedCount > 0) {
|
|
341
|
+
logMemCache.info(`Cache [${this._name}] Recurrent GC finished - ${JSON.stringify({
|
|
342
|
+
removed: removedCount,
|
|
343
|
+
remaining: this._storage.size,
|
|
344
|
+
sizeMB: (this._currentSize / 1024 / 1024).toFixed(2)
|
|
345
|
+
})}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Evict items to make space/reduce count.
|
|
351
|
+
* Strategy: Calculate a score. Lower score = evicted first.
|
|
352
|
+
* Factors:
|
|
353
|
+
* - expired (immediate kill)
|
|
354
|
+
* - importance (higher = keep)
|
|
355
|
+
* - accessCount (higher = keep)
|
|
356
|
+
*
|
|
357
|
+
* Score = (importance * 1000) + (accessCount)
|
|
358
|
+
* (Simplified logic)
|
|
359
|
+
*/
|
|
360
|
+
private evictFor(requiredSpace: number) {
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
|
|
363
|
+
logMemCache.info(`Cache [${this._name}] GC Eviction started (Memory Pressure) - ${JSON.stringify({
|
|
364
|
+
requiredSpace,
|
|
365
|
+
count: this._storage.size,
|
|
366
|
+
sizeMB: (this._currentSize / 1024 / 1024).toFixed(2)
|
|
367
|
+
})}`);
|
|
368
|
+
|
|
369
|
+
// 1. Remove expired first (In-place, no allocation)
|
|
370
|
+
for (const [key, entry] of this._storage) {
|
|
371
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
372
|
+
this.delete(key);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Optimization: Low Water Mark.
|
|
377
|
+
// To avoid frequent GC, when we evict, we try to go down to 90% capacity,
|
|
378
|
+
// creating a temporary buffer.
|
|
379
|
+
//
|
|
380
|
+
const safeSize = this._options.maxSize * 0.9;
|
|
381
|
+
const safeCount = this._options.maxCount * 0.9;
|
|
382
|
+
|
|
383
|
+
// Helper: Have we reached our goal?
|
|
384
|
+
// If strict is true, we aim for the buffer (90%).
|
|
385
|
+
// If strict is false, we just aim to fit the item (100%).
|
|
386
|
+
const isTargetReached = (strict: boolean) => {
|
|
387
|
+
// 1. Absolute requirement: Must fit the new item within MAX limits.
|
|
388
|
+
// needsEviction returns TRUE if we are OVER limits.
|
|
389
|
+
if (this.needsEviction(requiredSpace)) {
|
|
390
|
+
return false; // We are over max, so target is definitely NOT reached.
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 2. Buffer requirement: specific to strict mode
|
|
394
|
+
if (strict) {
|
|
395
|
+
// We are under MAX, but are we under SAFE limits?
|
|
396
|
+
return (this._currentSize + requiredSpace <= safeSize) && (this._storage.size <= safeCount);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// If not strict, and needsEviction was false, we are good.
|
|
400
|
+
return true;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// Helper to format log message
|
|
404
|
+
const logGC = (step: string, extra: object = {}) => {
|
|
405
|
+
logMemCache.info(`Cache [${this._name}] [GC] ${step} - ${JSON.stringify({
|
|
406
|
+
...extra,
|
|
407
|
+
count: this._storage.size,
|
|
408
|
+
sizeMB: (this._currentSize / 1024 / 1024).toFixed(2)
|
|
409
|
+
})}`);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
if (isTargetReached(true)) {
|
|
413
|
+
logGC("Step 1 (Expired) sufficient");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 2. Multi-step Eviction by Importance
|
|
418
|
+
const MAX_SEARCH_LEVEL = 10;
|
|
419
|
+
// We treat levels 1-5 as "recyclable" to build buffer.
|
|
420
|
+
// Levels 6-10 are "protected" -> only evicted if absolutely necessary to fit the item.
|
|
421
|
+
const BUFFER_TARGET_LEVEL = 5;
|
|
422
|
+
|
|
423
|
+
for (let level = 1; level <= MAX_SEARCH_LEVEL; level++) {
|
|
424
|
+
const candidates: InternalCacheEntry[] = [];
|
|
425
|
+
|
|
426
|
+
for (const entry of this._storage.values()) {
|
|
427
|
+
if (entry.importance === level) {
|
|
428
|
+
candidates.push(entry);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (candidates.length === 0) continue;
|
|
433
|
+
|
|
434
|
+
candidates.sort((a, b) => {
|
|
435
|
+
if (a.accessCount !== b.accessCount) {
|
|
436
|
+
return a.accessCount - b.accessCount;
|
|
437
|
+
}
|
|
438
|
+
return a.createdAt - b.createdAt;
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const aimForBuffer = level <= BUFFER_TARGET_LEVEL;
|
|
442
|
+
|
|
443
|
+
for (const candidate of candidates) {
|
|
444
|
+
this.delete(candidate.key);
|
|
445
|
+
// If we reached our target (buffer or just space), we stop.
|
|
446
|
+
if (isTargetReached(aimForBuffer)) {
|
|
447
|
+
logGC("Step 2 (Importance) finished", { level });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 3. Strategy: Delete biggest items
|
|
454
|
+
// If we are still here, it means we have high importance items filling the cache.
|
|
455
|
+
// We try to free space by removing the largest items first.
|
|
456
|
+
if (!isTargetReached(false)) {
|
|
457
|
+
let b1: InternalCacheEntry | null = null;
|
|
458
|
+
let b2: InternalCacheEntry | null = null;
|
|
459
|
+
let b3: InternalCacheEntry | null = null;
|
|
460
|
+
|
|
461
|
+
for (const entry of this._storage.values()) {
|
|
462
|
+
if (!b1 || entry.size > b1.size) {
|
|
463
|
+
b3 = b2;
|
|
464
|
+
b2 = b1;
|
|
465
|
+
b1 = entry;
|
|
466
|
+
} else if (!b2 || entry.size > b2.size) {
|
|
467
|
+
b3 = b2;
|
|
468
|
+
b2 = entry;
|
|
469
|
+
} else if (!b3 || entry.size > b3.size) {
|
|
470
|
+
b3 = entry;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (b1) { this.delete(b1.key); if (isTargetReached(false)) { logGC("Step 3 (Biggest) finished"); return; } }
|
|
475
|
+
if (b2) { this.delete(b2.key); if (isTargetReached(false)) { logGC("Step 3 (Biggest) finished"); return; } }
|
|
476
|
+
if (b3) { this.delete(b3.key); if (isTargetReached(false)) { logGC("Step 3 (Biggest) finished"); return; } }
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 4. Fallback / Emergency
|
|
480
|
+
if (!isTargetReached(false)) {
|
|
481
|
+
const iterator = this._storage.keys();
|
|
482
|
+
let result = iterator.next();
|
|
483
|
+
|
|
484
|
+
while (!result.done) {
|
|
485
|
+
this.delete(result.value);
|
|
486
|
+
if (isTargetReached(false)) {
|
|
487
|
+
logGC("Step 4 (Fallback) finished");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
result = iterator.next();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
package/src/jk_schemas/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// noinspection JSUnusedGlobalSymbols
|
|
2
|
-
|
|
3
|
-
import {generateUUIDv4} from "jopi-toolkit/jk_tools";
|
|
4
|
-
|
|
5
1
|
//region Validation
|
|
6
2
|
|
|
3
|
+
import type { Translatable } from "jopi-toolkit/jk_tools";
|
|
4
|
+
|
|
7
5
|
/**
|
|
8
6
|
* Throwing this error allows it to be caught
|
|
9
7
|
* when validating an object.
|
|
@@ -174,7 +172,7 @@ interface RegistryEntry {
|
|
|
174
172
|
|
|
175
173
|
export function registerSchema(schemaId: string|undefined, schema: Schema, meta?: any) {
|
|
176
174
|
if (!schemaId) {
|
|
177
|
-
throw new Error("jk_schemas - Schema id required
|
|
175
|
+
throw new Error("jk_schemas - Schema id required");
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
gRegistry[schemaId!] = {schema, meta};
|
|
@@ -251,17 +249,20 @@ class SchemaImpl<T extends SchemaDescriptor> implements Schema {
|
|
|
251
249
|
}
|
|
252
250
|
}
|
|
253
251
|
|
|
254
|
-
export
|
|
255
|
-
[field: string]: Field;
|
|
256
|
-
}
|
|
252
|
+
export type SchemaDescriptor = Record<string, Field>;
|
|
257
253
|
|
|
258
254
|
export interface SchemaMeta {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The title of this schema.
|
|
257
|
+
* Can be translated to languages.
|
|
258
|
+
*/
|
|
259
|
+
title?: Translatable;
|
|
260
|
+
description?: Translatable;
|
|
262
261
|
|
|
263
262
|
normalize?: (allValues: any, checkHelper: ValueCheckingHelper) => void;
|
|
264
263
|
validate?: (allValues: any, checkHelper: ValueCheckingHelper) => void;
|
|
264
|
+
|
|
265
|
+
[key: string]: any;
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
export interface SchemaInfo {
|
|
@@ -415,10 +416,11 @@ export interface ScFieldStore {
|
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
export interface ScField<T, Opt extends boolean> {
|
|
418
|
-
|
|
419
|
+
id: string;
|
|
419
420
|
type: string;
|
|
421
|
+
title?: Translatable;
|
|
420
422
|
|
|
421
|
-
description?:
|
|
423
|
+
description?: Translatable;
|
|
422
424
|
default?: T;
|
|
423
425
|
optional?: Opt;
|
|
424
426
|
|
|
@@ -456,7 +458,7 @@ export interface ScField<T, Opt extends boolean> {
|
|
|
456
458
|
export type Field = ScField<any, any>;
|
|
457
459
|
export type SchemaFieldInfos = Field;
|
|
458
460
|
|
|
459
|
-
type OnlyInfos<T> = Omit<T, "
|
|
461
|
+
type OnlyInfos<T> = Omit<T, "id" | "optional" | "type">;
|
|
460
462
|
|
|
461
463
|
//endregion
|
|
462
464
|
|
|
@@ -471,16 +473,16 @@ export interface ScString<Opt extends boolean = boolean> extends ScField<string,
|
|
|
471
473
|
maxLength?: number;
|
|
472
474
|
errorMessage_maxLength?: string;
|
|
473
475
|
|
|
474
|
-
placeholder?:
|
|
476
|
+
placeholder?: Translatable;
|
|
475
477
|
}
|
|
476
478
|
|
|
477
|
-
export function string<Opt extends boolean>(
|
|
479
|
+
export function string<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScString<Opt>>): ScString<Opt> {
|
|
478
480
|
if (!optional) {
|
|
479
481
|
if (!infos) infos = {};
|
|
480
482
|
if (infos.minLength===undefined) infos.minLength = 1;
|
|
481
483
|
}
|
|
482
484
|
|
|
483
|
-
return {...infos,
|
|
485
|
+
return {...infos, id, optional, type: "string"};
|
|
484
486
|
}
|
|
485
487
|
|
|
486
488
|
byTypeValidator["string"] = (v,f) => {
|
|
@@ -514,8 +516,8 @@ export interface ScBoolean<Opt extends boolean = boolean> extends ScField<boolea
|
|
|
514
516
|
errorMessage_requireFalse?: string;
|
|
515
517
|
}
|
|
516
518
|
|
|
517
|
-
export function boolean<Opt extends boolean>(
|
|
518
|
-
return {...infos,
|
|
519
|
+
export function boolean<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScBoolean<Opt>>): ScBoolean<Opt> {
|
|
520
|
+
return {...infos, id, optional, type: "boolean"};
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
byTypeValidator["boolean"] = (v, f) => {
|
|
@@ -574,8 +576,8 @@ export interface ScNumber<Opt extends boolean = boolean> extends ScField<number,
|
|
|
574
576
|
currency?: string;
|
|
575
577
|
}
|
|
576
578
|
|
|
577
|
-
export function number<Opt extends boolean>(
|
|
578
|
-
return {...infos,
|
|
579
|
+
export function number<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScNumber<Opt>>): ScNumber<Opt> {
|
|
580
|
+
return {...infos, id, optional, type: "number"};
|
|
579
581
|
}
|
|
580
582
|
|
|
581
583
|
export function formatNumber(value: string, fieldNumber: ScNumber, defaultLocalFormat: string = "en-US", defaultCurrency: string = "USD") {
|
|
@@ -616,16 +618,16 @@ byTypeValidator["number"] = (v,f) => {
|
|
|
616
618
|
|
|
617
619
|
//region Currency
|
|
618
620
|
|
|
619
|
-
export function currency<Opt extends boolean>(
|
|
620
|
-
return number(
|
|
621
|
+
export function currency<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScNumber<Opt>>): ScNumber<Opt> {
|
|
622
|
+
return number(id, optional, {...infos, displayType: "currency"})
|
|
621
623
|
}
|
|
622
624
|
|
|
623
625
|
//endregion
|
|
624
626
|
|
|
625
627
|
//region Percent
|
|
626
628
|
|
|
627
|
-
export function percent<Opt extends boolean>(
|
|
628
|
-
return number(
|
|
629
|
+
export function percent<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScNumber<Opt>>): ScNumber<Opt> {
|
|
630
|
+
return number(id, optional, {...infos, displayType: "percent"})
|
|
629
631
|
}
|
|
630
632
|
|
|
631
633
|
|
|
@@ -650,8 +652,8 @@ export interface ScFile<Opt extends boolean> extends ScField<File[], Opt> {
|
|
|
650
652
|
errorMessage_maxFileSize?: string;
|
|
651
653
|
}
|
|
652
654
|
|
|
653
|
-
export function file<Opt extends boolean>(
|
|
654
|
-
return {...infos,
|
|
655
|
+
export function file<Opt extends boolean>(id: string, optional: Opt, infos?: OnlyInfos<ScFile<Opt>>): ScFile<Opt> {
|
|
656
|
+
return {...infos, id, optional, type: "file"};
|
|
655
657
|
}
|
|
656
658
|
|
|
657
659
|
//endregion
|
package/src/jk_tools/common.ts
CHANGED
|
@@ -69,6 +69,12 @@ export interface ValueWithPriority<T> {
|
|
|
69
69
|
priority: PriorityLevel;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Sorts a list of items based on their priority level in ascending order.
|
|
74
|
+
* Items with lower priority values (e.g., -200) will appear first in the returned array.
|
|
75
|
+
* @param values The list of items to sort, each containing a value and a priority.
|
|
76
|
+
* @returns An array of just the values, sorted by priority. Returns undefined if the input is undefined.
|
|
77
|
+
*/
|
|
72
78
|
export function sortByPriority<T>(values: undefined|ValueWithPriority<T>[]): undefined|(T[]) {
|
|
73
79
|
if (values === undefined) return undefined;
|
|
74
80
|
|
package/src/jk_tools/index.ts
CHANGED
package/src/jk_compress/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./jBundler_ifServer.ts";
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { gunzipSync as n_gunzipSync, gzipSync as n_gzipSync } from "node:zlib";
|
|
2
|
-
import { isNodeJS } from "jopi-toolkit/jk_what";
|
|
3
|
-
function node_gunzipSync(data) {
|
|
4
|
-
return n_gunzipSync(Buffer.from(data));
|
|
5
|
-
}
|
|
6
|
-
function node_gzipSync(data) {
|
|
7
|
-
return n_gzipSync(Buffer.from(data));
|
|
8
|
-
}
|
|
9
|
-
export var gunzipSync = isNodeJS ? node_gunzipSync : Bun.gunzipSync;
|
|
10
|
-
export var gzipSync = isNodeJS ? node_gzipSync : Bun.gzipSync;
|