@cloudnux/local-cloud-provider 0.17.0 → 0.19.0

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.
@@ -1,1019 +1,15 @@
1
- // src/logging/pretty-writer.ts
2
- import chalk from "chalk";
3
- import { EOL as EOL2 } from "os";
4
-
5
- // ../../../node_modules/lodash-es/_freeGlobal.js
6
- var freeGlobal = typeof global == "object" && global && global.Object === Object && global;
7
- var freeGlobal_default = freeGlobal;
8
-
9
- // ../../../node_modules/lodash-es/_root.js
10
- var freeSelf = typeof self == "object" && self && self.Object === Object && self;
11
- var root = freeGlobal_default || freeSelf || Function("return this")();
12
- var root_default = root;
13
-
14
- // ../../../node_modules/lodash-es/_Symbol.js
15
- var Symbol = root_default.Symbol;
16
- var Symbol_default = Symbol;
17
-
18
- // ../../../node_modules/lodash-es/_getRawTag.js
19
- var objectProto = Object.prototype;
20
- var hasOwnProperty = objectProto.hasOwnProperty;
21
- var nativeObjectToString = objectProto.toString;
22
- var symToStringTag = Symbol_default ? Symbol_default.toStringTag : void 0;
23
- function getRawTag(value) {
24
- var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
25
- try {
26
- value[symToStringTag] = void 0;
27
- var unmasked = true;
28
- } catch (e) {
29
- }
30
- var result = nativeObjectToString.call(value);
31
- if (unmasked) {
32
- if (isOwn) {
33
- value[symToStringTag] = tag;
34
- } else {
35
- delete value[symToStringTag];
36
- }
37
- }
38
- return result;
39
- }
40
- var getRawTag_default = getRawTag;
41
-
42
- // ../../../node_modules/lodash-es/_objectToString.js
43
- var objectProto2 = Object.prototype;
44
- var nativeObjectToString2 = objectProto2.toString;
45
- function objectToString(value) {
46
- return nativeObjectToString2.call(value);
47
- }
48
- var objectToString_default = objectToString;
49
-
50
- // ../../../node_modules/lodash-es/_baseGetTag.js
51
- var nullTag = "[object Null]";
52
- var undefinedTag = "[object Undefined]";
53
- var symToStringTag2 = Symbol_default ? Symbol_default.toStringTag : void 0;
54
- function baseGetTag(value) {
55
- if (value == null) {
56
- return value === void 0 ? undefinedTag : nullTag;
57
- }
58
- return symToStringTag2 && symToStringTag2 in Object(value) ? getRawTag_default(value) : objectToString_default(value);
59
- }
60
- var baseGetTag_default = baseGetTag;
61
-
62
- // ../../../node_modules/lodash-es/isObjectLike.js
63
- function isObjectLike(value) {
64
- return value != null && typeof value == "object";
65
- }
66
- var isObjectLike_default = isObjectLike;
67
-
68
- // ../../../node_modules/lodash-es/isSymbol.js
69
- var symbolTag = "[object Symbol]";
70
- function isSymbol(value) {
71
- return typeof value == "symbol" || isObjectLike_default(value) && baseGetTag_default(value) == symbolTag;
72
- }
73
- var isSymbol_default = isSymbol;
74
-
75
- // ../../../node_modules/lodash-es/_arrayMap.js
76
- function arrayMap(array2, iteratee) {
77
- var index = -1, length = array2 == null ? 0 : array2.length, result = Array(length);
78
- while (++index < length) {
79
- result[index] = iteratee(array2[index], index, array2);
80
- }
81
- return result;
82
- }
83
- var arrayMap_default = arrayMap;
84
-
85
- // ../../../node_modules/lodash-es/isArray.js
86
- var isArray = Array.isArray;
87
- var isArray_default = isArray;
88
-
89
- // ../../../node_modules/lodash-es/_baseToString.js
90
- var INFINITY = 1 / 0;
91
- var symbolProto = Symbol_default ? Symbol_default.prototype : void 0;
92
- var symbolToString = symbolProto ? symbolProto.toString : void 0;
93
- function baseToString(value) {
94
- if (typeof value == "string") {
95
- return value;
96
- }
97
- if (isArray_default(value)) {
98
- return arrayMap_default(value, baseToString) + "";
99
- }
100
- if (isSymbol_default(value)) {
101
- return symbolToString ? symbolToString.call(value) : "";
102
- }
103
- var result = value + "";
104
- return result == "0" && 1 / value == -INFINITY ? "-0" : result;
105
- }
106
- var baseToString_default = baseToString;
107
-
108
- // ../../../node_modules/lodash-es/_trimmedEndIndex.js
109
- var reWhitespace = /\s/;
110
- function trimmedEndIndex(string) {
111
- var index = string.length;
112
- while (index-- && reWhitespace.test(string.charAt(index))) {
113
- }
114
- return index;
115
- }
116
- var trimmedEndIndex_default = trimmedEndIndex;
117
-
118
- // ../../../node_modules/lodash-es/_baseTrim.js
119
- var reTrimStart = /^\s+/;
120
- function baseTrim(string) {
121
- return string ? string.slice(0, trimmedEndIndex_default(string) + 1).replace(reTrimStart, "") : string;
122
- }
123
- var baseTrim_default = baseTrim;
124
-
125
- // ../../../node_modules/lodash-es/isObject.js
126
- function isObject(value) {
127
- var type = typeof value;
128
- return value != null && (type == "object" || type == "function");
129
- }
130
- var isObject_default = isObject;
131
-
132
- // ../../../node_modules/lodash-es/isFunction.js
133
- var asyncTag = "[object AsyncFunction]";
134
- var funcTag = "[object Function]";
135
- var genTag = "[object GeneratorFunction]";
136
- var proxyTag = "[object Proxy]";
137
- function isFunction(value) {
138
- if (!isObject_default(value)) {
139
- return false;
140
- }
141
- var tag = baseGetTag_default(value);
142
- return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
143
- }
144
- var isFunction_default = isFunction;
145
-
146
- // ../../../node_modules/lodash-es/_coreJsData.js
147
- var coreJsData = root_default["__core-js_shared__"];
148
- var coreJsData_default = coreJsData;
149
-
150
- // ../../../node_modules/lodash-es/_isMasked.js
151
- var maskSrcKey = function() {
152
- var uid = /[^.]+$/.exec(coreJsData_default && coreJsData_default.keys && coreJsData_default.keys.IE_PROTO || "");
153
- return uid ? "Symbol(src)_1." + uid : "";
154
- }();
155
- function isMasked(func) {
156
- return !!maskSrcKey && maskSrcKey in func;
157
- }
158
- var isMasked_default = isMasked;
159
-
160
- // ../../../node_modules/lodash-es/_toSource.js
161
- var funcProto = Function.prototype;
162
- var funcToString = funcProto.toString;
163
- function toSource(func) {
164
- if (func != null) {
165
- try {
166
- return funcToString.call(func);
167
- } catch (e) {
168
- }
169
- try {
170
- return func + "";
171
- } catch (e) {
172
- }
173
- }
174
- return "";
175
- }
176
- var toSource_default = toSource;
177
-
178
- // ../../../node_modules/lodash-es/_baseIsNative.js
179
- var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
180
- var reIsHostCtor = /^\[object .+?Constructor\]$/;
181
- var funcProto2 = Function.prototype;
182
- var objectProto3 = Object.prototype;
183
- var funcToString2 = funcProto2.toString;
184
- var hasOwnProperty2 = objectProto3.hasOwnProperty;
185
- var reIsNative = RegExp(
186
- "^" + funcToString2.call(hasOwnProperty2).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"
187
- );
188
- function baseIsNative(value) {
189
- if (!isObject_default(value) || isMasked_default(value)) {
190
- return false;
191
- }
192
- var pattern = isFunction_default(value) ? reIsNative : reIsHostCtor;
193
- return pattern.test(toSource_default(value));
194
- }
195
- var baseIsNative_default = baseIsNative;
196
-
197
- // ../../../node_modules/lodash-es/_getValue.js
198
- function getValue(object, key) {
199
- return object == null ? void 0 : object[key];
200
- }
201
- var getValue_default = getValue;
202
-
203
- // ../../../node_modules/lodash-es/_getNative.js
204
- function getNative(object, key) {
205
- var value = getValue_default(object, key);
206
- return baseIsNative_default(value) ? value : void 0;
207
- }
208
- var getNative_default = getNative;
209
-
210
- // ../../../node_modules/lodash-es/_baseFindIndex.js
211
- function baseFindIndex(array2, predicate, fromIndex, fromRight) {
212
- var length = array2.length, index = fromIndex + (fromRight ? 1 : -1);
213
- while (fromRight ? index-- : ++index < length) {
214
- if (predicate(array2[index], index, array2)) {
215
- return index;
216
- }
217
- }
218
- return -1;
219
- }
220
- var baseFindIndex_default = baseFindIndex;
221
-
222
- // ../../../node_modules/lodash-es/_baseIsNaN.js
223
- function baseIsNaN(value) {
224
- return value !== value;
225
- }
226
- var baseIsNaN_default = baseIsNaN;
227
-
228
- // ../../../node_modules/lodash-es/_strictIndexOf.js
229
- function strictIndexOf(array2, value, fromIndex) {
230
- var index = fromIndex - 1, length = array2.length;
231
- while (++index < length) {
232
- if (array2[index] === value) {
233
- return index;
234
- }
235
- }
236
- return -1;
237
- }
238
- var strictIndexOf_default = strictIndexOf;
239
-
240
- // ../../../node_modules/lodash-es/_baseIndexOf.js
241
- function baseIndexOf(array2, value, fromIndex) {
242
- return value === value ? strictIndexOf_default(array2, value, fromIndex) : baseFindIndex_default(array2, baseIsNaN_default, fromIndex);
243
- }
244
- var baseIndexOf_default = baseIndexOf;
245
-
246
- // ../../../node_modules/lodash-es/_isIndex.js
247
- var MAX_SAFE_INTEGER = 9007199254740991;
248
- var reIsUint = /^(?:0|[1-9]\d*)$/;
249
- function isIndex(value, length) {
250
- var type = typeof value;
251
- length = length == null ? MAX_SAFE_INTEGER : length;
252
- return !!length && (type == "number" || type != "symbol" && reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length);
253
- }
254
- var isIndex_default = isIndex;
255
-
256
- // ../../../node_modules/lodash-es/eq.js
257
- function eq(value, other) {
258
- return value === other || value !== value && other !== other;
259
- }
260
- var eq_default = eq;
261
-
262
- // ../../../node_modules/lodash-es/isLength.js
263
- var MAX_SAFE_INTEGER2 = 9007199254740991;
264
- function isLength(value) {
265
- return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER2;
266
- }
267
- var isLength_default = isLength;
268
-
269
- // ../../../node_modules/lodash-es/_baseIsArguments.js
270
- var argsTag = "[object Arguments]";
271
- function baseIsArguments(value) {
272
- return isObjectLike_default(value) && baseGetTag_default(value) == argsTag;
273
- }
274
- var baseIsArguments_default = baseIsArguments;
275
-
276
- // ../../../node_modules/lodash-es/isArguments.js
277
- var objectProto4 = Object.prototype;
278
- var hasOwnProperty3 = objectProto4.hasOwnProperty;
279
- var propertyIsEnumerable = objectProto4.propertyIsEnumerable;
280
- var isArguments = baseIsArguments_default(/* @__PURE__ */ function() {
281
- return arguments;
282
- }()) ? baseIsArguments_default : function(value) {
283
- return isObjectLike_default(value) && hasOwnProperty3.call(value, "callee") && !propertyIsEnumerable.call(value, "callee");
284
- };
285
- var isArguments_default = isArguments;
286
-
287
- // ../../../node_modules/lodash-es/_isKey.js
288
- var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
289
- var reIsPlainProp = /^\w*$/;
290
- function isKey(value, object) {
291
- if (isArray_default(value)) {
292
- return false;
293
- }
294
- var type = typeof value;
295
- if (type == "number" || type == "symbol" || type == "boolean" || value == null || isSymbol_default(value)) {
296
- return true;
297
- }
298
- return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || object != null && value in Object(object);
299
- }
300
- var isKey_default = isKey;
301
-
302
- // ../../../node_modules/lodash-es/_nativeCreate.js
303
- var nativeCreate = getNative_default(Object, "create");
304
- var nativeCreate_default = nativeCreate;
305
-
306
- // ../../../node_modules/lodash-es/_hashClear.js
307
- function hashClear() {
308
- this.__data__ = nativeCreate_default ? nativeCreate_default(null) : {};
309
- this.size = 0;
310
- }
311
- var hashClear_default = hashClear;
312
-
313
- // ../../../node_modules/lodash-es/_hashDelete.js
314
- function hashDelete(key) {
315
- var result = this.has(key) && delete this.__data__[key];
316
- this.size -= result ? 1 : 0;
317
- return result;
318
- }
319
- var hashDelete_default = hashDelete;
320
-
321
- // ../../../node_modules/lodash-es/_hashGet.js
322
- var HASH_UNDEFINED = "__lodash_hash_undefined__";
323
- var objectProto5 = Object.prototype;
324
- var hasOwnProperty4 = objectProto5.hasOwnProperty;
325
- function hashGet(key) {
326
- var data = this.__data__;
327
- if (nativeCreate_default) {
328
- var result = data[key];
329
- return result === HASH_UNDEFINED ? void 0 : result;
330
- }
331
- return hasOwnProperty4.call(data, key) ? data[key] : void 0;
332
- }
333
- var hashGet_default = hashGet;
334
-
335
- // ../../../node_modules/lodash-es/_hashHas.js
336
- var objectProto6 = Object.prototype;
337
- var hasOwnProperty5 = objectProto6.hasOwnProperty;
338
- function hashHas(key) {
339
- var data = this.__data__;
340
- return nativeCreate_default ? data[key] !== void 0 : hasOwnProperty5.call(data, key);
341
- }
342
- var hashHas_default = hashHas;
343
-
344
- // ../../../node_modules/lodash-es/_hashSet.js
345
- var HASH_UNDEFINED2 = "__lodash_hash_undefined__";
346
- function hashSet(key, value) {
347
- var data = this.__data__;
348
- this.size += this.has(key) ? 0 : 1;
349
- data[key] = nativeCreate_default && value === void 0 ? HASH_UNDEFINED2 : value;
350
- return this;
351
- }
352
- var hashSet_default = hashSet;
353
-
354
- // ../../../node_modules/lodash-es/_Hash.js
355
- function Hash(entries) {
356
- var index = -1, length = entries == null ? 0 : entries.length;
357
- this.clear();
358
- while (++index < length) {
359
- var entry = entries[index];
360
- this.set(entry[0], entry[1]);
361
- }
362
- }
363
- Hash.prototype.clear = hashClear_default;
364
- Hash.prototype["delete"] = hashDelete_default;
365
- Hash.prototype.get = hashGet_default;
366
- Hash.prototype.has = hashHas_default;
367
- Hash.prototype.set = hashSet_default;
368
- var Hash_default = Hash;
369
-
370
- // ../../../node_modules/lodash-es/_listCacheClear.js
371
- function listCacheClear() {
372
- this.__data__ = [];
373
- this.size = 0;
374
- }
375
- var listCacheClear_default = listCacheClear;
376
-
377
- // ../../../node_modules/lodash-es/_assocIndexOf.js
378
- function assocIndexOf(array2, key) {
379
- var length = array2.length;
380
- while (length--) {
381
- if (eq_default(array2[length][0], key)) {
382
- return length;
383
- }
384
- }
385
- return -1;
386
- }
387
- var assocIndexOf_default = assocIndexOf;
388
-
389
- // ../../../node_modules/lodash-es/_listCacheDelete.js
390
- var arrayProto = Array.prototype;
391
- var splice = arrayProto.splice;
392
- function listCacheDelete(key) {
393
- var data = this.__data__, index = assocIndexOf_default(data, key);
394
- if (index < 0) {
395
- return false;
396
- }
397
- var lastIndex = data.length - 1;
398
- if (index == lastIndex) {
399
- data.pop();
400
- } else {
401
- splice.call(data, index, 1);
402
- }
403
- --this.size;
404
- return true;
405
- }
406
- var listCacheDelete_default = listCacheDelete;
407
-
408
- // ../../../node_modules/lodash-es/_listCacheGet.js
409
- function listCacheGet(key) {
410
- var data = this.__data__, index = assocIndexOf_default(data, key);
411
- return index < 0 ? void 0 : data[index][1];
412
- }
413
- var listCacheGet_default = listCacheGet;
414
-
415
- // ../../../node_modules/lodash-es/_listCacheHas.js
416
- function listCacheHas(key) {
417
- return assocIndexOf_default(this.__data__, key) > -1;
418
- }
419
- var listCacheHas_default = listCacheHas;
420
-
421
- // ../../../node_modules/lodash-es/_listCacheSet.js
422
- function listCacheSet(key, value) {
423
- var data = this.__data__, index = assocIndexOf_default(data, key);
424
- if (index < 0) {
425
- ++this.size;
426
- data.push([key, value]);
427
- } else {
428
- data[index][1] = value;
429
- }
430
- return this;
431
- }
432
- var listCacheSet_default = listCacheSet;
433
-
434
- // ../../../node_modules/lodash-es/_ListCache.js
435
- function ListCache(entries) {
436
- var index = -1, length = entries == null ? 0 : entries.length;
437
- this.clear();
438
- while (++index < length) {
439
- var entry = entries[index];
440
- this.set(entry[0], entry[1]);
441
- }
442
- }
443
- ListCache.prototype.clear = listCacheClear_default;
444
- ListCache.prototype["delete"] = listCacheDelete_default;
445
- ListCache.prototype.get = listCacheGet_default;
446
- ListCache.prototype.has = listCacheHas_default;
447
- ListCache.prototype.set = listCacheSet_default;
448
- var ListCache_default = ListCache;
449
-
450
- // ../../../node_modules/lodash-es/_Map.js
451
- var Map2 = getNative_default(root_default, "Map");
452
- var Map_default = Map2;
453
-
454
- // ../../../node_modules/lodash-es/_mapCacheClear.js
455
- function mapCacheClear() {
456
- this.size = 0;
457
- this.__data__ = {
458
- "hash": new Hash_default(),
459
- "map": new (Map_default || ListCache_default)(),
460
- "string": new Hash_default()
461
- };
462
- }
463
- var mapCacheClear_default = mapCacheClear;
464
-
465
- // ../../../node_modules/lodash-es/_isKeyable.js
466
- function isKeyable(value) {
467
- var type = typeof value;
468
- return type == "string" || type == "number" || type == "symbol" || type == "boolean" ? value !== "__proto__" : value === null;
469
- }
470
- var isKeyable_default = isKeyable;
471
-
472
- // ../../../node_modules/lodash-es/_getMapData.js
473
- function getMapData(map, key) {
474
- var data = map.__data__;
475
- return isKeyable_default(key) ? data[typeof key == "string" ? "string" : "hash"] : data.map;
476
- }
477
- var getMapData_default = getMapData;
478
-
479
- // ../../../node_modules/lodash-es/_mapCacheDelete.js
480
- function mapCacheDelete(key) {
481
- var result = getMapData_default(this, key)["delete"](key);
482
- this.size -= result ? 1 : 0;
483
- return result;
484
- }
485
- var mapCacheDelete_default = mapCacheDelete;
486
-
487
- // ../../../node_modules/lodash-es/_mapCacheGet.js
488
- function mapCacheGet(key) {
489
- return getMapData_default(this, key).get(key);
490
- }
491
- var mapCacheGet_default = mapCacheGet;
492
-
493
- // ../../../node_modules/lodash-es/_mapCacheHas.js
494
- function mapCacheHas(key) {
495
- return getMapData_default(this, key).has(key);
496
- }
497
- var mapCacheHas_default = mapCacheHas;
498
-
499
- // ../../../node_modules/lodash-es/_mapCacheSet.js
500
- function mapCacheSet(key, value) {
501
- var data = getMapData_default(this, key), size = data.size;
502
- data.set(key, value);
503
- this.size += data.size == size ? 0 : 1;
504
- return this;
505
- }
506
- var mapCacheSet_default = mapCacheSet;
507
-
508
- // ../../../node_modules/lodash-es/_MapCache.js
509
- function MapCache(entries) {
510
- var index = -1, length = entries == null ? 0 : entries.length;
511
- this.clear();
512
- while (++index < length) {
513
- var entry = entries[index];
514
- this.set(entry[0], entry[1]);
515
- }
516
- }
517
- MapCache.prototype.clear = mapCacheClear_default;
518
- MapCache.prototype["delete"] = mapCacheDelete_default;
519
- MapCache.prototype.get = mapCacheGet_default;
520
- MapCache.prototype.has = mapCacheHas_default;
521
- MapCache.prototype.set = mapCacheSet_default;
522
- var MapCache_default = MapCache;
523
-
524
- // ../../../node_modules/lodash-es/memoize.js
525
- var FUNC_ERROR_TEXT = "Expected a function";
526
- function memoize(func, resolver) {
527
- if (typeof func != "function" || resolver != null && typeof resolver != "function") {
528
- throw new TypeError(FUNC_ERROR_TEXT);
529
- }
530
- var memoized = function() {
531
- var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], cache = memoized.cache;
532
- if (cache.has(key)) {
533
- return cache.get(key);
534
- }
535
- var result = func.apply(this, args);
536
- memoized.cache = cache.set(key, result) || cache;
537
- return result;
538
- };
539
- memoized.cache = new (memoize.Cache || MapCache_default)();
540
- return memoized;
541
- }
542
- memoize.Cache = MapCache_default;
543
- var memoize_default = memoize;
544
-
545
- // ../../../node_modules/lodash-es/_memoizeCapped.js
546
- var MAX_MEMOIZE_SIZE = 500;
547
- function memoizeCapped(func) {
548
- var result = memoize_default(func, function(key) {
549
- if (cache.size === MAX_MEMOIZE_SIZE) {
550
- cache.clear();
551
- }
552
- return key;
553
- });
554
- var cache = result.cache;
555
- return result;
556
- }
557
- var memoizeCapped_default = memoizeCapped;
558
-
559
- // ../../../node_modules/lodash-es/_stringToPath.js
560
- var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
561
- var reEscapeChar = /\\(\\)?/g;
562
- var stringToPath = memoizeCapped_default(function(string) {
563
- var result = [];
564
- if (string.charCodeAt(0) === 46) {
565
- result.push("");
566
- }
567
- string.replace(rePropName, function(match, number, quote, subString) {
568
- result.push(quote ? subString.replace(reEscapeChar, "$1") : number || match);
569
- });
570
- return result;
571
- });
572
- var stringToPath_default = stringToPath;
573
-
574
- // ../../../node_modules/lodash-es/toString.js
575
- function toString(value) {
576
- return value == null ? "" : baseToString_default(value);
577
- }
578
- var toString_default = toString;
579
-
580
- // ../../../node_modules/lodash-es/_castPath.js
581
- function castPath(value, object) {
582
- if (isArray_default(value)) {
583
- return value;
584
- }
585
- return isKey_default(value, object) ? [value] : stringToPath_default(toString_default(value));
586
- }
587
- var castPath_default = castPath;
588
-
589
- // ../../../node_modules/lodash-es/_toKey.js
590
- var INFINITY2 = 1 / 0;
591
- function toKey(value) {
592
- if (typeof value == "string" || isSymbol_default(value)) {
593
- return value;
594
- }
595
- var result = value + "";
596
- return result == "0" && 1 / value == -INFINITY2 ? "-0" : result;
597
- }
598
- var toKey_default = toKey;
599
-
600
- // ../../../node_modules/lodash-es/_baseSlice.js
601
- function baseSlice(array2, start, end) {
602
- var index = -1, length = array2.length;
603
- if (start < 0) {
604
- start = -start > length ? 0 : length + start;
605
- }
606
- end = end > length ? length : end;
607
- if (end < 0) {
608
- end += length;
609
- }
610
- length = start > end ? 0 : end - start >>> 0;
611
- start >>>= 0;
612
- var result = Array(length);
613
- while (++index < length) {
614
- result[index] = array2[index + start];
615
- }
616
- return result;
617
- }
618
- var baseSlice_default = baseSlice;
619
-
620
- // ../../../node_modules/lodash-es/_castSlice.js
621
- function castSlice(array2, start, end) {
622
- var length = array2.length;
623
- end = end === void 0 ? length : end;
624
- return !start && end >= length ? array2 : baseSlice_default(array2, start, end);
625
- }
626
- var castSlice_default = castSlice;
627
-
628
- // ../../../node_modules/lodash-es/_hasUnicode.js
629
- var rsAstralRange = "\\ud800-\\udfff";
630
- var rsComboMarksRange = "\\u0300-\\u036f";
631
- var reComboHalfMarksRange = "\\ufe20-\\ufe2f";
632
- var rsComboSymbolsRange = "\\u20d0-\\u20ff";
633
- var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange;
634
- var rsVarRange = "\\ufe0e\\ufe0f";
635
- var rsZWJ = "\\u200d";
636
- var reHasUnicode = RegExp("[" + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + "]");
637
- function hasUnicode(string) {
638
- return reHasUnicode.test(string);
639
- }
640
- var hasUnicode_default = hasUnicode;
641
-
642
- // ../../../node_modules/lodash-es/_asciiToArray.js
643
- function asciiToArray(string) {
644
- return string.split("");
645
- }
646
- var asciiToArray_default = asciiToArray;
647
-
648
- // ../../../node_modules/lodash-es/_unicodeToArray.js
649
- var rsAstralRange2 = "\\ud800-\\udfff";
650
- var rsComboMarksRange2 = "\\u0300-\\u036f";
651
- var reComboHalfMarksRange2 = "\\ufe20-\\ufe2f";
652
- var rsComboSymbolsRange2 = "\\u20d0-\\u20ff";
653
- var rsComboRange2 = rsComboMarksRange2 + reComboHalfMarksRange2 + rsComboSymbolsRange2;
654
- var rsVarRange2 = "\\ufe0e\\ufe0f";
655
- var rsAstral = "[" + rsAstralRange2 + "]";
656
- var rsCombo = "[" + rsComboRange2 + "]";
657
- var rsFitz = "\\ud83c[\\udffb-\\udfff]";
658
- var rsModifier = "(?:" + rsCombo + "|" + rsFitz + ")";
659
- var rsNonAstral = "[^" + rsAstralRange2 + "]";
660
- var rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}";
661
- var rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]";
662
- var rsZWJ2 = "\\u200d";
663
- var reOptMod = rsModifier + "?";
664
- var rsOptVar = "[" + rsVarRange2 + "]?";
665
- var rsOptJoin = "(?:" + rsZWJ2 + "(?:" + [rsNonAstral, rsRegional, rsSurrPair].join("|") + ")" + rsOptVar + reOptMod + ")*";
666
- var rsSeq = rsOptVar + reOptMod + rsOptJoin;
667
- var rsSymbol = "(?:" + [rsNonAstral + rsCombo + "?", rsCombo, rsRegional, rsSurrPair, rsAstral].join("|") + ")";
668
- var reUnicode = RegExp(rsFitz + "(?=" + rsFitz + ")|" + rsSymbol + rsSeq, "g");
669
- function unicodeToArray(string) {
670
- return string.match(reUnicode) || [];
671
- }
672
- var unicodeToArray_default = unicodeToArray;
673
-
674
- // ../../../node_modules/lodash-es/_stringToArray.js
675
- function stringToArray(string) {
676
- return hasUnicode_default(string) ? unicodeToArray_default(string) : asciiToArray_default(string);
677
- }
678
- var stringToArray_default = stringToArray;
679
-
680
- // ../../../node_modules/lodash-es/_hasPath.js
681
- function hasPath(object, path4, hasFunc) {
682
- path4 = castPath_default(path4, object);
683
- var index = -1, length = path4.length, result = false;
684
- while (++index < length) {
685
- var key = toKey_default(path4[index]);
686
- if (!(result = object != null && hasFunc(object, key))) {
687
- break;
688
- }
689
- object = object[key];
690
- }
691
- if (result || ++index != length) {
692
- return result;
693
- }
694
- length = object == null ? 0 : object.length;
695
- return !!length && isLength_default(length) && isIndex_default(key, length) && (isArray_default(object) || isArguments_default(object));
696
- }
697
- var hasPath_default = hasPath;
698
-
699
- // ../../../node_modules/lodash-es/_baseHas.js
700
- var objectProto7 = Object.prototype;
701
- var hasOwnProperty6 = objectProto7.hasOwnProperty;
702
- function baseHas(object, key) {
703
- return object != null && hasOwnProperty6.call(object, key);
704
- }
705
- var baseHas_default = baseHas;
706
-
707
- // ../../../node_modules/lodash-es/has.js
708
- function has(object, path4) {
709
- return object != null && hasPath_default(object, path4, baseHas_default);
710
- }
711
- var has_default = has;
712
-
713
- // ../../../node_modules/lodash-es/_charsEndIndex.js
714
- function charsEndIndex(strSymbols, chrSymbols) {
715
- var index = strSymbols.length;
716
- while (index-- && baseIndexOf_default(chrSymbols, strSymbols[index], 0) > -1) {
717
- }
718
- return index;
719
- }
720
- var charsEndIndex_default = charsEndIndex;
721
-
722
- // ../../../node_modules/lodash-es/_charsStartIndex.js
723
- function charsStartIndex(strSymbols, chrSymbols) {
724
- var index = -1, length = strSymbols.length;
725
- while (++index < length && baseIndexOf_default(chrSymbols, strSymbols[index], 0) > -1) {
726
- }
727
- return index;
728
- }
729
- var charsStartIndex_default = charsStartIndex;
730
-
731
- // ../../../node_modules/lodash-es/trim.js
732
- function trim(string, chars, guard) {
733
- string = toString_default(string);
734
- if (string && (guard || chars === void 0)) {
735
- return baseTrim_default(string);
736
- }
737
- if (!string || !(chars = baseToString_default(chars))) {
738
- return string;
739
- }
740
- var strSymbols = stringToArray_default(string), chrSymbols = stringToArray_default(chars), start = charsStartIndex_default(strSymbols, chrSymbols), end = charsEndIndex_default(strSymbols, chrSymbols) + 1;
741
- return castSlice_default(strSymbols, start, end).join("");
742
- }
743
- var trim_default = trim;
744
-
745
- // ../../utils/src/config/env.ts
746
- function _env(key, defaultValue) {
747
- return has_default(process.env, key) ? process.env[key] ?? "" : defaultValue ?? "";
748
- }
749
- function int(key, defaultValue) {
750
- if (!has_default(process.env, key)) {
751
- return defaultValue ?? 0;
752
- }
753
- const value = process.env[key] ?? "0";
754
- return parseInt(value, 10);
755
- }
756
- function float(key, defaultValue) {
757
- if (!has_default(process.env, key)) {
758
- return defaultValue ?? 0;
759
- }
760
- const value = process.env[key] ?? "0";
761
- return parseFloat(value);
762
- }
763
- function bool(key, defaultValue) {
764
- if (!has_default(process.env, key)) {
765
- return defaultValue ?? false;
766
- }
767
- const value = process.env[key];
768
- return value === "true";
769
- }
770
- function json(key, defaultValue) {
771
- if (!has_default(process.env, key)) {
772
- return defaultValue ?? {};
773
- }
774
- const value = process.env[key] ?? "{}";
775
- try {
776
- return JSON.parse(value);
777
- } catch (error) {
778
- throw new Error(`Invalid json environment variable ${key}: ${error.message}`);
779
- }
780
- }
781
- function array(key, defaultValue) {
782
- if (!has_default(process.env, key)) {
783
- return defaultValue ?? [];
784
- }
785
- let value = process.env[key] ?? "";
786
- if (value.startsWith("[") && value.endsWith("]")) {
787
- value = value.substring(1, value.length - 1);
788
- }
789
- return value.split(",").map((v) => {
790
- return trim_default(trim_default(v, " "), '"');
791
- });
792
- }
793
- function date(key, defaultValue) {
794
- if (!has_default(process.env, key)) {
795
- return defaultValue ?? /* @__PURE__ */ new Date();
796
- }
797
- const value = process.env[key] ?? Date();
798
- return new Date(value);
799
- }
800
- var utils = {
801
- int,
802
- float,
803
- bool,
804
- json,
805
- array,
806
- date,
807
- /**
808
- * Gets a value from env that matches oneOf provided values
809
- * @param {string} key
810
- * @param {string[]} expectedValues
811
- * @param {string|undefined} defaultValue
812
- * @returns {string|undefined}
813
- */
814
- oneOf(key, expectedValues, defaultValue) {
815
- if (!expectedValues) {
816
- throw new Error(`env.oneOf requires expectedValues`);
817
- }
818
- if (defaultValue && !expectedValues.includes(defaultValue)) {
819
- throw new Error(`env.oneOf requires defaultValue to be included in expectedValues`);
820
- }
821
- const rawValue = env(key, defaultValue);
822
- return expectedValues.includes(rawValue) ? rawValue : defaultValue ?? "";
823
- }
824
- };
825
- var env = Object.assign(_env, utils);
826
-
827
- // ../../utils/src/logging/types.ts
828
- var logLevels = {
829
- fatal: -1,
830
- error: 0,
831
- warn: 1,
832
- info: 2,
833
- debug: 3,
834
- trace: 4
835
- };
836
-
837
- // ../../utils/src/logging/error-to-string.ts
838
- var safeJsonStringify = (value) => {
839
- const seen = /* @__PURE__ */ new WeakSet();
840
- return JSON.stringify(value, (key, currentValue) => {
841
- if (key === "constructor") return void 0;
842
- if (typeof currentValue === "object" && currentValue !== null) {
843
- if (seen.has(currentValue)) return "[Circular]";
844
- seen.add(currentValue);
845
- }
846
- return currentValue;
847
- });
848
- };
849
- var hasOwnConstructorProp = (value) => {
850
- if (typeof value !== "object" || value === null) return false;
851
- return Object.prototype.hasOwnProperty.call(value, "constructor");
852
- };
853
- var errorToString = (error) => {
854
- if (error === null) return "Null error";
855
- if (error === void 0) return "Undefined error";
856
- if (error instanceof Error) {
857
- return [
858
- `Name: ${error.name}`,
859
- `Message: ${error.message}`,
860
- `Stack: ${error.stack || "No stack trace available"}`,
861
- // Handle additional properties that might exist on custom errors
862
- ...Object.entries(error).filter(
863
- ([key, value]) => !["name", "message", "stack"].includes(key) && !hasOwnConstructorProp(value)
864
- ).map(([key, value]) => `${key}: ${safeJsonStringify(value)}`)
865
- ].join("\n");
866
- }
867
- if (typeof error === "string") return error;
868
- if (typeof error === "object") {
869
- try {
870
- return JSON.stringify(
871
- error,
872
- (key, value) => {
873
- if (key === "constructor") return void 0;
874
- return value;
875
- },
876
- 2
877
- );
878
- } catch {
879
- return `[Object that cannot be stringified: ${Object.prototype.toString.call(error)}]`;
880
- }
881
- }
882
- return String(error);
883
- };
884
-
885
- // ../../utils/src/logging/writer.ts
886
- import { EOL } from "os";
887
- var jsonWriter = (entry) => {
888
- process.stdout.write(JSON.stringify(entry) + EOL);
889
- };
890
- var _writer = jsonWriter;
891
- function setWriter(writer) {
892
- _writer = writer;
893
- }
894
- function getWriter() {
895
- return _writer;
896
- }
897
-
898
- // ../../utils/src/logging/index.ts
899
- var levelValues = {
900
- fatal: 70,
901
- error: 60,
902
- warn: 50,
903
- info: 40,
904
- debug: 30,
905
- trace: 20
906
- };
907
- var currentLogLevel = 2;
908
- var _module = "default";
909
- var _requestId = "";
910
- function writeLine(level, mergeObject, msg, bindings) {
911
- getWriter()({
912
- level: levelValues[level],
913
- levelName: level,
914
- time: Date.now(),
915
- module: bindings.module || _module,
916
- reqId: bindings.reqId || _requestId,
917
- msg: level === "error" || level === "fatal" ? errorToString(msg) : msg,
918
- ...mergeObject ? { meta: mergeObject } : {}
919
- });
920
- }
921
- function createLogger(bindings = {}) {
922
- const currentLogLevelName = env("LOG_LEVEL")?.toLowerCase() ?? "info";
923
- currentLogLevel = logLevels[currentLogLevelName] ?? logLevels.info;
924
- return {
925
- level: currentLogLevelName,
926
- fatal: (mergeObject, msg) => {
927
- if (currentLogLevel >= logLevels.fatal) {
928
- typeof mergeObject === "string" || mergeObject instanceof Error ? writeLine("fatal", null, mergeObject, bindings) : writeLine("fatal", mergeObject, msg ?? "", bindings);
929
- }
930
- },
931
- error: (mergeObject, msg) => {
932
- if (currentLogLevel >= logLevels.error) {
933
- typeof mergeObject === "string" || mergeObject instanceof Error ? writeLine("error", null, mergeObject, bindings) : writeLine("error", mergeObject, msg ?? "", bindings);
934
- }
935
- },
936
- warn: (mergeObject, msg) => {
937
- if (currentLogLevel >= logLevels.warn) {
938
- typeof mergeObject === "string" ? writeLine("warn", null, mergeObject, bindings) : writeLine("warn", mergeObject, msg ?? "", bindings);
939
- }
940
- },
941
- info: (mergeObject, msg) => {
942
- if (currentLogLevel >= logLevels.info) {
943
- typeof mergeObject === "string" ? writeLine("info", null, mergeObject, bindings) : writeLine("info", mergeObject, msg ?? "", bindings);
944
- }
945
- },
946
- debug: (mergeObject, msg) => {
947
- if (currentLogLevel >= logLevels.debug) {
948
- typeof mergeObject === "string" ? writeLine("debug", null, mergeObject, bindings) : writeLine("debug", mergeObject, msg ?? "", bindings);
949
- }
950
- },
951
- trace: (mergeObject, msg) => {
952
- if (currentLogLevel >= logLevels.trace) {
953
- typeof mergeObject === "string" ? writeLine("trace", null, mergeObject, bindings) : writeLine("trace", mergeObject, msg ?? "", bindings);
954
- }
955
- },
956
- silent: () => {
957
- },
958
- child: (childBindings) => createLogger({ ...bindings, ...childBindings })
959
- };
960
- }
961
- var logger = createLogger();
962
-
963
- // src/logging/pretty-writer.ts
964
- var levelColor = {
965
- fatal: chalk.bgRed.white,
966
- error: chalk.red,
967
- warn: chalk.yellow,
968
- info: chalk.cyan,
969
- debug: chalk.gray,
970
- trace: chalk.white
971
- };
972
- function formatTime(ms) {
973
- return new Date(ms).toTimeString().slice(0, 8);
974
- }
975
- function formatMeta(meta) {
976
- const { req, res, ...rest } = meta;
977
- const lines = [];
978
- if (req) {
979
- const reqParts = [`${req.method} ${req.url}`];
980
- if (req.remoteAddress) reqParts.push(`from ${req.remoteAddress}:${req.remotePort ?? ""}`);
981
- if (req.headers) reqParts.push(`headers=${JSON.stringify(req.headers, null, 2)}`);
982
- if (req.body !== void 0) reqParts.push(`body=${typeof req.body === "string" ? req.body : JSON.stringify(req.body, null, 2)}`);
983
- lines.push(chalk.dim(" req: " + reqParts.join(" ")));
984
- }
985
- if (res) {
986
- const resParts = [`${res.statusCode}`];
987
- if (res.headers) resParts.push(`headers=${JSON.stringify(res.headers, null, 2)}`);
988
- if (res.body !== void 0) resParts.push(`body=${typeof res.body === "string" ? res.body : JSON.stringify(res.body, null, 2)}`);
989
- lines.push(chalk.dim(" res: " + resParts.join(" ")));
990
- }
991
- if (Object.keys(rest).length) {
992
- lines.push(chalk.dim(" " + JSON.stringify(rest, null, 2)));
993
- }
994
- return lines.length ? EOL2 + lines.join(EOL2) : "";
995
- }
996
- var prettyWriter = (entry) => {
997
- const color = levelColor[entry.levelName] ?? chalk.white;
998
- const time = chalk.dim(formatTime(entry.time));
999
- const level = color(entry.levelName.toUpperCase().padEnd(5));
1000
- const module = chalk.magenta(entry.module);
1001
- const reqId = entry.reqId ? chalk.dim(`[${entry.reqId}]`) + " " : "";
1002
- const meta = entry.meta ? formatMeta(entry.meta) : "";
1003
- process.stdout.write(`${time} ${level} ${module} ${reqId}- ${entry.msg}${meta}${EOL2}`);
1004
- };
1005
- setWriter(prettyWriter);
1006
-
1007
1
  // src/dev-console-plugin/plugin.ts
