@cloudnux/local-cloud-provider 0.18.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,1009 +1,3 @@
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";
@@ -1015,6 +9,7 @@ import { WebSocketConnectionGoneError as WebSocketConnectionGoneError2 } from "@
1015
9
  import fsPlugin from "fastify-plugin";
1016
10
 
1017
11
  // src/queue-plugin/core.ts
12
+ var MAX_MESSAGE_HISTORY = 500;
1018
13
  var DEFAULT_CONFIG = {
1019
14
  batchSize: 10,
1020
15
  batchWindowMs: 500,
@@ -1022,12 +17,12 @@ var DEFAULT_CONFIG = {
1022
17
  parallel: true,
1023
18
  maxConcurrent: 5,
1024
19
  retryBackoff: true,
20
+ tickIntervalMs: 50,
1025
21
  persistence: {
1026
22
  enabled: true,
1027
23
  directory: "./.develop/queue-data",
1028
24
  saveInterval: 6e4,
1029
- saveOnShutdown: false,
1030
- loadOnStartup: true
25
+ saveOnShutdown: false
1031
26
  }
1032
27
  };
1033
28
  var mergeConfig = (defaultConfig, userConfig) => ({
@@ -1043,7 +38,6 @@ var createQueueService = (handler, module) => ({
1043
38
  incoming: [],
1044
39
  processing: [],
1045
40
  dlq: [],
1046
- timeoutId: null,
1047
41
  processingBatch: false,
1048
42
  module
1049
43
  });
@@ -1062,6 +56,12 @@ var createQueueMessage = (body, headers) => {
1062
56
  }
1063
57
  return message;
1064
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
+ };
1065
65
  var isMessageReady = (message, now = Date.now()) => {
1066
66
  return !message.nextAttempt || message.nextAttempt.getTime() <= now;
1067
67
  };
@@ -1079,11 +79,6 @@ var moveToProcessing = (queueService, batchSize) => {
1079
79
  queueService.incoming = remaining;
1080
80
  return ready;
1081
81
  };
1082
- var getNextReadyDelay = (queueService) => {
1083
- const nextAttempts = queueService.incoming.filter((message) => message.nextAttempt).map((message) => message.nextAttempt.getTime());
1084
- if (nextAttempts.length === 0) return null;
1085
- return Math.max(0, Math.min(...nextAttempts) - Date.now());
1086
- };
1087
82
  var removeFromProcessing = (queueService, messageId) => {
1088
83
  queueService.processing = queueService.processing.filter((msg) => msg.id !== messageId);
1089
84
  };
@@ -1095,21 +90,9 @@ var moveToDLQ = (queueService, message, error) => {
1095
90
  });
1096
91
  removeFromProcessing(queueService, message.id);
1097
92
  };
1098
- var incrementAttempts = (queueService, messageId) => {
1099
- const index = queueService.processing.findIndex((m) => m.id === messageId);
1100
- if (index >= 0) {
1101
- queueService.processing[index].attempts += 1;
1102
- return queueService.processing[index];
1103
- }
1104
- return null;
1105
- };
1106
93
  var calculateBackoffDelay = (attempts) => {
1107
94
  return Math.pow(2, attempts) * 100;
1108
95
  };
1109
- var setNextAttemptTime = (message, delayMs) => ({
1110
- ...message,
1111
- nextAttempt: new Date(Date.now() + delayMs)
1112
- });
1113
96
  var createQueueSummary = (queueService, config) => ({
1114
97
  incoming: queueService.incoming.length,
1115
98
  processing: queueService.processing.length,
@@ -1149,93 +132,21 @@ var purgeDLQ = (queueService) => {
1149
132
  queueService.dlq = [];
1150
133
  return dlqCount;
1151
134
  };
1152
- var logSuccess = (message, messageId, queueName) => {
1153
- logger.debug(`Success: ${message} ${messageId} in queue ${queueName}`);
1154
- };
1155
- var logError = (message, messageId, queueName, error) => {
1156
- logger.error(`Error: ${message} ${messageId} in queue ${queueName}: ${error}`);
1157
- };
1158
- var logRetryScheduled = (messageId, delayMs) => {
1159
- logger.debug(`Scheduling retry for message ${messageId} in ${delayMs}ms`);
1160
- };
1161
- var logDLQOperation = (operation, count, queueName) => {
1162
- logger.warn(`${operation} ${count} messages from DLQ for ${queueName}`);
1163
- };
1164
-
1165
- // src/queue-plugin/processing.ts
1166
- var createProcessMessageHandler = (config) => async (queueName, message, queueService) => {
1167
- try {
1168
- const result = await queueService.handler(message);
1169
- if (result?.failureId) {
1170
- await handleProcessingError(queueName, message, queueService, config);
1171
- } else {
1172
- removeFromProcessing(queueService, message.id);
1173
- logSuccess("Successfully processed message", message.id, queueName);
1174
- }
1175
- } catch {
1176
- await handleProcessingError(queueName, message, queueService, config);
1177
- }
1178
- };
1179
- var handleProcessingError = async (queueName, message, queueService, config) => {
1180
- logError("Error processing message", message.id, queueName, "handler returned failure");
1181
- if (message.attempts >= config.maxRetries) {
1182
- moveToDLQ(queueService, message, "max retries exceeded");
1183
- return;
1184
- }
1185
- const updatedMessage = incrementAttempts(queueService, message.id);
1186
- if (!updatedMessage) return;
1187
- if (config.retryBackoff) {
1188
- const delayMs = calculateBackoffDelay(updatedMessage.attempts);
1189
- const messageWithNextAttempt = setNextAttemptTime(updatedMessage, delayMs);
1190
- logRetryScheduled(message.id, delayMs);
1191
- setTimeout(() => {
1192
- createProcessMessageHandler(config)(queueName, messageWithNextAttempt, queueService);
1193
- }, delayMs);
1194
- }
1195
- };
1196
- var createBatchProcessor = (processMessage, config, dirtyQueues, schedulerRef) => async (queueName, queueService) => {
1197
- if (queueService.timeoutId) {
1198
- clearTimeout(queueService.timeoutId);
1199
- queueService.timeoutId = null;
1200
- }
1201
- if (queueService.processingBatch) {
1202
- return;
1203
- }
1204
- try {
1205
- queueService.processingBatch = true;
1206
- const messagesToProcess = moveToProcessing(queueService, config.batchSize);
1207
- if (messagesToProcess.length > 0) {
1208
- queueService.processing.push(...messagesToProcess);
1209
- await Promise.all(messagesToProcess.map((message) => {
1210
- return processMessage(queueName, message, queueService);
1211
- }));
1212
- if (config.persistence.enabled && dirtyQueues) {
1213
- dirtyQueues.add(queueName);
1214
- }
1215
- }
1216
- } finally {
1217
- queueService.processingBatch = false;
1218
- const nextReadyDelay = getNextReadyDelay(queueService);
1219
- if (nextReadyDelay !== null) {
1220
- schedulerRef?.current?.(queueName, queueService, nextReadyDelay);
1221
- }
1222
- }
1223
- };
1224
- var createProcessingScheduler = (processBatch, config) => (queueName, queueService, overrideDelay = null) => {
1225
- if (!queueService.timeoutId) {
1226
- const delay = overrideDelay ?? config.batchWindowMs;
1227
- queueService.timeoutId = setTimeout(() => {
1228
- processBatch(queueName, queueService);
1229
- }, delay);
1230
- }
1231
- };
1232
- var handleImmediateProcessing = (queueService, config, processBatch, queueName) => {
1233
- if (queueService.incoming.length >= config.batchSize) {
1234
- if (queueService.timeoutId) {
1235
- clearTimeout(queueService.timeoutId);
1236
- queueService.timeoutId = null;
1237
- }
1238
- 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);
1239
150
  }
1240
151
  };
1241
152
 
@@ -1255,123 +166,358 @@ var restoreDates = (messages) => {
1255
166
  if (msg.failedAt) msg.failedAt = new Date(msg.failedAt);
1256
167
  });
1257
168
  };
