@unrdf/knowledge-engine 5.0.1 → 26.4.3
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/package.json +13 -7
- package/src/ai-enhanced-search.mjs +371 -0
- package/src/anomaly-detector.mjs +226 -0
- package/src/artifact-generator.mjs +251 -0
- package/src/browser.mjs +1 -1
- package/src/chatman/disruption-arithmetic.mjs +140 -0
- package/src/chatman/market-dynamics.mjs +140 -0
- package/src/chatman/organizational-dynamics.mjs +140 -0
- package/src/chatman/strategic-dynamics.mjs +140 -0
- package/src/chatman-config-loader.mjs +282 -0
- package/src/chatman-engine.mjs +431 -0
- package/src/chatman-operator.mjs +342 -0
- package/src/dark-field-detector.mjs +312 -0
- package/src/formation-theorems.mjs +345 -0
- package/src/index.mjs +20 -2
- package/src/knowledge-hook-manager.mjs +1 -1
- package/src/lockchain-writer-browser.mjs +2 -2
- package/src/observability.mjs +40 -4
- package/src/query-optimizer.mjs +1 -1
- package/src/resolution-layer.mjs +1 -1
- package/src/transaction.mjs +11 -9
- package/README.md +0 -84
- package/src/browser-shims.mjs +0 -343
- package/src/canonicalize.mjs +0 -414
- package/src/condition-cache.mjs +0 -109
- package/src/condition-evaluator.mjs +0 -722
- package/src/dark-matter-core.mjs +0 -742
- package/src/define-hook.mjs +0 -213
- package/src/effect-sandbox-browser.mjs +0 -283
- package/src/effect-sandbox-worker.mjs +0 -170
- package/src/effect-sandbox.mjs +0 -517
- package/src/engines/index.mjs +0 -11
- package/src/engines/rdf-engine.mjs +0 -299
- package/src/file-resolver.mjs +0 -387
- package/src/hook-executor-batching.mjs +0 -277
- package/src/hook-executor.mjs +0 -870
- package/src/hook-management.mjs +0 -150
- package/src/ken-parliment.mjs +0 -119
- package/src/ken.mjs +0 -149
- package/src/knowledge-engine/builtin-rules.mjs +0 -190
- package/src/knowledge-engine/inference-engine.mjs +0 -418
- package/src/knowledge-engine/knowledge-engine.mjs +0 -317
- package/src/knowledge-engine/pattern-dsl.mjs +0 -142
- package/src/knowledge-engine/pattern-matcher.mjs +0 -215
- package/src/knowledge-engine/rules.mjs +0 -184
- package/src/knowledge-engine.mjs +0 -319
- package/src/knowledge-hook-engine.mjs +0 -360
- package/src/knowledge-substrate-core.mjs +0 -927
- package/src/lite.mjs +0 -222
- package/src/lockchain-writer.mjs +0 -602
- package/src/monitoring/andon-signals.mjs +0 -775
- package/src/parse.mjs +0 -290
- package/src/performance-optimizer.mjs +0 -678
- package/src/policy-pack.mjs +0 -572
- package/src/query-cache.mjs +0 -116
- package/src/query.mjs +0 -306
- package/src/reason.mjs +0 -350
- package/src/schemas.mjs +0 -1063
- package/src/security/error-sanitizer.mjs +0 -257
- package/src/security/path-validator.mjs +0 -194
- package/src/security/sandbox-restrictions.mjs +0 -331
- package/src/security-validator.mjs +0 -389
- package/src/store-cache.mjs +0 -137
- package/src/telemetry.mjs +0 -167
- package/src/utils/adaptive-monitor.mjs +0 -746
- package/src/utils/circuit-breaker.mjs +0 -513
- package/src/utils/edge-case-handler.mjs +0 -503
- package/src/utils/memory-manager.mjs +0 -498
- package/src/utils/ring-buffer.mjs +0 -282
- package/src/validate.mjs +0 -319
- package/src/validators/index.mjs +0 -338
|
@@ -1,503 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Edge Case Handler - Null/undefined safety utilities
|
|
3
|
-
* @module edge-case-handler
|
|
4
|
-
*
|
|
5
|
-
* @description
|
|
6
|
-
* Centralized utilities for handling edge cases, null/undefined checks,
|
|
7
|
-
* and defensive programming patterns across the knowledge engine.
|
|
8
|
-
*
|
|
9
|
-
* Addresses 45 test failures related to missing null guards.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Safe property access with optional chaining and default value.
|
|
14
|
-
*
|
|
15
|
-
* @param {any} obj - Object to access
|
|
16
|
-
* @param {string} path - Dot-notation path (e.g., "a.b.c")
|
|
17
|
-
* @param {any} defaultValue - Default value if path not found
|
|
18
|
-
* @returns {any} Property value or default
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* safeGet({ a: { b: { c: 42 } } }, 'a.b.c', 0) // 42
|
|
22
|
-
* safeGet({ a: null }, 'a.b.c', 0) // 0
|
|
23
|
-
* safeGet(null, 'a.b.c', 0) // 0
|
|
24
|
-
*/
|
|
25
|
-
export function safeGet(obj, path, defaultValue = undefined) {
|
|
26
|
-
if (obj == null || typeof path !== 'string') {
|
|
27
|
-
return defaultValue;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return path.split('.').reduce((current, key) => current?.[key], obj) ?? defaultValue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Validate that an object has all required properties.
|
|
35
|
-
* Throws TypeError if any properties are missing.
|
|
36
|
-
*
|
|
37
|
-
* @param {Object} obj - Object to validate
|
|
38
|
-
* @param {string[]} props - Required property paths
|
|
39
|
-
* @param {string} [errorMessage] - Custom error message prefix
|
|
40
|
-
* @throws {TypeError} If any required properties are missing
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* requireProperties({ a: 1, b: 2 }, ['a', 'b']) // OK
|
|
44
|
-
* requireProperties({ a: 1 }, ['a', 'b'], 'Config') // Throws: "Config: missing b"
|
|
45
|
-
*/
|
|
46
|
-
export function requireProperties(obj, props, errorMessage = 'Object') {
|
|
47
|
-
if (obj == null) {
|
|
48
|
-
throw new TypeError(`${errorMessage} cannot be null or undefined`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!Array.isArray(props)) {
|
|
52
|
-
throw new TypeError('props must be an array');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const missing = [];
|
|
56
|
-
for (const prop of props) {
|
|
57
|
-
if (safeGet(obj, prop) === undefined) {
|
|
58
|
-
missing.push(prop);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (missing.length > 0) {
|
|
63
|
-
throw new TypeError(`${errorMessage}: missing required properties: ${missing.join(', ')}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Safe array access with bounds checking.
|
|
69
|
-
*
|
|
70
|
-
* @param {Array} arr - Array to access
|
|
71
|
-
* @param {number} index - Index to access
|
|
72
|
-
* @param {any} defaultValue - Default value if index out of bounds
|
|
73
|
-
* @returns {any} Array element or default value
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* safeArrayAccess([1, 2, 3], 1) // 2
|
|
77
|
-
* safeArrayAccess([1, 2, 3], 10, 0) // 0
|
|
78
|
-
* safeArrayAccess(null, 0, 0) // 0
|
|
79
|
-
*/
|
|
80
|
-
export function safeArrayAccess(arr, index, defaultValue = undefined) {
|
|
81
|
-
if (!Array.isArray(arr)) {
|
|
82
|
-
return defaultValue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (typeof index !== 'number' || index < 0 || index >= arr.length) {
|
|
86
|
-
return defaultValue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return arr[index];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Safe first element access.
|
|
94
|
-
*
|
|
95
|
-
* @param {Array} arr - Array to access
|
|
96
|
-
* @param {any} defaultValue - Default if array is empty
|
|
97
|
-
* @returns {any} First element or default
|
|
98
|
-
*/
|
|
99
|
-
export function safeFirst(arr, defaultValue = undefined) {
|
|
100
|
-
return safeArrayAccess(arr, 0, defaultValue);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Safe last element access.
|
|
105
|
-
*
|
|
106
|
-
* @param {Array} arr - Array to access
|
|
107
|
-
* @param {any} defaultValue - Default if array is empty
|
|
108
|
-
* @returns {any} Last element or default
|
|
109
|
-
*/
|
|
110
|
-
export function safeLast(arr, defaultValue = undefined) {
|
|
111
|
-
if (!Array.isArray(arr) || arr.length === 0) {
|
|
112
|
-
return defaultValue;
|
|
113
|
-
}
|
|
114
|
-
return arr[arr.length - 1];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Check if a value is empty.
|
|
119
|
-
* Works with: null, undefined, arrays, strings, objects, Maps, Sets, RDF Stores.
|
|
120
|
-
*
|
|
121
|
-
* @param {any} value - Value to check
|
|
122
|
-
* @returns {boolean} True if value is empty
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* isEmpty(null) // true
|
|
126
|
-
* isEmpty([]) // true
|
|
127
|
-
* isEmpty({ size: 0 }) // true (Store, Map, Set)
|
|
128
|
-
* isEmpty({ a: 1 }) // false
|
|
129
|
-
*/
|
|
130
|
-
export function isEmpty(value) {
|
|
131
|
-
// Null/undefined
|
|
132
|
-
if (value == null) return true;
|
|
133
|
-
|
|
134
|
-
// Array
|
|
135
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
136
|
-
|
|
137
|
-
// String
|
|
138
|
-
if (typeof value === 'string') return value.length === 0;
|
|
139
|
-
|
|
140
|
-
// Objects with .size property (Store, Map, Set)
|
|
141
|
-
if (typeof value === 'object' && 'size' in value) {
|
|
142
|
-
return value.size === 0;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Plain objects
|
|
146
|
-
if (typeof value === 'object') {
|
|
147
|
-
return Object.keys(value).length === 0;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if value is not empty.
|
|
155
|
-
*
|
|
156
|
-
* @param {any} value - Value to check
|
|
157
|
-
* @returns {boolean} True if value is not empty
|
|
158
|
-
*/
|
|
159
|
-
export function isNotEmpty(value) {
|
|
160
|
-
return !isEmpty(value);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Detect circular references in an object.
|
|
165
|
-
* Uses WeakSet to track visited objects.
|
|
166
|
-
*
|
|
167
|
-
* @param {any} obj - Object to check
|
|
168
|
-
* @param {WeakSet} [seen] - Internal tracking (don't pass)
|
|
169
|
-
* @returns {boolean} True if circular references detected
|
|
170
|
-
*
|
|
171
|
-
* @example
|
|
172
|
-
* const obj = { a: 1 }
|
|
173
|
-
* obj.self = obj
|
|
174
|
-
* hasCircularRefs(obj) // true
|
|
175
|
-
*/
|
|
176
|
-
export function hasCircularRefs(obj, seen = new WeakSet()) {
|
|
177
|
-
// Primitives can't have circular refs
|
|
178
|
-
if (obj == null || typeof obj !== 'object') {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Circular reference detected
|
|
183
|
-
if (seen.has(obj)) {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
seen.add(obj);
|
|
188
|
-
|
|
189
|
-
// Check all values
|
|
190
|
-
for (const value of Object.values(obj)) {
|
|
191
|
-
if (hasCircularRefs(value, seen)) {
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Get strongly connected components in a graph (for circular ref analysis).
|
|
201
|
-
*
|
|
202
|
-
* @param {Object} graph - Graph as adjacency list { node: [neighbors] }
|
|
203
|
-
* @returns {Array<Array<string>>} Array of strongly connected components
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* const graph = { a: ['b'], b: ['c'], c: ['a'] }
|
|
207
|
-
* getStronglyConnectedComponents(graph) // [['a', 'b', 'c']]
|
|
208
|
-
*/
|
|
209
|
-
export function getStronglyConnectedComponents(graph) {
|
|
210
|
-
if (!graph || typeof graph !== 'object') {
|
|
211
|
-
return [];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const visited = new Set();
|
|
215
|
-
const stack = [];
|
|
216
|
-
const components = [];
|
|
217
|
-
|
|
218
|
-
// DFS to fill stack
|
|
219
|
-
function dfs1(node) {
|
|
220
|
-
if (visited.has(node)) return;
|
|
221
|
-
visited.add(node);
|
|
222
|
-
|
|
223
|
-
const neighbors = graph[node] || [];
|
|
224
|
-
for (const neighbor of neighbors) {
|
|
225
|
-
dfs1(neighbor);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
stack.push(node);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Get transpose graph
|
|
232
|
-
const transpose = {};
|
|
233
|
-
for (const [node, neighbors] of Object.entries(graph)) {
|
|
234
|
-
for (const neighbor of neighbors) {
|
|
235
|
-
if (!transpose[neighbor]) transpose[neighbor] = [];
|
|
236
|
-
transpose[neighbor].push(node);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// DFS on transpose
|
|
241
|
-
function dfs2(node, component) {
|
|
242
|
-
if (visited.has(node)) return;
|
|
243
|
-
visited.add(node);
|
|
244
|
-
component.push(node);
|
|
245
|
-
|
|
246
|
-
const neighbors = transpose[node] || [];
|
|
247
|
-
for (const neighbor of neighbors) {
|
|
248
|
-
dfs2(neighbor, component);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Fill stack
|
|
253
|
-
for (const node of Object.keys(graph)) {
|
|
254
|
-
dfs1(node);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Find components
|
|
258
|
-
visited.clear();
|
|
259
|
-
while (stack.length > 0) {
|
|
260
|
-
const node = stack.pop();
|
|
261
|
-
if (!visited.has(node)) {
|
|
262
|
-
const component = [];
|
|
263
|
-
dfs2(node, component);
|
|
264
|
-
if (component.length > 0) {
|
|
265
|
-
components.push(component);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return components;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Check if a graph has self-referencing nodes.
|
|
275
|
-
*
|
|
276
|
-
* @param {Object} graph - Graph as adjacency list
|
|
277
|
-
* @returns {boolean} True if any node references itself
|
|
278
|
-
*/
|
|
279
|
-
export function hasSelfReferences(graph) {
|
|
280
|
-
if (!graph || typeof graph !== 'object') {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
for (const [node, neighbors] of Object.entries(graph)) {
|
|
285
|
-
if (Array.isArray(neighbors) && neighbors.includes(node)) {
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Count quads/triples in an RDF graph/store.
|
|
295
|
-
*
|
|
296
|
-
* @param {Object} store - RDF Store instance
|
|
297
|
-
* @returns {number} Number of quads
|
|
298
|
-
*/
|
|
299
|
-
export function quadCount(store) {
|
|
300
|
-
if (!store || typeof store !== 'object') {
|
|
301
|
-
return 0;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Check for .size property (n3.Store)
|
|
305
|
-
if ('size' in store && typeof store.size === 'number') {
|
|
306
|
-
return store.size;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Check for .length property
|
|
310
|
-
if ('length' in store && typeof store.length === 'number') {
|
|
311
|
-
return store.length;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Try counting quads
|
|
315
|
-
if (typeof store.getQuads === 'function') {
|
|
316
|
-
return store.getQuads().length;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Try iterating
|
|
320
|
-
if (typeof store[Symbol.iterator] === 'function') {
|
|
321
|
-
return Array.from(store).length;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return 0;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Count timestamps in RDF data.
|
|
329
|
-
*
|
|
330
|
-
* @param {Object} store - RDF Store instance
|
|
331
|
-
* @returns {number} Number of xsd:dateTime literals
|
|
332
|
-
*/
|
|
333
|
-
export function timestampCount(store) {
|
|
334
|
-
if (!store || typeof store.getQuads !== 'function') {
|
|
335
|
-
return 0;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
let count = 0;
|
|
339
|
-
const quads = store.getQuads();
|
|
340
|
-
|
|
341
|
-
for (const quad of quads) {
|
|
342
|
-
const obj = quad.object;
|
|
343
|
-
if (obj?.datatype?.value === 'http://www.w3.org/2001/XMLSchema#dateTime') {
|
|
344
|
-
count++;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return count;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Detect DST (Daylight Saving Time) transitions in timestamp data.
|
|
353
|
-
*
|
|
354
|
-
* @param {Object} store - RDF Store with timestamp data
|
|
355
|
-
* @returns {Array<Object>} DST transitions detected
|
|
356
|
-
*/
|
|
357
|
-
export function detectDstTransitions(store) {
|
|
358
|
-
if (!store || typeof store.getQuads !== 'function') {
|
|
359
|
-
return [];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const transitions = [];
|
|
363
|
-
const timestamps = [];
|
|
364
|
-
|
|
365
|
-
// Extract all timestamps
|
|
366
|
-
const quads = store.getQuads();
|
|
367
|
-
for (const quad of quads) {
|
|
368
|
-
const obj = quad.object;
|
|
369
|
-
if (obj?.datatype?.value === 'http://www.w3.org/2001/XMLSchema#dateTime') {
|
|
370
|
-
try {
|
|
371
|
-
timestamps.push(new Date(obj.value));
|
|
372
|
-
} catch {
|
|
373
|
-
// Skip invalid timestamps
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Sort timestamps
|
|
379
|
-
timestamps.sort((a, b) => a - b);
|
|
380
|
-
|
|
381
|
-
// Detect hour-offset jumps (DST transitions)
|
|
382
|
-
for (let i = 1; i < timestamps.length; i++) {
|
|
383
|
-
const diff = timestamps[i] - timestamps[i - 1];
|
|
384
|
-
const hourDiff = diff / (1000 * 60 * 60);
|
|
385
|
-
|
|
386
|
-
// DST transition is typically 1-hour jump
|
|
387
|
-
if (Math.abs(hourDiff - 1) < 0.1 || Math.abs(hourDiff + 1) < 0.1) {
|
|
388
|
-
transitions.push({
|
|
389
|
-
from: timestamps[i - 1],
|
|
390
|
-
to: timestamps[i],
|
|
391
|
-
offsetHours: hourDiff,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return transitions;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Safe numeric parsing with validation.
|
|
401
|
-
*
|
|
402
|
-
* @param {any} value - Value to parse
|
|
403
|
-
* @param {number} defaultValue - Default if parsing fails
|
|
404
|
-
* @returns {number} Parsed number or default
|
|
405
|
-
*/
|
|
406
|
-
export function safeParseNumber(value, defaultValue = 0) {
|
|
407
|
-
if (typeof value === 'number') {
|
|
408
|
-
return isNaN(value) ? defaultValue : value;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (typeof value === 'string') {
|
|
412
|
-
const parsed = parseFloat(value);
|
|
413
|
-
return isNaN(parsed) ? defaultValue : parsed;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return defaultValue;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Safe integer parsing with validation.
|
|
421
|
-
*
|
|
422
|
-
* @param {any} value - Value to parse
|
|
423
|
-
* @param {number} defaultValue - Default if parsing fails
|
|
424
|
-
* @returns {number} Parsed integer or default
|
|
425
|
-
*/
|
|
426
|
-
export function safeParseInt(value, defaultValue = 0) {
|
|
427
|
-
if (typeof value === 'number') {
|
|
428
|
-
return Number.isInteger(value) ? value : defaultValue;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (typeof value === 'string') {
|
|
432
|
-
const parsed = parseInt(value, 10);
|
|
433
|
-
return isNaN(parsed) ? defaultValue : parsed;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return defaultValue;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Clamp a number to a range.
|
|
441
|
-
*
|
|
442
|
-
* @param {number} value - Value to clamp
|
|
443
|
-
* @param {number} min - Minimum value
|
|
444
|
-
* @param {number} max - Maximum value
|
|
445
|
-
* @returns {number} Clamped value
|
|
446
|
-
*/
|
|
447
|
-
export function clamp(value, min, max) {
|
|
448
|
-
return Math.max(min, Math.min(max, value));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Safe division (prevents divide by zero).
|
|
453
|
-
*
|
|
454
|
-
* @param {number} numerator - Numerator
|
|
455
|
-
* @param {number} denominator - Denominator
|
|
456
|
-
* @param {number} defaultValue - Default if division by zero
|
|
457
|
-
* @returns {number} Result or default
|
|
458
|
-
*/
|
|
459
|
-
export function safeDivide(numerator, denominator, defaultValue = 0) {
|
|
460
|
-
if (denominator === 0) {
|
|
461
|
-
return defaultValue;
|
|
462
|
-
}
|
|
463
|
-
return numerator / denominator;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Retry an async operation with exponential backoff.
|
|
468
|
-
*
|
|
469
|
-
* @param {Function} fn - Async function to retry
|
|
470
|
-
* @param {Object} options - Retry options
|
|
471
|
-
* @returns {Promise<any>} Result of successful execution
|
|
472
|
-
*/
|
|
473
|
-
export async function retry(fn, options = {}) {
|
|
474
|
-
const {
|
|
475
|
-
maxAttempts = 3,
|
|
476
|
-
baseDelay = 100,
|
|
477
|
-
maxDelay = 5000,
|
|
478
|
-
backoffFactor = 2,
|
|
479
|
-
onRetry = null,
|
|
480
|
-
} = options;
|
|
481
|
-
|
|
482
|
-
let lastError;
|
|
483
|
-
|
|
484
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
485
|
-
try {
|
|
486
|
-
return await fn();
|
|
487
|
-
} catch (error) {
|
|
488
|
-
lastError = error;
|
|
489
|
-
|
|
490
|
-
if (attempt < maxAttempts - 1) {
|
|
491
|
-
const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt), maxDelay);
|
|
492
|
-
|
|
493
|
-
if (onRetry) {
|
|
494
|
-
onRetry(error, attempt + 1, delay);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
throw lastError;
|
|
503
|
-
}
|