1008
2
  import path3 from "path";
1009
3
  import fastifyStatic from "@fastify/static";
1010
4
  import fsPlugin4 from "fastify-plugin";
1011
5
  import { createRequire } from "module";
6
+ import { WebSocketConnectionGoneError as WebSocketConnectionGoneError2 } from "@cloudnux/core-cloud-provider";
1012
7
 
1013
8
  // src/queue-plugin/plugin.ts
1014
9
  import fsPlugin from "fastify-plugin";
1015
10
 
1016
11
  // src/queue-plugin/core.ts
12
+ var MAX_MESSAGE_HISTORY = 500;
1017
13
  var DEFAULT_CONFIG = {
1018
14
  batchSize: 10,
1019
15
  batchWindowMs: 500,
@@ -1021,12 +17,12 @@ var DEFAULT_CONFIG = {
1021
17
  parallel: true,
1022
18
  maxConcurrent: 5,
1023
19
  retryBackoff: true,
20
+ tickIntervalMs: 50,
1024
21
  persistence: {
1025
22
  enabled: true,
1026
23
  directory: "./.develop/queue-data",
1027
24
  saveInterval: 6e4,
1028
- saveOnShutdown: false,
1029
- loadOnStartup: true
25
+ saveOnShutdown: false
1030
26
  }
1031
27
  };
1032
28
  var mergeConfig = (defaultConfig, userConfig) => ({
@@ -1042,22 +38,46 @@ var createQueueService = (handler, module) => ({
1042
38
  incoming: [],
1043
39
  processing: [],
1044
40
  dlq: [],
1045
- timeoutId: null,
1046
41
  processingBatch: false,
1047
42
  module
1048
43
  });
1049
44
  var createQueueMessage = (body, headers) => {
1050
45
  const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
1051
- return {
46
+ const message = {
1052
47
  id,
1053
48
  timestamp: /* @__PURE__ */ new Date(),
1054
49
  attempts: 0,
1055
50
  payload: body,
1056
51
  attributes: headers
1057
52
  };
53
+ const delaySeconds = Number(headers?.["x-delay-seconds"]);
54
+ if (delaySeconds > 0) {
55
+ message.nextAttempt = new Date(Date.now() + delaySeconds * 1e3);
56
+ }
57
+ return message;
58
+ };
59
+ var scheduleRetry = (queueService, message, delayMs) => {
60
+ removeFromProcessing(queueService, message.id);
61
+ message.attempts += 1;
62
+ message.nextAttempt = new Date(Date.now() + delayMs);
63
+ queueService.incoming.push(message);
64
+ };
65
+ var isMessageReady = (message, now = Date.now()) => {
66
+ return !message.nextAttempt || message.nextAttempt.getTime() <= now;
1058
67
  };
1059
68
  var moveToProcessing = (queueService, batchSize) => {
1060
- return queueService.incoming.splice(0, batchSize);
69
+ const now = Date.now();
70
+ const ready = [];
71
+ const remaining = [];
72
+ for (const message of queueService.incoming) {
73
+ if (ready.length < batchSize && isMessageReady(message, now)) {
74
+ ready.push(message);
75
+ } else {
76
+ remaining.push(message);
77
+ }
78
+ }
79
+ queueService.incoming = remaining;
80
+ return ready;
1061
81
  };
1062
82
  var removeFromProcessing = (queueService, messageId) => {
1063
83
  queueService.processing = queueService.processing.filter((msg) => msg.id !== messageId);
@@ -1070,21 +90,9 @@ var moveToDLQ = (queueService, message, error) => {
1070
90
  });
1071
91
  removeFromProcessing(queueService, message.id);
1072
92
  };
1073
- var incrementAttempts = (queueService, messageId) => {
1074
- const index = queueService.processing.findIndex((m) => m.id === messageId);
1075
- if (index >= 0) {
1076
- queueService.processing[index].attempts += 1;
1077
- return queueService.processing[index];
1078
- }
1079
- return null;
1080
- };
1081
93
  var calculateBackoffDelay = (attempts) => {
1082
94
  return Math.pow(2, attempts) * 100;
1083
95
  };
1084
- var setNextAttemptTime = (message, delayMs) => ({
1085
- ...message,
1086
- nextAttempt: new Date(Date.now() + delayMs)
1087
- });
1088
96
  var createQueueSummary = (queueService, config) => ({
1089
97
  incoming: queueService.incoming.length,
1090
98
  processing: queueService.processing.length,
@@ -1124,90 +132,21 @@ var purgeDLQ = (queueService) => {
1124
132
  queueService.dlq = [];
1125
133
  return dlqCount;
1126
134
  };
1127
- var logSuccess = (message, messageId, queueName) => {
1128
- logger.debug(`Success: ${message} ${messageId} in queue ${queueName}`);
1129
- };
1130
- var logError = (message, messageId, queueName, error) => {
1131
- logger.error(`Error: ${message} ${messageId} in queue ${queueName}: ${error}`);
1132
- };
1133
- var logRetryScheduled = (messageId, delayMs) => {
1134
- logger.debug(`Scheduling retry for message ${messageId} in ${delayMs}ms`);
1135
- };
1136
- var logDLQOperation = (operation, count, queueName) => {
1137
- logger.warn(`${operation} ${count} messages from DLQ for ${queueName}`);
1138
- };
1139
-
1140
- // src/queue-plugin/processing.ts
1141
- var createProcessMessageHandler = (config) => async (queueName, message, queueService) => {
1142
- try {
1143
- const result = await queueService.handler(message);
1144
- if (result?.failureId) {
1145
- await handleProcessingError(queueName, message, queueService, config);
1146
- } else {
1147
- removeFromProcessing(queueService, message.id);
1148
- logSuccess("Successfully processed message", message.id, queueName);
1149
- }
1150
- } catch {
1151
- await handleProcessingError(queueName, message, queueService, config);
1152
- }
1153
- };
1154
- var handleProcessingError = async (queueName, message, queueService, config) => {
1155
- logError("Error processing message", message.id, queueName, "handler returned failure");
1156
- if (message.attempts >= config.maxRetries) {
1157
- moveToDLQ(queueService, message, "max retries exceeded");
1158
- return;
1159
- }
1160
- const updatedMessage = incrementAttempts(queueService, message.id);
1161
- if (!updatedMessage) return;
1162
- if (config.retryBackoff) {
1163
- const delayMs = calculateBackoffDelay(updatedMessage.attempts);
1164
- const messageWithNextAttempt = setNextAttemptTime(updatedMessage, delayMs);
1165
- logRetryScheduled(message.id, delayMs);
1166
- setTimeout(() => {
1167
- createProcessMessageHandler(config)(queueName, messageWithNextAttempt, queueService);
1168
- }, delayMs);
1169
- }
1170
- };
1171
- var createBatchProcessor = (processMessage, config, dirtyQueues) => async (queueName, queueService) => {
1172
- if (queueService.timeoutId) {
1173
- clearTimeout(queueService.timeoutId);
1174
- queueService.timeoutId = null;
1175
- }
1176
- if (queueService.processingBatch) {
1177
- return;
1178
- }
1179
- try {
1180
- queueService.processingBatch = true;
1181
- const messagesToProcess = moveToProcessing(queueService, config.batchSize);
1182
- if (messagesToProcess.length === 0) {
1183
- return;
1184
- }
1185
- queueService.processing.push(...messagesToProcess);
1186
- await Promise.all(messagesToProcess.map((message) => {
1187
- return processMessage(queueName, message, queueService);
1188
- }));
1189
- if (config.persistence.enabled && dirtyQueues) {
1190
- dirtyQueues.add(queueName);
1191
- }
1192
- } finally {
1193
- queueService.processingBatch = false;
1194
- }
1195
- };
1196
- var createProcessingScheduler = (processBatch, config) => (queueName, queueService, overrideDelay = null) => {
1197
- if (!queueService.timeoutId) {
1198
- const delay = overrideDelay ?? config.batchWindowMs;
1199
- queueService.timeoutId = setTimeout(() => {
1200
- processBatch(queueName, queueService);
1201
- }, delay);
1202
- }
1203
- };
1204
- var handleImmediateProcessing = (queueService, config, processBatch, queueName) => {
1205
- if (queueService.incoming.length >= config.batchSize) {
1206
- if (queueService.timeoutId) {
1207
- clearTimeout(queueService.timeoutId);
1208
- queueService.timeoutId = null;
1209
- }
1210
- setImmediate(() => processBatch(queueName, queueService));
135
+ var createHistoryEntry = (queueName, message, status, error) => ({
136
+ id: message.id,
137
+ queueName,
138
+ status,
139
+ timestamp: message.timestamp,
140
+ completedAt: /* @__PURE__ */ new Date(),
141
+ attempts: message.attempts,
142
+ payload: message.payload,
143
+ attributes: message.attributes,
144
+ error
145
+ });
146
+ var recordMessageHistory = (history, entry) => {
147
+ history.push(entry);
148
+ if (history.length > MAX_MESSAGE_HISTORY) {
149
+ history.splice(0, history.length - MAX_MESSAGE_HISTORY);
1211
150
  }
1212
151
  };
1213
152
 
@@ -1227,123 +166,358 @@ var restoreDates = (messages) => {
1227
166
  if (msg.failedAt) msg.failedAt = new Date(msg.failedAt);
1228
167
  });
1229
168
  };
1230
- var createPersistenceInitializer = (config, loadAllQueueStates, saveDirtyQueueStates, saveAllQueueStates, intervalIdHolder) => async () => {
169
+ var saveQueueState = async (runtime, queueName, queueService) => {
1231
170
  try {
1232
- await fs.mkdir(config.persistence.directory, { recursive: true });
1233
- if (config.persistence.loadOnStartup) {
1234
- await loadAllQueueStates();
1235
- }
1236
- if (config.persistence.saveInterval && config.persistence.saveInterval > 0) {
1237
- intervalIdHolder.id = setInterval(saveDirtyQueueStates, config.persistence.saveInterval);
1238
- }
1239
- if (config.persistence.saveOnShutdown) {
1240
- const shutdownHandler = async () => {
1241
- logger.debug("Saving queue state before shutdown...");
1242
- if (intervalIdHolder.id) {
1243
- clearInterval(intervalIdHolder.id);
1244
- }
1245
- await saveAllQueueStates();
1246
- process.exit(0);
1247
- };
1248
- process.on("SIGINT", shutdownHandler);
1249
- process.on("SIGTERM", shutdownHandler);
1250
- }
1251
- logger.debug(`Queue persistence initialized: ${config.persistence.directory}`);
1252
- } catch (error) {
1253
- logger.error(`Failed to initialize queue persistence: ${error?.message}`);
1254
- }
1255
- };
1256
- var createQueueStateSaver = (config) => async (queueName, queueService) => {
1257
- try {
1258
- if (!queueService) return;
1259
171
  const queueData = createQueueData(queueService);
1260
172
  const now = Date.now();
1261
- const queueFilePath = path.join(config.persistence.directory, `${queueName}.json`);
1262
- const tempFilePath = path.join(config.persistence.directory, `${queueName}.${now}temp.json`);
1263
- await fs.writeFile(tempFilePath, JSON.stringify(queueData, null, 2), "utf8");
1264
- await fs.rename(tempFilePath, queueFilePath);
1265
- logger.debug(`Queue state saved: ${queueName}`);
173
+ const queueFilePath = path.join(runtime.config.persistence.directory, `${queueName}.json`);
174
+ const tempFilePath2 = path.join(runtime.config.persistence.directory, `${queueName}.${now}temp.json`);
175
+ await fs.writeFile(tempFilePath2, JSON.stringify(queueData, null, 2), "utf8");
176
+ await fs.rename(tempFilePath2, queueFilePath);
177
+ runtime.logging.debug({ module: queueService.module }, `Queue state saved: ${queueName}`);
1266
178
  } catch (error) {
1267
- logger.error(`Failed to save queue state for ${queueName}:`, error?.message);
179
+ runtime.logging.error(
180
+ { module: queueService.module, error: error?.message },
181
+ `Failed to save queue state for ${queueName}`
182
+ );
1268
183
  }
1269
184
  };
1270
- var createAllQueuesStateSaver = (config, saveQueueState) => (queues) => async () => {
185
+ var saveAllQueueStates = async (runtime) => {
1271
186
  try {
1272
- for (const [queueName, queueService] of Object.entries(queues)) {
1273
- await saveQueueState(queueName, queueService);
187
+ for (const [queueName, queueService] of Object.entries(runtime.queues)) {
188
+ await saveQueueState(runtime, queueName, queueService);
1274
189
  }
1275
- logger.debug(`All queue states saved to ${config.persistence.directory}`);
190
+ runtime.logging.debug(`All queue states saved to ${runtime.config.persistence.directory}`);
1276
191
  } catch (error) {
1277
- logger.error(`Failed to save all queue states: ${error?.message}`);
192
+ runtime.logging.error({ error: error?.message }, `Failed to save all queue states`);
1278
193
  }
1279
194
  };
1280
- var createDirtyQueuesStateSaver = (saveQueueState, dirtyQueues) => (queues) => async () => {
1281
- if (dirtyQueues.size === 0) return;
195
+ var saveDirtyQueueStates = async (runtime) => {
196
+ if (runtime.dirtyQueues.size === 0) return;
1282
197
  try {
1283
- const queueNames = [...dirtyQueues];
1284
- dirtyQueues.clear();
198
+ const queueNames = [...runtime.dirtyQueues];
199
+ runtime.dirtyQueues.clear();
1285
200
  for (const queueName of queueNames) {
1286
- if (queues[queueName]) {
1287
- await saveQueueState(queueName, queues[queueName]);
201
+ if (runtime.queues[queueName]) {
202
+ await saveQueueState(runtime, queueName, runtime.queues[queueName]);
1288
203
  }
1289
204
  }
1290
205
  } catch (error) {
1291
- logger.error(`Failed to save dirty queue states: ${error?.message}`);
206
+ runtime.logging.error({ error: error?.message }, `Failed to save dirty queue states`);
1292
207
  }
1293
208
  };
1294
- var createQueueStateLoader = (config, scheduleProcessing, processMessage) => (queues) => async (queueName) => {
209
+ var loadQueueState = async (runtime, queueName) => {
1295
210
  try {
1296
- if (!queues[queueName]) {
1297
- logger.warn(`Skipping load for non-existent queue: ${queueName}`);
211
+ const queueService = runtime.queues[queueName];
212
+ if (!queueService) {
213
+ runtime.logging.warn(`Skipping load for non-existent queue: ${queueName}`);
1298
214
  return;
1299
215
  }
1300
- const queueFilePath = path.join(config.persistence.directory, `${queueName}.json`);
216
+ const queueFilePath = path.join(runtime.config.persistence.directory, `${queueName}.json`);
1301
217
  try {
1302
218
  const data = await fs.readFile(queueFilePath, "utf8");
1303
219
  const queueData = JSON.parse(data);
1304
220
  restoreDates(queueData.incoming);
1305
221
  restoreDates(queueData.processing);
1306
222
  restoreDates(queueData.dlq);
1307
- queues[queueName].incoming = queueData.incoming;
1308
- queues[queueName].processing = queueData.processing;
1309
- queues[queueName].dlq = queueData.dlq;
1310
- logger.debug(`Queue state loaded: ${queueName} (saved at ${queueData.savedAt})`);
1311
- if (queues[queueName].incoming.length > 0) {
1312
- scheduleProcessing(queueName, queues[queueName]);
1313
- }
1314
- if (queues[queueName].processing.length > 0) {
1315
- for (const message of [...queues[queueName].processing]) {
1316
- if (message.attempts >= config.maxRetries) continue;
1317
- setTimeout(() => {
1318
- processMessage(queueName, message, queues[queueName]);
1319
- }, 100 * (Math.random() * 10));
223
+ queueService.incoming = queueData.incoming;
224
+ queueService.dlq = queueData.dlq;
225
+ queueService.processing = [];
226
+ for (const message of queueData.processing) {
227
+ if (message.attempts >= runtime.config.maxRetries) {
228
+ queueService.dlq.push({ ...message, error: "max retries exceeded before restart", failedAt: /* @__PURE__ */ new Date() });
229
+ } else {
230
+ message.nextAttempt = /* @__PURE__ */ new Date();
231
+ queueService.incoming.push(message);
1320
232
  }
1321
233
  }
234
+ runtime.logging.debug(
235
+ { module: queueService.module },
236
+ `Queue state loaded: ${queueName} (saved at ${queueData.savedAt})`
237
+ );
1322
238
  } catch (error) {
1323
239
  if (error.code === "ENOENT") {
1324
- logger.debug(`No saved state found for queue: ${queueName}`);
240
+ runtime.logging.debug({ module: queueService.module }, `No saved state found for queue: ${queueName}`);
1325
241
  } else {
1326
242
  throw error;
1327
243
  }
1328
244
  }
1329
245
  } catch (error) {
1330
- logger.error(`Failed to load queue state for ${queueName}:`, error?.message);
246
+ runtime.logging.error({ error: error?.message }, `Failed to load queue state for ${queueName}`);
1331
247
  }
1332
248
  };
1333
- var createAllQueuesStateLoader = (config, loadQueueState) => async () => {
249
+ var initializePersistence = async (runtime) => {
1334
250
  try {
1335
- const files = await fs.readdir(config.persistence.directory);
1336
- const queueFiles = files.filter((file) => file.endsWith(".json") && !file.includes(".temp."));
1337
- for (const file of queueFiles) {
1338
- const queueName = path.basename(file, ".json");
1339
- await loadQueueState(queueName);
251
+ await fs.mkdir(runtime.config.persistence.directory, { recursive: true });
252
+ if (runtime.config.persistence.saveOnShutdown) {
253
+ const shutdownHandler = async () => {
254
+ runtime.logging.debug("Saving queue state before shutdown...");
255
+ await saveAllQueueStates(runtime);
256
+ process.exit(0);
257
+ };
258
+ process.on("SIGINT", shutdownHandler);
259
+ process.on("SIGTERM", shutdownHandler);
1340
260
  }
1341
- logger.debug("All queue states loaded");
261
+ runtime.logging.debug(`Queue persistence initialized: ${runtime.config.persistence.directory}`);
1342
262
  } catch (error) {
1343
- logger.error("Failed to load queue states:", error?.message);
263
+ runtime.logging.error({ error: error?.message }, `Failed to initialize queue persistence`);
1344
264
  }
1345
265
  };
1346
266
 
267
+ // src/queue-plugin/processing.ts
268
+ var processMessage = async (runtime, queueName, message, queueService) => {
269
+ try {
270
+ const result = await queueService.handler(message);
271
+ if (result?.failureId) {
272
+ handleFailure(runtime, queueName, message, queueService);
273
+ } else {
274
+ removeFromProcessing(queueService, message.id);
275
+ recordMessageHistory(runtime.messageHistory, createHistoryEntry(queueName, message, "completed"));
276
+ runtime.logging.debug(`Successfully processed message ${message.id} in queue ${queueName}`);
277
+ }
278
+ } catch {
279
+ handleFailure(runtime, queueName, message, queueService);
280
+ }
281
+ };
282
+ var handleFailure = (runtime, queueName, message, queueService) => {
283
+ runtime.logging.error(`Error processing message ${message.id} in queue ${queueName}: handler returned failure`);
284
+ if (message.attempts >= runtime.config.maxRetries) {
285
+ moveToDLQ(queueService, message, "max retries exceeded");
286
+ recordMessageHistory(runtime.messageHistory, createHistoryEntry(queueName, message, "failed", "max retries exceeded"));
287
+ return;
288
+ }
289
+ const delayMs = runtime.config.retryBackoff ? calculateBackoffDelay(message.attempts + 1) : 0;
290
+ scheduleRetry(queueService, message, delayMs);
291
+ runtime.logging.debug(`Scheduling retry for message ${message.id} in ${delayMs}ms`);
292
+ };
293
+ var processBatch = async (runtime, queueName, queueService) => {
294
+ if (queueService.processingBatch) {
295
+ return;
296
+ }
297
+ try {
298
+ queueService.processingBatch = true;
299
+ const messagesToProcess = moveToProcessing(queueService, runtime.config.batchSize);
300
+ if (messagesToProcess.length === 0) {
301
+ return;
302
+ }
303
+ queueService.processing.push(...messagesToProcess);
304
+ await Promise.all(
305
+ messagesToProcess.map((message) => runtime.logging.runWithLogContext(
306
+ { module: queueService.module, reqId: message.id },
307
+ () => processMessage(runtime, queueName, message, queueService)
308
+ ))
309
+ );
310
+ if (runtime.config.persistence.enabled) {
311
+ runtime.dirtyQueues.add(queueName);
312
+ }
313
+ } finally {
314
+ queueService.processingBatch = false;
315
+ }
316
+ };
317
+ var dispatchBatchIfDue = (runtime, queueName, queueService, now) => {
318
+ if (queueService.processingBatch) {
319
+ return;
320
+ }
321
+ const ready = queueService.incoming.filter((message) => isMessageReady(message, now));
322
+ if (ready.length === 0) {
323
+ return;
324
+ }
325
+ const dueBySize = ready.length >= runtime.config.batchSize;
326
+ const oldestReadyAge = now - Math.min(...ready.map((message) => message.timestamp.getTime()));
327
+ const dueByWindow = oldestReadyAge >= runtime.config.batchWindowMs;
328
+ if (dueBySize || dueByWindow) {
329
+ processBatch(runtime, queueName, queueService);
330
+ }
331
+ };
332
+ var maybeSavePersistence = (runtime, now) => {
333
+ if (!runtime.config.persistence.enabled) {
334
+ return;
335
+ }
336
+ const saveInterval = runtime.config.persistence.saveInterval ?? 0;
337
+ if (saveInterval > 0 && now - runtime.lastSavedAt >= saveInterval) {
338
+ runtime.lastSavedAt = now;
339
+ saveDirtyQueueStates(runtime);
340
+ }
341
+ };
342
+ var tick = (runtime) => {
343
+ const now = Date.now();
344
+ for (const [queueName, queueService] of Object.entries(runtime.queues)) {
345
+ dispatchBatchIfDue(runtime, queueName, queueService, now);
346
+ }
347
+ maybeSavePersistence(runtime, now);
348
+ };
349
+
350
+ // src/queue-plugin/decorator.ts
351
+ var isValidQueueName = (queueName) => {
352
+ return typeof queueName === "string" && queueName.length > 0 && /^[a-zA-Z0-9_-]+$/.test(queueName);
353
+ };
354
+ var validateQueueName = (queueName) => {
355
+ if (!queueName || typeof queueName !== "string") {
356
+ throw new Error("Queue name must be a non-empty string");
357
+ }
358
+ if (!isValidQueueName(queueName)) {
359
+ throw new Error("Queue name must contain only alphanumeric characters, hyphens, and underscores");
360
+ }
361
+ };
362
+ var validateHandler = (handler) => {
363
+ if (typeof handler !== "function") {
364
+ throw new Error("Handler must be a function");
365
+ }
366
+ };
367
+ var addQueue = async (runtime, queueName, handler, module) => {
368
+ try {
369
+ validateQueueName(queueName);
370
+ validateHandler(handler);
371
+ if (runtime.queues[queueName]) {
372
+ runtime.logging.warn({ module }, `Queue already exists: ${queueName}.`);
373
+ return;
374
+ }
375
+ runtime.queues[queueName] = createQueueService(handler, module);
376
+ if (runtime.config.persistence.enabled) {
377
+ await loadQueueState(runtime, queueName);
378
+ }
379
+ runtime.logging.info({ module }, `Queue added: ${queueName}`);
380
+ } catch (error) {
381
+ runtime.logging.error({ module, error: error.message }, `Failed to add queue ${queueName}`);
382
+ throw error;
383
+ }
384
+ };
385
+ var removeQueue = async (runtime, queueName) => {
386
+ try {
387
+ validateQueueName(queueName);
388
+ const queueService = runtime.queues[queueName];
389
+ if (!queueService) {
390
+ throw new Error(`Queue '${queueName}' does not exist`);
391
+ }
392
+ const totalMessages = queueService.incoming.length + queueService.processing.length;
393
+ if (totalMessages > 0) {
394
+ runtime.logging.warn(
395
+ { module: queueService.module },
396
+ `Removing queue with ${totalMessages} pending messages: ${queueName}`
397
+ );
398
+ }
399
+ if (runtime.config.persistence.enabled) {
400
+ await saveQueueState(runtime, queueName, queueService);
401
+ }
402
+ delete runtime.queues[queueName];
403
+ runtime.logging.info({ module: queueService.module }, `Queue removed: ${queueName}`);
404
+ } catch (error) {
405
+ runtime.logging.error({ error: error.message }, `Failed to remove queue ${queueName}`);
406
+ throw error;
407
+ }
408
+ };
409
+ var hasQueue = (runtime, queueName) => {
410
+ try {
411
+ validateQueueName(queueName);
412
+ return queueName in runtime.queues;
413
+ } catch {
414
+ return false;
415
+ }
416
+ };
417
+ var listQueues = (runtime, module) => {
418
+ return Object.keys(runtime.queues).filter((queueName) => !module || runtime.queues[queueName].module === module).sort();
419
+ };
420
+ var getQueueStats = (runtime, queueName) => {
421
+ try {
422
+ validateQueueName(queueName);
423
+ return runtime.queues[queueName] || null;
424
+ } catch {
425
+ return null;
426
+ }
427
+ };
428
+ var getQueuesMap = (runtime) => {
429
+ return { ...runtime.queues };
430
+ };
431
+ var getDashboardSummary = (runtime) => createDashboardSummary(runtime.queues, runtime.config);
432
+ var getQueueSummary = (runtime, queueName) => {
433
+ const queue = runtime.queues[queueName];
434
+ if (!queue) {
435
+ return null;
436
+ }
437
+ return {
438
+ stats: createQueueSummary(queue, runtime.config),
439
+ messages: {
440
+ incoming: queue.incoming,
441
+ processing: queue.processing,
442
+ dlq: queue.dlq
443
+ }
444
+ };
445
+ };
446
+ var enqueueMessage = async (runtime, queueName, body, attributes) => {
447
+ const queueService = runtime.queues[queueName];
448
+ if (!queueService) {
449
+ return null;
450
+ }
451
+ const message = createQueueMessage(body, attributes);
452
+ queueService.incoming.push(message);
453
+ if (runtime.config.persistence.enabled) {
454
+ runtime.dirtyQueues.add(queueName);
455
+ }
456
+ return { id: message.id, queueName };
457
+ };
458
+ var processDlq = async (runtime, queueName) => {
459
+ const queueService = runtime.queues[queueName];
460
+ if (!queueService) {
461
+ return null;
462
+ }
463
+ const processedCount = moveDLQToIncoming(queueService);
464
+ if (processedCount === 0) {
465
+ return { status: "success", message: "No messages in DLQ to process", processed: 0 };
466
+ }
467
+ runtime.logging.warn(
468
+ { module: queueService.module },
469
+ `Moving ${processedCount} messages from DLQ for ${queueName}`
470
+ );
471
+ if (runtime.config.persistence.enabled) {
472
+ runtime.dirtyQueues.add(queueName);
473
+ }
474
+ return {
475
+ status: "success",
476
+ message: `Moved ${processedCount} messages from DLQ to processing queue`,
477
+ processed: processedCount
478
+ };
479
+ };
480
+ var purgeDlq = async (runtime, queueName) => {
481
+ const queueService = runtime.queues[queueName];
482
+ if (!queueService) {
483
+ return null;
484
+ }
485
+ const purgedCount = purgeDLQ(queueService);
486
+ if (purgedCount === 0) {
487
+ return { status: "success", message: "No messages in DLQ to purge", purged: 0 };
488
+ }
489
+ runtime.logging.warn(
490
+ { module: queueService.module },
491
+ `Purging ${purgedCount} messages from DLQ for ${queueName}`
492
+ );
493
+ if (runtime.config.persistence.enabled) {
494
+ runtime.dirtyQueues.add(queueName);
495
+ }
496
+ return {
497
+ status: "success",
498
+ message: `Purged ${purgedCount} messages from DLQ`,
499
+ purged: purgedCount
500
+ };
501
+ };
502
+ var getMessageHistory = (runtime, queueName, limit = 50) => {
503
+ return runtime.messageHistory.filter((entry) => entry.queueName === queueName).slice(-limit).reverse();
504
+ };
505
+ var createQueueManager = (runtime) => ({
506
+ addQueue: (queueName, handler, module) => addQueue(runtime, queueName, handler, module),
507
+ removeQueue: (queueName) => removeQueue(runtime, queueName),
508
+ hasQueue: (queueName) => hasQueue(runtime, queueName),
509
+ listQueues: (module) => listQueues(runtime, module),
510
+ getQueueStats: (queueName) => getQueueStats(runtime, queueName),
511
+ getQueuesMap: () => getQueuesMap(runtime),
512
+ getConfig: () => runtime.config,
513
+ getDashboardSummary: () => getDashboardSummary(runtime),
514
+ getQueueSummary: (queueName) => getQueueSummary(runtime, queueName),
515
+ enqueueMessage: (queueName, body, attributes) => enqueueMessage(runtime, queueName, body, attributes),
516
+ processDlq: (queueName) => processDlq(runtime, queueName),
517
+ purgeDlq: (queueName) => purgeDlq(runtime, queueName),
518
+ getMessageHistory: (queueName, limit) => getMessageHistory(runtime, queueName, limit)
519
+ });
520
+
1347
521
  // src/queue-plugin/routes.ts
1348
522
  var registerQueueRoutes = (app, prefix = "") => {
1349
523
  app.get(
@@ -1380,267 +554,45 @@ var registerQueueRoutes = (app, prefix = "") => {
1380
554
  async (request, reply) => {
1381
555
  const queueName = request.params.queue;
1382
556
  const result = await app.queues.processDlq(queueName);
1383
- if (result === null) {
1384
- return reply.status(404).send(result);
1385
- }
1386
- return reply.status(200).send(result);
1387
- }
1388
- );
1389
- app.post(
1390
- `/${prefix}/:queue/purge-dlq`,
1391
- async (request, reply) => {
1392
- const queueName = request.params.queue;
1393
- const result = await app.queues.purgeDlq(queueName);
1394
- if (result === null) {
1395
- return reply.status(404).send({ error: "Queue not found" });
1396
- }
1397
- return reply.status(200).send(result);
1398
- }
1399
- );
1400
- };
1401
-
1402
- // src/queue-plugin/decorator.ts
1403
- var isValidQueueName = (queueName) => {
1404
- return typeof queueName === "string" && queueName.length > 0 && /^[a-zA-Z0-9_-]+$/.test(queueName);
1405
- };
1406
- var createQueueManager = ({
1407
- config,
1408
- queues,
1409
- dirtyQueues,
1410
- saveQueueState,
1411
- loadQueueState,
1412
- scheduleProcessing,
1413
- processBatch
1414
- }) => {
1415
- const validateQueueName = (queueName) => {
1416
- if (!queueName || typeof queueName !== "string") {
1417
- throw new Error("Queue name must be a non-empty string");
1418
- }
1419
- if (!isValidQueueName(queueName)) {
1420
- throw new Error("Queue name must contain only alphanumeric characters, hyphens, and underscores");
1421
- }
1422
- };
1423
- const validateHandler = (handler) => {
1424
- if (typeof handler !== "function") {
1425
- throw new Error("Handler must be a function");
1426
- }
1427
- };
1428
- const addQueue = async (queueName, handler, module) => {
1429
- try {
1430
- validateQueueName(queueName);
1431
- validateHandler(handler);
1432
- if (queues[queueName]) {
1433
- logger.warn(`Queue already exists: ${queueName}.`);
1434
- return;
1435
- }
1436
- queues[queueName] = createQueueService(handler, module);
1437
- if (config.persistence.enabled && loadQueueState) {
1438
- await loadQueueState(queueName);
1439
- }
1440
- logger.info(`Queue added: ${queueName}`);
1441
- } catch (error) {
1442
- logger.error(`Failed to add queue ${queueName}: ${error.message}`);
1443
- throw error;
1444
- }
1445
- };
1446
- const removeQueue = async (queueName) => {
1447
- try {
1448
- validateQueueName(queueName);
1449
- if (!queues[queueName]) {
1450
- throw new Error(`Queue '${queueName}' does not exist`);
1451
- }
1452
- const queueService = queues[queueName];
1453
- const totalMessages = queueService.incoming.length + queueService.processing.length;
1454
- if (totalMessages > 0) {
1455
- logger.warn(`Removing queue with ${totalMessages} pending messages: ${queueName}`);
1456
- }
1457
- if (queueService.timeoutId) {
1458
- clearTimeout(queueService.timeoutId);
1459
- }
1460
- if (config.persistence.enabled && saveQueueState) {
1461
- await saveQueueState(queueName, queueService);
1462
- }
1463
- delete queues[queueName];
1464
- logger.info(`Queue removed: ${queueName}`);
1465
- } catch (error) {
1466
- logger.error(`Failed to remove queue ${queueName}: ${error.message}`);
1467
- throw error;
1468
- }
1469
- };
1470
- const hasQueue = (queueName) => {
1471
- try {
1472
- validateQueueName(queueName);
1473
- return queueName in queues;
1474
- } catch {
1475
- return false;
1476
- }
1477
- };
1478
- const listQueues = (module) => {
1479
- return Object.keys(queues).filter((queueName) => {
1480
- const queueService = queues[queueName];
1481
- return !module || queueService.module === module;
1482
- }).sort();
1483
- };
1484
- const getQueueStats = (queueName) => {
1485
- try {
1486
- validateQueueName(queueName);
1487
- return queues[queueName] || null;
1488
- } catch {
1489
- return null;
1490
- }
1491
- };
1492
- const getQueuesMap = () => {
1493
- return { ...queues };
1494
- };
1495
- const getConfig = () => config;
1496
- const getDashboardSummary = () => createDashboardSummary(queues, config);
1497
- const getQueueSummary = (queueName) => {
1498
- if (!queues[queueName]) {
1499
- return null;
1500
- }
1501
- const queue = queues[queueName];
1502
- const stats = createQueueSummary(queue, config);
1503
- return {
1504
- stats,
1505
- messages: {
1506
- incoming: queue.incoming,
1507
- processing: queue.processing,
1508
- dlq: queue.dlq
1509
- }
1510
- };
1511
- };
1512
- const enqueueMessage = async (queueName, body, attributes) => {
1513
- if (!queues[queueName]) {
1514
- return null;
1515
- }
1516
- const message = createQueueMessage(body, attributes);
1517
- queues[queueName].incoming.push(message);
1518
- scheduleProcessing(queueName, queues[queueName]);
1519
- handleImmediateProcessing(queues[queueName], config, processBatch, queueName);
1520
- if (config.persistence.enabled) {
1521
- dirtyQueues.add(queueName);
1522
- }
1523
- return {
1524
- id: message.id,
1525
- queueName
1526
- };
1527
- };
1528
- const processDlq = async (queueName) => {
1529
- if (!queues[queueName]) {
1530
- return null;
1531
- }
1532
- const processedCount = moveDLQToIncoming(queues[queueName]);
1533
- if (processedCount === 0) {
1534
- return {
1535
- status: "success",
1536
- message: "No messages in DLQ to process",
1537
- processed: 0
1538
- };
1539
- }
1540
- logDLQOperation("Moving", processedCount, queueName);
1541
- scheduleProcessing(queueName, queues[queueName]);
1542
- if (config.persistence.enabled) {
1543
- dirtyQueues.add(queueName);
1544
- }
1545
- return {
1546
- status: "success",
1547
- message: `Moved ${processedCount} messages from DLQ to processing queue`,
1548
- processed: processedCount
1549
- };
1550
- };
1551
- const purgeDlq = async (queueName) => {
1552
- if (!queues[queueName]) {
1553
- return null;
1554
- }
1555
- const purgedCount = purgeDLQ(queues[queueName]);
1556
- if (purgedCount === 0) {
1557
- return {
1558
- status: "success",
1559
- message: "No messages in DLQ to purge",
1560
- purged: 0
1561
- };
557
+ if (result === null) {
558
+ return reply.status(404).send(result);
559
+ }
560
+ return reply.status(200).send(result);
1562
561
  }
1563
- logDLQOperation("Purging", purgedCount, queueName);
1564
- if (config.persistence.enabled) {
1565
- dirtyQueues.add(queueName);
562
+ );
563
+ app.post(
564
+ `/${prefix}/:queue/purge-dlq`,
565
+ async (request, reply) => {
566
+ const queueName = request.params.queue;
567
+ const result = await app.queues.purgeDlq(queueName);
568
+ if (result === null) {
569
+ return reply.status(404).send({ error: "Queue not found" });
570
+ }
571
+ return reply.status(200).send(result);
1566
572
  }
1567
- return {
1568
- status: "success",
1569
- message: `Purged ${purgedCount} messages from DLQ`,
1570
- purged: purgedCount
1571
- };
1572
- };
1573
- return {
1574
- addQueue,
1575
- removeQueue,
1576
- hasQueue,
1577
- listQueues,
1578
- getQueueStats,
1579
- getQueuesMap,
1580
- getConfig,
1581
- getDashboardSummary,
1582
- getQueueSummary,
1583
- enqueueMessage,
1584
- processDlq,
1585
- purgeDlq
1586
- };
1587
- };
1588
- var createQueueDecorator = (queueManager) => (app) => {
1589
- app.decorate("queues", queueManager);
573
+ );
1590
574
  };
1591
575
 
1592
576
  // src/queue-plugin/plugin.ts
1593
577
  var queuesPlugin = fsPlugin(async (app, options) => {
1594
- const config = mergeConfig(DEFAULT_CONFIG, options.config);
1595
- const queues = {};
1596
- const dirtyQueues = /* @__PURE__ */ new Set();
1597
- const intervalIdHolder = {};
1598
- const processMessage = createProcessMessageHandler(config);
1599
- const saveQueueState = config.persistence.enabled ? createQueueStateSaver(config) : void 0;
1600
- const saveAllQueueStates = config.persistence.enabled && saveQueueState ? createAllQueuesStateSaver(config, saveQueueState)(queues) : async () => {
1601
- };
1602
- const saveDirtyQueueStates = config.persistence.enabled && saveQueueState ? createDirtyQueuesStateSaver(saveQueueState, dirtyQueues)(queues) : async () => {
1603
- };
1604
- const processBatch = createBatchProcessor(
1605
- processMessage,
1606
- config,
1607
- config.persistence.enabled ? dirtyQueues : void 0
1608
- );
1609
- const scheduleProcessing = createProcessingScheduler(processBatch, config);
1610
- const loadQueueState = config.persistence.enabled ? createQueueStateLoader(
1611
- config,
1612
- scheduleProcessing,
1613
- processMessage
1614
- )(queues) : async () => {
578
+ const runtime = {
579
+ config: mergeConfig(DEFAULT_CONFIG, options.config),
580
+ queues: {},
581
+ dirtyQueues: /* @__PURE__ */ new Set(),
582
+ lastSavedAt: Date.now(),
583
+ messageHistory: [],
584
+ logging: app.logging
1615
585
  };
1616
- const loadAllQueueStates = config.persistence.enabled ? createAllQueuesStateLoader(config, loadQueueState) : async () => {
1617
- };
1618
- const initializePersistence = config.persistence.enabled ? createPersistenceInitializer(config, loadAllQueueStates, saveDirtyQueueStates, saveAllQueueStates, intervalIdHolder) : async () => {
1619
- };
1620
- const queueManager = createQueueManager({
1621
- config,
1622
- queues,
1623
- dirtyQueues,
1624
- saveQueueState: saveQueueState ? (queueName) => saveQueueState(queueName, queues[queueName]) : void 0,
1625
- loadQueueState,
1626
- scheduleProcessing,
1627
- processBatch
1628
- });
1629
- const decorateQueues = createQueueDecorator(queueManager);
1630
- decorateQueues(app);
1631
- registerQueueRoutes(
1632
- app,
1633
- options.prefix
1634
- );
1635
- await initializePersistence();
586
+ const tickIntervalId = setInterval(() => tick(runtime), runtime.config.tickIntervalMs);
587
+ app.addHook("onClose", async () => clearInterval(tickIntervalId));
588
+ app.decorate("queues", createQueueManager(runtime));
589
+ registerQueueRoutes(app, options.prefix);
590
+ await initializePersistence(runtime);
1636
591
  });
1637
592
 
1638
593
  // src/schedule-plugin/plugin.ts
1639
594
  import fsPlugin2 from "fastify-plugin";
1640
595
 
1641
- // src/schedule-plugin/core.ts
1642
- import * as fs3 from "fs/promises";
1643
-
1644
596
  // src/schedule-plugin/cron-utils.ts
1645
597
  import { parseExpression } from "cron-parser";
1646
598
  function parseCronExpression(cronExpression, lastRun, options = {}) {
@@ -1673,7 +625,6 @@ function parseCronExpression(cronExpression, lastRun, options = {}) {
1673
625
  description: describeCronExpression(normalizedCron)
1674
626
  };
1675
627
  } catch (error) {
1676
- logger.error(`Invalid cron expression: ${cronExpression} - ${error.message}`);
1677
628
  const fallbackTime = /* @__PURE__ */ new Date();
1678
629
  fallbackTime.setHours(fallbackTime.getHours() + 1);
1679
630
  fallbackTime.setMinutes(0);
@@ -1701,8 +652,7 @@ function getNextExecutions(cronExpression, count = 5, options = {}) {
1701
652
  executions.push(interval.next().toDate());
1702
653
  }
1703
654
  return executions;
1704
- } catch (error) {
1705
- logger.error(`Error getting next executions: ${error.message}`);
655
+ } catch {
1706
656
  return [];
1707
657
  }
1708
658
  }
@@ -1975,9 +925,111 @@ var shouldSkipJob = (job) => {
1975
925
  if (job.maxRuns && job.runCount >= job.maxRuns) return true;
1976
926
  return false;
1977
927
  };
928
+ var isJobReadyToRun = (job) => {
929
+ const now = /* @__PURE__ */ new Date();
930
+ return job.nextRun.getTime() <= now.getTime();
931
+ };
1978
932
  var getTimeUntilNextRun = (job) => Math.max(0, job.nextRun.getTime() - Date.now());
1979
933
 
1980
- // src/schedule-plugin/execution.ts
934
+ // src/schedule-plugin/core.ts
935
+ var DEFAULT_CONFIG2 = {
936
+ tickIntervalMs: 1e3,
937
+ persistence: {
938
+ enabled: true,
939
+ directory: "./.develop/scheduler-data",
940
+ saveInterval: 3e4,
941
+ // 30 seconds
942
+ saveOnShutdown: false
943
+ },
944
+ execution: {
945
+ maxConcurrent: 5,
946
+ defaultTimeout: 3e5
947
+ // 5 minutes
948
+ },
949
+ cleanup: {
950
+ maxExecutionHistory: 1e3,
951
+ cleanupInterval: 6e5
952
+ // 10 minutes
953
+ },
954
+ restartBehavior: {
955
+ preserveNaturalTiming: true,
956
+ skipMissedRuns: true,
957
+ maxTimingDrift: 6e4,
958
+ // 1 minute
959
+ rapidRestartThreshold: 1e4
960
+ // 10 seconds
961
+ },
962
+ cron: {
963
+ defaultTimezone: void 0,
964
+ // Use system timezone
965
+ logCronDetails: true
966
+ }
967
+ };
968
+ var mergeConfig2 = (defaultConfig, userConfig) => ({
969
+ ...defaultConfig,
970
+ ...userConfig,
971
+ persistence: { ...defaultConfig.persistence, ...userConfig?.persistence },
972
+ execution: { ...defaultConfig.execution, ...userConfig?.execution },
973
+ cleanup: { ...defaultConfig.cleanup, ...userConfig?.cleanup },
974
+ restartBehavior: { ...defaultConfig.restartBehavior, ...userConfig?.restartBehavior },
975
+ cron: { ...defaultConfig.cron, ...userConfig?.cron }
976
+ });
977
+ var createDashboardSummary2 = (runtime) => {
978
+ const jobs = Object.values(runtime.schedulers).map((scheduler) => {
979
+ const upcomingExecutions = scheduler.job.cronExpression ? getNextExecutions(scheduler.job.cronExpression, 3, { timezone: scheduler.job.timezone }) : [];
980
+ const cronDescription = scheduler.job.cronExpression ? parseCronExpression(scheduler.job.cronExpression, void 0, { timezone: scheduler.job.timezone }).description : void 0;
981
+ return {
982
+ ...scheduler.job,
983
+ isRunning: scheduler.isRunning,
984
+ timeUntilNextRun: getTimeUntilNextRun(scheduler.job),
985
+ upcomingExecutions,
986
+ cronDescription
987
+ };
988
+ });
989
+ return { jobs, runningExecutions: runtime.runningExecutions };
990
+ };
991
+ var createExecutionsSummary = (runtime) => ({
992
+ executions: runtime.executionHistory.slice(-50),
993
+ // Last 50 executions
994
+ running: Object.values(runtime.executions).filter((e) => e.status === "running")
995
+ });
996
+
997
+ // src/schedule-plugin/jobs.ts
998
+ var validateJobDefinition = (jobDef) => {
999
+ if (!jobDef.cronExpression && !jobDef.intervalMs && !jobDef.delayMs) {
1000
+ throw new Error(`Job ${jobDef.name} must specify a cronExpression, intervalMs, or delayMs`);
1001
+ }
1002
+ if (jobDef.cronExpression && detectExpressionType(jobDef.cronExpression) === "unknown") {
1003
+ throw new Error(`Invalid cron expression for job: ${jobDef.name}`);
1004
+ }
1005
+ };
1006
+ var createJobFromDefinition = (jobDef, config) => {
1007
+ validateJobDefinition(jobDef);
1008
+ const expressionType = detectExpressionType(jobDef.cronExpression);
1009
+ const cronExpression = expressionType === "rate" ? convertRateToCron(jobDef.cronExpression).cronExpression : jobDef.cronExpression;
1010
+ const job = {
1011
+ id: generateJobId(),
1012
+ name: jobDef.name,
1013
+ cronExpression,
1014
+ intervalMs: jobDef.intervalMs,
1015
+ delayMs: jobDef.delayMs,
1016
+ nextRun: /* @__PURE__ */ new Date(),
1017
+ enabled: jobDef.enabled ?? true,
1018
+ runCount: 0,
1019
+ maxRuns: jobDef.maxRuns,
1020
+ metadata: jobDef.metadata,
1021
+ createdAt: /* @__PURE__ */ new Date(),
1022
+ timezone: jobDef.timezone,
1023
+ module: jobDef.module
1024
+ };
1025
+ job.nextRun = calculateNextRun(job, config.restartBehavior.preserveNaturalTiming);
1026
+ return job;
1027
+ };
1028
+ var createSchedulerService = (job, handler) => ({
1029
+ job,
1030
+ handler,
1031
+ isRunning: false
1032
+ });
1981
1033
  var createJobExecution = (jobId) => ({
1982
1034
  id: generateExecutionId(),
1983
1035
  jobId,
@@ -1991,661 +1043,523 @@ var completeExecution = (execution, result, error) => ({
1991
1043
  result,
1992
1044
  error
1993
1045
  });
1994
- var canExecuteJob = (scheduler, state) => {
1995
- if (state.isShuttingDown) {
1996
- return { canExecute: false, reason: "System is shutting down" };
1997
- }
1998
- if (scheduler.isRunning) {
1999
- return { canExecute: false, reason: "Job is already running" };
2000
- }
2001
- if (shouldSkipJob(scheduler.job)) {
2002
- const reason = !scheduler.job.enabled ? "Job is disabled" : "Job reached maximum runs";
2003
- return { canExecute: false, reason };
2004
- }
2005
- if (state.runningExecutions >= state.config.execution.maxConcurrent) {
2006
- return { canExecute: false, reason: "Maximum concurrent executions reached" };
2007
- }
2008
- return { canExecute: true };
2009
- };
2010
- var scheduleJob = (scheduler, state, executeJobFn) => {
2011
- if (state.isShuttingDown) return scheduler;
2012
- const timeUntilNextRun = getTimeUntilNextRun(scheduler.job);
2013
- const updatedScheduler = { ...scheduler };
2014
- if (timeUntilNextRun <= 0) {
2015
- setImmediate(() => executeJobFn(updatedScheduler));
2016
- } else {
2017
- updatedScheduler.timerId = setTimeout(() => {
2018
- executeJobFn(updatedScheduler);
2019
- }, timeUntilNextRun);
2020
- logger.debug(`Scheduled job ${scheduler.job.name} to run in ${Math.round(timeUntilNextRun / 1e3)}s`);
2021
- }
2022
- return updatedScheduler;
2023
- };
2024
- var executeJobWithTimeout = async (handler, job, execution, timeoutMs) => {
2025
- let timerId;
2026
- try {
2027
- return await Promise.race([
2028
- handler(job, execution),
2029
- new Promise((_, reject) => {
2030
- timerId = setTimeout(() => reject(new Error("Job execution timeout")), timeoutMs);
2031
- })
2032
- ]);
2033
- } finally {
2034
- clearTimeout(timerId);
2035
- }
2036
- };
2037
1046
  var updateJobAfterExecution = (job, execution, config) => {
2038
1047
  const updatedJob = {
2039
1048
  ...job,
2040
1049
  lastRun: execution.startTime,
2041
1050
  runCount: job.runCount + 1
2042
1051
  };
2043
- if (job.enabled) {
1052
+ if (updatedJob.enabled) {
2044
1053
  updatedJob.nextRun = calculateNextRunFromLastRun(updatedJob, updatedJob.lastRun, config);
2045
1054
  }
2046
1055
  return updatedJob;
2047
1056
  };
2048
- var handleExecutionError = (execution, error) => {
2049
- logger.error(`Job failed ${execution.jobId}: ${error.message}`);
2050
- return completeExecution(execution, void 0, error.message);
2051
- };
2052
- var handleExecutionSuccess = (execution, result) => {
2053
- const duration = execution.endTime ? execution.endTime.getTime() - execution.startTime.getTime() : 0;
2054
- logger.debug(`Job completed in ${duration}ms`);
2055
- return completeExecution(execution, result);
2056
- };
1057
+ var enableJob = (scheduler) => ({
1058
+ ...scheduler,
1059
+ job: {
1060
+ ...scheduler.job,
1061
+ enabled: true,
1062
+ nextRun: calculateNextRun(scheduler.job)
1063
+ }
1064
+ });
1065
+ var disableJob = (scheduler) => ({
1066
+ ...scheduler,
1067
+ job: { ...scheduler.job, enabled: false }
1068
+ });
2057
1069
 
2058
1070
  // src/schedule-plugin/persistence.ts
2059
1071
  import * as fs2 from "fs/promises";
2060
1072
  import * as path2 from "path";
2061
- var createStateFilePath = (directory) => path2.join(directory, "scheduler-state.json");
2062
- var createTempFilePath = (directory) => path2.join(directory, `scheduler-state.${Date.now()}.temp.json`);
2063
- var serializeSchedulerState = (schedulers, executionHistory) => ({
2064
- jobs: Object.values(schedulers).map((s) => s.job),
2065
- executions: executionHistory.slice(-100),
2066
- savedAt: /* @__PURE__ */ new Date(),
2067
- version: "1.0"
2068
- });
2069
- var saveSchedulerState = async (state) => {
1073
+ var stateFilePath = (directory) => path2.join(directory, "scheduler-state.json");
1074
+ var tempFilePath = (directory) => path2.join(directory, `scheduler-state.${Date.now()}.temp.json`);
1075
+ var restoreExecutionHistory = (savedExecutions) => savedExecutions.map((exec) => ({
1076
+ ...exec,
1077
+ startTime: new Date(exec.startTime),
1078
+ endTime: exec.endTime ? new Date(exec.endTime) : void 0
1079
+ }));
1080
+ var readStateData = async (directory) => {
1081
+ const data = await fs2.readFile(stateFilePath(directory), "utf8");
1082
+ return JSON.parse(data);
1083
+ };
1084
+ var saveSchedulerState = async (runtime) => {
2070
1085
  try {
2071
- const serializedState = serializeSchedulerState(state.schedulers, state.executionHistory);
2072
- const stateFile = createStateFilePath(state.config.persistence.directory);
2073
- const tempFile = createTempFilePath(state.config.persistence.directory);
2074
- await fs2.writeFile(tempFile, JSON.stringify(serializedState, null, 2), "utf8");
2075
- await fs2.rename(tempFile, stateFile);
2076
- logger.debug("Enhanced scheduler state saved");
2077
- } catch (error) {
2078
- logger.error("Failed to save scheduler state:", error);
2079
- }
2080
- };
2081
- var loadSchedulerStateData = async (directory) => {
2082
- try {
2083
- const stateFile = createStateFilePath(directory);
2084
- const data = await fs2.readFile(stateFile, "utf8");
2085
- return JSON.parse(data);
1086
+ const stateData = {
1087
+ jobs: Object.values(runtime.schedulers).map((s) => s.job),
1088
+ executions: runtime.executionHistory.slice(-100),
1089
+ savedAt: /* @__PURE__ */ new Date(),
1090
+ version: "1.0"
1091
+ };
1092
+ const file = stateFilePath(runtime.config.persistence.directory);
1093
+ const temp = tempFilePath(runtime.config.persistence.directory);
1094
+ await fs2.writeFile(temp, JSON.stringify(stateData, null, 2), "utf8");
1095
+ await fs2.rename(temp, file);
1096
+ runtime.logging.debug("Scheduler state saved");
2086
1097
  } catch (error) {
2087
- if (error.code === "ENOENT") {
2088
- logger.debug("No previous scheduler state found - starting fresh");
2089
- } else {
2090
- logger.error("Failed to load scheduler state:", error);
2091
- }
2092
- return null;
1098
+ runtime.logging.error({ error: error?.message }, "Failed to save scheduler state");
2093
1099
  }
2094
1100
  };
2095
- var validateAndAdjustNextRun = (job, savedNextRun, lastRestartTime, config) => {
1101
+ var validateAndAdjustNextRun = (job, savedNextRun, runtime) => {
1102
+ const { config, lastRestartTime, logging } = runtime;
2096
1103
  const now = /* @__PURE__ */ new Date();
2097
- if (!savedNextRun) {
2098
- return calculateNextRunFromLastRun(job, job.lastRun, config);
2099
- }
2100
- if (savedNextRun <= now) {
2101
- logger.debug(`Saved next run is in the past for ${job.name} - recalculating`);
1104
+ if (!savedNextRun || savedNextRun <= now) {
1105
+ logging.debug({ module: job.module }, `Saved next run is in the past for ${job.name} - recalculating`);
2102
1106
  return calculateNextRunFromLastRun(job, job.lastRun, config);
2103
1107
  }
2104
- const timeSinceRestart = now.getTime() - lastRestartTime.getTime();
2105
- const isRapidRestart = timeSinceRestart < config.restartBehavior.rapidRestartThreshold;
1108
+ const isRapidRestart = now.getTime() - lastRestartTime.getTime() < config.restartBehavior.rapidRestartThreshold;
2106
1109
  if (isRapidRestart) {
2107
- logger.debug(`Rapid restart detected for ${job.name} - preserving saved timing`);
1110
+ logging.debug({ module: job.module }, `Rapid restart detected for ${job.name} - preserving saved timing`);
2108
1111
  return savedNextRun;
2109
1112
  }
2110
1113
  if (job.cronExpression || job.intervalMs) {
2111
1114
  const expectedNextRun = calculateNextRunFromLastRun(job, job.lastRun, config);
2112
- const timeDiff = Math.abs(savedNextRun.getTime() - expectedNextRun.getTime());
2113
- if (timeDiff > config.restartBehavior.maxTimingDrift) {
2114
- logger.debug(`Adjusting timing for ${job.name} - drift of ${Math.round(timeDiff / 1e3)}s detected`);
1115
+ const drift = Math.abs(savedNextRun.getTime() - expectedNextRun.getTime());
1116
+ if (drift > config.restartBehavior.maxTimingDrift) {
1117
+ logging.debug({ module: job.module }, `Adjusting timing for ${job.name} - drift of ${Math.round(drift / 1e3)}s detected`);
2115
1118
  return expectedNextRun;
2116
1119
  }
2117
1120
  }
2118
1121
  return savedNextRun;
2119
1122
  };
2120
- var restoreJobFromSavedData = (scheduler, savedJob, config, lastRestartTime) => {
2121
- const definitionChanged = hasJobDefinitionChanged(scheduler.job, savedJob);
2122
- const updatedJob = {
2123
- ...scheduler.job,
2124
- runCount: savedJob.runCount,
2125
- lastRun: savedJob.lastRun ? new Date(savedJob.lastRun) : void 0
2126
- };
2127
- if (definitionChanged) {
2128
- logger.debug(`Job definition changed: ${savedJob.name} - recalculating schedule`);
2129
- updatedJob.nextRun = calculateNextRunFromLastRun(updatedJob, updatedJob.lastRun, config);
2130
- } else {
2131
- const savedNextRun = savedJob.nextRun ? new Date(savedJob.nextRun) : void 0;
2132
- updatedJob.nextRun = validateAndAdjustNextRun(updatedJob, savedNextRun, lastRestartTime, config);
2133
- }
2134
- if (config.cron.logCronDetails && updatedJob.cronExpression) {
2135
- const result = parseCronExpression(updatedJob.cronExpression, updatedJob.lastRun, {
2136
- timezone: updatedJob.timezone
2137
- });
2138
- logger.debug(`Job restored: ${savedJob.name} - ${result.description} - next: ${updatedJob.nextRun.toLocaleString()}`);
1123
+ var loadJobState = async (runtime, scheduler) => {
1124
+ try {
1125
+ const stateData = await readStateData(runtime.config.persistence.directory);
1126
+ if (!stateData) {
1127
+ return scheduler;
1128
+ }
1129
+ const savedJob = stateData.jobs.find((job) => job.name === scheduler.job.name);
1130
+ if (!savedJob) {
1131
+ return scheduler;
1132
+ }
1133
+ const definitionChanged = hasJobDefinitionChanged(scheduler.job, savedJob);
1134
+ const lastRun = savedJob.lastRun ? new Date(savedJob.lastRun) : void 0;
1135
+ const updatedJob = {
1136
+ ...scheduler.job,
1137
+ runCount: savedJob.runCount,
1138
+ lastRun
1139
+ };
1140
+ if (definitionChanged) {
1141
+ runtime.logging.debug({ module: scheduler.job.module }, `Job definition changed: ${savedJob.name} - recalculating schedule`);
1142
+ updatedJob.nextRun = calculateNextRunFromLastRun(updatedJob, lastRun, runtime.config);
1143
+ } else {
1144
+ updatedJob.nextRun = validateAndAdjustNextRun(updatedJob, savedJob.nextRun ? new Date(savedJob.nextRun) : void 0, runtime);
1145
+ }
1146
+ if (runtime.config.cron.logCronDetails && updatedJob.cronExpression) {
1147
+ const result = parseCronExpression(updatedJob.cronExpression, updatedJob.lastRun, { timezone: updatedJob.timezone });
1148
+ runtime.logging.debug(
1149
+ { module: scheduler.job.module },
1150
+ `Job restored: ${savedJob.name} - ${result.description} - next: ${updatedJob.nextRun.toLocaleString()}`
1151
+ );
1152
+ } else {
1153
+ runtime.logging.debug({ module: scheduler.job.module }, `Job state restored: ${savedJob.name}`);
1154
+ }
1155
+ return { ...scheduler, job: updatedJob };
1156
+ } catch (error) {
1157
+ if (error.code === "ENOENT") {
1158
+ runtime.logging.debug("No saved scheduler state found - starting fresh");
1159
+ } else {
1160
+ runtime.logging.error({ error: error?.message }, `Failed to load job state for ${scheduler.job.name}`);
1161
+ }
1162
+ return scheduler;
2139
1163
  }
2140
- return { ...scheduler, job: updatedJob };
2141
- };
2142
- var restoreExecutionHistory = (savedExecutions) => {
2143
- return savedExecutions.map((exec) => ({
2144
- ...exec,
2145
- startTime: new Date(exec.startTime),
2146
- endTime: exec.endTime ? new Date(exec.endTime) : void 0
2147
- }));
2148
1164
  };
2149
- var loadSchedulerState = async (schedulers, config, lastRestartTime) => {
2150
- const stateData = await loadSchedulerStateData(config.persistence.directory);
2151
- if (!stateData) {
2152
- return { schedulers, executionHistory: [] };
2153
- }
2154
- const restoredSchedulers = { ...schedulers };
2155
- for (const savedJob of stateData.jobs) {
2156
- const existingScheduler = Object.values(schedulers).find((s) => s.job.name === savedJob.name);
2157
- if (existingScheduler) {
2158
- const restoredScheduler = restoreJobFromSavedData(
2159
- existingScheduler,
2160
- savedJob,
2161
- config,
2162
- lastRestartTime
2163
- );
2164
- restoredSchedulers[restoredScheduler.job.id] = restoredScheduler;
1165
+ var loadExecutionHistory = async (runtime) => {
1166
+ try {
1167
+ const stateData = await readStateData(runtime.config.persistence.directory);
1168
+ if (stateData?.executions) {
1169
+ runtime.executionHistory = restoreExecutionHistory(stateData.executions);
1170
+ }
1171
+ } catch (error) {
1172
+ if (error.code !== "ENOENT") {
1173
+ runtime.logging.error({ error: error?.message }, "Failed to load execution history");
2165
1174
  }
2166
1175
  }
2167
- const executionHistory = stateData.executions ? restoreExecutionHistory(stateData.executions) : [];
2168
- logger.debug(`Enhanced scheduler state loaded (saved at ${stateData.savedAt})`);
2169
- return { schedulers: restoredSchedulers, executionHistory };
2170
1176
  };
2171
-
2172
- // src/schedule-plugin/cleanup.ts
2173
- var cleanupExecutionHistory = (state) => {
2174
- if (state.executionHistory.length > state.config.cleanup.maxExecutionHistory) {
2175
- const removed = state.executionHistory.splice(
2176
- 0,
2177
- state.executionHistory.length - state.config.cleanup.maxExecutionHistory
2178
- );
2179
- logger.debug(`Cleaned up ${removed.length} old execution records`);
2180
- return removed;
1177
+ var initializePersistence2 = async (runtime) => {
1178
+ try {
1179
+ await fs2.mkdir(runtime.config.persistence.directory, { recursive: true });
1180
+ await loadExecutionHistory(runtime);
1181
+ if (runtime.config.persistence.saveOnShutdown) {
1182
+ const shutdownHandler = async () => {
1183
+ runtime.logging.debug("Saving scheduler state before shutdown...");
1184
+ await saveSchedulerState(runtime);
1185
+ process.exit(0);
1186
+ };
1187
+ process.on("SIGINT", shutdownHandler);
1188
+ process.on("SIGTERM", shutdownHandler);
1189
+ }
1190
+ runtime.logging.debug(`Scheduler persistence initialized: ${runtime.config.persistence.directory}`);
1191
+ } catch (error) {
1192
+ runtime.logging.error({ error: error?.message }, "Failed to initialize scheduler persistence");
2181
1193
  }
2182
- return [];
2183
1194
  };
2184
1195
 
2185
- // src/schedule-plugin/core.ts
2186
- var DEFAULT_CONFIG2 = {
2187
- persistence: {
2188
- enabled: true,
2189
- directory: "./.develop/scheduler-data",
2190
- saveInterval: 3e4
2191
- // 30 seconds
2192
- },
2193
- execution: {
2194
- maxConcurrent: 5,
2195
- defaultTimeout: 3e5,
2196
- // 5 minutes
2197
- retryOnError: false,
2198
- maxRetries: 3
2199
- },
2200
- cleanup: {
2201
- maxExecutionHistory: 1e3,
2202
- cleanupInterval: 6e5
2203
- // 10 minutes
2204
- },
2205
- restartBehavior: {
2206
- preserveNaturalTiming: true,
2207
- skipMissedRuns: true,
2208
- maxTimingDrift: 6e4,
2209
- // 1 minute
2210
- rapidRestartThreshold: 1e4
2211
- // 10 seconds
2212
- },
2213
- cron: {
2214
- defaultTimezone: void 0,
2215
- // Use system timezone
2216
- logCronDetails: true
1196
+ // src/schedule-plugin/processing.ts
1197
+ var canExecuteJob = (runtime, scheduler) => {
1198
+ if (runtime.isShuttingDown) {
1199
+ return { canExecute: false, reason: "System is shutting down" };
2217
1200
  }
1201
+ if (scheduler.isRunning) {
1202
+ return { canExecute: false, reason: "Job is already running" };
1203
+ }
1204
+ if (shouldSkipJob(scheduler.job)) {
1205
+ const reason = !scheduler.job.enabled ? "Job is disabled" : "Job reached maximum runs";
1206
+ return { canExecute: false, reason };
1207
+ }
1208
+ if (runtime.runningExecutions >= runtime.config.execution.maxConcurrent) {
1209
+ return { canExecute: false, reason: "Maximum concurrent executions reached" };
1210
+ }
1211
+ return { canExecute: true };
2218
1212
  };
2219
- var mergeConfig2 = (defaultConfig, userConfig) => ({
2220
- ...defaultConfig,
2221
- ...userConfig,
2222
- persistence: { ...defaultConfig.persistence, ...userConfig?.persistence },
2223
- execution: { ...defaultConfig.execution, ...userConfig?.execution },
2224
- cleanup: { ...defaultConfig.cleanup, ...userConfig?.cleanup },
2225
- restartBehavior: { ...defaultConfig.restartBehavior, ...userConfig?.restartBehavior },
2226
- cron: { ...defaultConfig.cron, ...userConfig?.cron }
2227
- });
2228
- var createInitialState = (config) => {
2229
- return {
2230
- schedulers: {},
2231
- executions: {},
2232
- executionHistory: [],
2233
- isShuttingDown: false,
2234
- runningExecutions: 0,
2235
- isDirty: false,
2236
- lastCleanupTime: Date.now(),
2237
- lastRestartTime: /* @__PURE__ */ new Date(),
2238
- config
2239
- };
2240
- };
2241
- var createSchedulerFunctions = (state) => {
2242
- const executeJob = async (scheduler) => {
2243
- const { canExecute, reason } = canExecuteJob(scheduler, state);
2244
- if (!canExecute) {
2245
- if (reason === "Maximum concurrent executions reached") {
2246
- logger.warn(`Delaying job ${scheduler.job.name} - ${reason}`);
2247
- scheduler.job.nextRun = new Date(Date.now() + 3e4);
2248
- scheduleJobFn(scheduler);
2249
- } else if (reason === "Job reached maximum runs") {
2250
- logger.info(`Job ${scheduler.job.name} reached max runs (${scheduler.job.maxRuns})`);
2251
- }
2252
- return;
1213
+ var executeJobWithTimeout = async (handler, job, execution, timeoutMs) => {
1214
+ let timerId;
1215
+ try {
1216
+ return await Promise.race([
1217
+ handler(job, execution),
1218
+ new Promise((_, reject) => {
1219
+ timerId = setTimeout(() => reject(new Error("Job execution timeout")), timeoutMs);
1220
+ })
1221
+ ]);
1222
+ } finally {
1223
+ clearTimeout(timerId);
1224
+ }
1225
+ };
1226
+ var executeJob = async (runtime, scheduler) => {
1227
+ const execution = createJobExecution(scheduler.job.id);
1228
+ runtime.executions[execution.id] = execution;
1229
+ runtime.executionHistory.push(execution);
1230
+ scheduler.isRunning = true;
1231
+ runtime.runningExecutions++;
1232
+ runtime.logging.info(`Executing job ${scheduler.job.name} (${execution.id})`);
1233
+ try {
1234
+ const result = await executeJobWithTimeout(
1235
+ scheduler.handler,
1236
+ scheduler.job,
1237
+ execution,
1238
+ runtime.config.execution.defaultTimeout
1239
+ );
1240
+ Object.assign(execution, completeExecution(execution, result));
1241
+ const duration = execution.endTime.getTime() - execution.startTime.getTime();
1242
+ runtime.logging.debug(`Job completed in ${duration}ms`);
1243
+ } catch (error) {
1244
+ Object.assign(execution, completeExecution(execution, void 0, error.message));
1245
+ runtime.logging.error(`Job failed ${scheduler.job.name}: ${error.message}`);
1246
+ } finally {
1247
+ scheduler.isRunning = false;
1248
+ runtime.runningExecutions--;
1249
+ scheduler.job = updateJobAfterExecution(scheduler.job, execution, runtime.config);
1250
+ if (scheduler.job.maxRuns && scheduler.job.runCount >= scheduler.job.maxRuns) {
1251
+ runtime.logging.info(`Job ${scheduler.job.name} reached max runs (${scheduler.job.maxRuns})`);
2253
1252
  }
2254
- const execution = createJobExecution(scheduler.job.id);
2255
- state.executions[execution.id] = execution;
2256
- state.executionHistory.push(execution);
2257
- scheduler.isRunning = true;
2258
- state.runningExecutions++;
2259
- logger.info(`Executing job ${scheduler.job.name} (${execution.id})`);
2260
- try {
2261
- const result = await executeJobWithTimeout(
2262
- scheduler.handler,
2263
- scheduler.job,
2264
- execution,
2265
- state.config.execution.defaultTimeout
2266
- );
2267
- const completedExecution = handleExecutionSuccess(execution, result);
2268
- state.executions[execution.id] = completedExecution;
2269
- } catch (error) {
2270
- const failedExecution = handleExecutionError(execution, error);
2271
- state.executions[execution.id] = failedExecution;
2272
- } finally {
2273
- scheduler.isRunning = false;
2274
- state.runningExecutions--;
2275
- scheduler.job = updateJobAfterExecution(scheduler.job, execution, state.config);
2276
- if (scheduler.job.enabled && !state.isShuttingDown) {
2277
- scheduleJobFn(scheduler);
2278
- }
2279
- if (state.config.persistence.enabled) {
2280
- state.isDirty = true;
2281
- }
1253
+ if (runtime.config.persistence.enabled) {
1254
+ runtime.isDirty = true;
2282
1255
  }
2283
- };
2284
- const scheduleJobFn = (scheduler) => {
2285
- const updatedScheduler = scheduleJob(scheduler, state, executeJob);
2286
- state.schedulers[scheduler.job.id] = updatedScheduler;
2287
- return updatedScheduler;
2288
- };
2289
- return { executeJob, scheduleJobFn };
1256
+ }
2290
1257
  };
2291
- var initializeScheduler = async (state, scheduleJobFn) => {
2292
- try {
2293
- if (state.config.persistence.enabled) {
2294
- await fs3.mkdir(state.config.persistence.directory, { recursive: true });
2295
- const { schedulers, executionHistory } = await loadSchedulerState(
2296
- state.schedulers,
2297
- state.config,
2298
- state.lastRestartTime
2299
- );
2300
- state.schedulers = schedulers;
2301
- state.executionHistory = executionHistory;
2302
- }
2303
- const tickInterval = (state.config.persistence.saveInterval || 0) > 0 ? state.config.persistence.saveInterval : state.config.cleanup.cleanupInterval;
2304
- state.tickInterval = setInterval(async () => {
2305
- if (state.isDirty && state.config.persistence.enabled) {
2306
- state.isDirty = false;
2307
- await saveSchedulerState(state);
2308
- }
2309
- if (Date.now() - state.lastCleanupTime >= state.config.cleanup.cleanupInterval) {
2310
- state.lastCleanupTime = Date.now();
2311
- cleanupExecutionHistory(state);
2312
- }
2313
- }, tickInterval);
2314
- for (const scheduler of Object.values(state.schedulers)) {
2315
- if (scheduler.job.enabled) {
2316
- scheduleJobFn(scheduler);
2317
- }
1258
+ var dispatchJobIfDue = (runtime, scheduler) => {
1259
+ if (!scheduler.job.enabled || !isJobReadyToRun(scheduler.job)) {
1260
+ return;
1261
+ }
1262
+ const { canExecute, reason } = canExecuteJob(runtime, scheduler);
1263
+ if (!canExecute) {
1264
+ if (reason === "Maximum concurrent executions reached") {
1265
+ runtime.logging.debug({ module: scheduler.job.module }, `Delaying job ${scheduler.job.name} - ${reason}`);
2318
1266
  }
2319
- logger.debug(`Scheduler initialized: ${Object.keys(state.schedulers).length} jobs loaded`);
2320
- } catch (error) {
2321
- logger.error(`Failed to initialize scheduler: ${error.message}`);
2322
- throw error;
1267
+ return;
2323
1268
  }
1269
+ runtime.logging.runWithLogContext(
1270
+ { module: scheduler.job.module, reqId: scheduler.job.id },
1271
+ () => executeJob(runtime, scheduler)
1272
+ );
2324
1273
  };
2325
-
2326
- // src/schedule-plugin/jobs.ts
2327
- var validateJobDefinition = (jobDef) => {
2328
- const expressionType = detectExpressionType(jobDef.cronExpression);
2329
- if (expressionType === "unknown") {
2330
- logger.error(`Invalid cron expression for job: ${jobDef.name}`);
2331
- throw new Error(`Invalid cron expression for job: ${jobDef.name}`);
1274
+ var cleanupExecutionHistory = (runtime) => {
1275
+ const { maxExecutionHistory } = runtime.config.cleanup;
1276
+ if (runtime.executionHistory.length <= maxExecutionHistory) {
1277
+ return;
2332
1278
  }
1279
+ const removed = runtime.executionHistory.splice(0, runtime.executionHistory.length - maxExecutionHistory);
1280
+ runtime.logging.debug(`Cleaned up ${removed.length} old execution records`);
2333
1281
  };
2334
- var createJobFromDefinition = (jobDef, config) => {
2335
- validateJobDefinition(jobDef);
2336
- const expressionType = detectExpressionType(jobDef.cronExpression);
2337
- if (config.cron.logCronDetails && jobDef.cronExpression) {
2338
- const cronExpression = expressionType === "rate" ? convertRateToCron(jobDef.cronExpression).cronExpression : jobDef.cronExpression;
2339
- const result = parseCronExpression(cronExpression, void 0, {
2340
- timezone: jobDef.timezone ?? config.cron.defaultTimezone
2341
- });
2342
- logger.debug(`Job ${jobDef.name}: ${result.description} - Next: ${result.nextRun.toLocaleString()}`);
2343
- const upcoming = getNextExecutions(cronExpression, 3, {
2344
- timezone: jobDef.timezone ?? config.cron.defaultTimezone
2345
- });
2346
- logger.debug(`Upcoming: ${upcoming.map((d) => d.toLocaleTimeString()).join(", ")}`);
1282
+ var maybeCleanup = (runtime, now) => {
1283
+ if (now - runtime.lastCleanupTime < runtime.config.cleanup.cleanupInterval) {
1284
+ return;
2347
1285
  }
2348
- const job = {
2349
- id: generateJobId(),
2350
- name: jobDef.name,
2351
- cronExpression: expressionType === "rate" ? convertRateToCron(jobDef.cronExpression).cronExpression : jobDef.cronExpression,
2352
- intervalMs: jobDef.intervalMs,
2353
- delayMs: jobDef.delayMs,
2354
- nextRun: /* @__PURE__ */ new Date(),
2355
- enabled: jobDef.enabled ?? true,
2356
- runCount: 0,
2357
- maxRuns: jobDef.maxRuns,
2358
- metadata: jobDef.metadata,
2359
- createdAt: /* @__PURE__ */ new Date(),
2360
- timezone: jobDef.timezone,
2361
- module: jobDef.module
2362
- };
2363
- job.nextRun = calculateNextRun(job);
2364
- return job;
1286
+ runtime.lastCleanupTime = now;
1287
+ cleanupExecutionHistory(runtime);
2365
1288
  };
2366
- var createSchedulerService = (job, handler) => ({
2367
- job,
2368
- handler,
2369
- isRunning: false
2370
- });
2371
- var enableJob = (scheduler) => ({
2372
- ...scheduler,
2373
- job: {
2374
- ...scheduler.job,
2375
- enabled: true,
2376
- nextRun: calculateNextRun(scheduler.job)
1289
+ var maybeSavePersistence2 = (runtime, now) => {
1290
+ if (!runtime.config.persistence.enabled || !runtime.isDirty) {
1291
+ return;
2377
1292
  }
2378
- });
2379
- var disableJob = (scheduler) => {
2380
- if (scheduler.timerId) {
2381
- clearTimeout(scheduler.timerId);
1293
+ const saveInterval = runtime.config.persistence.saveInterval ?? 0;
1294
+ if (saveInterval > 0 && now - runtime.lastSavedAt >= saveInterval) {
1295
+ runtime.lastSavedAt = now;
1296
+ runtime.isDirty = false;
1297
+ saveSchedulerState(runtime);
2382
1298
  }
2383
- return {
2384
- ...scheduler,
2385
- job: { ...scheduler.job, enabled: false },
2386
- timerId: void 0
2387
- };
1299
+ };
1300
+ var tick2 = (runtime) => {
1301
+ const now = Date.now();
1302
+ for (const scheduler of Object.values(runtime.schedulers)) {
1303
+ dispatchJobIfDue(runtime, scheduler);
1304
+ }
1305
+ maybeCleanup(runtime, now);
1306
+ maybeSavePersistence2(runtime, now);
2388
1307
  };
2389
1308
 
2390
- // src/schedule-plugin/routes.ts
2391
- var createDashboardData = (state) => {
2392
- const jobs = Object.values(state.schedulers).map((s) => {
2393
- const nextExecutions = s.job.cronExpression ? getNextExecutions(s.job.cronExpression, 3, { timezone: s.job.timezone }) : [];
2394
- return {
2395
- ...s.job,
2396
- isRunning: s.isRunning,
2397
- timeUntilNextRun: getTimeUntilNextRun(s.job),
2398
- upcomingExecutions: nextExecutions,
2399
- cronDescription: s.job.cronExpression ? parseCronExpression(s.job.cronExpression, void 0, { timezone: s.job.timezone }).description : void 0
2400
- };
2401
- });
2402
- return { jobs, runningExecutions: state.runningExecutions };
1309
+ // src/schedule-plugin/decorator.ts
1310
+ var isValidJobName = (jobName) => {
1311
+ return typeof jobName === "string" && jobName.length > 0 && /^[a-zA-Z0-9_-]+$/.test(jobName);
2403
1312
  };
2404
- var createExecutionsData = (state) => ({
2405
- executions: state.executionHistory.slice(-50),
2406
- // Last 50 executions
2407
- running: Object.values(state.executions).filter((e) => e.status === "running")
2408
- });
2409
- var registerDashboardRoute = (app, state) => {
2410
- app.get("/schedule/dashboard", async function(_, reply) {
2411
- const dashboardData = createDashboardData(state);
2412
- return reply.status(200).send(dashboardData);
2413
- });
1313
+ var validateJobName = (jobName) => {
1314
+ if (!jobName || typeof jobName !== "string") {
1315
+ throw new Error("Job name must be a non-empty string");
1316
+ }
1317
+ if (!isValidJobName(jobName)) {
1318
+ throw new Error("Job name must contain only alphanumeric characters, hyphens, and underscores");
1319
+ }
2414
1320
  };
2415
- var registerExecutionsRoute = (app, state) => {
2416
- app.get("/schedule/executions", async function(_, reply) {
2417
- const executionsData = createExecutionsData(state);
2418
- return reply.status(200).send(executionsData);
2419
- });
1321
+ var validateHandler2 = (handler) => {
1322
+ if (typeof handler !== "function") {
1323
+ throw new Error("Handler must be a function");
1324
+ }
2420
1325
  };
2421
- var registerTriggerJobRoute = (app, state, executeJobFn) => {
2422
- app.post("/schedule/jobs/:jobId/trigger", async function(request, reply) {
2423
- const scheduler = state.schedulers[request.params.jobId];
2424
- if (!scheduler) {
2425
- return reply.status(404).send({ error: "Job not found" });
1326
+ var findSchedulerByName = (runtime, jobName) => Object.values(runtime.schedulers).find((scheduler) => scheduler.job.name === jobName);
1327
+ var addJob = async (runtime, jobDefinition) => {
1328
+ try {
1329
+ validateJobName(jobDefinition.name);
1330
+ validateHandler2(jobDefinition.handler);
1331
+ const existing = findSchedulerByName(runtime, jobDefinition.name);
1332
+ if (existing) {
1333
+ runtime.logging.warn({ module: jobDefinition.module }, `Job already exists: ${jobDefinition.name}.`);
1334
+ return existing.job.id;
1335
+ }
1336
+ const job = createJobFromDefinition(jobDefinition, runtime.config);
1337
+ if (runtime.config.cron.logCronDetails && job.cronExpression) {
1338
+ const timezone = job.timezone ?? runtime.config.cron.defaultTimezone;
1339
+ const result = parseCronExpression(job.cronExpression, void 0, { timezone });
1340
+ runtime.logging.debug(
1341
+ { module: jobDefinition.module },
1342
+ `Job ${jobDefinition.name}: ${result.description} - Next: ${result.nextRun.toLocaleString()}`
1343
+ );
1344
+ const upcoming = getNextExecutions(job.cronExpression, 3, { timezone });
1345
+ runtime.logging.debug(
1346
+ { module: jobDefinition.module },
1347
+ `Upcoming: ${upcoming.map((d) => d.toLocaleTimeString()).join(", ")}`
1348
+ );
2426
1349
  }
2427
- if (scheduler.isRunning) {
2428
- return reply.status(409).send({ error: "Job is already running" });
1350
+ let scheduler = createSchedulerService(job, jobDefinition.handler);
1351
+ if (runtime.config.persistence.enabled) {
1352
+ scheduler = await loadJobState(runtime, scheduler);
2429
1353
  }
2430
- setImmediate(() => executeJobFn(scheduler));
2431
- return reply.status(200).send({ message: "Job triggered successfully" });
2432
- });
1354
+ runtime.schedulers[scheduler.job.id] = scheduler;
1355
+ runtime.logging.info({ module: jobDefinition.module }, `Job added: ${jobDefinition.name} (${scheduler.job.id})`);
1356
+ return scheduler.job.id;
1357
+ } catch (error) {
1358
+ runtime.logging.error({ module: jobDefinition.module, error: error.message }, `Failed to add job ${jobDefinition.name}`);
1359
+ throw error;
1360
+ }
2433
1361
  };
2434
- var registerEnableJobRoute = (app, state, scheduleJobFn) => {
2435
- app.put("/schedule/jobs/:jobId/enable", async function(request, reply) {
2436
- const scheduler = state.schedulers[request.params.jobId];
1362
+ var removeJob = async (runtime, jobName) => {
1363
+ try {
1364
+ validateJobName(jobName);
1365
+ const scheduler = findSchedulerByName(runtime, jobName);
2437
1366
  if (!scheduler) {
2438
- return reply.status(404).send({ error: "Job not found" });
1367
+ throw new Error(`Job '${jobName}' does not exist`);
2439
1368
  }
2440
- const enabledScheduler = enableJob(scheduler);
2441
- state.schedulers[request.params.jobId] = enabledScheduler;
2442
- scheduleJobFn(enabledScheduler);
2443
- return reply.status(200).send({ message: "Job enabled successfully" });
2444
- });
2445
- };
2446
- var registerDisableJobRoute = (app, state) => {
2447
- app.put("/schedule/jobs/:jobId/disable", async function(request, reply) {
2448
- const scheduler = state.schedulers[request.params.jobId];
2449
- if (!scheduler) {
2450
- return reply.status(404).send({ error: "Job not found" });
1369
+ if (scheduler.isRunning) {
1370
+ runtime.logging.warn({ module: scheduler.job.module }, `Removing job that is currently running: ${jobName}`);
2451
1371
  }
2452
- const disabledScheduler = disableJob(scheduler);
2453
- state.schedulers[request.params.jobId] = disabledScheduler;
2454
- return reply.status(200).send({ message: "Job disabled successfully" });
2455
- });
1372
+ delete runtime.schedulers[scheduler.job.id];
1373
+ runtime.logging.info({ module: scheduler.job.module }, `Job removed: ${jobName}`);
1374
+ } catch (error) {
1375
+ runtime.logging.error({ error: error.message }, `Failed to remove job ${jobName}`);
1376
+ throw error;
1377
+ }
1378
+ };
1379
+ var hasJob = (runtime, jobName) => {
1380
+ try {
1381
+ validateJobName(jobName);
1382
+ return !!findSchedulerByName(runtime, jobName);
1383
+ } catch {
1384
+ return false;
1385
+ }
2456
1386
  };
2457
- var registerAllRoutes = (app, state, executeJobFn, scheduleJobFn) => {
2458
- registerDashboardRoute(app, state);
2459
- registerExecutionsRoute(app, state);
2460
- registerTriggerJobRoute(app, state, executeJobFn);
2461
- registerEnableJobRoute(app, state, scheduleJobFn);
2462
- registerDisableJobRoute(app, state);
1387
+ var listJobs = (runtime, module) => {
1388
+ return Object.values(runtime.schedulers).filter((scheduler) => !module || scheduler.job.module === module).map((scheduler) => scheduler.job.name).sort();
2463
1389
  };
2464
-
2465
- // src/schedule-plugin/decorator.ts
2466
- var isValidJobName = (jobName) => {
2467
- return typeof jobName === "string" && jobName.length > 0 && /^[a-zA-Z0-9_-]+$/.test(jobName);
1390
+ var getJobStats = (runtime, jobName) => {
1391
+ try {
1392
+ validateJobName(jobName);
1393
+ return findSchedulerByName(runtime, jobName) ?? null;
1394
+ } catch {
1395
+ return null;
1396
+ }
2468
1397
  };
2469
- var createSchedulerManager = ({
2470
- state,
2471
- scheduleJobFn,
2472
- executeJob,
2473
- config
2474
- }) => {
2475
- const validateJobName = (jobName) => {
2476
- if (!jobName || typeof jobName !== "string") {
2477
- throw new Error("Job name must be a non-empty string");
1398
+ var getJobsMap = (runtime) => {
1399
+ return { ...runtime.schedulers };
1400
+ };
1401
+ var enableJob2 = async (runtime, jobName) => {
1402
+ try {
1403
+ validateJobName(jobName);
1404
+ const scheduler = findSchedulerByName(runtime, jobName);
1405
+ if (!scheduler) {
1406
+ throw new Error(`Job '${jobName}' does not exist`);
2478
1407
  }
2479
- if (!isValidJobName(jobName)) {
2480
- throw new Error("Job name must contain only alphanumeric characters, hyphens, and underscores");
1408
+ if (scheduler.job.enabled) {
1409
+ runtime.logging.debug({ module: scheduler.job.module }, `Job is already enabled: ${jobName}`);
1410
+ return;
2481
1411
  }
2482
- };
2483
- const validateHandler = (handler) => {
2484
- if (typeof handler !== "function") {
2485
- throw new Error("Handler must be a function");
1412
+ runtime.schedulers[scheduler.job.id] = enableJob(scheduler);
1413
+ runtime.logging.info({ module: scheduler.job.module }, `Job enabled: ${jobName}`);
1414
+ } catch (error) {
1415
+ runtime.logging.error({ error: error.message }, `Failed to enable job ${jobName}`);
1416
+ throw error;
1417
+ }
1418
+ };
1419
+ var disableJob2 = async (runtime, jobName) => {
1420
+ try {
1421
+ validateJobName(jobName);
1422
+ const scheduler = findSchedulerByName(runtime, jobName);
1423
+ if (!scheduler) {
1424
+ throw new Error(`Job '${jobName}' does not exist`);
2486
1425
  }
2487
- };
2488
- const addJob = async (jobDefinition) => {
2489
- try {
2490
- validateJobName(jobDefinition.name);
2491
- validateHandler(jobDefinition.handler);
2492
- validateJobDefinition(jobDefinition);
2493
- const existingJob = Object.values(state.schedulers).find((s) => s.job.name === jobDefinition.name);
2494
- if (existingJob) {
2495
- logger.warn(`Job already exists: ${jobDefinition.name}.`);
2496
- return existingJob.job.id;
2497
- }
2498
- const job = createJobFromDefinition(jobDefinition, config);
2499
- const scheduler = createSchedulerService(job, jobDefinition.handler);
2500
- state.schedulers[job.id] = scheduler;
2501
- if (job.enabled) {
2502
- scheduleJobFn(scheduler);
2503
- }
2504
- logger.info(`Job added: ${jobDefinition.name} (${job.id})`);
2505
- return job.id;
2506
- } catch (error) {
2507
- logger.error(`Failed to add job ${jobDefinition.name}: ${error.message}`);
2508
- throw error;
1426
+ if (!scheduler.job.enabled) {
1427
+ runtime.logging.debug({ module: scheduler.job.module }, `Job is already disabled: ${jobName}`);
1428
+ return;
2509
1429
  }
2510
- };
2511
- const removeJob = async (jobName) => {
2512
- try {
2513
- validateJobName(jobName);
2514
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2515
- if (!scheduler) {
2516
- throw new Error(`Job '${jobName}' does not exist`);
2517
- }
2518
- if (scheduler.isRunning) {
2519
- logger.warn(`Removing job that is currently running: ${jobName}`);
2520
- }
2521
- if (scheduler.timerId) {
2522
- clearTimeout(scheduler.timerId);
2523
- }
2524
- delete state.schedulers[scheduler.job.id];
2525
- logger.info(`Job removed: ${jobName}`);
2526
- } catch (error) {
2527
- logger.error(`Failed to remove job ${jobName}: ${error.message}`);
2528
- throw error;
1430
+ runtime.schedulers[scheduler.job.id] = disableJob(scheduler);
1431
+ runtime.logging.info({ module: scheduler.job.module }, `Job disabled: ${jobName}`);
1432
+ } catch (error) {
1433
+ runtime.logging.error({ error: error.message }, `Failed to disable job ${jobName}`);
1434
+ throw error;
1435
+ }
1436
+ };
1437
+ var triggerJob = async (runtime, jobName) => {
1438
+ try {
1439
+ validateJobName(jobName);
1440
+ const scheduler = findSchedulerByName(runtime, jobName);
1441
+ if (!scheduler) {
1442
+ throw new Error(`Job '${jobName}' does not exist`);
2529
1443
  }
2530
- };
2531
- const hasJob = (jobName) => {
2532
- try {
2533
- validateJobName(jobName);
2534
- return Object.values(state.schedulers).some((s) => s.job.name === jobName);
2535
- } catch {
2536
- return false;
1444
+ if (scheduler.isRunning) {
1445
+ throw new Error(`Job '${jobName}' is already running`);
2537
1446
  }
2538
- };
2539
- const listJobs = (module) => {
2540
- return Object.values(state.schedulers).filter((s) => !module || s.job.module === module).map((s) => s.job.name).sort();
2541
- };
2542
- const getJobStats = (jobName) => {
2543
- try {
2544
- validateJobName(jobName);
2545
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2546
- return scheduler || null;
2547
- } catch {
2548
- return null;
1447
+ runtime.logging.info({ module: scheduler.job.module }, `Job triggered: ${jobName}`);
1448
+ runtime.logging.runWithLogContext(
1449
+ { module: scheduler.job.module, reqId: scheduler.job.id },
1450
+ () => executeJob(runtime, scheduler)
1451
+ );
1452
+ } catch (error) {
1453
+ runtime.logging.error({ error: error.message }, `Failed to trigger job ${jobName}`);
1454
+ throw error;
1455
+ }
1456
+ };
1457
+ var getDashboardSummary2 = (runtime) => createDashboardSummary2(runtime);
1458
+ var getExecutionsSummary = (runtime) => createExecutionsSummary(runtime);
1459
+ var getJobExecutionHistory = (runtime, jobName, limit = 50) => {
1460
+ const scheduler = findSchedulerByName(runtime, jobName);
1461
+ if (!scheduler) {
1462
+ return [];
1463
+ }
1464
+ return runtime.executionHistory.filter((execution) => execution.jobId === scheduler.job.id).slice(-limit).reverse();
1465
+ };
1466
+ var createSchedulerManager = (runtime) => ({
1467
+ addJob: (jobDefinition) => addJob(runtime, jobDefinition),
1468
+ removeJob: (jobName) => removeJob(runtime, jobName),
1469
+ hasJob: (jobName) => hasJob(runtime, jobName),
1470
+ listJobs: (module) => listJobs(runtime, module),
1471
+ getJobStats: (jobName) => getJobStats(runtime, jobName),
1472
+ getJobsMap: () => getJobsMap(runtime),
1473
+ getConfig: () => runtime.config,
1474
+ enableJob: (jobName) => enableJob2(runtime, jobName),
1475
+ disableJob: (jobName) => disableJob2(runtime, jobName),
1476
+ triggerJob: (jobName) => triggerJob(runtime, jobName),
1477
+ getDashboardSummary: () => getDashboardSummary2(runtime),
1478
+ getExecutionsSummary: () => getExecutionsSummary(runtime),
1479
+ getJobExecutionHistory: (jobName, limit) => getJobExecutionHistory(runtime, jobName, limit)
1480
+ });
1481
+
1482
+ // src/schedule-plugin/routes.ts
1483
+ var registerSchedulerRoutes = (app, prefix = "schedule") => {
1484
+ app.get(
1485
+ `/${prefix}/dashboard`,
1486
+ async (_, reply) => {
1487
+ return reply.status(200).send(app.scheduler.getDashboardSummary());
2549
1488
  }
2550
- };
2551
- const enableJob2 = async (jobName) => {
2552
- try {
2553
- validateJobName(jobName);
2554
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2555
- if (!scheduler) {
2556
- throw new Error(`Job '${jobName}' does not exist`);
2557
- }
2558
- if (scheduler.job.enabled) {
2559
- logger.debug(`Job is already enabled: ${jobName}`);
2560
- return;
2561
- }
2562
- scheduler.job.enabled = true;
2563
- scheduleJobFn(scheduler);
2564
- logger.info(`Job enabled: ${jobName}`);
2565
- } catch (error) {
2566
- logger.error(`Failed to enable job ${jobName}: ${error.message}`);
2567
- throw error;
1489
+ );
1490
+ app.get(
1491
+ `/${prefix}/executions`,
1492
+ async (_, reply) => {
1493
+ return reply.status(200).send(app.scheduler.getExecutionsSummary());
2568
1494
  }
2569
- };
2570
- const disableJob2 = async (jobName) => {
2571
- try {
2572
- validateJobName(jobName);
2573
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2574
- if (!scheduler) {
2575
- throw new Error(`Job '${jobName}' does not exist`);
2576
- }
2577
- if (!scheduler.job.enabled) {
2578
- logger.debug(`Job is already disabled: ${jobName}`);
2579
- return;
2580
- }
2581
- scheduler.job.enabled = false;
2582
- if (scheduler.timerId) {
2583
- clearTimeout(scheduler.timerId);
2584
- scheduler.timerId = void 0;
1495
+ );
1496
+ app.post(
1497
+ `/${prefix}/jobs/:jobName/trigger`,
1498
+ async (request, reply) => {
1499
+ try {
1500
+ await app.scheduler.triggerJob(request.params.jobName);
1501
+ return reply.status(200).send({ message: "Job triggered successfully" });
1502
+ } catch (error) {
1503
+ return reply.status(404).send({ error: error.message });
2585
1504
  }
2586
- logger.info(`Job disabled: ${jobName}`);
2587
- } catch (error) {
2588
- logger.error(`Failed to disable job ${jobName}: ${error.message}`);
2589
- throw error;
2590
1505
  }
2591
- };
2592
- const triggerJob = async (jobName) => {
2593
- try {
2594
- validateJobName(jobName);
2595
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2596
- if (!scheduler) {
2597
- throw new Error(`Job '${jobName}' does not exist`);
1506
+ );
1507
+ app.put(
1508
+ `/${prefix}/jobs/:jobName/enable`,
1509
+ async (request, reply) => {
1510
+ try {
1511
+ await app.scheduler.enableJob(request.params.jobName);
1512
+ return reply.status(200).send({ message: "Job enabled successfully" });
1513
+ } catch (error) {
1514
+ return reply.status(404).send({ error: error.message });
2598
1515
  }
2599
- if (scheduler.isRunning) {
2600
- throw new Error(`Job '${jobName}' is already running`);
1516
+ }
1517
+ );
1518
+ app.put(
1519
+ `/${prefix}/jobs/:jobName/disable`,
1520
+ async (request, reply) => {
1521
+ try {
1522
+ await app.scheduler.disableJob(request.params.jobName);
1523
+ return reply.status(200).send({ message: "Job disabled successfully" });
1524
+ } catch (error) {
1525
+ return reply.status(404).send({ error: error.message });
2601
1526
  }
2602
- setImmediate(() => executeJob(scheduler));
2603
- logger.info(`Job triggered: ${jobName}`);
2604
- } catch (error) {
2605
- logger.error(`Failed to trigger job ${jobName}: ${error.message}`);
2606
- throw error;
2607
1527
  }
2608
- };
2609
- const getJobsMap = () => {
2610
- return { ...state.schedulers };
2611
- };
2612
- return {
2613
- addJob,
2614
- removeJob,
2615
- hasJob,
2616
- listJobs,
2617
- getJobStats,
2618
- enableJob: enableJob2,
2619
- disableJob: disableJob2,
2620
- triggerJob,
2621
- getJobsMap
2622
- };
2623
- };
2624
- var createSchedulerDecorator = (schedulerManager) => (app) => {
2625
- app.decorate("scheduler", schedulerManager);
1528
+ );
2626
1529
  };
2627
1530
 
2628
1531
  // src/schedule-plugin/plugin.ts
2629
1532
  var schedulerPlugin = fsPlugin2(async (app, options) => {
2630
- const config = mergeConfig2(DEFAULT_CONFIG2, options.config);
2631
- const state = createInitialState(config);
2632
- const { executeJob, scheduleJobFn } = createSchedulerFunctions(state);
2633
- await initializeScheduler(state, scheduleJobFn);
2634
- const schedulerManager = createSchedulerManager({
2635
- state,
2636
- config,
2637
- scheduleJobFn,
2638
- executeJob
1533
+ const runtime = {
1534
+ config: mergeConfig2(DEFAULT_CONFIG2, options.config),
1535
+ schedulers: {},
1536
+ executions: {},
1537
+ executionHistory: [],
1538
+ isShuttingDown: false,
1539
+ runningExecutions: 0,
1540
+ isDirty: false,
1541
+ lastCleanupTime: Date.now(),
1542
+ lastSavedAt: Date.now(),
1543
+ lastRestartTime: /* @__PURE__ */ new Date(),
1544
+ logging: app.logging
1545
+ };
1546
+ const tickIntervalId = setInterval(() => tick2(runtime), runtime.config.tickIntervalMs);
1547
+ app.addHook("onClose", async () => {
1548
+ runtime.isShuttingDown = true;
1549
+ clearInterval(tickIntervalId);
2639
1550
  });
2640
- const decorateScheduler = createSchedulerDecorator(schedulerManager);
2641
- decorateScheduler(app);
2642
- registerAllRoutes(app, state, executeJob, scheduleJobFn);
1551
+ app.decorate("scheduler", createSchedulerManager(runtime));
1552
+ registerSchedulerRoutes(app, options.prefix);
1553
+ if (runtime.config.persistence.enabled) {
1554
+ await initializePersistence2(runtime);
1555
+ }
2643
1556
  });
2644
1557
 
2645
1558
  // src/websocket-plugin/plugin.ts
2646
1559
  import crypto from "crypto";
2647
1560
  import fsPlugin3 from "fastify-plugin";
2648
1561
  import websocketPlugin from "@fastify/websocket";
1562
+ import { WebSocketConnectionGoneError } from "@cloudnux/core-cloud-provider";
2649
1563
  var DEFAULT_CONFIG3 = {
2650
1564
  routeKeyField: "action"
2651
1565
  };
@@ -2667,15 +1581,20 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2667
1581
  preHandler: async (request, reply) => {
2668
1582
  const connectionId = crypto.randomUUID();
2669
1583
  request._wsConnectionId = connectionId;
1584
+ const reqId = `${request.id}:${connectionId}`;
2670
1585
  const connectHandlers = handlers.filter((h) => h.path === path4 && h.event === "connect");
2671
1586
  for (const h of connectHandlers) {
2672
- await h.handler(connectionId, "connect", null, request, reply);
1587
+ await app.logging.runWithLogContext(
1588
+ { module: h.module, reqId },
1589
+ () => h.handler(connectionId, "connect", null, request, reply)
1590
+ );
2673
1591
  }
2674
1592
  if (reply.statusCode !== 200)
2675
1593
  return reply;
2676
1594
  }
2677
1595
  }, (socket, request) => {
2678
1596
  const connectionId = request._wsConnectionId ?? crypto.randomUUID();
1597
+ const reqId = `${request.id}:${connectionId}`;
2679
1598
  const connection = {
2680
1599
  connectionId,
2681
1600
  socket,
@@ -2700,9 +1619,10 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2700
1619
  );
2701
1620
  for (const h of routeHandlers) {
2702
1621
  matched = true;
2703
- h.handler(connectionId, "message", data, request).catch((e) => {
2704
- logger.error(e);
2705
- });
1622
+ app.logging.runWithLogContext(
1623
+ { module: h.module, reqId },
1624
+ () => h.handler(connectionId, "message", data, request).catch((e) => app.logging.error(e))
1625
+ );
2706
1626
  }
2707
1627
  }
2708
1628
  if (!matched) {
@@ -2710,18 +1630,20 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2710
1630
  (h) => h.event === "message" && !h.route
2711
1631
  );
2712
1632
  for (const h of defaultHandlers) {
2713
- h.handler(connectionId, "message", data, request).catch((e) => {
2714
- logger.error(e);
2715
- });
1633
+ app.logging.runWithLogContext(
1634
+ { module: h.module, reqId },
1635
+ () => h.handler(connectionId, "message", data, request).catch((e) => app.logging.error(e))
1636
+ );
2716
1637
  }
2717
1638
  }
2718
1639
  });
2719
1640
  socket.on("close", () => {
2720
1641
  const disconnectHandlers = pathHandlers().filter((h) => h.event === "disconnect");
2721
1642
  for (const h of disconnectHandlers) {
2722
- h.handler(connectionId, "disconnect", null, request).catch((e) => {
2723
- logger.error(e);
2724
- });
1643
+ app.logging.runWithLogContext(
1644
+ { module: h.module, reqId },
1645
+ () => h.handler(connectionId, "disconnect", null, request).catch((e) => app.logging.error(e))
1646
+ );
2725
1647
  }
2726
1648
  connections.delete(connectionId);
2727
1649
  });
@@ -2752,7 +1674,7 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2752
1674
  async sendToClient(connectionId, data) {
2753
1675
  const connection = connections.get(connectionId);
2754
1676
  if (!connection) {
2755
- throw new Error(`WebSocket connection ${connectionId} not found`);
1677
+ throw new WebSocketConnectionGoneError(connectionId);
2756
1678
  }
2757
1679
  const payload = typeof data === "string" ? data : JSON.stringify(data);
2758
1680
  connection.socket.send(payload);
@@ -2760,7 +1682,7 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2760
1682
  async disconnect(connectionId) {
2761
1683
  const connection = connections.get(connectionId);
2762
1684
  if (!connection) {
2763
- throw new Error(`WebSocket connection ${connectionId} not found`);
1685
+ throw new WebSocketConnectionGoneError(connectionId);
2764
1686
  }
2765
1687
  connection.socket.close();
2766
1688
  }
@@ -2782,7 +1704,7 @@ var RouteRegistry = class _RouteRegistry {
2782
1704
  url: routeOptions.url,
2783
1705
  handler: routeOptions.handler?.name || "anonymous",
2784
1706
  registeredAt: /* @__PURE__ */ new Date(),
2785
- module: routeOptions.module
1707
+ module: routeOptions.config?.module
2786
1708
  };
2787
1709
  this.routes.push(routeInfo);
2788
1710
  }
@@ -2833,10 +1755,29 @@ var LogStore = class {
2833
1755
  }
2834
1756
  };
2835
1757
  var logStore = new LogStore();
1758
+ var HttpHistoryStore = class {
1759
+ entries = /* @__PURE__ */ new Map();
1760
+ maxPerRoute = 50;
1761
+ add(entry) {
1762
+ const key = `${entry.method} ${entry.routeUrl}`;
1763
+ const existing = this.entries.get(key) ?? [];
1764
+ existing.unshift(entry);
1765
+ if (existing.length > this.maxPerRoute) {
1766
+ existing.length = this.maxPerRoute;
1767
+ }
1768
+ this.entries.set(key, existing);
1769
+ }
1770
+ get(method, routeUrl) {
1771
+ return this.entries.get(`${method.toUpperCase()} ${routeUrl}`) ?? [];
1772
+ }
1773
+ clear() {
1774
+ this.entries.clear();
1775
+ }
1776
+ };
1777
+ var httpHistoryStore = new HttpHistoryStore();
2836
1778
  async function devConsolePluginFunction(fastify, options = {}) {
2837
1779
  const { prefix = "console", enableUI = true } = options;
2838
- const upstream = getWriter();
2839
- setWriter((entry) => {
1780
+ const unsubscribe = fastify.logging.subscribeToLogs((entry) => {
2840
1781
  logStore.addLog({
2841
1782
  id: Date.now().toString() + Math.random().toString(36).slice(2, 9),
2842
1783
  timestamp: new Date(entry.time),
@@ -2846,11 +1787,11 @@ async function devConsolePluginFunction(fastify, options = {}) {
2846
1787
  module: entry.module,
2847
1788
  reqId: entry.reqId
2848
1789
  });
2849
- upstream(entry);
2850
1790
  });
2851
1791
  fastify.addHook("onClose", (_instance, done) => {
2852
- setWriter(upstream);
1792
+ unsubscribe();
2853
1793
  logStore.clear();
1794
+ httpHistoryStore.clear();
2854
1795
  done();
2855
1796
  });
2856
1797
  fastify.addHook("onRoute", (routeOptions) => {
@@ -2858,6 +1799,29 @@ async function devConsolePluginFunction(fastify, options = {}) {
2858
1799
  routeRegistry.register(routeOptions);
2859
1800
  }
2860
1801
  });
1802
+ fastify.addHook("onRequest", (request, _reply, done) => {
1803
+ request._historyStart = Date.now();
1804
+ done();
1805
+ });
1806
+ fastify.addHook("onResponse", (request, reply, done) => {
1807
+ const routeUrl = request.routeOptions.url;
1808
+ if (routeUrl?.startsWith("/api")) {
1809
+ const start = request._historyStart;
1810
+ httpHistoryStore.add({
1811
+ id: Date.now().toString() + Math.random().toString(36).slice(2, 9),
1812
+ method: request.method,
1813
+ routeUrl,
1814
+ url: request.url,
1815
+ statusCode: reply.statusCode,
1816
+ duration: start ? Date.now() - start : 0,
1817
+ reqId: request.id,
1818
+ requestHeaders: request.headers,
1819
+ requestBody: request.body,
1820
+ timestamp: /* @__PURE__ */ new Date()
1821
+ });
1822
+ }
1823
+ done();
1824
+ });
2861
1825
  fastify.get(`/${prefix}/routes`, async () => {
2862
1826
  return { routes: routeRegistry.getAll() };
2863
1827
  });
@@ -2865,6 +1829,13 @@ async function devConsolePluginFunction(fastify, options = {}) {
2865
1829
  const { method } = request.params;
2866
1830
  return { routes: routeRegistry.getByMethod(method) };
2867
1831
  });
1832
+ fastify.get(`/${prefix}/routes/history`, async (request, reply) => {
1833
+ const { method, url } = request.query;
1834
+ if (!method || !url) {
1835
+ return reply.status(400).send({ error: "method and url query params are required" });
1836
+ }
1837
+ return { history: httpHistoryStore.get(method, url) };
1838
+ });
2868
1839
  fastify.get(`/${prefix}/registry/stats`, async () => {
2869
1840
  return {
2870
1841
  totalRoutes: routeRegistry.count(),
@@ -2981,6 +1952,17 @@ async function devConsolePluginFunction(fastify, options = {}) {
2981
1952
  }
2982
1953
  };
2983
1954
  });
1955
+ fastify.get(`/${prefix}/queues/:queueName/history`, async (request, reply) => {
1956
+ const { queueName } = request.params;
1957
+ const queueManager = fastify.queues;
1958
+ if (!queueManager) {
1959
+ return reply.status(503).send({ error: "Queue service not available" });
1960
+ }
1961
+ if (!queueManager.getQueueStats(queueName)) {
1962
+ return reply.status(404).send({ error: "Queue not found" });
1963
+ }
1964
+ return { history: queueManager.getMessageHistory(queueName) };
1965
+ });
2984
1966
  fastify.get(`/${prefix}/queues/:queueName/process-dlq`, async (request, reply) => {
2985
1967
  const { queueName } = request.params;
2986
1968
  const queueManager = fastify.queues;
@@ -3110,6 +2092,17 @@ async function devConsolePluginFunction(fastify, options = {}) {
3110
2092
  timerId: jobStats.timerId ? "scheduled" : "not_scheduled"
3111
2093
  };
3112
2094
  });
2095
+ fastify.get(`/${prefix}/schedules/:jobName/executions`, async (request, reply) => {
2096
+ const { jobName } = request.params;
2097
+ const schedulerManager = fastify.scheduler;
2098
+ if (!schedulerManager) {
2099
+ return reply.status(503).send({ error: "Scheduler service not available" });
2100
+ }
2101
+ if (!schedulerManager.getJobStats(jobName)) {
2102
+ return reply.status(404).send({ error: "Job not found" });
2103
+ }
2104
+ return { executions: schedulerManager.getJobExecutionHistory(jobName) };
2105
+ });
3113
2106
  fastify.post(`/${prefix}/schedules/:jobName/trigger`, async (request, reply) => {
3114
2107
  const { jobName } = request.params;
3115
2108
  const schedulerManager = fastify.scheduler;
@@ -3222,25 +2215,37 @@ async function devConsolePluginFunction(fastify, options = {}) {
3222
2215
  await wsManager.sendToClient(connectionId, request.body);
3223
2216
  return reply.status(200).send({ status: "success", message: `Message sent to ${connectionId}` });
3224
2217
  } catch (error) {
2218
+ if (error instanceof WebSocketConnectionGoneError2) {
2219
+ return reply.status(410).send({ error: error.message });
2220
+ }
3225
2221
  return reply.status(404).send({ error: error.message });
3226
2222
  }
3227
2223
  });
3228
2224
  fastify.get(`/${prefix}/logs/stream`, async (request, reply) => {
2225
+ const { level, module, reqId } = request.query;
2226
+ const matchesFilters = (log) => {
2227
+ if (level && log.levelName !== level) return false;
2228
+ if (module && log.module !== module) return false;
2229
+ if (reqId && log.reqId !== reqId) return false;
2230
+ return true;
2231
+ };
3229
2232
  reply.type("text/event-stream");
3230
2233
  reply.header("Cache-Control", "no-cache");
3231
2234
  reply.header("Connection", "keep-alive");
3232
2235
  reply.header("Access-Control-Allow-Origin", "*");
3233
- const initialLogs = logStore.getLogs(50);
2236
+ const initialLogs = logStore.getLogs(50).filter(matchesFilters);
3234
2237
  reply.raw.write(`data: ${JSON.stringify({ type: "initial", logs: initialLogs })}
3235
2238
 
3236
2239
  `);
3237
- const unsubscribe = logStore.subscribe((log) => {
3238
- reply.raw.write(`data: ${JSON.stringify({ type: "log", log })}
2240
+ const unsubscribe2 = logStore.subscribe((log) => {
2241
+ if (matchesFilters(log)) {
2242
+ reply.raw.write(`data: ${JSON.stringify({ type: "log", log })}
3239
2243
 
3240
2244
  `);
2245
+ }
3241
2246
  });
3242
2247
  request.raw.on("close", () => {
3243
- unsubscribe();
2248
+ unsubscribe2();
3244
2249
  });
3245
2250
  });
3246
2251
  if (enableUI) {
@@ -3264,17 +2269,4 @@ export {
3264
2269
  devConsolePlugin,
3265
2270
  routeRegistry
3266
2271
  };
3267
- /*! Bundled license information:
3268
-
3269
- lodash-es/lodash.js:
3270
- (**
3271
- * @license
3272
- * Lodash (Custom Build) <https://lodash.com/>
3273
- * Build: `lodash modularize exports="es" -o ./`
3274
- * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
3275
- * Released under MIT license <https://lodash.com/license>
3276
- * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
3277
- * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
3278
- *)
3279
- */
3280
2272
  //# sourceMappingURL=index.js.map