1258
- var createPersistenceInitializer = (config, loadAllQueueStates, saveDirtyQueueStates, saveAllQueueStates, intervalIdHolder) => async () => {
1259
- try {
1260
- await fs.mkdir(config.persistence.directory, { recursive: true });
1261
- if (config.persistence.loadOnStartup) {
1262
- await loadAllQueueStates();
1263
- }
1264
- if (config.persistence.saveInterval && config.persistence.saveInterval > 0) {
1265
- intervalIdHolder.id = setInterval(saveDirtyQueueStates, config.persistence.saveInterval);
1266
- }
1267
- if (config.persistence.saveOnShutdown) {
1268
- const shutdownHandler = async () => {
1269
- logger.debug("Saving queue state before shutdown...");
1270
- if (intervalIdHolder.id) {
1271
- clearInterval(intervalIdHolder.id);
1272
- }
1273
- await saveAllQueueStates();
1274
- process.exit(0);
1275
- };
1276
- process.on("SIGINT", shutdownHandler);
1277
- process.on("SIGTERM", shutdownHandler);
1278
- }
1279
- logger.debug(`Queue persistence initialized: ${config.persistence.directory}`);
1280
- } catch (error) {
1281
- logger.error(`Failed to initialize queue persistence: ${error?.message}`);
1282
- }
1283
- };
1284
- var createQueueStateSaver = (config) => async (queueName, queueService) => {
169
+ var saveQueueState = async (runtime, queueName, queueService) => {
1285
170
  try {
1286
- if (!queueService) return;
1287
171
  const queueData = createQueueData(queueService);
1288
172
  const now = Date.now();
1289
- const queueFilePath = path.join(config.persistence.directory, `${queueName}.json`);
1290
- const tempFilePath = path.join(config.persistence.directory, `${queueName}.${now}temp.json`);
1291
- await fs.writeFile(tempFilePath, JSON.stringify(queueData, null, 2), "utf8");
1292
- await fs.rename(tempFilePath, queueFilePath);
1293
- 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}`);
1294
178
  } catch (error) {
1295
- 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
+ );
1296
183
  }
1297
184
  };
1298
- var createAllQueuesStateSaver = (config, saveQueueState) => (queues) => async () => {
185
+ var saveAllQueueStates = async (runtime) => {
1299
186
  try {
1300
- for (const [queueName, queueService] of Object.entries(queues)) {
1301
- await saveQueueState(queueName, queueService);
187
+ for (const [queueName, queueService] of Object.entries(runtime.queues)) {
188
+ await saveQueueState(runtime, queueName, queueService);
1302
189
  }
1303
- logger.debug(`All queue states saved to ${config.persistence.directory}`);
190
+ runtime.logging.debug(`All queue states saved to ${runtime.config.persistence.directory}`);
1304
191
  } catch (error) {
1305
- logger.error(`Failed to save all queue states: ${error?.message}`);
192
+ runtime.logging.error({ error: error?.message }, `Failed to save all queue states`);
1306
193
  }
1307
194
  };
1308
- var createDirtyQueuesStateSaver = (saveQueueState, dirtyQueues) => (queues) => async () => {
1309
- if (dirtyQueues.size === 0) return;
195
+ var saveDirtyQueueStates = async (runtime) => {
196
+ if (runtime.dirtyQueues.size === 0) return;
1310
197
  try {
1311
- const queueNames = [...dirtyQueues];
1312
- dirtyQueues.clear();
198
+ const queueNames = [...runtime.dirtyQueues];
199
+ runtime.dirtyQueues.clear();
1313
200
  for (const queueName of queueNames) {
1314
- if (queues[queueName]) {
1315
- await saveQueueState(queueName, queues[queueName]);
201
+ if (runtime.queues[queueName]) {
202
+ await saveQueueState(runtime, queueName, runtime.queues[queueName]);
1316
203
  }
1317
204
  }
1318
205
  } catch (error) {
1319
- logger.error(`Failed to save dirty queue states: ${error?.message}`);
206
+ runtime.logging.error({ error: error?.message }, `Failed to save dirty queue states`);
1320
207
  }
1321
208
  };
1322
- var createQueueStateLoader = (config, scheduleProcessing, processMessage) => (queues) => async (queueName) => {
209
+ var loadQueueState = async (runtime, queueName) => {
1323
210
  try {
1324
- if (!queues[queueName]) {
1325
- 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}`);
1326
214
  return;
1327
215
  }
1328
- const queueFilePath = path.join(config.persistence.directory, `${queueName}.json`);
216
+ const queueFilePath = path.join(runtime.config.persistence.directory, `${queueName}.json`);
1329
217
  try {
1330
218
  const data = await fs.readFile(queueFilePath, "utf8");
1331
219
  const queueData = JSON.parse(data);
1332
220
  restoreDates(queueData.incoming);
1333
221
  restoreDates(queueData.processing);
1334
222
  restoreDates(queueData.dlq);
1335
- queues[queueName].incoming = queueData.incoming;
1336
- queues[queueName].processing = queueData.processing;
1337
- queues[queueName].dlq = queueData.dlq;
1338
- logger.debug(`Queue state loaded: ${queueName} (saved at ${queueData.savedAt})`);
1339
- if (queues[queueName].incoming.length > 0) {
1340
- scheduleProcessing(queueName, queues[queueName]);
1341
- }
1342
- if (queues[queueName].processing.length > 0) {
1343
- for (const message of [...queues[queueName].processing]) {
1344
- if (message.attempts >= config.maxRetries) continue;
1345
- setTimeout(() => {
1346
- processMessage(queueName, message, queues[queueName]);
1347
- }, 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);
1348
232
  }
1349
233
  }
234
+ runtime.logging.debug(
235
+ { module: queueService.module },
236
+ `Queue state loaded: ${queueName} (saved at ${queueData.savedAt})`
237
+ );
1350
238
  } catch (error) {
1351
239
  if (error.code === "ENOENT") {
1352
- logger.debug(`No saved state found for queue: ${queueName}`);
240
+ runtime.logging.debug({ module: queueService.module }, `No saved state found for queue: ${queueName}`);
1353
241
  } else {
1354
242
  throw error;
1355
243
  }
1356
244
  }
1357
245
  } catch (error) {
1358
- 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}`);
1359
247
  }
1360
248
  };
1361
- var createAllQueuesStateLoader = (config, loadQueueState) => async () => {
249
+ var initializePersistence = async (runtime) => {
1362
250
  try {
1363
- const files = await fs.readdir(config.persistence.directory);
1364
- const queueFiles = files.filter((file) => file.endsWith(".json") && !file.includes(".temp."));
1365
- for (const file of queueFiles) {
1366
- const queueName = path.basename(file, ".json");
1367
- 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);
1368
260
  }
1369
- logger.debug("All queue states loaded");
261
+ runtime.logging.debug(`Queue persistence initialized: ${runtime.config.persistence.directory}`);
1370
262
  } catch (error) {
1371
- logger.error("Failed to load queue states:", error?.message);
263
+ runtime.logging.error({ error: error?.message }, `Failed to initialize queue persistence`);
1372
264
  }
1373
265
  };
1374
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
+
1375
521
  // src/queue-plugin/routes.ts
1376
522
  var registerQueueRoutes = (app, prefix = "") => {
1377
523
  app.get(
@@ -1408,270 +554,45 @@ var registerQueueRoutes = (app, prefix = "") => {
1408
554
  async (request, reply) => {
1409
555
  const queueName = request.params.queue;
1410
556
  const result = await app.queues.processDlq(queueName);
1411
- if (result === null) {
1412
- return reply.status(404).send(result);
1413
- }
1414
- return reply.status(200).send(result);
1415
- }
1416
- );
1417
- app.post(
1418
- `/${prefix}/:queue/purge-dlq`,
1419
- async (request, reply) => {
1420
- const queueName = request.params.queue;
1421
- const result = await app.queues.purgeDlq(queueName);
1422
- if (result === null) {
1423
- return reply.status(404).send({ error: "Queue not found" });
1424
- }
1425
- return reply.status(200).send(result);
1426
- }
1427
- );
1428
- };
1429
-
1430
- // src/queue-plugin/decorator.ts
1431
- var isValidQueueName = (queueName) => {
1432
- return typeof queueName === "string" && queueName.length > 0 && /^[a-zA-Z0-9_-]+$/.test(queueName);
1433
- };
1434
- var createQueueManager = ({
1435
- config,
1436
- queues,
1437
- dirtyQueues,
1438
- saveQueueState,
1439
- loadQueueState,
1440
- scheduleProcessing,
1441
- processBatch
1442
- }) => {
1443
- const validateQueueName = (queueName) => {
1444
- if (!queueName || typeof queueName !== "string") {
1445
- throw new Error("Queue name must be a non-empty string");
1446
- }
1447
- if (!isValidQueueName(queueName)) {
1448
- throw new Error("Queue name must contain only alphanumeric characters, hyphens, and underscores");
1449
- }
1450
- };
1451
- const validateHandler = (handler) => {
1452
- if (typeof handler !== "function") {
1453
- throw new Error("Handler must be a function");
1454
- }
1455
- };
1456
- const addQueue = async (queueName, handler, module) => {
1457
- try {
1458
- validateQueueName(queueName);
1459
- validateHandler(handler);
1460
- if (queues[queueName]) {
1461
- logger.warn(`Queue already exists: ${queueName}.`);
1462
- return;
1463
- }
1464
- queues[queueName] = createQueueService(handler, module);
1465
- if (config.persistence.enabled && loadQueueState) {
1466
- await loadQueueState(queueName);
1467
- }
1468
- logger.info(`Queue added: ${queueName}`);
1469
- } catch (error) {
1470
- logger.error(`Failed to add queue ${queueName}: ${error.message}`);
1471
- throw error;
1472
- }
1473
- };
1474
- const removeQueue = async (queueName) => {
1475
- try {
1476
- validateQueueName(queueName);
1477
- if (!queues[queueName]) {
1478
- throw new Error(`Queue '${queueName}' does not exist`);
1479
- }
1480
- const queueService = queues[queueName];
1481
- const totalMessages = queueService.incoming.length + queueService.processing.length;
1482
- if (totalMessages > 0) {
1483
- logger.warn(`Removing queue with ${totalMessages} pending messages: ${queueName}`);
1484
- }
1485
- if (queueService.timeoutId) {
1486
- clearTimeout(queueService.timeoutId);
1487
- }
1488
- if (config.persistence.enabled && saveQueueState) {
1489
- await saveQueueState(queueName, queueService);
1490
- }
1491
- delete queues[queueName];
1492
- logger.info(`Queue removed: ${queueName}`);
1493
- } catch (error) {
1494
- logger.error(`Failed to remove queue ${queueName}: ${error.message}`);
1495
- throw error;
1496
- }
1497
- };
1498
- const hasQueue = (queueName) => {
1499
- try {
1500
- validateQueueName(queueName);
1501
- return queueName in queues;
1502
- } catch {
1503
- return false;
1504
- }
1505
- };
1506
- const listQueues = (module) => {
1507
- return Object.keys(queues).filter((queueName) => {
1508
- const queueService = queues[queueName];
1509
- return !module || queueService.module === module;
1510
- }).sort();
1511
- };
1512
- const getQueueStats = (queueName) => {
1513
- try {
1514
- validateQueueName(queueName);
1515
- return queues[queueName] || null;
1516
- } catch {
1517
- return null;
1518
- }
1519
- };
1520
- const getQueuesMap = () => {
1521
- return { ...queues };
1522
- };
1523
- const getConfig = () => config;
1524
- const getDashboardSummary = () => createDashboardSummary(queues, config);
1525
- const getQueueSummary = (queueName) => {
1526
- if (!queues[queueName]) {
1527
- return null;
1528
- }
1529
- const queue = queues[queueName];
1530
- const stats = createQueueSummary(queue, config);
1531
- return {
1532
- stats,
1533
- messages: {
1534
- incoming: queue.incoming,
1535
- processing: queue.processing,
1536
- dlq: queue.dlq
1537
- }
1538
- };
1539
- };
1540
- const enqueueMessage = async (queueName, body, attributes) => {
1541
- if (!queues[queueName]) {
1542
- return null;
1543
- }
1544
- const message = createQueueMessage(body, attributes);
1545
- queues[queueName].incoming.push(message);
1546
- scheduleProcessing(queueName, queues[queueName]);
1547
- handleImmediateProcessing(queues[queueName], config, processBatch, queueName);
1548
- if (config.persistence.enabled) {
1549
- dirtyQueues.add(queueName);
1550
- }
1551
- return {
1552
- id: message.id,
1553
- queueName
1554
- };
1555
- };
1556
- const processDlq = async (queueName) => {
1557
- if (!queues[queueName]) {
1558
- return null;
1559
- }
1560
- const processedCount = moveDLQToIncoming(queues[queueName]);
1561
- if (processedCount === 0) {
1562
- return {
1563
- status: "success",
1564
- message: "No messages in DLQ to process",
1565
- processed: 0
1566
- };
1567
- }
1568
- logDLQOperation("Moving", processedCount, queueName);
1569
- scheduleProcessing(queueName, queues[queueName]);
1570
- if (config.persistence.enabled) {
1571
- dirtyQueues.add(queueName);
1572
- }
1573
- return {
1574
- status: "success",
1575
- message: `Moved ${processedCount} messages from DLQ to processing queue`,
1576
- processed: processedCount
1577
- };
1578
- };
1579
- const purgeDlq = async (queueName) => {
1580
- if (!queues[queueName]) {
1581
- return null;
1582
- }
1583
- const purgedCount = purgeDLQ(queues[queueName]);
1584
- if (purgedCount === 0) {
1585
- return {
1586
- status: "success",
1587
- message: "No messages in DLQ to purge",
1588
- purged: 0
1589
- };
557
+ if (result === null) {
558
+ return reply.status(404).send(result);
559
+ }
560
+ return reply.status(200).send(result);
1590
561
  }
1591
- logDLQOperation("Purging", purgedCount, queueName);
1592
- if (config.persistence.enabled) {
1593
- 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);
1594
572
  }
1595
- return {
1596
- status: "success",
1597
- message: `Purged ${purgedCount} messages from DLQ`,
1598
- purged: purgedCount
1599
- };
1600
- };
1601
- return {
1602
- addQueue,
1603
- removeQueue,
1604
- hasQueue,
1605
- listQueues,
1606
- getQueueStats,
1607
- getQueuesMap,
1608
- getConfig,
1609
- getDashboardSummary,
1610
- getQueueSummary,
1611
- enqueueMessage,
1612
- processDlq,
1613
- purgeDlq
1614
- };
1615
- };
1616
- var createQueueDecorator = (queueManager) => (app) => {
1617
- app.decorate("queues", queueManager);
573
+ );
1618
574
  };
1619
575
 
1620
576
  // src/queue-plugin/plugin.ts
1621
577
  var queuesPlugin = fsPlugin(async (app, options) => {
1622
- const config = mergeConfig(DEFAULT_CONFIG, options.config);
1623
- const queues = {};
1624
- const dirtyQueues = /* @__PURE__ */ new Set();
1625
- const intervalIdHolder = {};
1626
- const processMessage = createProcessMessageHandler(config);
1627
- const saveQueueState = config.persistence.enabled ? createQueueStateSaver(config) : void 0;
1628
- const saveAllQueueStates = config.persistence.enabled && saveQueueState ? createAllQueuesStateSaver(config, saveQueueState)(queues) : async () => {
1629
- };
1630
- const saveDirtyQueueStates = config.persistence.enabled && saveQueueState ? createDirtyQueuesStateSaver(saveQueueState, dirtyQueues)(queues) : async () => {
1631
- };
1632
- const schedulerRef = {};
1633
- const processBatch = createBatchProcessor(
1634
- processMessage,
1635
- config,
1636
- config.persistence.enabled ? dirtyQueues : void 0,
1637
- schedulerRef
1638
- );
1639
- const scheduleProcessing = createProcessingScheduler(processBatch, config);
1640
- schedulerRef.current = scheduleProcessing;
1641
- const loadQueueState = config.persistence.enabled ? createQueueStateLoader(
1642
- config,
1643
- scheduleProcessing,
1644
- processMessage
1645
- )(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
1646
585
  };
1647
- const loadAllQueueStates = config.persistence.enabled ? createAllQueuesStateLoader(config, loadQueueState) : async () => {
1648
- };
1649
- const initializePersistence = config.persistence.enabled ? createPersistenceInitializer(config, loadAllQueueStates, saveDirtyQueueStates, saveAllQueueStates, intervalIdHolder) : async () => {
1650
- };
1651
- const queueManager = createQueueManager({
1652
- config,
1653
- queues,
1654
- dirtyQueues,
1655
- saveQueueState: saveQueueState ? (queueName) => saveQueueState(queueName, queues[queueName]) : void 0,
1656
- loadQueueState,
1657
- scheduleProcessing,
1658
- processBatch
1659
- });
1660
- const decorateQueues = createQueueDecorator(queueManager);
1661
- decorateQueues(app);
1662
- registerQueueRoutes(
1663
- app,
1664
- options.prefix
1665
- );
1666
- 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);
1667
591
  });
1668
592
 
1669
593
  // src/schedule-plugin/plugin.ts
1670
594
  import fsPlugin2 from "fastify-plugin";
1671
595
 
1672
- // src/schedule-plugin/core.ts
1673
- import * as fs3 from "fs/promises";
1674
-
1675
596
  // src/schedule-plugin/cron-utils.ts
1676
597
  import { parseExpression } from "cron-parser";
1677
598
  function parseCronExpression(cronExpression, lastRun, options = {}) {
@@ -1704,7 +625,6 @@ function parseCronExpression(cronExpression, lastRun, options = {}) {
1704
625
  description: describeCronExpression(normalizedCron)
1705
626
  };
1706
627
  } catch (error) {
1707
- logger.error(`Invalid cron expression: ${cronExpression} - ${error.message}`);
1708
628
  const fallbackTime = /* @__PURE__ */ new Date();
1709
629
  fallbackTime.setHours(fallbackTime.getHours() + 1);
1710
630
  fallbackTime.setMinutes(0);
@@ -1732,8 +652,7 @@ function getNextExecutions(cronExpression, count = 5, options = {}) {
1732
652
  executions.push(interval.next().toDate());
1733
653
  }
1734
654
  return executions;
1735
- } catch (error) {
1736
- logger.error(`Error getting next executions: ${error.message}`);
655
+ } catch {
1737
656
  return [];
1738
657
  }
1739
658
  }
@@ -2006,9 +925,111 @@ var shouldSkipJob = (job) => {
2006
925
  if (job.maxRuns && job.runCount >= job.maxRuns) return true;
2007
926
  return false;
2008
927
  };
928
+ var isJobReadyToRun = (job) => {
929
+ const now = /* @__PURE__ */ new Date();
930
+ return job.nextRun.getTime() <= now.getTime();
931
+ };
2009
932
  var getTimeUntilNextRun = (job) => Math.max(0, job.nextRun.getTime() - Date.now());
2010
933
 
2011
- // 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
+ });
2012
1033
  var createJobExecution = (jobId) => ({
2013
1034
  id: generateExecutionId(),
2014
1035
  jobId,
@@ -2022,655 +1043,516 @@ var completeExecution = (execution, result, error) => ({
2022
1043
  result,
2023
1044
  error
2024
1045
  });
2025
- var canExecuteJob = (scheduler, state) => {
2026
- if (state.isShuttingDown) {
2027
- return { canExecute: false, reason: "System is shutting down" };
2028
- }
2029
- if (scheduler.isRunning) {
2030
- return { canExecute: false, reason: "Job is already running" };
2031
- }
2032
- if (shouldSkipJob(scheduler.job)) {
2033
- const reason = !scheduler.job.enabled ? "Job is disabled" : "Job reached maximum runs";
2034
- return { canExecute: false, reason };
2035
- }
2036
- if (state.runningExecutions >= state.config.execution.maxConcurrent) {
2037
- return { canExecute: false, reason: "Maximum concurrent executions reached" };
2038
- }
2039
- return { canExecute: true };
2040
- };
2041
- var scheduleJob = (scheduler, state, executeJobFn) => {
2042
- if (state.isShuttingDown) return scheduler;
2043
- const timeUntilNextRun = getTimeUntilNextRun(scheduler.job);
2044
- const updatedScheduler = { ...scheduler };
2045
- if (timeUntilNextRun <= 0) {
2046
- setImmediate(() => executeJobFn(updatedScheduler));
2047
- } else {
2048
- updatedScheduler.timerId = setTimeout(() => {
2049
- executeJobFn(updatedScheduler);
2050
- }, timeUntilNextRun);
2051
- logger.debug(`Scheduled job ${scheduler.job.name} to run in ${Math.round(timeUntilNextRun / 1e3)}s`);
2052
- }
2053
- return updatedScheduler;
2054
- };
2055
- var executeJobWithTimeout = async (handler, job, execution, timeoutMs) => {
2056
- let timerId;
2057
- try {
2058
- return await Promise.race([
2059
- handler(job, execution),
2060
- new Promise((_, reject) => {
2061
- timerId = setTimeout(() => reject(new Error("Job execution timeout")), timeoutMs);
2062
- })
2063
- ]);
2064
- } finally {
2065
- clearTimeout(timerId);
2066
- }
2067
- };
2068
1046
  var updateJobAfterExecution = (job, execution, config) => {
2069
1047
  const updatedJob = {
2070
1048
  ...job,
2071
1049
  lastRun: execution.startTime,
2072
1050
  runCount: job.runCount + 1
2073
1051
  };
2074
- if (job.enabled) {
1052
+ if (updatedJob.enabled) {
2075
1053
  updatedJob.nextRun = calculateNextRunFromLastRun(updatedJob, updatedJob.lastRun, config);
2076
1054
  }
2077
1055
  return updatedJob;
2078
1056
  };
2079
- var handleExecutionError = (execution, error) => {
2080
- logger.error(`Job failed ${execution.jobId}: ${error.message}`);
2081
- return completeExecution(execution, void 0, error.message);
2082
- };
2083
- var handleExecutionSuccess = (execution, result) => {
2084
- const duration = execution.endTime ? execution.endTime.getTime() - execution.startTime.getTime() : 0;
2085
- logger.debug(`Job completed in ${duration}ms`);
2086
- return completeExecution(execution, result);
2087
- };
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
+ });
2088
1069
 
2089
1070
  // src/schedule-plugin/persistence.ts
2090
1071
  import * as fs2 from "fs/promises";
2091
1072
  import * as path2 from "path";
2092
- var createStateFilePath = (directory) => path2.join(directory, "scheduler-state.json");
2093
- var createTempFilePath = (directory) => path2.join(directory, `scheduler-state.${Date.now()}.temp.json`);
2094
- var serializeSchedulerState = (schedulers, executionHistory) => ({
2095
- jobs: Object.values(schedulers).map((s) => s.job),
2096
- executions: executionHistory.slice(-100),
2097
- savedAt: /* @__PURE__ */ new Date(),
2098
- version: "1.0"
2099
- });
2100
- 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) => {
2101
1085
  try {
2102
- const serializedState = serializeSchedulerState(state.schedulers, state.executionHistory);
2103
- const stateFile = createStateFilePath(state.config.persistence.directory);
2104
- const tempFile = createTempFilePath(state.config.persistence.directory);
2105
- await fs2.writeFile(tempFile, JSON.stringify(serializedState, null, 2), "utf8");
2106
- await fs2.rename(tempFile, stateFile);
2107
- logger.debug("Enhanced scheduler state saved");
2108
- } catch (error) {
2109
- logger.error("Failed to save scheduler state:", error);
2110
- }
2111
- };
2112
- var loadSchedulerStateData = async (directory) => {
2113
- try {
2114
- const stateFile = createStateFilePath(directory);
2115
- const data = await fs2.readFile(stateFile, "utf8");
2116
- 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");
2117
1097
  } catch (error) {
2118
- if (error.code === "ENOENT") {
2119
- logger.debug("No previous scheduler state found - starting fresh");
2120
- } else {
2121
- logger.error("Failed to load scheduler state:", error);
2122
- }
2123
- return null;
1098
+ runtime.logging.error({ error: error?.message }, "Failed to save scheduler state");
2124
1099
  }
2125
1100
  };
2126
- var validateAndAdjustNextRun = (job, savedNextRun, lastRestartTime, config) => {
1101
+ var validateAndAdjustNextRun = (job, savedNextRun, runtime) => {
1102
+ const { config, lastRestartTime, logging } = runtime;
2127
1103
  const now = /* @__PURE__ */ new Date();
2128
- if (!savedNextRun) {
2129
- return calculateNextRunFromLastRun(job, job.lastRun, config);
2130
- }
2131
- if (savedNextRun <= now) {
2132
- 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`);
2133
1106
  return calculateNextRunFromLastRun(job, job.lastRun, config);
2134
1107
  }
2135
- const timeSinceRestart = now.getTime() - lastRestartTime.getTime();
2136
- const isRapidRestart = timeSinceRestart < config.restartBehavior.rapidRestartThreshold;
1108
+ const isRapidRestart = now.getTime() - lastRestartTime.getTime() < config.restartBehavior.rapidRestartThreshold;
2137
1109
  if (isRapidRestart) {
2138
- 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`);
2139
1111
  return savedNextRun;
2140
1112
  }
2141
1113
  if (job.cronExpression || job.intervalMs) {
2142
1114
  const expectedNextRun = calculateNextRunFromLastRun(job, job.lastRun, config);
2143
- const timeDiff = Math.abs(savedNextRun.getTime() - expectedNextRun.getTime());
2144
- if (timeDiff > config.restartBehavior.maxTimingDrift) {
2145
- 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`);
2146
1118
  return expectedNextRun;
2147
1119
  }
2148
1120
  }
2149
1121
  return savedNextRun;
2150
1122
  };
2151
- var restoreJobFromSavedData = (scheduler, savedJob, config, lastRestartTime) => {
2152
- const definitionChanged = hasJobDefinitionChanged(scheduler.job, savedJob);
2153
- const updatedJob = {
2154
- ...scheduler.job,
2155
- runCount: savedJob.runCount,
2156
- lastRun: savedJob.lastRun ? new Date(savedJob.lastRun) : void 0
2157
- };
2158
- if (definitionChanged) {
2159
- logger.debug(`Job definition changed: ${savedJob.name} - recalculating schedule`);
2160
- updatedJob.nextRun = calculateNextRunFromLastRun(updatedJob, updatedJob.lastRun, config);
2161
- } else {
2162
- const savedNextRun = savedJob.nextRun ? new Date(savedJob.nextRun) : void 0;
2163
- updatedJob.nextRun = validateAndAdjustNextRun(updatedJob, savedNextRun, lastRestartTime, config);
2164
- }
2165
- if (config.cron.logCronDetails && updatedJob.cronExpression) {
2166
- const result = parseCronExpression(updatedJob.cronExpression, updatedJob.lastRun, {
2167
- timezone: updatedJob.timezone
2168
- });
2169
- 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;
2170
1163
  }
2171
- return { ...scheduler, job: updatedJob };
2172
- };
2173
- var restoreExecutionHistory = (savedExecutions) => {
2174
- return savedExecutions.map((exec) => ({
2175
- ...exec,
2176
- startTime: new Date(exec.startTime),
2177
- endTime: exec.endTime ? new Date(exec.endTime) : void 0
2178
- }));
2179
1164
  };
2180
- var loadSchedulerState = async (schedulers, config, lastRestartTime) => {
2181
- const stateData = await loadSchedulerStateData(config.persistence.directory);
2182
- if (!stateData) {
2183
- return { schedulers, executionHistory: [] };
2184
- }
2185
- const restoredSchedulers = { ...schedulers };
2186
- for (const savedJob of stateData.jobs) {
2187
- const existingScheduler = Object.values(schedulers).find((s) => s.job.name === savedJob.name);
2188
- if (existingScheduler) {
2189
- const restoredScheduler = restoreJobFromSavedData(
2190
- existingScheduler,
2191
- savedJob,
2192
- config,
2193
- lastRestartTime
2194
- );
2195
- 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");
2196
1174
  }
2197
1175
  }
2198
- const executionHistory = stateData.executions ? restoreExecutionHistory(stateData.executions) : [];
2199
- logger.debug(`Enhanced scheduler state loaded (saved at ${stateData.savedAt})`);
2200
- return { schedulers: restoredSchedulers, executionHistory };
2201
1176
  };
2202
-
2203
- // src/schedule-plugin/cleanup.ts
2204
- var cleanupExecutionHistory = (state) => {
2205
- if (state.executionHistory.length > state.config.cleanup.maxExecutionHistory) {
2206
- const removed = state.executionHistory.splice(
2207
- 0,
2208
- state.executionHistory.length - state.config.cleanup.maxExecutionHistory
2209
- );
2210
- logger.debug(`Cleaned up ${removed.length} old execution records`);
2211
- 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");
2212
1193
  }
2213
- return [];
2214
1194
  };
2215
1195
 
2216
- // src/schedule-plugin/core.ts
2217
- var DEFAULT_CONFIG2 = {
2218
- persistence: {
2219
- enabled: true,
2220
- directory: "./.develop/scheduler-data",
2221
- saveInterval: 3e4
2222
- // 30 seconds
2223
- },
2224
- execution: {
2225
- maxConcurrent: 5,
2226
- defaultTimeout: 3e5,
2227
- // 5 minutes
2228
- retryOnError: false,
2229
- maxRetries: 3
2230
- },
2231
- cleanup: {
2232
- maxExecutionHistory: 1e3,
2233
- cleanupInterval: 6e5
2234
- // 10 minutes
2235
- },
2236
- restartBehavior: {
2237
- preserveNaturalTiming: true,
2238
- skipMissedRuns: true,
2239
- maxTimingDrift: 6e4,
2240
- // 1 minute
2241
- rapidRestartThreshold: 1e4
2242
- // 10 seconds
2243
- },
2244
- cron: {
2245
- defaultTimezone: void 0,
2246
- // Use system timezone
2247
- 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" };
2248
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 };
2249
1212
  };
2250
- var mergeConfig2 = (defaultConfig, userConfig) => ({
2251
- ...defaultConfig,
2252
- ...userConfig,
2253
- persistence: { ...defaultConfig.persistence, ...userConfig?.persistence },
2254
- execution: { ...defaultConfig.execution, ...userConfig?.execution },
2255
- cleanup: { ...defaultConfig.cleanup, ...userConfig?.cleanup },
2256
- restartBehavior: { ...defaultConfig.restartBehavior, ...userConfig?.restartBehavior },
2257
- cron: { ...defaultConfig.cron, ...userConfig?.cron }
2258
- });
2259
- var createInitialState = (config) => {
2260
- return {
2261
- schedulers: {},
2262
- executions: {},
2263
- executionHistory: [],
2264
- isShuttingDown: false,
2265
- runningExecutions: 0,
2266
- isDirty: false,
2267
- lastCleanupTime: Date.now(),
2268
- lastRestartTime: /* @__PURE__ */ new Date(),
2269
- config
2270
- };
2271
- };
2272
- var createSchedulerFunctions = (state) => {
2273
- const executeJob = async (scheduler) => {
2274
- const { canExecute, reason } = canExecuteJob(scheduler, state);
2275
- if (!canExecute) {
2276
- if (reason === "Maximum concurrent executions reached") {
2277
- logger.warn(`Delaying job ${scheduler.job.name} - ${reason}`);
2278
- scheduler.job.nextRun = new Date(Date.now() + 3e4);
2279
- scheduleJobFn(scheduler);
2280
- } else if (reason === "Job reached maximum runs") {
2281
- logger.info(`Job ${scheduler.job.name} reached max runs (${scheduler.job.maxRuns})`);
2282
- }
2283
- 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})`);
2284
1252
  }
2285
- const execution = createJobExecution(scheduler.job.id);
2286
- state.executions[execution.id] = execution;
2287
- state.executionHistory.push(execution);
2288
- scheduler.isRunning = true;
2289
- state.runningExecutions++;
2290
- logger.info(`Executing job ${scheduler.job.name} (${execution.id})`);
2291
- try {
2292
- const result = await executeJobWithTimeout(
2293
- scheduler.handler,
2294
- scheduler.job,
2295
- execution,
2296
- state.config.execution.defaultTimeout
2297
- );
2298
- const completedExecution = handleExecutionSuccess(execution, result);
2299
- state.executions[execution.id] = completedExecution;
2300
- } catch (error) {
2301
- const failedExecution = handleExecutionError(execution, error);
2302
- state.executions[execution.id] = failedExecution;
2303
- } finally {
2304
- scheduler.isRunning = false;
2305
- state.runningExecutions--;
2306
- scheduler.job = updateJobAfterExecution(scheduler.job, execution, state.config);
2307
- if (scheduler.job.enabled && !state.isShuttingDown) {
2308
- scheduleJobFn(scheduler);
2309
- }
2310
- if (state.config.persistence.enabled) {
2311
- state.isDirty = true;
2312
- }
1253
+ if (runtime.config.persistence.enabled) {
1254
+ runtime.isDirty = true;
2313
1255
  }
2314
- };
2315
- const scheduleJobFn = (scheduler) => {
2316
- const updatedScheduler = scheduleJob(scheduler, state, executeJob);
2317
- state.schedulers[scheduler.job.id] = updatedScheduler;
2318
- return updatedScheduler;
2319
- };
2320
- return { executeJob, scheduleJobFn };
1256
+ }
2321
1257
  };
2322
- var initializeScheduler = async (state, scheduleJobFn) => {
2323
- try {
2324
- if (state.config.persistence.enabled) {
2325
- await fs3.mkdir(state.config.persistence.directory, { recursive: true });
2326
- const { schedulers, executionHistory } = await loadSchedulerState(
2327
- state.schedulers,
2328
- state.config,
2329
- state.lastRestartTime
2330
- );
2331
- state.schedulers = schedulers;
2332
- state.executionHistory = executionHistory;
2333
- }
2334
- const tickInterval = (state.config.persistence.saveInterval || 0) > 0 ? state.config.persistence.saveInterval : state.config.cleanup.cleanupInterval;
2335
- state.tickInterval = setInterval(async () => {
2336
- if (state.isDirty && state.config.persistence.enabled) {
2337
- state.isDirty = false;
2338
- await saveSchedulerState(state);
2339
- }
2340
- if (Date.now() - state.lastCleanupTime >= state.config.cleanup.cleanupInterval) {
2341
- state.lastCleanupTime = Date.now();
2342
- cleanupExecutionHistory(state);
2343
- }
2344
- }, tickInterval);
2345
- for (const scheduler of Object.values(state.schedulers)) {
2346
- if (scheduler.job.enabled) {
2347
- scheduleJobFn(scheduler);
2348
- }
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}`);
2349
1266
  }
2350
- logger.debug(`Scheduler initialized: ${Object.keys(state.schedulers).length} jobs loaded`);
2351
- } catch (error) {
2352
- logger.error(`Failed to initialize scheduler: ${error.message}`);
2353
- throw error;
1267
+ return;
2354
1268
  }
1269
+ runtime.logging.runWithLogContext(
1270
+ { module: scheduler.job.module, reqId: scheduler.job.id },
1271
+ () => executeJob(runtime, scheduler)
1272
+ );
2355
1273
  };
2356
-
2357
- // src/schedule-plugin/jobs.ts
2358
- var validateJobDefinition = (jobDef) => {
2359
- const expressionType = detectExpressionType(jobDef.cronExpression);
2360
- if (expressionType === "unknown") {
2361
- logger.error(`Invalid cron expression for job: ${jobDef.name}`);
2362
- 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;
2363
1278
  }
1279
+ const removed = runtime.executionHistory.splice(0, runtime.executionHistory.length - maxExecutionHistory);
1280
+ runtime.logging.debug(`Cleaned up ${removed.length} old execution records`);
2364
1281
  };
2365
- var createJobFromDefinition = (jobDef, config) => {
2366
- validateJobDefinition(jobDef);
2367
- const expressionType = detectExpressionType(jobDef.cronExpression);
2368
- if (config.cron.logCronDetails && jobDef.cronExpression) {
2369
- const cronExpression = expressionType === "rate" ? convertRateToCron(jobDef.cronExpression).cronExpression : jobDef.cronExpression;
2370
- const result = parseCronExpression(cronExpression, void 0, {
2371
- timezone: jobDef.timezone ?? config.cron.defaultTimezone
2372
- });
2373
- logger.debug(`Job ${jobDef.name}: ${result.description} - Next: ${result.nextRun.toLocaleString()}`);
2374
- const upcoming = getNextExecutions(cronExpression, 3, {
2375
- timezone: jobDef.timezone ?? config.cron.defaultTimezone
2376
- });
2377
- 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;
2378
1285
  }
2379
- const job = {
2380
- id: generateJobId(),
2381
- name: jobDef.name,
2382
- cronExpression: expressionType === "rate" ? convertRateToCron(jobDef.cronExpression).cronExpression : jobDef.cronExpression,
2383
- intervalMs: jobDef.intervalMs,
2384
- delayMs: jobDef.delayMs,
2385
- nextRun: /* @__PURE__ */ new Date(),
2386
- enabled: jobDef.enabled ?? true,
2387
- runCount: 0,
2388
- maxRuns: jobDef.maxRuns,
2389
- metadata: jobDef.metadata,
2390
- createdAt: /* @__PURE__ */ new Date(),
2391
- timezone: jobDef.timezone,
2392
- module: jobDef.module
2393
- };
2394
- job.nextRun = calculateNextRun(job);
2395
- return job;
1286
+ runtime.lastCleanupTime = now;
1287
+ cleanupExecutionHistory(runtime);
2396
1288
  };
2397
- var createSchedulerService = (job, handler) => ({
2398
- job,
2399
- handler,
2400
- isRunning: false
2401
- });
2402
- var enableJob = (scheduler) => ({
2403
- ...scheduler,
2404
- job: {
2405
- ...scheduler.job,
2406
- enabled: true,
2407
- nextRun: calculateNextRun(scheduler.job)
1289
+ var maybeSavePersistence2 = (runtime, now) => {
1290
+ if (!runtime.config.persistence.enabled || !runtime.isDirty) {
1291
+ return;
2408
1292
  }
2409
- });
2410
- var disableJob = (scheduler) => {
2411
- if (scheduler.timerId) {
2412
- 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);
2413
1298
  }
2414
- return {
2415
- ...scheduler,
2416
- job: { ...scheduler.job, enabled: false },
2417
- timerId: void 0
2418
- };
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);
2419
1307
  };
2420
1308
 
2421
- // src/schedule-plugin/routes.ts
2422
- var createDashboardData = (state) => {
2423
- const jobs = Object.values(state.schedulers).map((s) => {
2424
- const nextExecutions = s.job.cronExpression ? getNextExecutions(s.job.cronExpression, 3, { timezone: s.job.timezone }) : [];
2425
- return {
2426
- ...s.job,
2427
- isRunning: s.isRunning,
2428
- timeUntilNextRun: getTimeUntilNextRun(s.job),
2429
- upcomingExecutions: nextExecutions,
2430
- cronDescription: s.job.cronExpression ? parseCronExpression(s.job.cronExpression, void 0, { timezone: s.job.timezone }).description : void 0
2431
- };
2432
- });
2433
- 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);
2434
1312
  };
2435
- var createExecutionsData = (state) => ({
2436
- executions: state.executionHistory.slice(-50),
2437
- // Last 50 executions
2438
- running: Object.values(state.executions).filter((e) => e.status === "running")
2439
- });
2440
- var registerDashboardRoute = (app, state) => {
2441
- app.get("/schedule/dashboard", async function(_, reply) {
2442
- const dashboardData = createDashboardData(state);
2443
- return reply.status(200).send(dashboardData);
2444
- });
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
+ }
2445
1320
  };
2446
- var registerExecutionsRoute = (app, state) => {
2447
- app.get("/schedule/executions", async function(_, reply) {
2448
- const executionsData = createExecutionsData(state);
2449
- return reply.status(200).send(executionsData);
2450
- });
1321
+ var validateHandler2 = (handler) => {
1322
+ if (typeof handler !== "function") {
1323
+ throw new Error("Handler must be a function");
1324
+ }
2451
1325
  };
2452
- var registerTriggerJobRoute = (app, state, executeJobFn) => {
2453
- app.post("/schedule/jobs/:jobId/trigger", async function(request, reply) {
2454
- const scheduler = state.schedulers[request.params.jobId];
2455
- if (!scheduler) {
2456
- 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
+ );
2457
1349
  }
2458
- if (scheduler.isRunning) {
2459
- 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);
2460
1353
  }
2461
- setImmediate(() => executeJobFn(scheduler));
2462
- return reply.status(200).send({ message: "Job triggered successfully" });
2463
- });
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
+ }
2464
1361
  };
2465
- var registerEnableJobRoute = (app, state, scheduleJobFn) => {
2466
- app.put("/schedule/jobs/:jobId/enable", async function(request, reply) {
2467
- const scheduler = state.schedulers[request.params.jobId];
1362
+ var removeJob = async (runtime, jobName) => {
1363
+ try {
1364
+ validateJobName(jobName);
1365
+ const scheduler = findSchedulerByName(runtime, jobName);
2468
1366
  if (!scheduler) {
2469
- return reply.status(404).send({ error: "Job not found" });
1367
+ throw new Error(`Job '${jobName}' does not exist`);
2470
1368
  }
2471
- const enabledScheduler = enableJob(scheduler);
2472
- state.schedulers[request.params.jobId] = enabledScheduler;
2473
- scheduleJobFn(enabledScheduler);
2474
- return reply.status(200).send({ message: "Job enabled successfully" });
2475
- });
2476
- };
2477
- var registerDisableJobRoute = (app, state) => {
2478
- app.put("/schedule/jobs/:jobId/disable", async function(request, reply) {
2479
- const scheduler = state.schedulers[request.params.jobId];
2480
- if (!scheduler) {
2481
- 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}`);
2482
1371
  }
2483
- const disabledScheduler = disableJob(scheduler);
2484
- state.schedulers[request.params.jobId] = disabledScheduler;
2485
- return reply.status(200).send({ message: "Job disabled successfully" });
2486
- });
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
+ }
2487
1386
  };
2488
- var registerAllRoutes = (app, state, executeJobFn, scheduleJobFn) => {
2489
- registerDashboardRoute(app, state);
2490
- registerExecutionsRoute(app, state);
2491
- registerTriggerJobRoute(app, state, executeJobFn);
2492
- registerEnableJobRoute(app, state, scheduleJobFn);
2493
- 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();
2494
1389
  };
2495
-
2496
- // src/schedule-plugin/decorator.ts
2497
- var isValidJobName = (jobName) => {
2498
- 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
+ }
1397
+ };
1398
+ var getJobsMap = (runtime) => {
1399
+ return { ...runtime.schedulers };
2499
1400
  };
2500
- var createSchedulerManager = ({
2501
- state,
2502
- scheduleJobFn,
2503
- executeJob,
2504
- config
2505
- }) => {
2506
- const validateJobName = (jobName) => {
2507
- if (!jobName || typeof jobName !== "string") {
2508
- throw new Error("Job name must be a non-empty string");
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`);
2509
1407
  }
2510
- if (!isValidJobName(jobName)) {
2511
- 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;
2512
1411
  }
2513
- };
2514
- const validateHandler = (handler) => {
2515
- if (typeof handler !== "function") {
2516
- 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`);
2517
1425
  }
2518
- };
2519
- const addJob = async (jobDefinition) => {
2520
- try {
2521
- validateJobName(jobDefinition.name);
2522
- validateHandler(jobDefinition.handler);
2523
- validateJobDefinition(jobDefinition);
2524
- const existingJob = Object.values(state.schedulers).find((s) => s.job.name === jobDefinition.name);
2525
- if (existingJob) {
2526
- logger.warn(`Job already exists: ${jobDefinition.name}.`);
2527
- return existingJob.job.id;
2528
- }
2529
- const job = createJobFromDefinition(jobDefinition, config);
2530
- const scheduler = createSchedulerService(job, jobDefinition.handler);
2531
- state.schedulers[job.id] = scheduler;
2532
- if (job.enabled) {
2533
- scheduleJobFn(scheduler);
2534
- }
2535
- logger.info(`Job added: ${jobDefinition.name} (${job.id})`);
2536
- return job.id;
2537
- } catch (error) {
2538
- logger.error(`Failed to add job ${jobDefinition.name}: ${error.message}`);
2539
- throw error;
1426
+ if (!scheduler.job.enabled) {
1427
+ runtime.logging.debug({ module: scheduler.job.module }, `Job is already disabled: ${jobName}`);
1428
+ return;
2540
1429
  }
2541
- };
2542
- const removeJob = async (jobName) => {
2543
- try {
2544
- validateJobName(jobName);
2545
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2546
- if (!scheduler) {
2547
- throw new Error(`Job '${jobName}' does not exist`);
2548
- }
2549
- if (scheduler.isRunning) {
2550
- logger.warn(`Removing job that is currently running: ${jobName}`);
2551
- }
2552
- if (scheduler.timerId) {
2553
- clearTimeout(scheduler.timerId);
2554
- }
2555
- delete state.schedulers[scheduler.job.id];
2556
- logger.info(`Job removed: ${jobName}`);
2557
- } catch (error) {
2558
- logger.error(`Failed to remove job ${jobName}: ${error.message}`);
2559
- 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`);
2560
1443
  }
2561
- };
2562
- const hasJob = (jobName) => {
2563
- try {
2564
- validateJobName(jobName);
2565
- return Object.values(state.schedulers).some((s) => s.job.name === jobName);
2566
- } catch {
2567
- return false;
1444
+ if (scheduler.isRunning) {
1445
+ throw new Error(`Job '${jobName}' is already running`);
2568
1446
  }
2569
- };
2570
- const listJobs = (module) => {
2571
- return Object.values(state.schedulers).filter((s) => !module || s.job.module === module).map((s) => s.job.name).sort();
2572
- };
2573
- const getJobStats = (jobName) => {
2574
- try {
2575
- validateJobName(jobName);
2576
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2577
- return scheduler || null;
2578
- } catch {
2579
- 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());
2580
1488
  }
2581
- };
2582
- const enableJob2 = async (jobName) => {
2583
- try {
2584
- validateJobName(jobName);
2585
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2586
- if (!scheduler) {
2587
- throw new Error(`Job '${jobName}' does not exist`);
2588
- }
2589
- if (scheduler.job.enabled) {
2590
- logger.debug(`Job is already enabled: ${jobName}`);
2591
- return;
2592
- }
2593
- scheduler.job.enabled = true;
2594
- scheduleJobFn(scheduler);
2595
- logger.info(`Job enabled: ${jobName}`);
2596
- } catch (error) {
2597
- logger.error(`Failed to enable job ${jobName}: ${error.message}`);
2598
- throw error;
1489
+ );
1490
+ app.get(
1491
+ `/${prefix}/executions`,
1492
+ async (_, reply) => {
1493
+ return reply.status(200).send(app.scheduler.getExecutionsSummary());
2599
1494
  }
2600
- };
2601
- const disableJob2 = async (jobName) => {
2602
- try {
2603
- validateJobName(jobName);
2604
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2605
- if (!scheduler) {
2606
- throw new Error(`Job '${jobName}' does not exist`);
2607
- }
2608
- if (!scheduler.job.enabled) {
2609
- logger.debug(`Job is already disabled: ${jobName}`);
2610
- return;
2611
- }
2612
- scheduler.job.enabled = false;
2613
- if (scheduler.timerId) {
2614
- clearTimeout(scheduler.timerId);
2615
- 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 });
2616
1504
  }
2617
- logger.info(`Job disabled: ${jobName}`);
2618
- } catch (error) {
2619
- logger.error(`Failed to disable job ${jobName}: ${error.message}`);
2620
- throw error;
2621
1505
  }
2622
- };
2623
- const triggerJob = async (jobName) => {
2624
- try {
2625
- validateJobName(jobName);
2626
- const scheduler = Object.values(state.schedulers).find((s) => s.job.name === jobName);
2627
- if (!scheduler) {
2628
- 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 });
2629
1515
  }
2630
- if (scheduler.isRunning) {
2631
- 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 });
2632
1526
  }
2633
- setImmediate(() => executeJob(scheduler));
2634
- logger.info(`Job triggered: ${jobName}`);
2635
- } catch (error) {
2636
- logger.error(`Failed to trigger job ${jobName}: ${error.message}`);
2637
- throw error;
2638
1527
  }
2639
- };
2640
- const getJobsMap = () => {
2641
- return { ...state.schedulers };
2642
- };
2643
- return {
2644
- addJob,
2645
- removeJob,
2646
- hasJob,
2647
- listJobs,
2648
- getJobStats,
2649
- enableJob: enableJob2,
2650
- disableJob: disableJob2,
2651
- triggerJob,
2652
- getJobsMap
2653
- };
2654
- };
2655
- var createSchedulerDecorator = (schedulerManager) => (app) => {
2656
- app.decorate("scheduler", schedulerManager);
1528
+ );
2657
1529
  };
2658
1530
 
2659
1531
  // src/schedule-plugin/plugin.ts
2660
1532
  var schedulerPlugin = fsPlugin2(async (app, options) => {
2661
- const config = mergeConfig2(DEFAULT_CONFIG2, options.config);
2662
- const state = createInitialState(config);
2663
- const { executeJob, scheduleJobFn } = createSchedulerFunctions(state);
2664
- await initializeScheduler(state, scheduleJobFn);
2665
- const schedulerManager = createSchedulerManager({
2666
- state,
2667
- config,
2668
- scheduleJobFn,
2669
- 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);
2670
1550
  });
2671
- const decorateScheduler = createSchedulerDecorator(schedulerManager);
2672
- decorateScheduler(app);
2673
- 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
+ }
2674
1556
  });
2675
1557
 
2676
1558
  // src/websocket-plugin/plugin.ts
@@ -2699,15 +1581,20 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2699
1581
  preHandler: async (request, reply) => {
2700
1582
  const connectionId = crypto.randomUUID();
2701
1583
  request._wsConnectionId = connectionId;
1584
+ const reqId = `${request.id}:${connectionId}`;
2702
1585
  const connectHandlers = handlers.filter((h) => h.path === path4 && h.event === "connect");
2703
1586
  for (const h of connectHandlers) {
2704
- 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
+ );
2705
1591
  }
2706
1592
  if (reply.statusCode !== 200)
2707
1593
  return reply;
2708
1594
  }
2709
1595
  }, (socket, request) => {
2710
1596
  const connectionId = request._wsConnectionId ?? crypto.randomUUID();
1597
+ const reqId = `${request.id}:${connectionId}`;
2711
1598
  const connection = {
2712
1599
  connectionId,
2713
1600
  socket,
@@ -2732,9 +1619,10 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2732
1619
  );
2733
1620
  for (const h of routeHandlers) {
2734
1621
  matched = true;
2735
- h.handler(connectionId, "message", data, request).catch((e) => {
2736
- logger.error(e);
2737
- });
1622
+ app.logging.runWithLogContext(
1623
+ { module: h.module, reqId },
1624
+ () => h.handler(connectionId, "message", data, request).catch((e) => app.logging.error(e))
1625
+ );
2738
1626
  }
2739
1627
  }
2740
1628
  if (!matched) {
@@ -2742,18 +1630,20 @@ var websocketsPlugin = fsPlugin3(async (app, options) => {
2742
1630
  (h) => h.event === "message" && !h.route
2743
1631
  );
2744
1632
  for (const h of defaultHandlers) {
2745
- h.handler(connectionId, "message", data, request).catch((e) => {
2746
- logger.error(e);
2747
- });
1633
+ app.logging.runWithLogContext(
1634
+ { module: h.module, reqId },
1635
+ () => h.handler(connectionId, "message", data, request).catch((e) => app.logging.error(e))
1636
+ );
2748
1637
  }
2749
1638
  }
2750
1639
  });
2751
1640
  socket.on("close", () => {
2752
1641
  const disconnectHandlers = pathHandlers().filter((h) => h.event === "disconnect");
2753
1642
  for (const h of disconnectHandlers) {
2754
- h.handler(connectionId, "disconnect", null, request).catch((e) => {
2755
- logger.error(e);
2756
- });
1643
+ app.logging.runWithLogContext(
1644
+ { module: h.module, reqId },
1645
+ () => h.handler(connectionId, "disconnect", null, request).catch((e) => app.logging.error(e))
1646
+ );
2757
1647
  }
2758
1648
  connections.delete(connectionId);
2759
1649
  });
@@ -2865,10 +1755,29 @@ var LogStore = class {
2865
1755
  }
2866
1756
  };
2867
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();
2868
1778
  async function devConsolePluginFunction(fastify, options = {}) {
2869
1779
  const { prefix = "console", enableUI = true } = options;
2870
- const upstream = getWriter();
2871
- setWriter((entry) => {
1780
+ const unsubscribe = fastify.logging.subscribeToLogs((entry) => {
2872
1781
  logStore.addLog({
2873
1782
  id: Date.now().toString() + Math.random().toString(36).slice(2, 9),
2874
1783
  timestamp: new Date(entry.time),
@@ -2878,11 +1787,11 @@ async function devConsolePluginFunction(fastify, options = {}) {
2878
1787
  module: entry.module,
2879
1788
  reqId: entry.reqId
2880
1789
  });
2881
- upstream(entry);
2882
1790
  });
2883
1791
  fastify.addHook("onClose", (_instance, done) => {
2884
- setWriter(upstream);
1792
+ unsubscribe();
2885
1793
  logStore.clear();
1794
+ httpHistoryStore.clear();
2886
1795
  done();
2887
1796
  });
2888
1797
  fastify.addHook("onRoute", (routeOptions) => {
@@ -2890,6 +1799,29 @@ async function devConsolePluginFunction(fastify, options = {}) {
2890
1799
  routeRegistry.register(routeOptions);
2891
1800
  }
2892
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
+ });
2893
1825
  fastify.get(`/${prefix}/routes`, async () => {
2894
1826
  return { routes: routeRegistry.getAll() };
2895
1827
  });
@@ -2897,6 +1829,13 @@ async function devConsolePluginFunction(fastify, options = {}) {
2897
1829
  const { method } = request.params;
2898
1830
  return { routes: routeRegistry.getByMethod(method) };
2899
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
+ });
2900
1839
  fastify.get(`/${prefix}/registry/stats`, async () => {
2901
1840
  return {
2902
1841
  totalRoutes: routeRegistry.count(),
@@ -3013,6 +1952,17 @@ async function devConsolePluginFunction(fastify, options = {}) {
3013
1952
  }
3014
1953
  };
3015
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
+ });
3016
1966
  fastify.get(`/${prefix}/queues/:queueName/process-dlq`, async (request, reply) => {
3017
1967
  const { queueName } = request.params;
3018
1968
  const queueManager = fastify.queues;
@@ -3142,6 +2092,17 @@ async function devConsolePluginFunction(fastify, options = {}) {
3142
2092
  timerId: jobStats.timerId ? "scheduled" : "not_scheduled"
3143
2093
  };
3144
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
+ });
3145
2106
  fastify.post(`/${prefix}/schedules/:jobName/trigger`, async (request, reply) => {
3146
2107
  const { jobName } = request.params;
3147
2108
  const schedulerManager = fastify.scheduler;
@@ -3261,21 +2222,30 @@ async function devConsolePluginFunction(fastify, options = {}) {
3261
2222
  }
3262
2223
  });
3263
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
+ };
3264
2232
  reply.type("text/event-stream");
3265
2233
  reply.header("Cache-Control", "no-cache");
3266
2234
  reply.header("Connection", "keep-alive");
3267
2235
  reply.header("Access-Control-Allow-Origin", "*");
3268
- const initialLogs = logStore.getLogs(50);
2236
+ const initialLogs = logStore.getLogs(50).filter(matchesFilters);
3269
2237
  reply.raw.write(`data: ${JSON.stringify({ type: "initial", logs: initialLogs })}
3270
2238
 
3271
2239
  `);
3272
- const unsubscribe = logStore.subscribe((log) => {
3273
- 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 })}
3274
2243
 
3275
2244
  `);
2245
+ }
3276
2246
  });
3277
2247
  request.raw.on("close", () => {
3278
- unsubscribe();
2248
+ unsubscribe2();
3279
2249
  });
3280
2250
  });
3281
2251
  if (enableUI) {
@@ -3299,17 +2269,4 @@ export {
3299
2269
  devConsolePlugin,
3300
2270
  routeRegistry
3301
2271
  };
3302
- /*! Bundled license information:
3303
-
3304
- lodash-es/lodash.js:
3305
- (**
3306
- * @license
3307
- * Lodash (Custom Build) <https://lodash.com/>
3308
- * Build: `lodash modularize exports="es" -o ./`
3309
- * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
3310
- * Released under MIT license <https://lodash.com/license>
3311
- * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
3312
- * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
3313
- *)
3314
- */
3315
2272
  //# sourceMappingURL=index.js.map