@nmakarov/cli-toolkit 0.21.0 → 0.23.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.
- package/dist/args.cjs +1 -4
- package/dist/args.cjs.map +1 -1
- package/dist/args.js +1 -1
- package/dist/args.js.map +1 -1
- package/dist/cli-runner.cjs +1252 -604
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +1299 -650
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +85 -157
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +84 -150
- package/dist/db.js.map +1 -1
- package/dist/errors.cjs +2 -2
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +2 -1
- package/dist/errors.js.map +1 -1
- package/dist/filedatabase.cjs +19 -19
- package/dist/filedatabase.cjs.map +1 -1
- package/dist/filedatabase.js +19 -16
- package/dist/filedatabase.js.map +1 -1
- package/dist/http-client.cjs +9 -11
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.js +10 -9
- package/dist/http-client.js.map +1 -1
- package/dist/http-client2.cjs +34 -37
- package/dist/http-client2.cjs.map +1 -1
- package/dist/http-client2.js +34 -34
- package/dist/http-client2.js.map +1 -1
- package/dist/index.cjs +1739 -720
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1746 -721
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +81 -68
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +96 -82
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +5 -5
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +5 -4
- package/dist/logger.js.map +1 -1
- package/dist/mock-server.cjs +21 -33
- package/dist/mock-server.cjs.map +1 -1
- package/dist/mock-server.js +21 -28
- package/dist/mock-server.js.map +1 -1
- package/dist/params.cjs +6 -9
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +6 -6
- package/dist/params.js.map +1 -1
- package/dist/s3.cjs +286 -0
- package/dist/s3.cjs.map +1 -0
- package/dist/s3.js +273 -0
- package/dist/s3.js.map +1 -0
- package/dist/screen.cjs +34 -39
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +48 -46
- package/dist/screen.js.map +1 -1
- package/dist/tasks.cjs +1354 -501
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +1369 -527
- package/dist/tasks.js.map +1 -1
- package/dist/utils.cjs +7 -8
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.js +6 -6
- package/dist/utils.js.map +1 -1
- package/package.json +32 -47
- package/scripts/ssm/{parse-cli.ts → parse-cli.js} +4 -4
- package/scripts/ssm/{ssm-admin.ts → ssm-admin.js} +12 -12
- package/scripts/ssm/{ssm-pull.ts → ssm-pull.js} +10 -13
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var __esmCache = {};
|
|
4
|
-
var __loadESMSync = function(moduleName) {
|
|
5
|
-
if (!__esmCache[moduleName]) {
|
|
6
|
-
throw new Error(`ESM module "${moduleName}" not loaded. Please call the load() function first: const toolkit = require("@nmakarov/cli-toolkit"); await toolkit.load();`);
|
|
7
|
-
}
|
|
8
|
-
return __esmCache[moduleName];
|
|
9
|
-
};
|
|
10
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -38,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
38
29
|
));
|
|
39
30
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
31
|
|
|
41
|
-
// src/screen/components.
|
|
32
|
+
// src/screen/components.js
|
|
42
33
|
function getScreenWidth(maxWidth = null) {
|
|
43
34
|
const terminalWidth = process.stdout.columns || 80;
|
|
44
35
|
const availableWidth = Math.max(20, terminalWidth - 4);
|
|
@@ -112,14 +103,13 @@ function ScreenFooter({ lines, textStyle }) {
|
|
|
112
103
|
}
|
|
113
104
|
var import_react, import_ink;
|
|
114
105
|
var init_components = __esm({
|
|
115
|
-
"src/screen/components.
|
|
116
|
-
"use strict";
|
|
106
|
+
"src/screen/components.js"() {
|
|
117
107
|
import_react = require("react");
|
|
118
108
|
import_ink = require("ink");
|
|
119
109
|
}
|
|
120
110
|
});
|
|
121
111
|
|
|
122
|
-
// src/screen/list-components.
|
|
112
|
+
// src/screen/list-components.js
|
|
123
113
|
function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
|
|
124
114
|
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
125
115
|
const termWidth = (process.stdout.columns || 80) - 8;
|
|
@@ -271,11 +261,13 @@ function MultiColumnListWithPreviewComponent({
|
|
|
271
261
|
...previewRows
|
|
272
262
|
);
|
|
273
263
|
}
|
|
274
|
-
function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " " }) {
|
|
264
|
+
function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " ", onSelectionChange }) {
|
|
275
265
|
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
276
266
|
const [sortOrder, setSortOrder] = (0, import_react2.useState)("none");
|
|
277
267
|
const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
|
|
278
268
|
const scrollStateRef = (0, import_react2.useRef)({ scrollOffset: 0, maxHeight: 0, totalItems: 0 });
|
|
269
|
+
const itemsRef = (0, import_react2.useRef)(items);
|
|
270
|
+
itemsRef.current = items;
|
|
279
271
|
const defaultGetTitle = (item) => {
|
|
280
272
|
return getTitle ? getTitle(item) : typeof item.value === "string" ? item.value : item.value?.title || item.name;
|
|
281
273
|
};
|
|
@@ -289,18 +281,24 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
289
281
|
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
290
282
|
}
|
|
291
283
|
}) : items;
|
|
284
|
+
const displayItemsRef = (0, import_react2.useRef)(displayItems);
|
|
285
|
+
displayItemsRef.current = displayItems;
|
|
292
286
|
const effectiveMaxHeight = maxHeight || displayItems.length;
|
|
293
|
-
const
|
|
287
|
+
const _canScroll = displayItems.length > effectiveMaxHeight;
|
|
294
288
|
const maxScrollOffset = Math.max(0, displayItems.length - effectiveMaxHeight);
|
|
295
289
|
const clampedScrollOffset = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
|
|
296
290
|
const visibleItems = displayItems.slice(clampedScrollOffset, clampedScrollOffset + effectiveMaxHeight);
|
|
297
291
|
const canScrollUp = clampedScrollOffset > 0;
|
|
298
292
|
const canScrollDown = clampedScrollOffset < maxScrollOffset;
|
|
299
293
|
scrollStateRef.current = { scrollOffset, maxHeight: effectiveMaxHeight, totalItems: displayItems.length };
|
|
294
|
+
const onSelectionChangeRef = (0, import_react2.useRef)(onSelectionChange);
|
|
295
|
+
onSelectionChangeRef.current = onSelectionChange;
|
|
300
296
|
(0, import_react2.useEffect)(() => {
|
|
301
297
|
ctx.setAction("moveUp", () => {
|
|
302
298
|
const newIndex = Math.max(0, selectedIndexRef.current - 1);
|
|
303
299
|
selectedIndexRef.current = newIndex;
|
|
300
|
+
const list = displayItemsRef.current;
|
|
301
|
+
onSelectionChangeRef.current?.(newIndex, list[newIndex]);
|
|
304
302
|
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
305
303
|
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
306
304
|
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
@@ -310,18 +308,11 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
310
308
|
forceUpdate({});
|
|
311
309
|
});
|
|
312
310
|
ctx.setAction("moveDown", () => {
|
|
313
|
-
const currentItems =
|
|
314
|
-
|
|
315
|
-
const titleB = titleGetter(b).toLowerCase();
|
|
316
|
-
if (sortOrder === "asc") {
|
|
317
|
-
return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
|
|
318
|
-
} else {
|
|
319
|
-
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
320
|
-
}
|
|
321
|
-
}) : items;
|
|
322
|
-
const maxIndex = currentItems.length - 1;
|
|
311
|
+
const currentItems = displayItemsRef.current;
|
|
312
|
+
const maxIndex = Math.max(0, currentItems.length - 1);
|
|
323
313
|
const newIndex = Math.min(maxIndex, selectedIndexRef.current + 1);
|
|
324
314
|
selectedIndexRef.current = newIndex;
|
|
315
|
+
onSelectionChangeRef.current?.(newIndex, currentItems[newIndex]);
|
|
325
316
|
const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
|
|
326
317
|
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
327
318
|
const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
|
|
@@ -331,8 +322,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
331
322
|
forceUpdate({});
|
|
332
323
|
});
|
|
333
324
|
ctx.setAction("scrollUp", () => {
|
|
334
|
-
const { scrollOffset: currentScrollOffset
|
|
335
|
-
const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
|
|
325
|
+
const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
|
|
336
326
|
const newScrollOffset = Math.max(0, currentScrollOffset - 1);
|
|
337
327
|
setScrollOffset(newScrollOffset);
|
|
338
328
|
forceUpdate({});
|
|
@@ -347,9 +337,9 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
347
337
|
if (sortable) {
|
|
348
338
|
ctx.setAction("toggleSort", () => {
|
|
349
339
|
const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
|
|
350
|
-
const currentSelectedItem =
|
|
340
|
+
const currentSelectedItem = displayItemsRef.current[selectedIndexRef.current];
|
|
351
341
|
setSortOrder(nextSort);
|
|
352
|
-
const newSortedItems = nextSort !== "none" ? [...
|
|
342
|
+
const newSortedItems = nextSort !== "none" ? [...itemsRef.current].sort((a, b) => {
|
|
353
343
|
const titleA = titleGetter(a).toLowerCase();
|
|
354
344
|
const titleB = titleGetter(b).toLowerCase();
|
|
355
345
|
if (nextSort === "asc") {
|
|
@@ -357,7 +347,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
357
347
|
} else {
|
|
358
348
|
return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
|
|
359
349
|
}
|
|
360
|
-
}) :
|
|
350
|
+
}) : itemsRef.current;
|
|
361
351
|
const newIndex = newSortedItems.findIndex((item) => item === currentSelectedItem);
|
|
362
352
|
if (newIndex !== -1) {
|
|
363
353
|
selectedIndexRef.current = newIndex;
|
|
@@ -495,8 +485,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
|
|
|
495
485
|
}
|
|
496
486
|
var import_react2, import_ink2, h2;
|
|
497
487
|
var init_list_components = __esm({
|
|
498
|
-
"src/screen/list-components.
|
|
499
|
-
"use strict";
|
|
488
|
+
"src/screen/list-components.js"() {
|
|
500
489
|
import_react2 = __toESM(require("react"), 1);
|
|
501
490
|
import_ink2 = require("ink");
|
|
502
491
|
init_components();
|
|
@@ -504,7 +493,7 @@ var init_list_components = __esm({
|
|
|
504
493
|
}
|
|
505
494
|
});
|
|
506
495
|
|
|
507
|
-
// src/screen/screens.
|
|
496
|
+
// src/screen/screens.js
|
|
508
497
|
function groupKeyBindings(bindings) {
|
|
509
498
|
const groups = {};
|
|
510
499
|
const enabledBindings = bindings.filter((b) => b.enabled !== false);
|
|
@@ -580,7 +569,7 @@ async function showScreen(config2) {
|
|
|
580
569
|
let renderResult = null;
|
|
581
570
|
let initialized = false;
|
|
582
571
|
const Screen = () => {
|
|
583
|
-
const [
|
|
572
|
+
const [, setUpdateCounter] = (0, import_react3.useState)(0);
|
|
584
573
|
if (!initialized) {
|
|
585
574
|
const defaultBindings = [
|
|
586
575
|
{ key: "escape", caption: "go back", action: "back", protected: true, order: 1 },
|
|
@@ -699,7 +688,7 @@ async function showScreen(config2) {
|
|
|
699
688
|
}
|
|
700
689
|
}
|
|
701
690
|
if (matchedBinding && actions[matchedBinding.action]) {
|
|
702
|
-
|
|
691
|
+
actions[matchedBinding.action]({
|
|
703
692
|
input,
|
|
704
693
|
key,
|
|
705
694
|
binding: matchedBinding
|
|
@@ -829,8 +818,7 @@ async function showMultiColumnListWithPreviewScreen(config2) {
|
|
|
829
818
|
}
|
|
830
819
|
var import_react3, import_ink3, showMenuScreen, showWordGridScreen;
|
|
831
820
|
var init_screens = __esm({
|
|
832
|
-
"src/screen/screens.
|
|
833
|
-
"use strict";
|
|
821
|
+
"src/screen/screens.js"() {
|
|
834
822
|
import_react3 = require("react");
|
|
835
823
|
import_ink3 = require("ink");
|
|
836
824
|
init_components();
|
|
@@ -840,7 +828,7 @@ var init_screens = __esm({
|
|
|
840
828
|
}
|
|
841
829
|
});
|
|
842
830
|
|
|
843
|
-
// src/screen/ui-elements.
|
|
831
|
+
// src/screen/ui-elements.js
|
|
844
832
|
function ListItem({
|
|
845
833
|
children,
|
|
846
834
|
isSelected = false,
|
|
@@ -865,7 +853,7 @@ function TextBlock({
|
|
|
865
853
|
color = "white",
|
|
866
854
|
dimmed = false,
|
|
867
855
|
bold = false,
|
|
868
|
-
maxWidth
|
|
856
|
+
maxWidth: _maxWidth
|
|
869
857
|
}) {
|
|
870
858
|
return (0, import_react4.createElement)(
|
|
871
859
|
import_ink4.Box,
|
|
@@ -905,7 +893,7 @@ function GridCell({
|
|
|
905
893
|
}, children)
|
|
906
894
|
);
|
|
907
895
|
}
|
|
908
|
-
function InputField({ prompt, value, onChange, onSubmit }) {
|
|
896
|
+
function InputField({ prompt, value, onChange: _onChange, onSubmit: _onSubmit }) {
|
|
909
897
|
return (0, import_react4.createElement)(
|
|
910
898
|
import_ink4.Box,
|
|
911
899
|
{ flexDirection: "column" },
|
|
@@ -919,14 +907,13 @@ function InputField({ prompt, value, onChange, onSubmit }) {
|
|
|
919
907
|
}
|
|
920
908
|
var import_react4, import_ink4;
|
|
921
909
|
var init_ui_elements = __esm({
|
|
922
|
-
"src/screen/ui-elements.
|
|
923
|
-
"use strict";
|
|
910
|
+
"src/screen/ui-elements.js"() {
|
|
924
911
|
import_react4 = require("react");
|
|
925
912
|
import_ink4 = require("ink");
|
|
926
913
|
}
|
|
927
914
|
});
|
|
928
915
|
|
|
929
|
-
// src/screen/utils.
|
|
916
|
+
// src/screen/utils.js
|
|
930
917
|
function buildBreadcrumb(parts) {
|
|
931
918
|
if (parts.length === 0) return "";
|
|
932
919
|
if (parts.length === 1) return parts[0];
|
|
@@ -940,12 +927,11 @@ function buildDetailBreadcrumb(path5, suffix = "") {
|
|
|
940
927
|
return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
|
|
941
928
|
}
|
|
942
929
|
var init_utils = __esm({
|
|
943
|
-
"src/screen/utils.
|
|
944
|
-
"use strict";
|
|
930
|
+
"src/screen/utils.js"() {
|
|
945
931
|
}
|
|
946
932
|
});
|
|
947
933
|
|
|
948
|
-
// src/screen/footer-builder.
|
|
934
|
+
// src/screen/footer-builder.js
|
|
949
935
|
function buildFooter(config2 = {}) {
|
|
950
936
|
const {
|
|
951
937
|
navigation = null,
|
|
@@ -996,8 +982,7 @@ function organizeFooterMessages(messages) {
|
|
|
996
982
|
}
|
|
997
983
|
var FooterPresets;
|
|
998
984
|
var init_footer_builder = __esm({
|
|
999
|
-
"src/screen/footer-builder.
|
|
1000
|
-
"use strict";
|
|
985
|
+
"src/screen/footer-builder.js"() {
|
|
1001
986
|
FooterPresets = {
|
|
1002
987
|
/**
|
|
1003
988
|
* Menu screen footer
|
|
@@ -1056,7 +1041,7 @@ var init_footer_builder = __esm({
|
|
|
1056
1041
|
}
|
|
1057
1042
|
});
|
|
1058
1043
|
|
|
1059
|
-
// src/screen/index.
|
|
1044
|
+
// src/screen/index.js
|
|
1060
1045
|
async function load() {
|
|
1061
1046
|
if (loadPromise) return loadPromise;
|
|
1062
1047
|
|
|
@@ -1065,8 +1050,7 @@ async function load() {
|
|
|
1065
1050
|
}
|
|
1066
1051
|
var import_react5, import_ink5, loadPromise;
|
|
1067
1052
|
var init_screen = __esm({
|
|
1068
|
-
"src/screen/index.
|
|
1069
|
-
"use strict";
|
|
1053
|
+
"src/screen/index.js"() {
|
|
1070
1054
|
import_react5 = __toESM(require("react"), 1);
|
|
1071
1055
|
import_ink5 = require("ink");
|
|
1072
1056
|
init_screens();
|
|
@@ -1083,9 +1067,10 @@ var init_screen = __esm({
|
|
|
1083
1067
|
}
|
|
1084
1068
|
});
|
|
1085
1069
|
|
|
1086
|
-
// src/index.
|
|
1070
|
+
// src/index.js
|
|
1087
1071
|
var src_exports = {};
|
|
1088
1072
|
__export(src_exports, {
|
|
1073
|
+
AbstractTask: () => AbstractTask,
|
|
1089
1074
|
Args: () => Args,
|
|
1090
1075
|
Box: () => import_ink5.Box,
|
|
1091
1076
|
Db: () => Db,
|
|
@@ -1101,13 +1086,15 @@ __export(src_exports, {
|
|
|
1101
1086
|
MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
|
|
1102
1087
|
Params: () => Params,
|
|
1103
1088
|
React: () => import_react5.default,
|
|
1089
|
+
S3: () => S3,
|
|
1090
|
+
SERVICE_TASK_NAMES: () => SERVICE_TASK_NAMES,
|
|
1104
1091
|
ScreenBody: () => ScreenBody,
|
|
1105
1092
|
ScreenContainer: () => ScreenContainer,
|
|
1106
1093
|
ScreenDivider: () => ScreenDivider,
|
|
1107
1094
|
ScreenFooter: () => ScreenFooter,
|
|
1108
1095
|
ScreenRow: () => ScreenRow,
|
|
1109
1096
|
ScreenTitle: () => ScreenTitle,
|
|
1110
|
-
|
|
1097
|
+
TaskGetLogs: () => TaskGetLogs,
|
|
1111
1098
|
TaskPing: () => TaskPing,
|
|
1112
1099
|
TaskSampleProcess: () => TaskSampleProcess,
|
|
1113
1100
|
TaskShellCommand: () => TaskShellCommand,
|
|
@@ -1122,17 +1109,17 @@ __export(src_exports, {
|
|
|
1122
1109
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1123
1110
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1124
1111
|
buildFooter: () => buildFooter,
|
|
1125
|
-
|
|
1126
|
-
dbFindAndConnect: () => dbFindAndConnect,
|
|
1127
|
-
dbInit: () => dbInit,
|
|
1112
|
+
convertPattern: () => convertPattern,
|
|
1128
1113
|
defaultFileSynopsisFunction: () => defaultFileSynopsisFunction,
|
|
1129
1114
|
defaultTasksRegistry: () => defaultTasksRegistry,
|
|
1130
1115
|
defaultVersionSynopsisFunction: () => defaultVersionSynopsisFunction,
|
|
1131
1116
|
enqueueStopTask: () => enqueueStopTask,
|
|
1132
1117
|
enqueueTask: () => enqueueTask,
|
|
1133
1118
|
ensureTaskTables: () => ensureTaskTables,
|
|
1119
|
+
flushTaskIpcLogs: () => flushTaskIpcLogs,
|
|
1134
1120
|
getArgsInstance: () => getArgsInstance,
|
|
1135
1121
|
h: () => import_react5.createElement,
|
|
1122
|
+
ipcFileLogsTableNameForSourceResource: () => ipcFileLogsTableNameForSourceResource,
|
|
1136
1123
|
joiEdateType: () => joiEdateType,
|
|
1137
1124
|
joiStringArrayType: () => joiStringArrayType,
|
|
1138
1125
|
listAliveRunnerHeartbeats: () => listServicesRegistry,
|
|
@@ -1140,14 +1127,22 @@ __export(src_exports, {
|
|
|
1140
1127
|
listSources: () => listSources,
|
|
1141
1128
|
listTables: () => listTables,
|
|
1142
1129
|
load: () => load,
|
|
1130
|
+
matchesParsedPattern: () => matchesParsedPattern,
|
|
1131
|
+
memo: () => import_react5.memo,
|
|
1132
|
+
mergeAllowedTasksWithServiceTasks: () => mergeAllowedTasksWithServiceTasks,
|
|
1133
|
+
nextTimeMatch: () => nextTimeMatch,
|
|
1134
|
+
normalizeAllowedTasks: () => normalizeAllowedTasks,
|
|
1143
1135
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1144
1136
|
queueToTableNames: () => queueToTableNames,
|
|
1137
|
+
readTaskIpcLogsSnapshot: () => readTaskIpcLogsSnapshot,
|
|
1145
1138
|
registerInServicesRegistry: () => registerInServicesRegistry,
|
|
1146
1139
|
registerRunnerHeartbeat: () => registerInServicesRegistry,
|
|
1140
|
+
resolveAsterisks: () => resolveAsterisks,
|
|
1141
|
+
resolveIpcFileLogsDir: () => resolveIpcFileLogsDir,
|
|
1142
|
+
resolveRanges: () => resolveRanges,
|
|
1143
|
+
resolveSteps: () => resolveSteps,
|
|
1147
1144
|
runNodeTaskScript: () => runNodeTaskScript,
|
|
1148
1145
|
runTasksLoop: () => runTasksLoop,
|
|
1149
|
-
runnerHeartbeatsTable: () => servicesRegistryTable,
|
|
1150
|
-
servicesRegistryTable: () => servicesRegistryTable,
|
|
1151
1146
|
setupContext: () => setupContext,
|
|
1152
1147
|
showListScreen: () => showListScreen,
|
|
1153
1148
|
showMenuScreen: () => showMenuScreen,
|
|
@@ -1155,6 +1150,8 @@ __export(src_exports, {
|
|
|
1155
1150
|
showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
|
|
1156
1151
|
showScreen: () => showScreen,
|
|
1157
1152
|
showWordGridScreen: () => showWordGridScreen,
|
|
1153
|
+
taskHistoryInsertFromQueueRow: () => taskHistoryInsertFromQueueRow,
|
|
1154
|
+
timeMatcher: () => timeMatcher,
|
|
1158
1155
|
touchRunnerHeartbeat: () => touchServicesRegistry,
|
|
1159
1156
|
touchServicesRegistry: () => touchServicesRegistry,
|
|
1160
1157
|
unregisterRunnerHeartbeat: () => unregisterServicesRegistry,
|
|
@@ -1164,6 +1161,7 @@ __export(src_exports, {
|
|
|
1164
1161
|
useCallback: () => import_react5.useCallback,
|
|
1165
1162
|
useEffect: () => import_react5.useEffect,
|
|
1166
1163
|
useInput: () => import_ink5.useInput,
|
|
1164
|
+
useLayoutEffect: () => import_react5.useLayoutEffect,
|
|
1167
1165
|
useMemo: () => import_react5.useMemo,
|
|
1168
1166
|
useRef: () => import_react5.useRef,
|
|
1169
1167
|
useState: () => import_react5.useState,
|
|
@@ -1171,7 +1169,7 @@ __export(src_exports, {
|
|
|
1171
1169
|
});
|
|
1172
1170
|
module.exports = __toCommonJS(src_exports);
|
|
1173
1171
|
|
|
1174
|
-
// src/args/index.
|
|
1172
|
+
// src/args/index.js
|
|
1175
1173
|
var import_fs = require("fs");
|
|
1176
1174
|
var import_path = require("path");
|
|
1177
1175
|
var import_dotenv = require("dotenv");
|
|
@@ -1658,10 +1656,10 @@ function getArgsInstance() {
|
|
|
1658
1656
|
return instance;
|
|
1659
1657
|
}
|
|
1660
1658
|
|
|
1661
|
-
// src/params/index.
|
|
1659
|
+
// src/params/index.js
|
|
1662
1660
|
var import_joi = __toESM(require("joi"), 1);
|
|
1663
1661
|
|
|
1664
|
-
// src/errors.
|
|
1662
|
+
// src/errors.js
|
|
1665
1663
|
var FrameworkError = class extends Error {
|
|
1666
1664
|
constructor(message) {
|
|
1667
1665
|
super(message);
|
|
@@ -1681,7 +1679,7 @@ var FileDatabaseError = class extends FrameworkError {
|
|
|
1681
1679
|
}
|
|
1682
1680
|
};
|
|
1683
1681
|
|
|
1684
|
-
// src/params/custom-types.
|
|
1682
|
+
// src/params/custom-types.js
|
|
1685
1683
|
var joiEdateType = (value, helpers) => {
|
|
1686
1684
|
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(value)) {
|
|
1687
1685
|
const testDate = new Date(value);
|
|
@@ -1774,7 +1772,7 @@ function calculateTimeOffset(amount, unit, sign) {
|
|
|
1774
1772
|
}
|
|
1775
1773
|
return sign === "+" ? amount * multiplier : -amount * multiplier;
|
|
1776
1774
|
}
|
|
1777
|
-
var joiStringArrayType = (type) => (value,
|
|
1775
|
+
var joiStringArrayType = (type) => (value, _helpers) => {
|
|
1778
1776
|
if (value === void 0 || typeof value === "function") {
|
|
1779
1777
|
return [];
|
|
1780
1778
|
}
|
|
@@ -1800,7 +1798,7 @@ var joiStringArrayType = (type) => (value, helpers) => {
|
|
|
1800
1798
|
return arr;
|
|
1801
1799
|
};
|
|
1802
1800
|
|
|
1803
|
-
// src/params/index.
|
|
1801
|
+
// src/params/index.js
|
|
1804
1802
|
var Params = class _Params {
|
|
1805
1803
|
context;
|
|
1806
1804
|
// Partial context during initialization
|
|
@@ -2060,7 +2058,7 @@ var Params = class _Params {
|
|
|
2060
2058
|
definition = val;
|
|
2061
2059
|
val = val.value;
|
|
2062
2060
|
}
|
|
2063
|
-
|
|
2061
|
+
this.assignDefinition(key, definition);
|
|
2064
2062
|
if (!this.runAllRegisteredSetters(key, val)) {
|
|
2065
2063
|
this.params[key] = val;
|
|
2066
2064
|
}
|
|
@@ -2179,14 +2177,14 @@ var Params = class _Params {
|
|
|
2179
2177
|
}
|
|
2180
2178
|
};
|
|
2181
2179
|
|
|
2182
|
-
// src/
|
|
2180
|
+
// src/index.js
|
|
2183
2181
|
init_screen();
|
|
2184
2182
|
|
|
2185
|
-
// src/filedatabase/index.
|
|
2183
|
+
// src/filedatabase/index.js
|
|
2186
2184
|
var import_fs4 = __toESM(require("fs"), 1);
|
|
2187
2185
|
var import_path4 = __toESM(require("path"), 1);
|
|
2188
2186
|
|
|
2189
|
-
// src/utils/os-utils.
|
|
2187
|
+
// src/utils/os-utils.js
|
|
2190
2188
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
2191
2189
|
var import_path2 = __toESM(require("path"), 1);
|
|
2192
2190
|
var import_child_process = require("child_process");
|
|
@@ -2215,7 +2213,7 @@ function getFreeDiskSpace(targetPath) {
|
|
|
2215
2213
|
}
|
|
2216
2214
|
}
|
|
2217
2215
|
|
|
2218
|
-
// src/utils/fs-utils.
|
|
2216
|
+
// src/utils/fs-utils.js
|
|
2219
2217
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
2220
2218
|
var import_path3 = __toESM(require("path"), 1);
|
|
2221
2219
|
async function ensurePath(...pathParts) {
|
|
@@ -2239,7 +2237,7 @@ function getFileExtension(dataType) {
|
|
|
2239
2237
|
}
|
|
2240
2238
|
}
|
|
2241
2239
|
|
|
2242
|
-
// src/utils/format-utils.
|
|
2240
|
+
// src/utils/format-utils.js
|
|
2243
2241
|
function bytesToHumanReadable(bytes) {
|
|
2244
2242
|
if (bytes === 0) return "0 B";
|
|
2245
2243
|
const k = 1024;
|
|
@@ -2248,7 +2246,7 @@ function bytesToHumanReadable(bytes) {
|
|
|
2248
2246
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
2249
2247
|
}
|
|
2250
2248
|
|
|
2251
|
-
// src/utils/date-utils.
|
|
2249
|
+
// src/utils/date-utils.js
|
|
2252
2250
|
function isTimestampFolder(folderName) {
|
|
2253
2251
|
const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|\.\d{3}Z)$/;
|
|
2254
2252
|
if (!isoRegex.test(folderName)) {
|
|
@@ -2258,7 +2256,7 @@ function isTimestampFolder(folderName) {
|
|
|
2258
2256
|
return !isNaN(date.getTime()) && date.getTime() > 0;
|
|
2259
2257
|
}
|
|
2260
2258
|
|
|
2261
|
-
// src/filedatabase/serializers.
|
|
2259
|
+
// src/filedatabase/serializers.js
|
|
2262
2260
|
function detectDataType(data) {
|
|
2263
2261
|
if (Array.isArray(data)) {
|
|
2264
2262
|
return "json-array";
|
|
@@ -2290,7 +2288,7 @@ function deserializeData(rawData, dataType) {
|
|
|
2290
2288
|
}
|
|
2291
2289
|
}
|
|
2292
2290
|
|
|
2293
|
-
// src/filedatabase/synopsis-functions.
|
|
2291
|
+
// src/filedatabase/synopsis-functions.js
|
|
2294
2292
|
function defaultFileSynopsisFunction(fileEntry, data) {
|
|
2295
2293
|
if (!Array.isArray(data) || data.length === 0) {
|
|
2296
2294
|
return { ...fileEntry };
|
|
@@ -2358,7 +2356,7 @@ function defaultVersionSynopsisFunction(metadata) {
|
|
|
2358
2356
|
return result;
|
|
2359
2357
|
}
|
|
2360
2358
|
|
|
2361
|
-
// src/filedatabase/index.
|
|
2359
|
+
// src/filedatabase/index.js
|
|
2362
2360
|
var FileDatabase = class _FileDatabase {
|
|
2363
2361
|
basePath;
|
|
2364
2362
|
namespace;
|
|
@@ -2444,7 +2442,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
2444
2442
|
if (errors.length) {
|
|
2445
2443
|
throw new FileDatabaseError(`[FileDatabase] ${errors.join("; ")}`);
|
|
2446
2444
|
}
|
|
2447
|
-
|
|
2445
|
+
const parts = [this.basePath, this.namespace];
|
|
2448
2446
|
if (this.tableName) {
|
|
2449
2447
|
parts.push(...this.tableName.split("/"));
|
|
2450
2448
|
}
|
|
@@ -2937,14 +2935,17 @@ var FileDatabase = class _FileDatabase {
|
|
|
2937
2935
|
* Prepare the instance for read or write operations
|
|
2938
2936
|
* This discovers state and sets up internal members based on mode and current data
|
|
2939
2937
|
*/
|
|
2940
|
-
async prepare(
|
|
2938
|
+
async prepare(options) {
|
|
2939
|
+
const { write, read, version, deferInitialVersion } = options;
|
|
2941
2940
|
if (write) {
|
|
2942
2941
|
if (this.versioned) {
|
|
2943
2942
|
if (this.currentVersion === null) {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2943
|
+
if (!deferInitialVersion) {
|
|
2944
|
+
await this.makeNewVersion();
|
|
2945
|
+
this.metadata = this.getDefaultMetadata();
|
|
2946
|
+
this.metadata.version = this.currentVersion;
|
|
2947
|
+
this.makeNewFile();
|
|
2948
|
+
}
|
|
2948
2949
|
} else {
|
|
2949
2950
|
if (!this.metadata.files.length) {
|
|
2950
2951
|
this.metadata = await this.figureMetadata(this.currentVersion);
|
|
@@ -3054,7 +3055,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3054
3055
|
if (options.forceNewVersion && !this.versioned) {
|
|
3055
3056
|
throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
|
|
3056
3057
|
}
|
|
3057
|
-
await this.prepare({ write: true });
|
|
3058
|
+
await this.prepare({ write: true, deferInitialVersion: !!(options.forceNewVersion && this.versioned) });
|
|
3058
3059
|
const incomingDataType = detectDataType(data);
|
|
3059
3060
|
this.metadata.dataType = incomingDataType;
|
|
3060
3061
|
if (options.forceNewVersion) {
|
|
@@ -3369,22 +3370,57 @@ function listSources(basePath) {
|
|
|
3369
3370
|
}
|
|
3370
3371
|
}
|
|
3371
3372
|
|
|
3372
|
-
// src/db/index.
|
|
3373
|
+
// src/db/index.js
|
|
3373
3374
|
var import_knex = __toESM(require("knex"), 1);
|
|
3375
|
+
var KNEX_DEFAULTS = {
|
|
3376
|
+
testConnection: true,
|
|
3377
|
+
pool: { min: 2, max: 10 },
|
|
3378
|
+
acquireConnectionTimeout: 1e4,
|
|
3379
|
+
ssl: { rejectUnauthorized: false }
|
|
3380
|
+
};
|
|
3374
3381
|
var Db = class {
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3382
|
+
static async init(context, options = {}) {
|
|
3383
|
+
const defs = {
|
|
3384
|
+
dbName: "string",
|
|
3385
|
+
dbConnectionString: "string",
|
|
3386
|
+
dbProfile: "boolean default false"
|
|
3387
|
+
};
|
|
3388
|
+
const discovered = context?.params?.getAllForModule?.("db", defs) ?? {};
|
|
3389
|
+
const merged = { ...discovered, ...options };
|
|
3390
|
+
let { dbName, dbConnectionString } = merged;
|
|
3391
|
+
const { dbProfile } = merged;
|
|
3392
|
+
if (!dbName && !dbConnectionString) {
|
|
3393
|
+
dbName = "local";
|
|
3394
|
+
}
|
|
3395
|
+
if (dbName && /^(postgresql|mysql):\/\//.test(dbName)) {
|
|
3396
|
+
dbConnectionString = dbName;
|
|
3397
|
+
dbName = void 0;
|
|
3398
|
+
}
|
|
3399
|
+
if (dbName && !dbConnectionString) {
|
|
3400
|
+
const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
|
|
3401
|
+
dbConnectionString = await context.params.get(paramName, "string");
|
|
3402
|
+
if (!dbConnectionString) {
|
|
3403
|
+
throw new ParamError(
|
|
3404
|
+
`Db: cannot find dbConnectionString for dbName="${dbName}" (looked for param "${paramName}")`
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
const config2 = {
|
|
3409
|
+
...KNEX_DEFAULTS,
|
|
3410
|
+
connectionString: dbConnectionString,
|
|
3411
|
+
name: dbName || merged.name || "default",
|
|
3412
|
+
profile: !!dbProfile,
|
|
3413
|
+
logger: context.logger
|
|
3414
|
+
};
|
|
3415
|
+
return dbConnect(context, config2);
|
|
3416
|
+
}
|
|
3384
3417
|
constructor(config2) {
|
|
3385
|
-
if (!config2.connectionString) {
|
|
3418
|
+
if (!config2 || !config2.connectionString) {
|
|
3386
3419
|
throw new ParamError("Db: connectionString is required");
|
|
3387
3420
|
}
|
|
3421
|
+
this.knexInstance = null;
|
|
3422
|
+
this.isConnected = false;
|
|
3423
|
+
this.queriesLog = [];
|
|
3388
3424
|
this.config = {
|
|
3389
3425
|
testConnection: true,
|
|
3390
3426
|
profile: false,
|
|
@@ -3397,25 +3433,23 @@ var Db = class {
|
|
|
3397
3433
|
};
|
|
3398
3434
|
this.logger = this.config.logger;
|
|
3399
3435
|
const instance2 = this;
|
|
3400
|
-
const callableWrapper = function(
|
|
3436
|
+
const callableWrapper = function() {
|
|
3401
3437
|
throw new Error("This should never be called directly");
|
|
3402
3438
|
};
|
|
3403
3439
|
callableWrapper._instance = instance2;
|
|
3404
3440
|
return new Proxy(callableWrapper, {
|
|
3405
|
-
|
|
3406
|
-
apply: (target, thisArg, argumentsList) => {
|
|
3441
|
+
apply: (target, _thisArg, argumentsList) => {
|
|
3407
3442
|
const inst = target._instance;
|
|
3408
3443
|
if (!inst.knexInstance) {
|
|
3409
3444
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3410
3445
|
}
|
|
3411
3446
|
return inst.knexInstance(...argumentsList);
|
|
3412
3447
|
},
|
|
3413
|
-
// Intercept property access: db.schema, db.raw, etc.
|
|
3414
3448
|
get: (target, prop) => {
|
|
3415
3449
|
if (prop === "_instance") {
|
|
3416
3450
|
return target._instance;
|
|
3417
3451
|
}
|
|
3418
|
-
const
|
|
3452
|
+
const inst = target._instance;
|
|
3419
3453
|
const ownMethods = [
|
|
3420
3454
|
"connect",
|
|
3421
3455
|
"disconnect",
|
|
@@ -3428,26 +3462,26 @@ var Db = class {
|
|
|
3428
3462
|
"detectClient",
|
|
3429
3463
|
"attachProfiler"
|
|
3430
3464
|
];
|
|
3431
|
-
if (prop in
|
|
3432
|
-
const value =
|
|
3465
|
+
if (prop in inst) {
|
|
3466
|
+
const value = inst[prop];
|
|
3433
3467
|
if (typeof value === "function" && ownMethods.includes(prop)) {
|
|
3434
|
-
return value.bind(
|
|
3468
|
+
return value.bind(inst);
|
|
3435
3469
|
}
|
|
3436
3470
|
if (typeof value !== "function") {
|
|
3437
3471
|
return value;
|
|
3438
3472
|
}
|
|
3439
3473
|
}
|
|
3440
|
-
if (
|
|
3441
|
-
const knexProp =
|
|
3474
|
+
if (inst.knexInstance) {
|
|
3475
|
+
const knexProp = inst.knexInstance[prop];
|
|
3442
3476
|
if (typeof knexProp === "function") {
|
|
3443
|
-
return knexProp.bind(
|
|
3477
|
+
return knexProp.bind(inst.knexInstance);
|
|
3444
3478
|
}
|
|
3445
3479
|
return knexProp;
|
|
3446
3480
|
}
|
|
3447
|
-
if (prop in
|
|
3448
|
-
const method =
|
|
3481
|
+
if (prop in inst) {
|
|
3482
|
+
const method = inst[prop];
|
|
3449
3483
|
if (typeof method === "function") {
|
|
3450
|
-
return method.bind(
|
|
3484
|
+
return method.bind(inst);
|
|
3451
3485
|
}
|
|
3452
3486
|
return method;
|
|
3453
3487
|
}
|
|
@@ -3455,9 +3489,6 @@ var Db = class {
|
|
|
3455
3489
|
}
|
|
3456
3490
|
});
|
|
3457
3491
|
}
|
|
3458
|
-
/**
|
|
3459
|
-
* Detect database client type from connection string
|
|
3460
|
-
*/
|
|
3461
3492
|
detectClient(connectionString) {
|
|
3462
3493
|
if (connectionString.match(/^postgresql/)) {
|
|
3463
3494
|
return "pg";
|
|
@@ -3467,9 +3498,6 @@ var Db = class {
|
|
|
3467
3498
|
}
|
|
3468
3499
|
return null;
|
|
3469
3500
|
}
|
|
3470
|
-
/**
|
|
3471
|
-
* Connect to the database
|
|
3472
|
-
*/
|
|
3473
3501
|
async connect() {
|
|
3474
3502
|
if (this.isConnected && this.knexInstance) {
|
|
3475
3503
|
this.logger.warn?.("[Db] Already connected");
|
|
@@ -3478,14 +3506,13 @@ var Db = class {
|
|
|
3478
3506
|
const client = this.detectClient(this.config.connectionString);
|
|
3479
3507
|
if (!client) {
|
|
3480
3508
|
throw new ParamError(
|
|
3481
|
-
|
|
3509
|
+
"Db: Cannot determine client type from connection string. Expected postgresql:// or mysql://"
|
|
3482
3510
|
);
|
|
3483
3511
|
}
|
|
3484
3512
|
try {
|
|
3485
3513
|
const connectionConfig = {
|
|
3486
3514
|
connectionString: this.config.connectionString,
|
|
3487
3515
|
family: 4
|
|
3488
|
-
// Force IPv4 only (disable IPv6)
|
|
3489
3516
|
};
|
|
3490
3517
|
this.knexInstance = (0, import_knex.default)({
|
|
3491
3518
|
client,
|
|
@@ -3501,7 +3528,9 @@ var Db = class {
|
|
|
3501
3528
|
await this.testConnection();
|
|
3502
3529
|
}
|
|
3503
3530
|
this.isConnected = true;
|
|
3504
|
-
this.logger.debug?.(
|
|
3531
|
+
this.logger.debug?.(
|
|
3532
|
+
`[Db] Connected to database "${this.config.name || this.config.connectionString}"`
|
|
3533
|
+
);
|
|
3505
3534
|
} catch (error) {
|
|
3506
3535
|
if (error instanceof ParamError) {
|
|
3507
3536
|
throw error;
|
|
@@ -3510,9 +3539,6 @@ var Db = class {
|
|
|
3510
3539
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
3511
3540
|
}
|
|
3512
3541
|
}
|
|
3513
|
-
/**
|
|
3514
|
-
* Disconnect from the database
|
|
3515
|
-
*/
|
|
3516
3542
|
async disconnect() {
|
|
3517
3543
|
if (!this.knexInstance) {
|
|
3518
3544
|
return;
|
|
@@ -3522,16 +3548,15 @@ var Db = class {
|
|
|
3522
3548
|
this.knexInstance = null;
|
|
3523
3549
|
this.isConnected = false;
|
|
3524
3550
|
this.queriesLog = [];
|
|
3525
|
-
this.logger.debug?.(
|
|
3551
|
+
this.logger.debug?.(
|
|
3552
|
+
`[Db] Disconnected from database "${this.config.name || this.config.connectionString}"`
|
|
3553
|
+
);
|
|
3526
3554
|
} catch (error) {
|
|
3527
3555
|
const errorMsg = this.getErrorMessage(error);
|
|
3528
3556
|
this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);
|
|
3529
3557
|
throw error;
|
|
3530
3558
|
}
|
|
3531
3559
|
}
|
|
3532
|
-
/**
|
|
3533
|
-
* Extract error message from various error types
|
|
3534
|
-
*/
|
|
3535
3560
|
getErrorMessage(error) {
|
|
3536
3561
|
if (error instanceof AggregateError) {
|
|
3537
3562
|
const errors = error.errors || [];
|
|
@@ -3556,9 +3581,11 @@ var Db = class {
|
|
|
3556
3581
|
return `${code} (tried: ${addresses.join(", ")})`;
|
|
3557
3582
|
}
|
|
3558
3583
|
}
|
|
3559
|
-
const uniqueMessages = [
|
|
3560
|
-
|
|
3561
|
-
|
|
3584
|
+
const uniqueMessages = [
|
|
3585
|
+
...new Set(
|
|
3586
|
+
errors.map((e) => e instanceof Error ? e.message : String(e))
|
|
3587
|
+
)
|
|
3588
|
+
];
|
|
3562
3589
|
if (uniqueMessages.length === 1) {
|
|
3563
3590
|
return uniqueMessages[0];
|
|
3564
3591
|
}
|
|
@@ -3567,28 +3594,25 @@ var Db = class {
|
|
|
3567
3594
|
return error.message || "Multiple errors occurred";
|
|
3568
3595
|
}
|
|
3569
3596
|
if (error instanceof Error) {
|
|
3570
|
-
const
|
|
3571
|
-
if (
|
|
3572
|
-
return `${
|
|
3597
|
+
const code = error.code;
|
|
3598
|
+
if (code) {
|
|
3599
|
+
return `${code}: ${error.message || String(error)}`;
|
|
3573
3600
|
}
|
|
3574
3601
|
return error.message || String(error);
|
|
3575
3602
|
}
|
|
3576
3603
|
if (typeof error === "string") {
|
|
3577
3604
|
return error;
|
|
3578
3605
|
}
|
|
3579
|
-
if (error
|
|
3606
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
3580
3607
|
const msg = String(error.message);
|
|
3581
|
-
const
|
|
3582
|
-
if (
|
|
3583
|
-
return `${
|
|
3608
|
+
const code = error.code;
|
|
3609
|
+
if (code) {
|
|
3610
|
+
return `${code}: ${msg}`;
|
|
3584
3611
|
}
|
|
3585
3612
|
return msg;
|
|
3586
3613
|
}
|
|
3587
3614
|
return String(error) || "Unknown error";
|
|
3588
3615
|
}
|
|
3589
|
-
/**
|
|
3590
|
-
* Test database connection
|
|
3591
|
-
*/
|
|
3592
3616
|
async testConnection() {
|
|
3593
3617
|
if (!this.knexInstance) {
|
|
3594
3618
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
@@ -3604,9 +3628,6 @@ var Db = class {
|
|
|
3604
3628
|
throw new ParamError(`Db: Connection test failed - ${errorMsg}`);
|
|
3605
3629
|
}
|
|
3606
3630
|
}
|
|
3607
|
-
/**
|
|
3608
|
-
* Attach query profiler to log all queries
|
|
3609
|
-
*/
|
|
3610
3631
|
attachProfiler() {
|
|
3611
3632
|
if (!this.knexInstance) {
|
|
3612
3633
|
return;
|
|
@@ -3616,7 +3637,7 @@ var Db = class {
|
|
|
3616
3637
|
this.knexInstance.on("query", (query) => {
|
|
3617
3638
|
query.__startTime = process.hrtime();
|
|
3618
3639
|
});
|
|
3619
|
-
this.knexInstance.on("query-response", (
|
|
3640
|
+
this.knexInstance.on("query-response", (_response, query) => {
|
|
3620
3641
|
const [seconds, nanoseconds] = process.hrtime(query.__startTime);
|
|
3621
3642
|
const executionTimeMs = (seconds * 1e3 + nanoseconds / 1e6).toFixed(2);
|
|
3622
3643
|
const logEntry = {
|
|
@@ -3631,15 +3652,9 @@ var Db = class {
|
|
|
3631
3652
|
this.logger.error?.(`[Db] Query failed: ${query.sql}`, error);
|
|
3632
3653
|
});
|
|
3633
3654
|
}
|
|
3634
|
-
/**
|
|
3635
|
-
* Get query log (only available if profiling is enabled)
|
|
3636
|
-
*/
|
|
3637
3655
|
getQueryLog() {
|
|
3638
3656
|
return [...this.queriesLog];
|
|
3639
3657
|
}
|
|
3640
|
-
/**
|
|
3641
|
-
* Check if a table exists
|
|
3642
|
-
*/
|
|
3643
3658
|
async tableExists(tableName) {
|
|
3644
3659
|
if (!this.knexInstance) {
|
|
3645
3660
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
@@ -3651,65 +3666,28 @@ var Db = class {
|
|
|
3651
3666
|
throw error;
|
|
3652
3667
|
}
|
|
3653
3668
|
}
|
|
3654
|
-
/**
|
|
3655
|
-
* Get the underlying Knex instance (for advanced usage)
|
|
3656
|
-
*/
|
|
3657
3669
|
getKnex() {
|
|
3658
3670
|
if (!this.knexInstance) {
|
|
3659
3671
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3660
3672
|
}
|
|
3661
3673
|
return this.knexInstance;
|
|
3662
3674
|
}
|
|
3663
|
-
/**
|
|
3664
|
-
* Get connection status
|
|
3665
|
-
*/
|
|
3666
3675
|
isConnectedToDb() {
|
|
3667
3676
|
return this.isConnected && this.knexInstance !== null;
|
|
3668
3677
|
}
|
|
3669
|
-
/**
|
|
3670
|
-
* Initialize Db with context (connects and registers disconnect cleanup).
|
|
3671
|
-
* Params are read via getAllForModule("db", defs). Same as dbInit(context, dbNameOrConnectionString).
|
|
3672
|
-
*/
|
|
3673
|
-
static async init(context, dbNameOrConnectionString) {
|
|
3674
|
-
return dbFindAndConnect(context, dbNameOrConnectionString);
|
|
3675
|
-
}
|
|
3676
3678
|
};
|
|
3677
3679
|
function capitalizeFirstLetter(str) {
|
|
3678
3680
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3679
3681
|
}
|
|
3680
|
-
async function dbConnect(context,
|
|
3681
|
-
const defs = {
|
|
3682
|
-
testDbConnection: "boolean default true",
|
|
3683
|
-
name: "string",
|
|
3684
|
-
poolMin: "number default 2",
|
|
3685
|
-
poolMax: "number default 10",
|
|
3686
|
-
acquireConnectionTimeout: "number default 10000",
|
|
3687
|
-
sslRejectUnauthorized: "boolean default false"
|
|
3688
|
-
};
|
|
3689
|
-
const paramsConfig = context.params.getAllForModule(defs);
|
|
3690
|
-
const config2 = {
|
|
3691
|
-
connectionString,
|
|
3692
|
-
name: paramsConfig.name || name || "default",
|
|
3693
|
-
testConnection: paramsConfig.testDbConnection,
|
|
3694
|
-
profile: dbProfile ?? false,
|
|
3695
|
-
pool: {
|
|
3696
|
-
min: paramsConfig.poolMin,
|
|
3697
|
-
max: paramsConfig.poolMax
|
|
3698
|
-
},
|
|
3699
|
-
acquireConnectionTimeout: paramsConfig.acquireConnectionTimeout,
|
|
3700
|
-
ssl: {
|
|
3701
|
-
rejectUnauthorized: paramsConfig.sslRejectUnauthorized
|
|
3702
|
-
},
|
|
3703
|
-
logger: context.logger
|
|
3704
|
-
};
|
|
3682
|
+
async function dbConnect(context, config2) {
|
|
3705
3683
|
try {
|
|
3706
3684
|
const db = new Db(config2);
|
|
3707
3685
|
context.registerCleanup(async () => {
|
|
3708
3686
|
await db.disconnect();
|
|
3709
|
-
context.logger.debug(`[Db] instance "${name
|
|
3687
|
+
context.logger.debug?.(`[Db] instance "${config2.name}" disconnected`);
|
|
3710
3688
|
});
|
|
3711
3689
|
await db.connect();
|
|
3712
|
-
context.logger.debug(`[Db] instance "${name
|
|
3690
|
+
context.logger.debug?.(`[Db] instance "${config2.name}" initialized`);
|
|
3713
3691
|
return db;
|
|
3714
3692
|
} catch (error) {
|
|
3715
3693
|
if (error instanceof ParamError) {
|
|
@@ -3719,52 +3697,255 @@ async function dbConnect(context, connectionString, name, dbProfile) {
|
|
|
3719
3697
|
throw new ParamError(`[Db] connect error: ${errorMsg}`);
|
|
3720
3698
|
}
|
|
3721
3699
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3700
|
+
|
|
3701
|
+
// src/s3/index.js
|
|
3702
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
3703
|
+
var DEFAULT_PROFILE = "local";
|
|
3704
|
+
function capitalize(str) {
|
|
3705
|
+
if (!str) return str;
|
|
3706
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3707
|
+
}
|
|
3708
|
+
var S3 = class _S3 {
|
|
3709
|
+
/**
|
|
3710
|
+
* Build an S3 instance. Reads bucket profile from --bucket (default "local"),
|
|
3711
|
+
* then resolves per-profile params, builds the SDK client, and returns the
|
|
3712
|
+
* instance. Optionally pings the bucket once to verify reachability.
|
|
3713
|
+
*/
|
|
3714
|
+
static async init(context, options = {}) {
|
|
3715
|
+
const profileDef = { bucket: "string" };
|
|
3716
|
+
const discovered = context?.params?.getAllForModule?.("s3", profileDef) ?? {};
|
|
3717
|
+
const profile = options.bucket ?? discovered.bucket ?? DEFAULT_PROFILE;
|
|
3718
|
+
const cap = capitalize(profile);
|
|
3719
|
+
const config2 = {
|
|
3720
|
+
profile,
|
|
3721
|
+
bucketName: options.bucketName ?? await context.params.get(`s3Bucket${cap}`, "string"),
|
|
3722
|
+
region: options.region ?? await context.params.get(`s3Region${cap}`, "string default us-east-1"),
|
|
3723
|
+
endpoint: options.endpoint ?? await context.params.get(`s3Endpoint${cap}`, "string"),
|
|
3724
|
+
forcePathStyle: options.forcePathStyle ?? await context.params.get(`s3ForcePathStyle${cap}`, "boolean default false"),
|
|
3725
|
+
accessKeyId: options.accessKeyId ?? await context.params.get(`s3AccessKeyId${cap}`, "string"),
|
|
3726
|
+
secretAccessKey: options.secretAccessKey ?? await context.params.get(`s3SecretAccessKey${cap}`, "string")
|
|
3738
3727
|
};
|
|
3739
|
-
|
|
3740
|
-
dbName = paramsConfig.dbName;
|
|
3741
|
-
dbConnectionString = paramsConfig.dbConnectionString;
|
|
3742
|
-
dbProfile = paramsConfig.dbProfile;
|
|
3743
|
-
}
|
|
3744
|
-
if (!dbName && !dbConnectionString) {
|
|
3745
|
-
throw new ParamError("Db: either dbName or dbConnectionString must be specified");
|
|
3746
|
-
}
|
|
3747
|
-
if (dbName) {
|
|
3748
|
-
const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
|
|
3749
|
-
dbConnectionString = await context.params.get(paramName, "string");
|
|
3750
|
-
if (!dbConnectionString) {
|
|
3728
|
+
if (!config2.bucketName) {
|
|
3751
3729
|
throw new ParamError(
|
|
3752
|
-
`
|
|
3730
|
+
`S3: bucket name not configured for profile "${profile}" (set s3Bucket${cap} or S3_BUCKET_${profile.toUpperCase()})`
|
|
3753
3731
|
);
|
|
3754
3732
|
}
|
|
3733
|
+
const s3 = new _S3(context, config2);
|
|
3734
|
+
if (options.testBucket !== false) {
|
|
3735
|
+
try {
|
|
3736
|
+
await s3.bucketExists();
|
|
3737
|
+
context.logger?.debug?.(
|
|
3738
|
+
`[S3] profile="${profile}" bucket="${config2.bucketName}" reachable`
|
|
3739
|
+
);
|
|
3740
|
+
} catch (err) {
|
|
3741
|
+
context.logger?.warn?.(
|
|
3742
|
+
`[S3] profile="${profile}" bucket="${config2.bucketName}" reachability test failed: ${err?.message ?? err}`
|
|
3743
|
+
);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
return s3;
|
|
3747
|
+
}
|
|
3748
|
+
constructor(context, config2) {
|
|
3749
|
+
this.logger = context?.logger ?? console;
|
|
3750
|
+
this.profile = config2.profile;
|
|
3751
|
+
this.bucketName = config2.bucketName;
|
|
3752
|
+
this.region = config2.region;
|
|
3753
|
+
this.endpoint = config2.endpoint || null;
|
|
3754
|
+
const clientConfig = { region: config2.region };
|
|
3755
|
+
if (config2.endpoint) clientConfig.endpoint = config2.endpoint;
|
|
3756
|
+
if (config2.forcePathStyle) clientConfig.forcePathStyle = true;
|
|
3757
|
+
if (config2.accessKeyId && config2.secretAccessKey) {
|
|
3758
|
+
clientConfig.credentials = {
|
|
3759
|
+
accessKeyId: config2.accessKeyId,
|
|
3760
|
+
secretAccessKey: config2.secretAccessKey
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
this.client = new import_client_s3.S3Client(clientConfig);
|
|
3755
3764
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3765
|
+
// ── info ────────────────────────────────────────────────────────────────
|
|
3766
|
+
getBucketName() {
|
|
3767
|
+
return this.bucketName;
|
|
3768
|
+
}
|
|
3769
|
+
getProfile() {
|
|
3770
|
+
return this.profile;
|
|
3771
|
+
}
|
|
3772
|
+
getRegion() {
|
|
3773
|
+
return this.region;
|
|
3774
|
+
}
|
|
3775
|
+
getEndpoint() {
|
|
3776
|
+
return this.endpoint;
|
|
3777
|
+
}
|
|
3778
|
+
// ── reachability ────────────────────────────────────────────────────────
|
|
3779
|
+
async bucketExists() {
|
|
3780
|
+
await this.client.send(new import_client_s3.HeadBucketCommand({ Bucket: this.bucketName }));
|
|
3781
|
+
return true;
|
|
3782
|
+
}
|
|
3783
|
+
// ── HEAD / GET ──────────────────────────────────────────────────────────
|
|
3784
|
+
/** Returns null on 404; never throws for "missing". Other errors throw. */
|
|
3785
|
+
async headObject(key) {
|
|
3786
|
+
try {
|
|
3787
|
+
const out = await this.client.send(new import_client_s3.HeadObjectCommand({
|
|
3788
|
+
Bucket: this.bucketName,
|
|
3789
|
+
Key: key
|
|
3790
|
+
}));
|
|
3791
|
+
return {
|
|
3792
|
+
etag: out.ETag,
|
|
3793
|
+
size: out.ContentLength,
|
|
3794
|
+
contentType: out.ContentType,
|
|
3795
|
+
lastModified: out.LastModified,
|
|
3796
|
+
metadata: out.Metadata,
|
|
3797
|
+
storageClass: out.StorageClass
|
|
3798
|
+
};
|
|
3799
|
+
} catch (err) {
|
|
3800
|
+
if (this._isNotFound(err)) return null;
|
|
3801
|
+
throw err;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
/** Returns { body: Readable, contentType, contentLength, etag, ... } or null on 404. */
|
|
3805
|
+
async getObject(key) {
|
|
3806
|
+
try {
|
|
3807
|
+
const out = await this.client.send(new import_client_s3.GetObjectCommand({
|
|
3808
|
+
Bucket: this.bucketName,
|
|
3809
|
+
Key: key
|
|
3810
|
+
}));
|
|
3811
|
+
return {
|
|
3812
|
+
body: out.Body,
|
|
3813
|
+
contentType: out.ContentType,
|
|
3814
|
+
contentLength: out.ContentLength,
|
|
3815
|
+
etag: out.ETag,
|
|
3816
|
+
lastModified: out.LastModified,
|
|
3817
|
+
metadata: out.Metadata
|
|
3818
|
+
};
|
|
3819
|
+
} catch (err) {
|
|
3820
|
+
if (this._isNotFound(err)) return null;
|
|
3821
|
+
throw err;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
/** Buffers the whole object. Use only for small objects (manifests, JSON). */
|
|
3825
|
+
async getObjectBytes(key) {
|
|
3826
|
+
const obj = await this.getObject(key);
|
|
3827
|
+
if (!obj) return null;
|
|
3828
|
+
const chunks = [];
|
|
3829
|
+
for await (const chunk of obj.body) chunks.push(chunk);
|
|
3830
|
+
return { ...obj, body: Buffer.concat(chunks) };
|
|
3831
|
+
}
|
|
3832
|
+
/** Convenience for JSON manifests. Returns parsed object or null on 404. */
|
|
3833
|
+
async getJson(key) {
|
|
3834
|
+
const obj = await this.getObjectBytes(key);
|
|
3835
|
+
if (!obj) return null;
|
|
3836
|
+
return JSON.parse(obj.body.toString("utf8"));
|
|
3837
|
+
}
|
|
3838
|
+
// ── PUT ─────────────────────────────────────────────────────────────────
|
|
3839
|
+
async putObject({ key, body, contentType, contentLength, tags, metadata }) {
|
|
3840
|
+
const cmd = new import_client_s3.PutObjectCommand({
|
|
3841
|
+
Bucket: this.bucketName,
|
|
3842
|
+
Key: key,
|
|
3843
|
+
Body: body,
|
|
3844
|
+
...contentType && { ContentType: contentType },
|
|
3845
|
+
...contentLength != null && { ContentLength: contentLength },
|
|
3846
|
+
...metadata && { Metadata: metadata },
|
|
3847
|
+
...tags && { Tagging: this._tagsToQuery(tags) }
|
|
3848
|
+
});
|
|
3849
|
+
return this.client.send(cmd);
|
|
3850
|
+
}
|
|
3851
|
+
/** Convenience for JSON manifests. */
|
|
3852
|
+
async putJson(key, value, opts = {}) {
|
|
3853
|
+
const json = JSON.stringify(value, null, opts.pretty ? 2 : 0);
|
|
3854
|
+
const body = Buffer.from(json, "utf8");
|
|
3855
|
+
return this.putObject({
|
|
3856
|
+
key,
|
|
3857
|
+
body,
|
|
3858
|
+
contentType: "application/json",
|
|
3859
|
+
contentLength: body.length,
|
|
3860
|
+
tags: opts.tags,
|
|
3861
|
+
metadata: opts.metadata
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
// ── DELETE ──────────────────────────────────────────────────────────────
|
|
3865
|
+
async deleteObject(key) {
|
|
3866
|
+
return this.client.send(new import_client_s3.DeleteObjectCommand({
|
|
3867
|
+
Bucket: this.bucketName,
|
|
3868
|
+
Key: key
|
|
3869
|
+
}));
|
|
3870
|
+
}
|
|
3871
|
+
// ── COPY (for migration: legacy → new bucket, or intra-bucket "rename") ─
|
|
3872
|
+
async copyObject({ sourceBucket, sourceKey, key, contentType, metadata, tags }) {
|
|
3873
|
+
const src = sourceBucket || this.bucketName;
|
|
3874
|
+
const cmd = new import_client_s3.CopyObjectCommand({
|
|
3875
|
+
Bucket: this.bucketName,
|
|
3876
|
+
Key: key,
|
|
3877
|
+
CopySource: encodeURIComponent(`${src}/${sourceKey}`),
|
|
3878
|
+
...contentType && {
|
|
3879
|
+
ContentType: contentType,
|
|
3880
|
+
MetadataDirective: "REPLACE"
|
|
3881
|
+
},
|
|
3882
|
+
...metadata && {
|
|
3883
|
+
Metadata: metadata,
|
|
3884
|
+
MetadataDirective: "REPLACE"
|
|
3885
|
+
},
|
|
3886
|
+
...tags && {
|
|
3887
|
+
Tagging: this._tagsToQuery(tags),
|
|
3888
|
+
TaggingDirective: "REPLACE"
|
|
3889
|
+
}
|
|
3890
|
+
});
|
|
3891
|
+
return this.client.send(cmd);
|
|
3892
|
+
}
|
|
3893
|
+
// ── LIST ────────────────────────────────────────────────────────────────
|
|
3894
|
+
async listObjects(prefix, { keysOnly = false, maxKeys = 1e3, continuationToken } = {}) {
|
|
3895
|
+
const out = await this.client.send(new import_client_s3.ListObjectsV2Command({
|
|
3896
|
+
Bucket: this.bucketName,
|
|
3897
|
+
Prefix: prefix,
|
|
3898
|
+
MaxKeys: maxKeys,
|
|
3899
|
+
ContinuationToken: continuationToken
|
|
3900
|
+
}));
|
|
3901
|
+
const items = (out.Contents ?? []).map((o) => ({
|
|
3902
|
+
key: o.Key,
|
|
3903
|
+
size: o.Size,
|
|
3904
|
+
etag: o.ETag,
|
|
3905
|
+
lastModified: o.LastModified,
|
|
3906
|
+
storageClass: o.StorageClass
|
|
3907
|
+
}));
|
|
3908
|
+
return {
|
|
3909
|
+
items: keysOnly ? items.map((i) => i.key) : items,
|
|
3910
|
+
isTruncated: !!out.IsTruncated,
|
|
3911
|
+
nextContinuationToken: out.NextContinuationToken
|
|
3912
|
+
};
|
|
3913
|
+
}
|
|
3914
|
+
// ── TAGS (used for lifecycle rules, e.g. status=closed → Glacier IR) ────
|
|
3915
|
+
async putObjectTagging(key, tags) {
|
|
3916
|
+
return this.client.send(new import_client_s3.PutObjectTaggingCommand({
|
|
3917
|
+
Bucket: this.bucketName,
|
|
3918
|
+
Key: key,
|
|
3919
|
+
Tagging: { TagSet: this._tagsToTagSet(tags) }
|
|
3920
|
+
}));
|
|
3921
|
+
}
|
|
3922
|
+
async getObjectTagging(key) {
|
|
3923
|
+
const out = await this.client.send(new import_client_s3.GetObjectTaggingCommand({
|
|
3924
|
+
Bucket: this.bucketName,
|
|
3925
|
+
Key: key
|
|
3926
|
+
}));
|
|
3927
|
+
const tags = {};
|
|
3928
|
+
for (const t of out.TagSet ?? []) tags[t.Key] = t.Value;
|
|
3929
|
+
return tags;
|
|
3930
|
+
}
|
|
3931
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
3932
|
+
_isNotFound(err) {
|
|
3933
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
3934
|
+
return status === 404 || err?.name === "NotFound" || err?.name === "NoSuchKey" || err?.Code === "NoSuchKey";
|
|
3935
|
+
}
|
|
3936
|
+
_tagsToQuery(tags) {
|
|
3937
|
+
return Object.entries(tags).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
3938
|
+
}
|
|
3939
|
+
_tagsToTagSet(tags) {
|
|
3940
|
+
return Object.entries(tags).map(([Key, Value]) => ({ Key, Value: String(Value) }));
|
|
3941
|
+
}
|
|
3942
|
+
};
|
|
3762
3943
|
|
|
3763
|
-
// src/logger/index.
|
|
3944
|
+
// src/logger/index.js
|
|
3764
3945
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
3765
3946
|
var import_util = __toESM(require("util"), 1);
|
|
3766
3947
|
|
|
3767
|
-
// src/logger/transports.
|
|
3948
|
+
// src/logger/transports.js
|
|
3768
3949
|
var ConsoleTransport = class {
|
|
3769
3950
|
write(payload) {
|
|
3770
3951
|
console.info(payload);
|
|
@@ -3784,7 +3965,7 @@ var ParentProcessTransport = class {
|
|
|
3784
3965
|
}
|
|
3785
3966
|
};
|
|
3786
3967
|
|
|
3787
|
-
// src/logger/index.
|
|
3968
|
+
// src/logger/index.js
|
|
3788
3969
|
var ALL_LEVELS = [
|
|
3789
3970
|
"silly",
|
|
3790
3971
|
"debug",
|
|
@@ -3857,6 +4038,7 @@ var Logger = class _Logger {
|
|
|
3857
4038
|
}
|
|
3858
4039
|
/**
|
|
3859
4040
|
* Initialize logger from context and CLI parameters. Whatever is in options goes (after discovered params).
|
|
4041
|
+
* Params are tracked under the `logger` module for --showUsedParams.
|
|
3860
4042
|
*/
|
|
3861
4043
|
static init(context, options) {
|
|
3862
4044
|
const paramDefs = {
|
|
@@ -3870,7 +4052,7 @@ var Logger = class _Logger {
|
|
|
3870
4052
|
progressWithTimes: "boolean default false",
|
|
3871
4053
|
progressThrottleMs: "number"
|
|
3872
4054
|
};
|
|
3873
|
-
const discovered = context.params.getAllForModule(paramDefs);
|
|
4055
|
+
const discovered = context.params.getAllForModule("logger", paramDefs);
|
|
3874
4056
|
const config2 = { ...discovered, ...options };
|
|
3875
4057
|
const logger = new _Logger(context, config2);
|
|
3876
4058
|
context.logger = logger;
|
|
@@ -4066,9 +4248,9 @@ var Logger = class _Logger {
|
|
|
4066
4248
|
}
|
|
4067
4249
|
};
|
|
4068
4250
|
|
|
4069
|
-
// src/init/index.
|
|
4251
|
+
// src/init/index.js
|
|
4070
4252
|
var import_events = require("events");
|
|
4071
|
-
function extractComponentOptions(opts,
|
|
4253
|
+
function extractComponentOptions(opts, _componentName) {
|
|
4072
4254
|
const reservedKeys = ["overrides", "defaults", "modules"];
|
|
4073
4255
|
const componentOptions = {};
|
|
4074
4256
|
for (const [key, value] of Object.entries(opts)) {
|
|
@@ -4113,7 +4295,10 @@ function setupContext(opts = {}) {
|
|
|
4113
4295
|
return setup(opts);
|
|
4114
4296
|
}
|
|
4115
4297
|
|
|
4116
|
-
// src/
|
|
4298
|
+
// src/tasks/index.js
|
|
4299
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
4300
|
+
|
|
4301
|
+
// src/utils/core-utils.js
|
|
4117
4302
|
function sleepMs(ms) {
|
|
4118
4303
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4119
4304
|
}
|
|
@@ -4122,14 +4307,82 @@ function toJsonColumn(value) {
|
|
|
4122
4307
|
return JSON.stringify(value);
|
|
4123
4308
|
}
|
|
4124
4309
|
|
|
4125
|
-
// src/tasks/servicesRegistry.
|
|
4126
|
-
var import_node_crypto2 = require("crypto");
|
|
4127
|
-
var import_promises = require("fs/promises");
|
|
4310
|
+
// src/tasks/servicesRegistry.js
|
|
4128
4311
|
var import_node_os = __toESM(require("os"), 1);
|
|
4129
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
4130
4312
|
|
|
4131
|
-
// src/tasks/taskUtils.
|
|
4313
|
+
// src/tasks/taskUtils.js
|
|
4132
4314
|
var import_node_crypto = require("crypto");
|
|
4315
|
+
|
|
4316
|
+
// src/tasks/time-matcher.js
|
|
4317
|
+
var RANGES = ["0-59", "0-59", "0-23", "1-31", "1-12", "0-6"];
|
|
4318
|
+
function resolveAsterisks(field, range) {
|
|
4319
|
+
return field.includes("*") ? field.replace("*", range) : field;
|
|
4320
|
+
}
|
|
4321
|
+
function resolveRanges(field) {
|
|
4322
|
+
const regex = /(\d+)-(\d+)/;
|
|
4323
|
+
let current = field;
|
|
4324
|
+
while (true) {
|
|
4325
|
+
const match = regex.exec(current);
|
|
4326
|
+
if (!match) break;
|
|
4327
|
+
const raw = match[0];
|
|
4328
|
+
let first = Number(match[1]);
|
|
4329
|
+
let last = Number(match[2]);
|
|
4330
|
+
if (last < first) {
|
|
4331
|
+
[first, last] = [last, first];
|
|
4332
|
+
}
|
|
4333
|
+
const values = [];
|
|
4334
|
+
for (let i = first; i <= last; i += 1) {
|
|
4335
|
+
values.push(i);
|
|
4336
|
+
}
|
|
4337
|
+
current = current.replace(raw, values.join(","));
|
|
4338
|
+
}
|
|
4339
|
+
return current;
|
|
4340
|
+
}
|
|
4341
|
+
function resolveSteps(field) {
|
|
4342
|
+
const match = /^(.+)\/(\d+)$/.exec(field);
|
|
4343
|
+
if (!match) return field;
|
|
4344
|
+
const base = match[1];
|
|
4345
|
+
const step = Number(match[2]);
|
|
4346
|
+
if (!Number.isFinite(step) || step <= 0) return field;
|
|
4347
|
+
return base.split(",").map((v) => Number(v)).filter((v) => Number.isFinite(v) && v % step === 0).join(",");
|
|
4348
|
+
}
|
|
4349
|
+
function convertPattern(pattern) {
|
|
4350
|
+
const parts = pattern.trim().split(/\s+/);
|
|
4351
|
+
if (parts.length !== 6) {
|
|
4352
|
+
throw new Error(`Invalid schedule "${pattern}". Expected 6 fields: sec min hour day month weekday`);
|
|
4353
|
+
}
|
|
4354
|
+
return parts.map((field, idx) => resolveAsterisks(field, RANGES[idx])).map((field) => resolveRanges(field)).map((field) => resolveSteps(field));
|
|
4355
|
+
}
|
|
4356
|
+
function fieldMatches(field, value) {
|
|
4357
|
+
const allowed = field.split(",").map((v) => Number(v));
|
|
4358
|
+
return allowed.includes(value);
|
|
4359
|
+
}
|
|
4360
|
+
function matchesParsedPattern(parsed, date) {
|
|
4361
|
+
return fieldMatches(parsed[0], date.getSeconds()) && fieldMatches(parsed[1], date.getMinutes()) && fieldMatches(parsed[2], date.getHours()) && fieldMatches(parsed[3], date.getDate()) && fieldMatches(parsed[4], date.getMonth() + 1) && fieldMatches(parsed[5], date.getDay());
|
|
4362
|
+
}
|
|
4363
|
+
function timeMatcher(pattern, date = /* @__PURE__ */ new Date()) {
|
|
4364
|
+
const parsed = convertPattern(pattern);
|
|
4365
|
+
return matchesParsedPattern(parsed, date);
|
|
4366
|
+
}
|
|
4367
|
+
var MS_PER_SECOND = 1e3;
|
|
4368
|
+
var DEFAULT_SEARCH_HORIZON_MS = 10 * 365 * 24 * 60 * 60 * MS_PER_SECOND;
|
|
4369
|
+
function nextTimeMatch(pattern, from = /* @__PURE__ */ new Date(), maxSearchMs = DEFAULT_SEARCH_HORIZON_MS) {
|
|
4370
|
+
const parsed = convertPattern(pattern);
|
|
4371
|
+
let t = Math.ceil((from.getTime() + 1) / MS_PER_SECOND) * MS_PER_SECOND;
|
|
4372
|
+
const end = t + maxSearchMs;
|
|
4373
|
+
while (t <= end) {
|
|
4374
|
+
const date = new Date(t);
|
|
4375
|
+
if (matchesParsedPattern(parsed, date)) {
|
|
4376
|
+
return date;
|
|
4377
|
+
}
|
|
4378
|
+
t += MS_PER_SECOND;
|
|
4379
|
+
}
|
|
4380
|
+
throw new Error(
|
|
4381
|
+
`nextTimeMatch: no match for "${pattern}" within ${maxSearchMs}ms after ${from.toISOString()}`
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
// src/tasks/taskUtils.js
|
|
4133
4386
|
function getDb(context) {
|
|
4134
4387
|
const db = context.db;
|
|
4135
4388
|
if (!db) {
|
|
@@ -4137,113 +4390,117 @@ function getDb(context) {
|
|
|
4137
4390
|
}
|
|
4138
4391
|
return db;
|
|
4139
4392
|
}
|
|
4140
|
-
function queueToTableNames(
|
|
4141
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(queue)) {
|
|
4142
|
-
throw new Error(`Invalid queue name "${queue}". Use letters, numbers, underscore only.`);
|
|
4143
|
-
}
|
|
4393
|
+
function queueToTableNames(queueName) {
|
|
4144
4394
|
return {
|
|
4145
|
-
tasksTable:
|
|
4146
|
-
historyTable: `${
|
|
4395
|
+
tasksTable: queueName,
|
|
4396
|
+
historyTable: `${queueName}_history`,
|
|
4397
|
+
registryTable: `${queueName}_services_registry`
|
|
4147
4398
|
};
|
|
4148
4399
|
}
|
|
4149
|
-
function
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4400
|
+
function defineTasksTable(t, db, tableNameForIndex) {
|
|
4401
|
+
t.uuid("id").primary().defaultTo(db.raw("uuid_generate_v4()"));
|
|
4402
|
+
t.timestamp("created_at").notNullable().defaultTo(db.fn.now());
|
|
4403
|
+
t.timestamp("started_at");
|
|
4404
|
+
t.timestamp("completed_at");
|
|
4405
|
+
t.integer("priority").notNullable().defaultTo(50);
|
|
4406
|
+
t.text("schedule");
|
|
4407
|
+
t.timestamp("next_run_at").defaultTo(null);
|
|
4408
|
+
t.timestamp("past_due").defaultTo(null);
|
|
4409
|
+
t.text("name").notNullable();
|
|
4410
|
+
t.text("opid");
|
|
4411
|
+
t.json("params");
|
|
4412
|
+
t.text("service_group");
|
|
4413
|
+
t.integer("instance_number");
|
|
4414
|
+
t.text("service_name");
|
|
4415
|
+
t.text("server_name");
|
|
4416
|
+
t.text("status").notNullable().defaultTo("idle");
|
|
4417
|
+
t.timestamp("status_changed_at").defaultTo(null);
|
|
4418
|
+
t.text("progress");
|
|
4419
|
+
t.boolean("success");
|
|
4420
|
+
t.json("results");
|
|
4421
|
+
t.index(["service_group", "status", "priority", "created_at"], `${tableNameForIndex}_claim_idx`);
|
|
4422
|
+
t.index(["service_group", "name"], `${tableNameForIndex}_group_name_idx`);
|
|
4423
|
+
}
|
|
4424
|
+
function taskHistoryInsertFromQueueRow(row, overrides) {
|
|
4425
|
+
const { id, ...snapshot } = row;
|
|
4426
|
+
void id;
|
|
4427
|
+
return {
|
|
4428
|
+
...snapshot,
|
|
4429
|
+
...overrides
|
|
4430
|
+
};
|
|
4154
4431
|
}
|
|
4155
4432
|
async function ensureTaskTables(context, options = {}) {
|
|
4156
|
-
const
|
|
4433
|
+
const queueName = options.queueName ?? "tasks";
|
|
4157
4434
|
const recreate = options.recreate ?? false;
|
|
4158
4435
|
const db = getDb(context);
|
|
4159
|
-
const { tasksTable, historyTable } = queueToTableNames(
|
|
4436
|
+
const { tasksTable, historyTable, registryTable } = queueToTableNames(queueName);
|
|
4160
4437
|
const needsTasks = recreate ? true : !await db.tableExists(tasksTable);
|
|
4161
4438
|
const needsHistory = recreate ? true : !await db.tableExists(historyTable);
|
|
4439
|
+
const needsRegistry = recreate ? true : !await db.tableExists(registryTable);
|
|
4162
4440
|
if (recreate) {
|
|
4163
4441
|
await db.schema.dropTableIfExists(historyTable);
|
|
4164
4442
|
await db.schema.dropTableIfExists(tasksTable);
|
|
4443
|
+
await db.schema.dropTableIfExists(registryTable);
|
|
4165
4444
|
}
|
|
4166
4445
|
if (needsTasks) {
|
|
4167
4446
|
await db.raw(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
|
4168
4447
|
await db.schema.createTable(tasksTable, (t) => {
|
|
4169
|
-
t
|
|
4170
|
-
t.timestamp("created_at").notNullable().defaultTo(db.fn.now());
|
|
4171
|
-
t.timestamp("started_at");
|
|
4172
|
-
t.timestamp("completed_at");
|
|
4173
|
-
t.integer("priority").notNullable().defaultTo(0);
|
|
4174
|
-
t.text("schedule");
|
|
4175
|
-
t.timestamp("past_due").defaultTo(null);
|
|
4176
|
-
t.text("target").notNullable();
|
|
4177
|
-
t.text("task").notNullable();
|
|
4178
|
-
t.json("params");
|
|
4179
|
-
t.text("opid");
|
|
4180
|
-
t.timestamp("paused_at").defaultTo(null);
|
|
4181
|
-
t.text("progress");
|
|
4182
|
-
t.boolean("success");
|
|
4183
|
-
t.json("results");
|
|
4184
|
-
});
|
|
4185
|
-
await db.schema.alterTable(tasksTable, (t) => {
|
|
4186
|
-
t.index(["target", "started_at", "created_at"], `${tasksTable}_target_started_created_idx`);
|
|
4187
|
-
t.index(["target", "past_due", "priority", "created_at"], `${tasksTable}_target_past_due_priority_created_idx`);
|
|
4188
|
-
t.index(["target", "task"], `${tasksTable}_target_task_idx`);
|
|
4448
|
+
defineTasksTable(t, db, tasksTable);
|
|
4189
4449
|
});
|
|
4190
4450
|
}
|
|
4191
4451
|
if (needsHistory) {
|
|
4192
4452
|
await db.schema.createTable(historyTable, (t) => {
|
|
4193
|
-
t
|
|
4194
|
-
t.timestamp("created_at").notNullable();
|
|
4195
|
-
t.timestamp("started_at");
|
|
4196
|
-
t.timestamp("completed_at");
|
|
4197
|
-
t.integer("priority").notNullable().defaultTo(0);
|
|
4198
|
-
t.text("schedule");
|
|
4199
|
-
t.timestamp("past_due").defaultTo(null);
|
|
4200
|
-
t.text("target").notNullable();
|
|
4201
|
-
t.text("task").notNullable();
|
|
4202
|
-
t.json("params");
|
|
4203
|
-
t.text("opid");
|
|
4204
|
-
t.text("progress");
|
|
4205
|
-
t.boolean("success");
|
|
4206
|
-
t.json("results");
|
|
4207
|
-
});
|
|
4208
|
-
await db.schema.alterTable(historyTable, (t) => {
|
|
4209
|
-
t.index(["target", "created_at"], `${historyTable}_target_created_idx`);
|
|
4210
|
-
t.index(["task", "created_at"], `${historyTable}_task_created_idx`);
|
|
4453
|
+
defineTasksTable(t, db, historyTable);
|
|
4211
4454
|
});
|
|
4212
4455
|
}
|
|
4213
|
-
const registryTable = servicesRegistryTable(queue);
|
|
4214
|
-
const needsRegistry = !await db.tableExists(registryTable);
|
|
4215
4456
|
if (needsRegistry) {
|
|
4216
4457
|
await db.schema.createTable(registryTable, (t) => {
|
|
4217
4458
|
t.uuid("id").primary().defaultTo(db.raw("uuid_generate_v4()"));
|
|
4218
|
-
t.
|
|
4219
|
-
t.text("queue").notNullable();
|
|
4459
|
+
t.text("queue_name").notNullable();
|
|
4220
4460
|
t.text("service_group").notNullable();
|
|
4461
|
+
t.integer("instance_number").notNullable().defaultTo(1);
|
|
4221
4462
|
t.text("service_name").notNullable();
|
|
4222
|
-
t.text("
|
|
4223
|
-
t.text("hostname");
|
|
4463
|
+
t.text("server_name").notNullable();
|
|
4224
4464
|
t.integer("pid");
|
|
4225
4465
|
t.json("metadata");
|
|
4226
4466
|
t.timestamp("created_at").notNullable().defaultTo(db.fn.now());
|
|
4227
4467
|
t.timestamp("last_seen_at").notNullable().defaultTo(db.fn.now());
|
|
4228
|
-
t.unique(["
|
|
4229
|
-
t.index(["
|
|
4230
|
-
t.index(["
|
|
4468
|
+
t.unique(["queue_name", "service_name"], `${registryTable}_queue_name_service_name_uniq`);
|
|
4469
|
+
t.index(["queue_name", "service_group", "last_seen_at"], `${registryTable}_queue_group_seen_idx`);
|
|
4470
|
+
t.index(["queue_name", "last_seen_at"], `${registryTable}_queue_seen_idx`);
|
|
4231
4471
|
});
|
|
4232
4472
|
}
|
|
4233
4473
|
}
|
|
4234
4474
|
async function enqueueTask(context, options) {
|
|
4235
4475
|
const db = getDb(context);
|
|
4236
|
-
const
|
|
4237
|
-
const { tasksTable } = queueToTableNames(
|
|
4476
|
+
const queueName = options.queueName ?? "tasks";
|
|
4477
|
+
const { tasksTable } = queueToTableNames(queueName);
|
|
4238
4478
|
const id = (0, import_node_crypto.randomUUID)();
|
|
4479
|
+
const name = options.name ?? options.task;
|
|
4480
|
+
if (!name) {
|
|
4481
|
+
throw new Error("enqueueTask: name (or task) is required");
|
|
4482
|
+
}
|
|
4483
|
+
const schedule = options.schedule?.trim() ? options.schedule : null;
|
|
4484
|
+
let nextRunAt = null;
|
|
4485
|
+
if (options.nextRunAt !== void 0) {
|
|
4486
|
+
nextRunAt = options.nextRunAt == null ? null : new Date(options.nextRunAt);
|
|
4487
|
+
} else if (schedule) {
|
|
4488
|
+
nextRunAt = nextTimeMatch(schedule, /* @__PURE__ */ new Date());
|
|
4489
|
+
}
|
|
4239
4490
|
await db(tasksTable).insert({
|
|
4240
4491
|
id,
|
|
4241
|
-
|
|
4242
|
-
task: options.task,
|
|
4492
|
+
name,
|
|
4243
4493
|
params: toJsonColumn(options.params ?? null),
|
|
4244
4494
|
opid: options.opid ?? null,
|
|
4245
|
-
priority: options.priority ??
|
|
4246
|
-
schedule
|
|
4495
|
+
priority: options.priority ?? 50,
|
|
4496
|
+
schedule,
|
|
4497
|
+
next_run_at: nextRunAt,
|
|
4498
|
+
service_group: options.serviceGroup ?? null,
|
|
4499
|
+
instance_number: options.instanceNumber ?? null,
|
|
4500
|
+
service_name: options.serviceName ?? null,
|
|
4501
|
+
server_name: options.serverName ?? null,
|
|
4502
|
+
status: "idle",
|
|
4503
|
+
status_changed_at: db.fn.now()
|
|
4247
4504
|
});
|
|
4248
4505
|
return id;
|
|
4249
4506
|
}
|
|
@@ -4254,7 +4511,7 @@ async function updateTaskProgress(context, tasksTable, taskId, progress) {
|
|
|
4254
4511
|
});
|
|
4255
4512
|
}
|
|
4256
4513
|
|
|
4257
|
-
// src/tasks/servicesRegistry.
|
|
4514
|
+
// src/tasks/servicesRegistry.js
|
|
4258
4515
|
function getDb2(context) {
|
|
4259
4516
|
const db = context.db;
|
|
4260
4517
|
if (!db) {
|
|
@@ -4268,7 +4525,7 @@ function parseMetadataColumn(value) {
|
|
|
4268
4525
|
if (typeof value === "string") {
|
|
4269
4526
|
try {
|
|
4270
4527
|
const p = JSON.parse(value);
|
|
4271
|
-
return p && typeof p === "object" && !Array.isArray(
|
|
4528
|
+
return p && typeof p === "object" && !Array.isArray(value) ? p : {};
|
|
4272
4529
|
} catch {
|
|
4273
4530
|
return {};
|
|
4274
4531
|
}
|
|
@@ -4278,6 +4535,7 @@ function parseMetadataColumn(value) {
|
|
|
4278
4535
|
var DEFAULT_GROUP_MAX_INSTANCES = {
|
|
4279
4536
|
intake: 1,
|
|
4280
4537
|
harvest: 1,
|
|
4538
|
+
harvester: 0,
|
|
4281
4539
|
loader: 0,
|
|
4282
4540
|
photos: 0,
|
|
4283
4541
|
photosprocessor: 0,
|
|
@@ -4287,25 +4545,6 @@ function sanitizeNamePart(raw) {
|
|
|
4287
4545
|
const s = String(raw || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4288
4546
|
return s.slice(0, 80) || "runner";
|
|
4289
4547
|
}
|
|
4290
|
-
function identityFilePath(identityDir, queue, serviceGroup) {
|
|
4291
|
-
const safeQ = sanitizeNamePart(queue);
|
|
4292
|
-
const safeG = sanitizeNamePart(serviceGroup);
|
|
4293
|
-
return import_node_path.default.join(identityDir, `${safeQ}_${safeG}.json`);
|
|
4294
|
-
}
|
|
4295
|
-
async function readIdentityFile(filePath) {
|
|
4296
|
-
try {
|
|
4297
|
-
const text = await (0, import_promises.readFile)(filePath, "utf8");
|
|
4298
|
-
const parsed = JSON.parse(text);
|
|
4299
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4300
|
-
} catch {
|
|
4301
|
-
return {};
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
async function writeIdentityFile(filePath, data) {
|
|
4305
|
-
await (0, import_promises.mkdir)(import_node_path.default.dirname(filePath), { recursive: true });
|
|
4306
|
-
await (0, import_promises.writeFile)(filePath, `${JSON.stringify(data, null, 2)}
|
|
4307
|
-
`, "utf8");
|
|
4308
|
-
}
|
|
4309
4548
|
function resolveMaxInstances(serviceGroup, override) {
|
|
4310
4549
|
if (override !== void 0 && Number.isFinite(override)) {
|
|
4311
4550
|
return Math.max(0, Math.floor(Number(override)));
|
|
@@ -4313,179 +4552,190 @@ function resolveMaxInstances(serviceGroup, override) {
|
|
|
4313
4552
|
const g = serviceGroup.trim().toLowerCase();
|
|
4314
4553
|
return DEFAULT_GROUP_MAX_INSTANCES[g] ?? 0;
|
|
4315
4554
|
}
|
|
4316
|
-
async function countAliveInGroup(db, registryTable,
|
|
4555
|
+
async function countAliveInGroup(db, registryTable, queueName, serviceGroup, staleMs, excludeRowId) {
|
|
4317
4556
|
const cutoff = new Date(Date.now() - staleMs);
|
|
4318
|
-
let q = db(registryTable).where({
|
|
4319
|
-
if (
|
|
4320
|
-
q = q.whereNot("
|
|
4557
|
+
let q = db(registryTable).where({ queue_name: queueName, service_group: serviceGroup }).where("last_seen_at", ">", cutoff);
|
|
4558
|
+
if (excludeRowId) {
|
|
4559
|
+
q = q.whereNot("id", excludeRowId);
|
|
4321
4560
|
}
|
|
4322
4561
|
const row = await q.count("id as count").first();
|
|
4323
4562
|
return Number(row?.count ?? 0);
|
|
4324
4563
|
}
|
|
4564
|
+
async function getOccupiedInstanceSlots(db, registryTable, queueName, serviceGroup, staleMs) {
|
|
4565
|
+
const cutoff = new Date(Date.now() - staleMs);
|
|
4566
|
+
const rows = await db(registryTable).where({ queue_name: queueName, service_group: serviceGroup }).where("last_seen_at", ">", cutoff).select("instance_number");
|
|
4567
|
+
const set = /* @__PURE__ */ new Set();
|
|
4568
|
+
for (const r of rows) {
|
|
4569
|
+
const n = Number(r.instance_number);
|
|
4570
|
+
if (Number.isFinite(n) && n >= 1) set.add(Math.floor(n));
|
|
4571
|
+
}
|
|
4572
|
+
return set;
|
|
4573
|
+
}
|
|
4325
4574
|
function isUniqueViolation(error) {
|
|
4326
4575
|
const code = error?.code ?? error?.errno;
|
|
4327
4576
|
return code === "23505" || String(error?.message || "").includes("duplicate key");
|
|
4328
4577
|
}
|
|
4578
|
+
function buildMetadata(options) {
|
|
4579
|
+
const base = options.metadata && typeof options.metadata === "object" ? { ...options.metadata } : {};
|
|
4580
|
+
if (options.target) {
|
|
4581
|
+
base.runnerTarget = options.target;
|
|
4582
|
+
}
|
|
4583
|
+
return toJsonColumn(Object.keys(base).length ? base : null);
|
|
4584
|
+
}
|
|
4585
|
+
function allocateInstanceNumber(occupied, explicit, maxSlots) {
|
|
4586
|
+
if (explicit !== void 0 && explicit !== null && Number.isFinite(Number(explicit))) {
|
|
4587
|
+
const e = Math.max(1, Math.floor(Number(explicit)));
|
|
4588
|
+
if (occupied.has(e)) {
|
|
4589
|
+
throw new Error(`[services-registry] instance slot ${e} is already occupied by an alive peer`);
|
|
4590
|
+
}
|
|
4591
|
+
if (maxSlots !== void 0 && maxSlots > 0 && e > maxSlots) {
|
|
4592
|
+
throw new Error(`[services-registry] instance ${e} exceeds configured max slots ${maxSlots} for this group`);
|
|
4593
|
+
}
|
|
4594
|
+
return e;
|
|
4595
|
+
}
|
|
4596
|
+
const cap = maxSlots !== void 0 && maxSlots > 0 ? maxSlots : 1e4;
|
|
4597
|
+
for (let n = 1; n <= cap; n++) {
|
|
4598
|
+
if (!occupied.has(n)) return n;
|
|
4599
|
+
}
|
|
4600
|
+
throw new Error(`[services-registry] no free instance slot (searched 1..${cap})`);
|
|
4601
|
+
}
|
|
4602
|
+
function defaultServiceName(groupBase, hostBase, instanceNumber) {
|
|
4603
|
+
return `${groupBase}-${hostBase}-${instanceNumber}`;
|
|
4604
|
+
}
|
|
4329
4605
|
async function registerInServicesRegistry(context, options) {
|
|
4330
4606
|
const db = getDb2(context);
|
|
4331
|
-
const registryTable =
|
|
4607
|
+
const registryTable = queueToTableNames(options.queueName).registryTable;
|
|
4332
4608
|
const serviceGroup = options.serviceGroup.trim();
|
|
4333
4609
|
if (!serviceGroup) {
|
|
4334
4610
|
throw new Error("registerInServicesRegistry: serviceGroup is required");
|
|
4335
4611
|
}
|
|
4336
|
-
const
|
|
4337
|
-
let identity = await readIdentityFile(identityPath);
|
|
4338
|
-
let instanceId = typeof identity.instanceId === "string" && identity.instanceId.trim() ? identity.instanceId.trim() : (0, import_node_crypto2.randomUUID)();
|
|
4339
|
-
identity.instanceId = instanceId;
|
|
4340
|
-
await writeIdentityFile(identityPath, identity);
|
|
4341
|
-
const hostname = import_node_os.default.hostname();
|
|
4612
|
+
const serverName = import_node_os.default.hostname();
|
|
4342
4613
|
const pid = typeof process.pid === "number" ? process.pid : null;
|
|
4343
|
-
const meta =
|
|
4344
|
-
const
|
|
4345
|
-
|
|
4346
|
-
await db(registryTable).where({ instance_id: instanceId }).update({
|
|
4347
|
-
target: options.target,
|
|
4348
|
-
hostname,
|
|
4349
|
-
pid,
|
|
4350
|
-
metadata: meta,
|
|
4351
|
-
last_seen_at: db.fn.now()
|
|
4352
|
-
});
|
|
4353
|
-
const serviceName = String(existing.service_name);
|
|
4354
|
-
identity.serviceName = serviceName;
|
|
4355
|
-
await writeIdentityFile(identityPath, identity);
|
|
4356
|
-
const reg = {
|
|
4357
|
-
instanceId,
|
|
4358
|
-
serviceName,
|
|
4359
|
-
serviceGroup,
|
|
4360
|
-
queue: options.queue,
|
|
4361
|
-
target: options.target,
|
|
4362
|
-
rowId: String(existing.id)
|
|
4363
|
-
};
|
|
4364
|
-
context.servicesRegistry = reg;
|
|
4365
|
-
context.runnerHeartbeat = reg;
|
|
4366
|
-
context.logger.info?.(
|
|
4367
|
-
`[services-registry] resumed instance_id=${instanceId} name=${serviceName} group=${serviceGroup} queue=${options.queue}`
|
|
4368
|
-
);
|
|
4369
|
-
return {
|
|
4370
|
-
instanceId,
|
|
4371
|
-
serviceName,
|
|
4372
|
-
serviceGroup,
|
|
4373
|
-
queue: options.queue,
|
|
4374
|
-
target: options.target,
|
|
4375
|
-
rowId: String(existing.id),
|
|
4376
|
-
registryTable
|
|
4377
|
-
};
|
|
4378
|
-
}
|
|
4614
|
+
const meta = buildMetadata(options);
|
|
4615
|
+
const groupBase = sanitizeNamePart(serviceGroup);
|
|
4616
|
+
const hostBase = sanitizeNamePart(serverName);
|
|
4379
4617
|
const maxAllowed = resolveMaxInstances(serviceGroup, options.groupMaxInstances);
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
options.queue,
|
|
4384
|
-
serviceGroup,
|
|
4385
|
-
options.staleMs,
|
|
4386
|
-
instanceId
|
|
4387
|
-
);
|
|
4388
|
-
if (maxAllowed > 0 && aliveOthers >= maxAllowed) {
|
|
4389
|
-
const msg = `[services-registry] group limit reached for "${serviceGroup}": ${aliveOthers} alive (max ${maxAllowed}, queue=${options.queue}).`;
|
|
4618
|
+
const aliveCount = await countAliveInGroup(db, registryTable, options.queueName, serviceGroup, options.staleMs, void 0);
|
|
4619
|
+
if (maxAllowed > 0 && aliveCount >= maxAllowed) {
|
|
4620
|
+
const msg = `[services-registry] group limit reached for "${serviceGroup}": ${aliveCount} alive (max ${maxAllowed}, queue=${options.queueName}).`;
|
|
4390
4621
|
if (options.enforceMaxInstances) {
|
|
4391
4622
|
throw new Error(msg);
|
|
4392
4623
|
}
|
|
4393
4624
|
context.logger.warn?.(`${msg} Starting anyway (runnerEnforceMaxInstances=false).`);
|
|
4394
4625
|
}
|
|
4395
|
-
const
|
|
4396
|
-
const
|
|
4397
|
-
const
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4626
|
+
const maxSlots = maxAllowed > 0 ? maxAllowed : void 0;
|
|
4627
|
+
const cutoff = new Date(Date.now() - options.staleMs);
|
|
4628
|
+
const MAX_ATTEMPTS = 8;
|
|
4629
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
4630
|
+
const occupied = await getOccupiedInstanceSlots(db, registryTable, options.queueName, serviceGroup, options.staleMs);
|
|
4631
|
+
const instanceNumber = allocateInstanceNumber(occupied, options.instanceNumber, maxSlots);
|
|
4632
|
+
const serviceNameRaw = options.serviceName?.trim() ? sanitizeNamePart(options.serviceName.trim()) : defaultServiceName(groupBase, hostBase, instanceNumber);
|
|
4633
|
+
const existing = await db(registryTable).where({ queue_name: options.queueName, service_name: serviceNameRaw }).first();
|
|
4634
|
+
if (existing) {
|
|
4635
|
+
const lastSeen = new Date(existing.last_seen_at);
|
|
4636
|
+
const isAlive = !Number.isNaN(lastSeen.getTime()) && lastSeen > cutoff;
|
|
4637
|
+
if (isAlive) {
|
|
4638
|
+
if (options.serviceName?.trim()) {
|
|
4639
|
+
throw new Error(
|
|
4640
|
+
`[services-registry] service_name "${serviceNameRaw}" is already registered by an alive peer`
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
context.logger.warn?.(
|
|
4644
|
+
`[services-registry] service_name "${serviceNameRaw}" already alive; retrying allocation (attempt ${attempt + 1})`
|
|
4645
|
+
);
|
|
4646
|
+
if (options.instanceNumber !== void 0 && options.instanceNumber !== null) {
|
|
4647
|
+
throw new Error(
|
|
4648
|
+
`[services-registry] instance slot ${instanceNumber} / name "${serviceNameRaw}" is already held by an alive peer`
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
await new Promise((r) => setTimeout(r, 50 + attempt * 30));
|
|
4652
|
+
continue;
|
|
4415
4653
|
}
|
|
4654
|
+
await db(registryTable).where({ id: existing.id }).update({
|
|
4655
|
+
server_name: serverName,
|
|
4656
|
+
pid,
|
|
4657
|
+
metadata: meta,
|
|
4658
|
+
service_group: serviceGroup,
|
|
4659
|
+
instance_number: instanceNumber,
|
|
4660
|
+
last_seen_at: db.fn.now()
|
|
4661
|
+
});
|
|
4662
|
+
const reg = {
|
|
4663
|
+
serviceName: serviceNameRaw,
|
|
4664
|
+
serviceGroup,
|
|
4665
|
+
queueName: options.queueName,
|
|
4666
|
+
target: options.target,
|
|
4667
|
+
rowId: String(existing.id),
|
|
4668
|
+
registryTable,
|
|
4669
|
+
instanceNumber
|
|
4670
|
+
};
|
|
4671
|
+
context.servicesRegistry = reg;
|
|
4672
|
+
context.runnerHeartbeat = reg;
|
|
4673
|
+
context.logger.info?.(
|
|
4674
|
+
`[services-registry] took over stale row name=${serviceNameRaw} instance=${instanceNumber} group=${serviceGroup} queue=${options.queueName}`
|
|
4675
|
+
);
|
|
4676
|
+
return reg;
|
|
4416
4677
|
}
|
|
4417
|
-
}
|
|
4418
|
-
let inserted;
|
|
4419
|
-
for (const candidate of eachServiceNameCandidate(baseCandidates)) {
|
|
4420
4678
|
try {
|
|
4421
4679
|
const rows = await db(registryTable).insert({
|
|
4422
|
-
|
|
4423
|
-
queue: options.queue,
|
|
4680
|
+
queue_name: options.queueName,
|
|
4424
4681
|
service_group: serviceGroup,
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4682
|
+
instance_number: instanceNumber,
|
|
4683
|
+
service_name: serviceNameRaw,
|
|
4684
|
+
server_name: serverName,
|
|
4428
4685
|
pid,
|
|
4429
4686
|
metadata: meta,
|
|
4430
|
-
last_seen_at: db.fn.now()
|
|
4687
|
+
last_seen_at: db.fn.now(),
|
|
4688
|
+
created_at: db.fn.now()
|
|
4431
4689
|
}).returning(["id", "service_name"]);
|
|
4432
4690
|
const row = Array.isArray(rows) ? rows[0] : rows;
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4691
|
+
let rowId = row && typeof row === "object" ? String(row.id ?? "") : "";
|
|
4692
|
+
if (!rowId) {
|
|
4693
|
+
const again = await db(registryTable).where({ queue_name: options.queueName, service_name: serviceNameRaw }).first();
|
|
4694
|
+
rowId = again?.id != null ? String(again.id) : "";
|
|
4436
4695
|
}
|
|
4696
|
+
if (!rowId) continue;
|
|
4697
|
+
const regNew = {
|
|
4698
|
+
serviceName: String(row?.service_name ?? serviceNameRaw),
|
|
4699
|
+
serviceGroup,
|
|
4700
|
+
queueName: options.queueName,
|
|
4701
|
+
target: options.target,
|
|
4702
|
+
rowId,
|
|
4703
|
+
registryTable,
|
|
4704
|
+
instanceNumber
|
|
4705
|
+
};
|
|
4706
|
+
context.servicesRegistry = regNew;
|
|
4707
|
+
context.runnerHeartbeat = regNew;
|
|
4708
|
+
context.logger.info?.(
|
|
4709
|
+
`[services-registry] registered name=${regNew.serviceName} instance=${instanceNumber} group=${serviceGroup} queue=${options.queueName}`
|
|
4710
|
+
);
|
|
4711
|
+
return regNew;
|
|
4437
4712
|
} catch (error) {
|
|
4438
4713
|
if (!isUniqueViolation(error)) {
|
|
4439
4714
|
throw error;
|
|
4440
4715
|
}
|
|
4716
|
+
context.logger.warn?.(`[services-registry] insert race on "${serviceNameRaw}", retrying (attempt ${attempt + 1})`);
|
|
4441
4717
|
}
|
|
4442
4718
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
`[services-registry] could not allocate a unique service_name for group=${serviceGroup} queue=${options.queue} (too many collisions).`
|
|
4446
|
-
);
|
|
4447
|
-
}
|
|
4448
|
-
identity.serviceName = inserted.service_name;
|
|
4449
|
-
await writeIdentityFile(identityPath, identity);
|
|
4450
|
-
const regNew = {
|
|
4451
|
-
instanceId,
|
|
4452
|
-
serviceName: inserted.service_name,
|
|
4453
|
-
serviceGroup,
|
|
4454
|
-
queue: options.queue,
|
|
4455
|
-
target: options.target,
|
|
4456
|
-
rowId: inserted.id
|
|
4457
|
-
};
|
|
4458
|
-
context.servicesRegistry = regNew;
|
|
4459
|
-
context.runnerHeartbeat = regNew;
|
|
4460
|
-
context.logger.info?.(
|
|
4461
|
-
`[services-registry] registered instance_id=${instanceId} name=${inserted.service_name} group=${serviceGroup} queue=${options.queue} target=${options.target}`
|
|
4719
|
+
throw new Error(
|
|
4720
|
+
`[services-registry] could not allocate a registry row for group=${serviceGroup} queue=${options.queueName} after ${MAX_ATTEMPTS} attempts`
|
|
4462
4721
|
);
|
|
4463
|
-
return {
|
|
4464
|
-
instanceId,
|
|
4465
|
-
serviceName: inserted.service_name,
|
|
4466
|
-
serviceGroup,
|
|
4467
|
-
queue: options.queue,
|
|
4468
|
-
target: options.target,
|
|
4469
|
-
rowId: inserted.id,
|
|
4470
|
-
registryTable
|
|
4471
|
-
};
|
|
4472
4722
|
}
|
|
4473
4723
|
async function touchServicesRegistry(context, registration) {
|
|
4474
4724
|
const db = getDb2(context);
|
|
4475
|
-
const
|
|
4725
|
+
const serverName = import_node_os.default.hostname();
|
|
4476
4726
|
const pid = typeof process.pid === "number" ? process.pid : null;
|
|
4477
|
-
await db(registration.registryTable).where({
|
|
4727
|
+
await db(registration.registryTable).where({ id: registration.rowId }).update({
|
|
4478
4728
|
last_seen_at: db.fn.now(),
|
|
4479
|
-
|
|
4729
|
+
server_name: serverName,
|
|
4480
4730
|
pid
|
|
4481
4731
|
});
|
|
4482
4732
|
}
|
|
4483
4733
|
async function updateServicesRegistryMetadata(context, registration, patch) {
|
|
4484
4734
|
const db = getDb2(context);
|
|
4485
|
-
const row = await db(registration.registryTable).where({
|
|
4735
|
+
const row = await db(registration.registryTable).where({ id: registration.rowId }).first();
|
|
4486
4736
|
const prev = parseMetadataColumn(row?.metadata);
|
|
4487
4737
|
const merged = { ...prev, ...patch };
|
|
4488
|
-
await db(registration.registryTable).where({
|
|
4738
|
+
await db(registration.registryTable).where({ id: registration.rowId }).update({
|
|
4489
4739
|
metadata: toJsonColumn(merged),
|
|
4490
4740
|
last_seen_at: db.fn.now()
|
|
4491
4741
|
});
|
|
@@ -4493,14 +4743,14 @@ async function updateServicesRegistryMetadata(context, registration, patch) {
|
|
|
4493
4743
|
}
|
|
4494
4744
|
async function unregisterServicesRegistry(context, registration) {
|
|
4495
4745
|
const db = getDb2(context);
|
|
4496
|
-
await db(registration.registryTable).where({
|
|
4497
|
-
context.logger.info?.(`[services-registry] unregistered name=${registration.serviceName}
|
|
4746
|
+
await db(registration.registryTable).where({ id: registration.rowId }).delete();
|
|
4747
|
+
context.logger.info?.(`[services-registry] unregistered name=${registration.serviceName} id=${registration.rowId}`);
|
|
4498
4748
|
}
|
|
4499
|
-
async function listServicesRegistry(context, options = {
|
|
4749
|
+
async function listServicesRegistry(context, options = { queueName: "tasks" }) {
|
|
4500
4750
|
const db = getDb2(context);
|
|
4501
4751
|
const staleMs = options.staleMs ?? 6e4;
|
|
4502
4752
|
const cutoff = new Date(Date.now() - staleMs);
|
|
4503
|
-
const table =
|
|
4753
|
+
const table = queueToTableNames(options.queueName).registryTable;
|
|
4504
4754
|
let q = db(table).where("last_seen_at", ">", cutoff).orderBy([{ column: "service_group", order: "asc" }, { column: "service_name", order: "asc" }]);
|
|
4505
4755
|
if (options.serviceGroup?.trim()) {
|
|
4506
4756
|
q = q.where({ service_group: options.serviceGroup.trim() });
|
|
@@ -4508,7 +4758,8 @@ async function listServicesRegistry(context, options = { queue: "tasks" }) {
|
|
|
4508
4758
|
return await q;
|
|
4509
4759
|
}
|
|
4510
4760
|
|
|
4511
|
-
// src/tasks/taskLogs.
|
|
4761
|
+
// src/tasks/taskLogs.js
|
|
4762
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
4512
4763
|
function getLogsState(context) {
|
|
4513
4764
|
const holder = context;
|
|
4514
4765
|
if (holder.__tasksLogsState) return holder.__tasksLogsState;
|
|
@@ -4561,16 +4812,106 @@ function getLogsState(context) {
|
|
|
4561
4812
|
holder.__tasksLogsState = state;
|
|
4562
4813
|
return state;
|
|
4563
4814
|
}
|
|
4564
|
-
function
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4815
|
+
function ipcLogTargetKey(target) {
|
|
4816
|
+
const bp = target.basePath ?? "";
|
|
4817
|
+
const ns = target.namespace ?? "";
|
|
4818
|
+
return `${bp}::${ns}::${target.tableName}`;
|
|
4819
|
+
}
|
|
4820
|
+
function ipcFileLogsTableNameForSourceResource(source, resource) {
|
|
4821
|
+
const seg = (s) => {
|
|
4822
|
+
const t = String(s).trim().replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4823
|
+
return t.length ? t : "x";
|
|
4824
|
+
};
|
|
4825
|
+
return `${seg(source)}/${seg(resource)}`;
|
|
4826
|
+
}
|
|
4827
|
+
async function readTaskIpcLogsSnapshot(context, options) {
|
|
4828
|
+
const holder = context;
|
|
4829
|
+
const basePath = holder.params?.get?.("tasksLogsBasePath") ?? "./data";
|
|
4830
|
+
const namespace = holder.params?.get?.("tasksLogsNamespace") ?? "tasks-logs";
|
|
4831
|
+
const tableName = ipcFileLogsTableNameForSourceResource(options.source, options.resource);
|
|
4832
|
+
const tail = Math.max(1, Math.min(1e4, Number(options.tail) > 0 ? Number(options.tail) : 100));
|
|
4833
|
+
const fd = new FileDatabase({
|
|
4834
|
+
basePath,
|
|
4835
|
+
namespace,
|
|
4836
|
+
tableName,
|
|
4837
|
+
versioned: true,
|
|
4838
|
+
useMetadata: true,
|
|
4839
|
+
maxVersions: 30,
|
|
4840
|
+
pageSize: 2e3,
|
|
4841
|
+
logger: holder.logger
|
|
4842
|
+
});
|
|
4843
|
+
const versions = await fd.getVersions();
|
|
4844
|
+
if (versions.length === 0) {
|
|
4845
|
+
return { records: [], latestTs: null };
|
|
4846
|
+
}
|
|
4847
|
+
const latest = versions[versions.length - 1];
|
|
4848
|
+
const raw = await fd.read({ version: latest });
|
|
4849
|
+
const arr = Array.isArray(raw) ? raw : [];
|
|
4850
|
+
let filtered = arr;
|
|
4851
|
+
if (options.afterTs && String(options.afterTs).trim()) {
|
|
4852
|
+
const cut = String(options.afterTs).trim();
|
|
4853
|
+
filtered = arr.filter((r) => r && typeof r.ts === "string" && String(r.ts) > cut);
|
|
4854
|
+
}
|
|
4855
|
+
let latestTs = null;
|
|
4856
|
+
for (const r of filtered) {
|
|
4857
|
+
const ts = typeof r?.ts === "string" ? String(r.ts) : null;
|
|
4858
|
+
if (ts && (!latestTs || ts > latestTs)) latestTs = ts;
|
|
4859
|
+
}
|
|
4860
|
+
const incremental = !!(options.afterTs && String(options.afterTs).trim());
|
|
4861
|
+
const maxReturn = incremental ? 1e4 : tail;
|
|
4862
|
+
const sliced = filtered.length > maxReturn ? filtered.slice(-maxReturn) : filtered;
|
|
4863
|
+
return { records: sliced, latestTs };
|
|
4864
|
+
}
|
|
4865
|
+
function resolveIpcFileLogsDir(context, target) {
|
|
4866
|
+
const holder = context;
|
|
4867
|
+
const basePath = target.basePath ?? (holder.params?.get?.("tasksLogsBasePath") || "./data");
|
|
4868
|
+
const namespace = target.namespace ?? (holder.params?.get?.("tasksLogsNamespace") || "tasks-logs");
|
|
4869
|
+
const segments = target.tableName.split("/").filter(Boolean);
|
|
4870
|
+
return import_node_path.default.resolve(basePath, namespace, ...segments);
|
|
4871
|
+
}
|
|
4872
|
+
function getLogsStateForTarget(context, target) {
|
|
4873
|
+
const holder = context;
|
|
4874
|
+
const enabledRaw = holder.params?.get?.("tasksLogsEnabled");
|
|
4875
|
+
const enabled = enabledRaw === void 0 ? true : !!enabledRaw;
|
|
4876
|
+
if (!enabled) return null;
|
|
4877
|
+
if (!holder.__tasksLogsTargetStates) holder.__tasksLogsTargetStates = /* @__PURE__ */ new Map();
|
|
4878
|
+
const map = holder.__tasksLogsTargetStates;
|
|
4879
|
+
const key = ipcLogTargetKey(target);
|
|
4880
|
+
if (map.has(key)) return map.get(key);
|
|
4881
|
+
const basePath = target.basePath ?? (holder.params?.get?.("tasksLogsBasePath") || "./data");
|
|
4882
|
+
const namespace = target.namespace ?? (holder.params?.get?.("tasksLogsNamespace") || "tasks-logs");
|
|
4883
|
+
const maxVersionsRaw = Number(holder.params?.get?.("tasksLogsMaxVersions"));
|
|
4884
|
+
const pageSizeRaw = Number(holder.params?.get?.("tasksLogsPageSize"));
|
|
4885
|
+
const db = new FileDatabase({
|
|
4886
|
+
basePath,
|
|
4887
|
+
namespace,
|
|
4888
|
+
tableName: target.tableName,
|
|
4889
|
+
versioned: true,
|
|
4890
|
+
useMetadata: true,
|
|
4891
|
+
maxVersions: Number.isFinite(maxVersionsRaw) && maxVersionsRaw > 0 ? maxVersionsRaw : 20,
|
|
4892
|
+
pageSize: Number.isFinite(pageSizeRaw) && pageSizeRaw > 0 ? pageSizeRaw : 2e3,
|
|
4893
|
+
logger: holder.logger
|
|
4894
|
+
});
|
|
4895
|
+
const state = {
|
|
4896
|
+
db,
|
|
4897
|
+
errorDb: null,
|
|
4898
|
+
queue: Promise.resolve(),
|
|
4899
|
+
initialized: false,
|
|
4900
|
+
errorInitialized: false
|
|
4901
|
+
};
|
|
4902
|
+
map.set(key, state);
|
|
4903
|
+
return state;
|
|
4904
|
+
}
|
|
4905
|
+
function isErrorPayload(payload) {
|
|
4906
|
+
if (!payload) return false;
|
|
4907
|
+
if (typeof payload === "object") {
|
|
4908
|
+
const level = typeof payload.level === "string" ? payload.level.toLowerCase() : "";
|
|
4909
|
+
if (level === "error" || level === "fatal") return true;
|
|
4910
|
+
if (typeof payload.message === "string" && /\berror\b/i.test(payload.message)) return true;
|
|
4911
|
+
return false;
|
|
4912
|
+
}
|
|
4913
|
+
if (typeof payload === "string") {
|
|
4914
|
+
return /\berror\b/i.test(payload);
|
|
4574
4915
|
}
|
|
4575
4916
|
return false;
|
|
4576
4917
|
}
|
|
@@ -4580,14 +4921,26 @@ function buildLogRecord(task, payload) {
|
|
|
4580
4921
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4581
4922
|
opid: task.opid ?? null,
|
|
4582
4923
|
taskId: task.id,
|
|
4583
|
-
taskName: task.
|
|
4584
|
-
target: task.
|
|
4924
|
+
taskName: task.name,
|
|
4925
|
+
target: task.service_group,
|
|
4585
4926
|
source: typeof params.source === "string" ? params.source : null,
|
|
4586
4927
|
resource: typeof params.resource === "string" ? params.resource : null,
|
|
4587
4928
|
payload
|
|
4588
4929
|
};
|
|
4589
4930
|
}
|
|
4590
|
-
function appendTaskIpcLog(context, task, payload) {
|
|
4931
|
+
function appendTaskIpcLog(context, task, payload, target) {
|
|
4932
|
+
if (target) {
|
|
4933
|
+
const state2 = getLogsStateForTarget(context, target);
|
|
4934
|
+
if (!state2?.db) return;
|
|
4935
|
+
const record2 = buildLogRecord(task, payload);
|
|
4936
|
+
state2.queue = state2.queue.then(async () => {
|
|
4937
|
+
await state2.db.write([record2], { forceNewVersion: !state2.initialized });
|
|
4938
|
+
state2.initialized = true;
|
|
4939
|
+
}).catch((error) => {
|
|
4940
|
+
context.logger.warn?.("[tasks] failed to persist IPC log entry (targeted):", error);
|
|
4941
|
+
});
|
|
4942
|
+
return;
|
|
4943
|
+
}
|
|
4591
4944
|
const state = getLogsState(context);
|
|
4592
4945
|
if (!state.db && !state.errorDb) return;
|
|
4593
4946
|
const record = buildLogRecord(task, payload);
|
|
@@ -4604,84 +4957,293 @@ function appendTaskIpcLog(context, task, payload) {
|
|
|
4604
4957
|
context.logger.warn?.("[tasks] failed to persist IPC log entry:", error);
|
|
4605
4958
|
});
|
|
4606
4959
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
let current = field;
|
|
4616
|
-
while (true) {
|
|
4617
|
-
const match = regex.exec(current);
|
|
4618
|
-
if (!match) break;
|
|
4619
|
-
const raw = match[0];
|
|
4620
|
-
let first = Number(match[1]);
|
|
4621
|
-
let last = Number(match[2]);
|
|
4622
|
-
if (last < first) {
|
|
4623
|
-
[first, last] = [last, first];
|
|
4624
|
-
}
|
|
4625
|
-
const values = [];
|
|
4626
|
-
for (let i = first; i <= last; i += 1) {
|
|
4627
|
-
values.push(i);
|
|
4960
|
+
async function flushTaskIpcLogs(context) {
|
|
4961
|
+
const holder = context;
|
|
4962
|
+
const promises = [];
|
|
4963
|
+
if (holder.__tasksLogsState?.queue) promises.push(holder.__tasksLogsState.queue);
|
|
4964
|
+
const map = holder.__tasksLogsTargetStates;
|
|
4965
|
+
if (map) {
|
|
4966
|
+
for (const s of map.values()) {
|
|
4967
|
+
if (s.queue) promises.push(s.queue);
|
|
4628
4968
|
}
|
|
4629
|
-
current = current.replace(raw, values.join(","));
|
|
4630
|
-
}
|
|
4631
|
-
return current;
|
|
4632
|
-
}
|
|
4633
|
-
function resolveSteps(field) {
|
|
4634
|
-
const match = /^(.+)\/(\d+)$/.exec(field);
|
|
4635
|
-
if (!match) return field;
|
|
4636
|
-
const base = match[1];
|
|
4637
|
-
const step = Number(match[2]);
|
|
4638
|
-
if (!Number.isFinite(step) || step <= 0) return field;
|
|
4639
|
-
return base.split(",").map((v) => Number(v)).filter((v) => Number.isFinite(v) && v % step === 0).join(",");
|
|
4640
|
-
}
|
|
4641
|
-
function convertPattern(pattern) {
|
|
4642
|
-
const parts = pattern.trim().split(/\s+/);
|
|
4643
|
-
if (parts.length !== 6) {
|
|
4644
|
-
throw new Error(`Invalid schedule "${pattern}". Expected 6 fields: sec min hour day month weekday`);
|
|
4645
4969
|
}
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4648
|
-
function fieldMatches(field, value) {
|
|
4649
|
-
const allowed = field.split(",").map((v) => Number(v));
|
|
4650
|
-
return allowed.includes(value);
|
|
4651
|
-
}
|
|
4652
|
-
function timeMatcher(pattern, date = /* @__PURE__ */ new Date()) {
|
|
4653
|
-
const parsed = convertPattern(pattern);
|
|
4654
|
-
return fieldMatches(parsed[0], date.getSeconds()) && fieldMatches(parsed[1], date.getMinutes()) && fieldMatches(parsed[2], date.getHours()) && fieldMatches(parsed[3], date.getDate()) && fieldMatches(parsed[4], date.getMonth() + 1) && fieldMatches(parsed[5], date.getDay());
|
|
4970
|
+
await Promise.all(promises);
|
|
4655
4971
|
}
|
|
4656
4972
|
|
|
4657
|
-
// src/tasks/
|
|
4658
|
-
var
|
|
4659
|
-
|
|
4660
|
-
|
|
4973
|
+
// src/tasks/AbstractTask.js
|
|
4974
|
+
var AbstractTask = class _AbstractTask {
|
|
4975
|
+
/**
|
|
4976
|
+
* Whether `send-task` should wait for completion (and print a result
|
|
4977
|
+
* report) when no explicit `--wait` / `--noWait` flag is given. Defaults
|
|
4978
|
+
* to false; short-lived probe tasks (e.g. `ping`) override to true.
|
|
4979
|
+
*
|
|
4980
|
+
* @type {boolean}
|
|
4981
|
+
*/
|
|
4982
|
+
static defaultWaitForResult = false;
|
|
4983
|
+
/**
|
|
4984
|
+
* @param {object} context Runner context (db, logger, params, emitter...).
|
|
4985
|
+
* @param {object} task Task row as claimed from the queue.
|
|
4986
|
+
*/
|
|
4661
4987
|
constructor(context, task) {
|
|
4662
4988
|
this.context = context;
|
|
4663
4989
|
this.task = task;
|
|
4664
4990
|
}
|
|
4991
|
+
/**
|
|
4992
|
+
* Return a short reason string when the task should be deferred (e.g. "locked
|
|
4993
|
+
* by source"), or `false`/falsy when it is free to run. Default: always `false`.
|
|
4994
|
+
*
|
|
4995
|
+
* @returns {string | false | Promise<string | false>}
|
|
4996
|
+
*/
|
|
4665
4997
|
cantRunReason() {
|
|
4666
4998
|
return false;
|
|
4667
4999
|
}
|
|
5000
|
+
/**
|
|
5001
|
+
* Called by the runner when a stop has been requested. Subclasses running
|
|
5002
|
+
* long loops should flip a flag here and check it between iterations.
|
|
5003
|
+
*
|
|
5004
|
+
* @param {number} [_allowanceMs] Grace period the runner promises before hard exit.
|
|
5005
|
+
*/
|
|
4668
5006
|
requestStop(_allowanceMs) {
|
|
4669
5007
|
}
|
|
5008
|
+
/**
|
|
5009
|
+
* Perform the task. Must be implemented by subclasses.
|
|
5010
|
+
*
|
|
5011
|
+
* @param {(progress: unknown) => Promise<void>} _reportProgress
|
|
5012
|
+
* Updates the DB `progress` column. Accepts any serializable value;
|
|
5013
|
+
* strings are stored verbatim, objects are JSON-stringified.
|
|
5014
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5015
|
+
*/
|
|
5016
|
+
async run(_reportProgress) {
|
|
5017
|
+
throw new Error("AbstractTask.run must be implemented by subclass");
|
|
5018
|
+
}
|
|
5019
|
+
/**
|
|
5020
|
+
* Resolve a complete row payload for this task — envelope fields (queue,
|
|
5021
|
+
* priority, targeting, schedule…) plus the inner `params` blob produced by
|
|
5022
|
+
* {@link AbstractTask.resolveCustomParams}. Output shape matches
|
|
5023
|
+
* {@link enqueueTask}'s `options` argument, so the typical call is:
|
|
5024
|
+
*
|
|
5025
|
+
* const payload = await TaskClass.resolveParams(context, { name });
|
|
5026
|
+
* await enqueueTask(context, payload);
|
|
5027
|
+
*
|
|
5028
|
+
* Validation failures throw {@link ParamError} so the script aborts before
|
|
5029
|
+
* a malformed row hits the DB.
|
|
5030
|
+
*
|
|
5031
|
+
* @param {object} context
|
|
5032
|
+
* @param {Record<string, unknown>} [overrides] Partial overrides; takes precedence over CLI/env.
|
|
5033
|
+
* Recognised keys: `name`, `queueName`, `priority`, `serviceGroup`,
|
|
5034
|
+
* `serviceName`, `instanceNumber`, `serverName`, `opid`, `schedule`,
|
|
5035
|
+
* `nextRunAt`, plus `params` (object — overlay onto inner blob).
|
|
5036
|
+
* @returns {Promise<object>}
|
|
5037
|
+
*/
|
|
5038
|
+
static async resolveParams(context, overrides = {}) {
|
|
5039
|
+
const main = _AbstractTask._resolveMainFields(context, overrides);
|
|
5040
|
+
const params = await this.resolveCustomParams(context, overrides);
|
|
5041
|
+
return { ...main, params };
|
|
5042
|
+
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Resolve the inner JSON blob stored in the `params` column. Default
|
|
5045
|
+
* implementation passes through `--paramsJson` (parsed as a JSON object)
|
|
5046
|
+
* overlaid with `overrides.params` when supplied; returns `null` when
|
|
5047
|
+
* neither is provided.
|
|
5048
|
+
*
|
|
5049
|
+
* Subclasses with typed fields should override and call
|
|
5050
|
+
* {@link AbstractTask._mergeTypedParams} for layered CLI/env/JSON/override
|
|
5051
|
+
* resolution, then validate and throw {@link ParamError} on bad input.
|
|
5052
|
+
*
|
|
5053
|
+
* @param {object} context
|
|
5054
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5055
|
+
* @returns {Promise<object|null>}
|
|
5056
|
+
*/
|
|
5057
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5058
|
+
return _AbstractTask._defaultParamsBlob(context, overrides);
|
|
5059
|
+
}
|
|
5060
|
+
/**
|
|
5061
|
+
* Read main task envelope fields from `context.params` (CLI/env), with
|
|
5062
|
+
* any matching key on `overrides` taking precedence. Internal; called by
|
|
5063
|
+
* {@link AbstractTask.resolveParams}.
|
|
5064
|
+
*
|
|
5065
|
+
* @param {object} context
|
|
5066
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5067
|
+
* @returns {object}
|
|
5068
|
+
*/
|
|
5069
|
+
static _resolveMainFields(context, overrides = {}) {
|
|
5070
|
+
const defs = {
|
|
5071
|
+
queueName: "string default tasks",
|
|
5072
|
+
priority: "number default 50",
|
|
5073
|
+
serviceGroup: "string",
|
|
5074
|
+
serviceName: "string",
|
|
5075
|
+
instanceNumber: "number",
|
|
5076
|
+
serverName: "string",
|
|
5077
|
+
opid: "string",
|
|
5078
|
+
schedule: "string"
|
|
5079
|
+
};
|
|
5080
|
+
const cli = context.params.getAllForModule("task-envelope", defs);
|
|
5081
|
+
const name = emptyToUndef(overrides.name) ?? emptyToUndef(context.params.get("name", "string"));
|
|
5082
|
+
if (!name) {
|
|
5083
|
+
throw new ParamError("Task --name is required (e.g. ping, stop, dummyHarvest)");
|
|
5084
|
+
}
|
|
5085
|
+
let instanceNumber;
|
|
5086
|
+
const rawInstance = overrides.instanceNumber ?? cli.instanceNumber;
|
|
5087
|
+
if (rawInstance !== void 0 && rawInstance !== null && String(rawInstance).trim() !== "") {
|
|
5088
|
+
const n = Number(rawInstance);
|
|
5089
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
5090
|
+
throw new ParamError("--instanceNumber must be a positive integer when set");
|
|
5091
|
+
}
|
|
5092
|
+
instanceNumber = n;
|
|
5093
|
+
} else {
|
|
5094
|
+
instanceNumber = null;
|
|
5095
|
+
}
|
|
5096
|
+
const priorityRaw = overrides.priority ?? cli.priority ?? 50;
|
|
5097
|
+
const priority = Number(priorityRaw);
|
|
5098
|
+
if (!Number.isFinite(priority)) {
|
|
5099
|
+
throw new ParamError(`--priority must be a number (got ${JSON.stringify(priorityRaw)})`);
|
|
5100
|
+
}
|
|
5101
|
+
return {
|
|
5102
|
+
name,
|
|
5103
|
+
queueName: overrides.queueName ?? emptyToUndef(cli.queueName) ?? "tasks",
|
|
5104
|
+
priority,
|
|
5105
|
+
serviceGroup: emptyToUndef(overrides.serviceGroup) ?? emptyToUndef(cli.serviceGroup) ?? null,
|
|
5106
|
+
serviceName: emptyToUndef(overrides.serviceName) ?? emptyToUndef(cli.serviceName) ?? null,
|
|
5107
|
+
instanceNumber,
|
|
5108
|
+
serverName: emptyToUndef(overrides.serverName) ?? emptyToUndef(cli.serverName) ?? null,
|
|
5109
|
+
opid: emptyToUndef(overrides.opid) ?? emptyToUndef(cli.opid) ?? null,
|
|
5110
|
+
schedule: emptyToUndef(overrides.schedule) ?? emptyToUndef(cli.schedule) ?? null,
|
|
5111
|
+
nextRunAt: overrides.nextRunAt ?? null
|
|
5112
|
+
};
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* Default inner-params resolver: parses `--paramsJson` (must be a JSON
|
|
5116
|
+
* object), then overlays `overrides.params` on top. Returns `null` when
|
|
5117
|
+
* neither is provided.
|
|
5118
|
+
*
|
|
5119
|
+
* @param {object} context
|
|
5120
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5121
|
+
* @returns {object|null}
|
|
5122
|
+
*/
|
|
5123
|
+
static _defaultParamsBlob(context, overrides = {}) {
|
|
5124
|
+
const cli = context.params.getAllForModule("task-params", { paramsJson: "string" });
|
|
5125
|
+
const fromJson = parseParamsJson(cli.paramsJson);
|
|
5126
|
+
const fromOverride = pickParamsObject(overrides);
|
|
5127
|
+
if (!fromJson && !fromOverride) return null;
|
|
5128
|
+
return { ...fromJson ?? {}, ...fromOverride ?? {} };
|
|
5129
|
+
}
|
|
5130
|
+
/**
|
|
5131
|
+
* Helper for subclass `resolveCustomParams` overrides. Reads typed CLI
|
|
5132
|
+
* params (per `defs`) plus `--paramsJson` under a module namespace, then
|
|
5133
|
+
* merges them with explicit `overrides.params` in increasing priority:
|
|
5134
|
+
*
|
|
5135
|
+
* typed CLI flags → --paramsJson → overrides.params
|
|
5136
|
+
*
|
|
5137
|
+
* Undefined values are dropped so defaults declared in `defs` aren't
|
|
5138
|
+
* overwritten by missing-flag noise. Returns the merged object; the
|
|
5139
|
+
* caller is responsible for validation and throwing `ParamError`.
|
|
5140
|
+
*
|
|
5141
|
+
* @param {object} context
|
|
5142
|
+
* @param {string} moduleName Namespace for `--showUsedParams` grouping.
|
|
5143
|
+
* @param {Record<string, string>} defs Param defs in `getAllForModule` syntax.
|
|
5144
|
+
* @param {Record<string, unknown>} [overrides] As passed to `resolveCustomParams`.
|
|
5145
|
+
* @returns {Record<string, unknown>}
|
|
5146
|
+
*/
|
|
5147
|
+
static _mergeTypedParams(context, moduleName, defs, overrides = {}) {
|
|
5148
|
+
const fullDefs = { ...defs, paramsJson: "string" };
|
|
5149
|
+
const cliRaw = context.params.getAllForModule(moduleName, fullDefs);
|
|
5150
|
+
const fromJson = parseParamsJson(cliRaw.paramsJson) ?? {};
|
|
5151
|
+
const fromCli = {};
|
|
5152
|
+
for (const [k, v] of Object.entries(cliRaw)) {
|
|
5153
|
+
if (k === "paramsJson") continue;
|
|
5154
|
+
if (v !== void 0 && v !== null) fromCli[k] = v;
|
|
5155
|
+
}
|
|
5156
|
+
const fromOverride = pickParamsObject(overrides) ?? {};
|
|
5157
|
+
return { ...fromCli, ...fromJson, ...fromOverride };
|
|
5158
|
+
}
|
|
4670
5159
|
};
|
|
5160
|
+
function emptyToUndef(s) {
|
|
5161
|
+
if (s === void 0 || s === null) return void 0;
|
|
5162
|
+
if (typeof s !== "string") return s;
|
|
5163
|
+
const t = s.trim();
|
|
5164
|
+
return t.length ? t : void 0;
|
|
5165
|
+
}
|
|
5166
|
+
function parseParamsJson(raw) {
|
|
5167
|
+
if (raw == null) return null;
|
|
5168
|
+
const t = String(raw).trim();
|
|
5169
|
+
if (!t) return null;
|
|
5170
|
+
let parsed;
|
|
5171
|
+
try {
|
|
5172
|
+
parsed = JSON.parse(t);
|
|
5173
|
+
} catch (e) {
|
|
5174
|
+
throw new ParamError(`--paramsJson: not valid JSON: ${e?.message ?? String(e)}`);
|
|
5175
|
+
}
|
|
5176
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5177
|
+
throw new ParamError("--paramsJson must be a JSON object");
|
|
5178
|
+
}
|
|
5179
|
+
return parsed;
|
|
5180
|
+
}
|
|
5181
|
+
function pickParamsObject(overrides) {
|
|
5182
|
+
const p = overrides?.params;
|
|
5183
|
+
if (p && typeof p === "object" && !Array.isArray(p)) return p;
|
|
5184
|
+
return void 0;
|
|
5185
|
+
}
|
|
4671
5186
|
|
|
4672
|
-
// src/tasks/coreTasks/TaskPing.
|
|
4673
|
-
var TaskPing = class extends
|
|
5187
|
+
// src/tasks/coreTasks/TaskPing.js
|
|
5188
|
+
var TaskPing = class extends AbstractTask {
|
|
5189
|
+
/** Default-wait so `send-task --name=ping` prints a result without `--wait`. */
|
|
5190
|
+
static defaultWaitForResult = true;
|
|
5191
|
+
/** Ping takes no params. */
|
|
5192
|
+
static async resolveCustomParams() {
|
|
5193
|
+
return null;
|
|
5194
|
+
}
|
|
5195
|
+
/**
|
|
5196
|
+
* @returns {Promise<{ success: true, results: "pong" }>}
|
|
5197
|
+
*/
|
|
4674
5198
|
async run() {
|
|
4675
5199
|
this.context.logger.info?.(`[TaskPing] pong (${this.task.id})`);
|
|
4676
5200
|
return { success: true, results: "pong" };
|
|
4677
5201
|
}
|
|
4678
5202
|
};
|
|
4679
5203
|
|
|
4680
|
-
// src/tasks/coreTasks/TaskSampleProcess.
|
|
4681
|
-
var TaskSampleProcess = class extends
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
5204
|
+
// src/tasks/coreTasks/TaskSampleProcess.js
|
|
5205
|
+
var TaskSampleProcess = class extends AbstractTask {
|
|
5206
|
+
/**
|
|
5207
|
+
* @param {object} context
|
|
5208
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5209
|
+
* @returns {Promise<{ total: number, delay: number, name?: string }>}
|
|
5210
|
+
*/
|
|
5211
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5212
|
+
const merged = AbstractTask._mergeTypedParams(context, "task-sample-process", {
|
|
5213
|
+
total: "number default 10",
|
|
5214
|
+
delay: "number default 1000",
|
|
5215
|
+
name: "string"
|
|
5216
|
+
}, overrides);
|
|
5217
|
+
const total = Number(merged.total);
|
|
5218
|
+
const delay = Number(merged.delay);
|
|
5219
|
+
if (!Number.isInteger(total) || total <= 0) {
|
|
5220
|
+
throw new ParamError(`sampleProcess: param "total" must be a positive integer (got ${JSON.stringify(merged.total)})`);
|
|
5221
|
+
}
|
|
5222
|
+
if (!Number.isInteger(delay) || delay < 0) {
|
|
5223
|
+
throw new ParamError(`sampleProcess: param "delay" must be an integer >= 0 (got ${JSON.stringify(merged.delay)})`);
|
|
5224
|
+
}
|
|
5225
|
+
const out = { total, delay };
|
|
5226
|
+
if (typeof merged.name === "string" && merged.name.trim()) {
|
|
5227
|
+
out.name = merged.name.trim();
|
|
5228
|
+
}
|
|
5229
|
+
return out;
|
|
5230
|
+
}
|
|
5231
|
+
/**
|
|
5232
|
+
* @param {object} context
|
|
5233
|
+
* @param {object} task
|
|
5234
|
+
*/
|
|
5235
|
+
constructor(context, task) {
|
|
5236
|
+
super(context, task);
|
|
5237
|
+
this.stopRequested = false;
|
|
5238
|
+
this.stopAllowanceMs = 0;
|
|
5239
|
+
this.stopDecisionLogged = false;
|
|
5240
|
+
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Runner-facing stop signal. Records the allowance window so the main loop
|
|
5243
|
+
* can decide per-iteration whether to finish or abort early.
|
|
5244
|
+
*
|
|
5245
|
+
* @param {number} allowanceMs
|
|
5246
|
+
*/
|
|
4685
5247
|
requestStop(allowanceMs) {
|
|
4686
5248
|
this.stopRequested = true;
|
|
4687
5249
|
this.stopAllowanceMs = Number.isFinite(allowanceMs) && allowanceMs > 0 ? allowanceMs : 0;
|
|
@@ -4689,6 +5251,14 @@ var TaskSampleProcess = class extends TaskMaster {
|
|
|
4689
5251
|
`[TaskSampleProcess] stop signal received (${this.task.id}), allowanceMs=${this.stopAllowanceMs}`
|
|
4690
5252
|
);
|
|
4691
5253
|
}
|
|
5254
|
+
/**
|
|
5255
|
+
* Iterate `total` times, sleeping `delay` ms between ticks and reporting
|
|
5256
|
+
* progress every iteration. Validates params up front; invalid values short-
|
|
5257
|
+
* circuit to a structured failure without starting the loop.
|
|
5258
|
+
*
|
|
5259
|
+
* @param {(progress: object) => Promise<void>} reportProgress
|
|
5260
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5261
|
+
*/
|
|
4692
5262
|
async run(reportProgress) {
|
|
4693
5263
|
const totalRaw = this.task?.params?.total ?? 10;
|
|
4694
5264
|
const delayRaw = this.task?.params?.delay ?? 1e3;
|
|
@@ -4770,7 +5340,7 @@ var TaskSampleProcess = class extends TaskMaster {
|
|
|
4770
5340
|
}
|
|
4771
5341
|
};
|
|
4772
5342
|
|
|
4773
|
-
// src/tasks/coreTasks/TaskShellCommand.
|
|
5343
|
+
// src/tasks/coreTasks/TaskShellCommand.js
|
|
4774
5344
|
var import_node_child_process = require("child_process");
|
|
4775
5345
|
function runShellCommand(command, cwd) {
|
|
4776
5346
|
return new Promise((resolve2, reject) => {
|
|
@@ -4800,7 +5370,27 @@ function runShellCommand(command, cwd) {
|
|
|
4800
5370
|
});
|
|
4801
5371
|
});
|
|
4802
5372
|
}
|
|
4803
|
-
var TaskShellCommand = class extends
|
|
5373
|
+
var TaskShellCommand = class extends AbstractTask {
|
|
5374
|
+
/**
|
|
5375
|
+
* @param {object} context
|
|
5376
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5377
|
+
* @returns {Promise<{ command: string, cwd?: string }>}
|
|
5378
|
+
*/
|
|
5379
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5380
|
+
const merged = AbstractTask._mergeTypedParams(context, "task-shell", {
|
|
5381
|
+
command: "string",
|
|
5382
|
+
cwd: "string"
|
|
5383
|
+
}, overrides);
|
|
5384
|
+
const command = typeof merged.command === "string" ? merged.command.trim() : "";
|
|
5385
|
+
if (!command) {
|
|
5386
|
+
throw new ParamError('shellCommand: param "command" must be a non-empty string');
|
|
5387
|
+
}
|
|
5388
|
+
const cwd = typeof merged.cwd === "string" && merged.cwd.trim() ? merged.cwd.trim() : null;
|
|
5389
|
+
return cwd ? { command, cwd } : { command };
|
|
5390
|
+
}
|
|
5391
|
+
/**
|
|
5392
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5393
|
+
*/
|
|
4804
5394
|
async run() {
|
|
4805
5395
|
const params = this.task?.params;
|
|
4806
5396
|
const commandRaw = typeof params === "string" ? params : params?.command;
|
|
@@ -4849,9 +5439,9 @@ var TaskShellCommand = class extends TaskMaster {
|
|
|
4849
5439
|
}
|
|
4850
5440
|
};
|
|
4851
5441
|
|
|
4852
|
-
// src/tasks/coreTasks/TaskSystemInfo.
|
|
5442
|
+
// src/tasks/coreTasks/TaskSystemInfo.js
|
|
4853
5443
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4854
|
-
var
|
|
5444
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
4855
5445
|
function toGb(valueBytes) {
|
|
4856
5446
|
return `${(valueBytes / 1024 ** 3).toFixed(2)} GB`;
|
|
4857
5447
|
}
|
|
@@ -4859,7 +5449,7 @@ function toMb(valueBytes) {
|
|
|
4859
5449
|
return `${(valueBytes / 1024 ** 2).toFixed(2)} MB`;
|
|
4860
5450
|
}
|
|
4861
5451
|
async function getDiskStats() {
|
|
4862
|
-
const stats = await
|
|
5452
|
+
const stats = await import_promises.default.statfs("/");
|
|
4863
5453
|
const total = Number(stats.bsize) * Number(stats.blocks);
|
|
4864
5454
|
const free = Number(stats.bsize) * Number(stats.bavail);
|
|
4865
5455
|
const used = total - free;
|
|
@@ -4869,7 +5459,16 @@ async function getDiskStats() {
|
|
|
4869
5459
|
free: toGb(free)
|
|
4870
5460
|
};
|
|
4871
5461
|
}
|
|
4872
|
-
var TaskSystemInfo = class extends
|
|
5462
|
+
var TaskSystemInfo = class extends AbstractTask {
|
|
5463
|
+
/** Same UX expectation as `ping` — short probe, print the result. */
|
|
5464
|
+
static defaultWaitForResult = true;
|
|
5465
|
+
/** systemInfo takes no params. */
|
|
5466
|
+
static async resolveCustomParams() {
|
|
5467
|
+
return null;
|
|
5468
|
+
}
|
|
5469
|
+
/**
|
|
5470
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5471
|
+
*/
|
|
4873
5472
|
async run() {
|
|
4874
5473
|
try {
|
|
4875
5474
|
const totalMemory = import_node_os2.default.totalmem();
|
|
@@ -4921,8 +5520,31 @@ var TaskSystemInfo = class extends TaskMaster {
|
|
|
4921
5520
|
}
|
|
4922
5521
|
};
|
|
4923
5522
|
|
|
4924
|
-
// src/tasks/coreTasks/TaskSumAB.
|
|
4925
|
-
var TaskSumAB = class extends
|
|
5523
|
+
// src/tasks/coreTasks/TaskSumAB.js
|
|
5524
|
+
var TaskSumAB = class extends AbstractTask {
|
|
5525
|
+
/** Short, deterministic — wait by default so callers see the sum. */
|
|
5526
|
+
static defaultWaitForResult = true;
|
|
5527
|
+
/**
|
|
5528
|
+
* @param {object} context
|
|
5529
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5530
|
+
* @returns {Promise<{ a: number, b: number }>}
|
|
5531
|
+
*/
|
|
5532
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5533
|
+
const merged = AbstractTask._mergeTypedParams(context, "task-sumab", {
|
|
5534
|
+
a: "number",
|
|
5535
|
+
b: "number"
|
|
5536
|
+
}, overrides);
|
|
5537
|
+
if (typeof merged.a !== "number" || Number.isNaN(merged.a)) {
|
|
5538
|
+
throw new ParamError(`taskSumAB: param "a" must be a valid number (got ${JSON.stringify(merged.a)})`);
|
|
5539
|
+
}
|
|
5540
|
+
if (typeof merged.b !== "number" || Number.isNaN(merged.b)) {
|
|
5541
|
+
throw new ParamError(`taskSumAB: param "b" must be a valid number (got ${JSON.stringify(merged.b)})`);
|
|
5542
|
+
}
|
|
5543
|
+
return { a: merged.a, b: merged.b };
|
|
5544
|
+
}
|
|
5545
|
+
/**
|
|
5546
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5547
|
+
*/
|
|
4926
5548
|
async run() {
|
|
4927
5549
|
const a = this.task?.params?.a;
|
|
4928
5550
|
const b = this.task?.params?.b;
|
|
@@ -4953,8 +5575,46 @@ var TaskSumAB = class extends TaskMaster {
|
|
|
4953
5575
|
}
|
|
4954
5576
|
};
|
|
4955
5577
|
|
|
4956
|
-
// src/tasks/coreTasks/TaskStopRunner.
|
|
4957
|
-
var TaskStopRunner = class extends
|
|
5578
|
+
// src/tasks/coreTasks/TaskStopRunner.js
|
|
5579
|
+
var TaskStopRunner = class extends AbstractTask {
|
|
5580
|
+
/**
|
|
5581
|
+
* Stop tasks must target a concrete instance — without `serviceName` the
|
|
5582
|
+
* row would race against any worker on the queue. Layered on top of the
|
|
5583
|
+
* envelope built by {@link AbstractTask.resolveParams}.
|
|
5584
|
+
*
|
|
5585
|
+
* @param {object} context
|
|
5586
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5587
|
+
* @returns {Promise<object>}
|
|
5588
|
+
*/
|
|
5589
|
+
static async resolveParams(context, overrides = {}) {
|
|
5590
|
+
const main = await super.resolveParams(context, overrides);
|
|
5591
|
+
if (!main.serviceName) {
|
|
5592
|
+
throw new ParamError(
|
|
5593
|
+
"stop/stopRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
|
|
5594
|
+
);
|
|
5595
|
+
}
|
|
5596
|
+
return main;
|
|
5597
|
+
}
|
|
5598
|
+
/**
|
|
5599
|
+
* @param {object} context
|
|
5600
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5601
|
+
* @returns {Promise<{ allowanceMs: number }>}
|
|
5602
|
+
*/
|
|
5603
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5604
|
+
const merged = AbstractTask._mergeTypedParams(context, "task-stop", {
|
|
5605
|
+
allowanceMs: "number default 5000"
|
|
5606
|
+
}, overrides);
|
|
5607
|
+
const allowanceMs = Number(merged.allowanceMs);
|
|
5608
|
+
if (!Number.isFinite(allowanceMs) || allowanceMs < 0) {
|
|
5609
|
+
throw new ParamError(
|
|
5610
|
+
`stopRunner: allowanceMs must be a non-negative number (got ${JSON.stringify(merged.allowanceMs)})`
|
|
5611
|
+
);
|
|
5612
|
+
}
|
|
5613
|
+
return { allowanceMs };
|
|
5614
|
+
}
|
|
5615
|
+
/**
|
|
5616
|
+
* @returns {Promise<{ success: true, results: { stopRunner: true, allowanceMs: number, message: string } }>}
|
|
5617
|
+
*/
|
|
4958
5618
|
async run() {
|
|
4959
5619
|
const allowanceMs = Number(this.task?.params?.allowanceMs ?? 5e3);
|
|
4960
5620
|
this.context.logger.warn?.(`[TaskStopRunner] stop requested (allowanceMs=${allowanceMs})`);
|
|
@@ -4969,172 +5629,396 @@ var TaskStopRunner = class extends TaskMaster {
|
|
|
4969
5629
|
}
|
|
4970
5630
|
};
|
|
4971
5631
|
|
|
4972
|
-
// src/tasks/
|
|
5632
|
+
// src/tasks/coreTasks/TaskGetLogs.js
|
|
5633
|
+
var TaskGetLogs = class extends AbstractTask {
|
|
5634
|
+
/**
|
|
5635
|
+
* @param {object} context
|
|
5636
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5637
|
+
* @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
|
|
5638
|
+
*/
|
|
5639
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
5640
|
+
const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
|
|
5641
|
+
source: "string",
|
|
5642
|
+
resource: "string",
|
|
5643
|
+
tail: "number default 100",
|
|
5644
|
+
afterTs: "string"
|
|
5645
|
+
}, overrides);
|
|
5646
|
+
const source = typeof merged.source === "string" ? merged.source.trim() : "";
|
|
5647
|
+
const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
|
|
5648
|
+
if (!source) throw new ParamError('getLogs: param "source" is required');
|
|
5649
|
+
if (!resource) throw new ParamError('getLogs: param "resource" is required');
|
|
5650
|
+
let tail = Number(merged.tail);
|
|
5651
|
+
if (!Number.isFinite(tail) || tail < 1) tail = 100;
|
|
5652
|
+
tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
|
|
5653
|
+
const out = { source, resource, tail };
|
|
5654
|
+
if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
|
|
5655
|
+
out.afterTs = merged.afterTs.trim();
|
|
5656
|
+
}
|
|
5657
|
+
return out;
|
|
5658
|
+
}
|
|
5659
|
+
/**
|
|
5660
|
+
* @param {unknown} _reportProgress Unused (single-shot read, no progress events).
|
|
5661
|
+
* @returns {Promise<{ success: boolean, results: unknown }>}
|
|
5662
|
+
*/
|
|
5663
|
+
async run(_reportProgress) {
|
|
5664
|
+
const p = this.task.params ?? {};
|
|
5665
|
+
const source = String(p.source ?? "").trim();
|
|
5666
|
+
const resource = String(p.resource ?? "").trim();
|
|
5667
|
+
const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
|
|
5668
|
+
const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
|
|
5669
|
+
if (!source || !resource) {
|
|
5670
|
+
return {
|
|
5671
|
+
success: false,
|
|
5672
|
+
results: { error: 'getLogs requires params "source" and "resource"' }
|
|
5673
|
+
};
|
|
5674
|
+
}
|
|
5675
|
+
try {
|
|
5676
|
+
const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
|
|
5677
|
+
source,
|
|
5678
|
+
resource,
|
|
5679
|
+
tail,
|
|
5680
|
+
afterTs
|
|
5681
|
+
});
|
|
5682
|
+
return {
|
|
5683
|
+
success: true,
|
|
5684
|
+
results: { records, latestTs, source, resource }
|
|
5685
|
+
};
|
|
5686
|
+
} catch (e) {
|
|
5687
|
+
return {
|
|
5688
|
+
success: false,
|
|
5689
|
+
results: { error: e?.message ?? String(e) }
|
|
5690
|
+
};
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
};
|
|
5694
|
+
|
|
5695
|
+
// src/tasks/TasksRegistry.js
|
|
4973
5696
|
var TasksRegistry = class _TasksRegistry {
|
|
4974
|
-
|
|
5697
|
+
/**
|
|
5698
|
+
* @param {Record<string, Function>} [initial] Optional seed entries to copy in.
|
|
5699
|
+
*/
|
|
4975
5700
|
constructor(initial) {
|
|
5701
|
+
this.map = {};
|
|
4976
5702
|
if (initial) {
|
|
4977
5703
|
this.addMany(initial);
|
|
4978
5704
|
}
|
|
4979
5705
|
}
|
|
5706
|
+
/**
|
|
5707
|
+
* Build a registry pre-populated with every core task plus legacy aliases.
|
|
5708
|
+
* Prefer this over `new TasksRegistry()` for anything that wants `ping`/`stop`/etc.
|
|
5709
|
+
*
|
|
5710
|
+
* @returns {TasksRegistry}
|
|
5711
|
+
*/
|
|
4980
5712
|
static withCoreTasks() {
|
|
4981
|
-
return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner);
|
|
5713
|
+
return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("getLogs", TaskGetLogs);
|
|
4982
5714
|
}
|
|
5715
|
+
/**
|
|
5716
|
+
* Register a single task class under a name. Overwrites any previous entry.
|
|
5717
|
+
*
|
|
5718
|
+
* @param {string} taskName
|
|
5719
|
+
* @param {Function} taskClass Subclass of `AbstractTask`.
|
|
5720
|
+
* @returns {this}
|
|
5721
|
+
*/
|
|
4983
5722
|
add(taskName, taskClass) {
|
|
4984
5723
|
this.map[taskName] = taskClass;
|
|
4985
5724
|
return this;
|
|
4986
5725
|
}
|
|
5726
|
+
/**
|
|
5727
|
+
* Bulk-register a name → class map. Later calls override earlier ones.
|
|
5728
|
+
*
|
|
5729
|
+
* @param {Record<string, Function>} entries
|
|
5730
|
+
* @returns {this}
|
|
5731
|
+
*/
|
|
4987
5732
|
addMany(entries) {
|
|
4988
5733
|
for (const [name, klass] of Object.entries(entries)) {
|
|
4989
5734
|
this.add(name, klass);
|
|
4990
5735
|
}
|
|
4991
5736
|
return this;
|
|
4992
5737
|
}
|
|
5738
|
+
/**
|
|
5739
|
+
* Look up a task class by name. Returns `undefined` when the name is unknown;
|
|
5740
|
+
* the runner treats that as "some other worker may handle this" and skips.
|
|
5741
|
+
*
|
|
5742
|
+
* @param {string} taskName
|
|
5743
|
+
* @returns {Function | undefined}
|
|
5744
|
+
*/
|
|
4993
5745
|
get(taskName) {
|
|
4994
5746
|
return this.map[taskName];
|
|
4995
5747
|
}
|
|
5748
|
+
/**
|
|
5749
|
+
* Strict variant of {@link get}: throws {@link ParamError} (with the list
|
|
5750
|
+
* of supported names) when `taskName` is unknown. Use from enqueuer code
|
|
5751
|
+
* paths where an unknown name is a hard CLI/programmer error.
|
|
5752
|
+
*
|
|
5753
|
+
* @param {string} taskName
|
|
5754
|
+
* @returns {Function}
|
|
5755
|
+
*/
|
|
5756
|
+
requireClass(taskName) {
|
|
5757
|
+
const TaskClass = taskName ? this.map[taskName] : void 0;
|
|
5758
|
+
if (!TaskClass) {
|
|
5759
|
+
const supported = this.listSupportedTasks().join(", ") || "(none)";
|
|
5760
|
+
throw new ParamError(
|
|
5761
|
+
`Unknown task "${taskName ?? ""}". Supported on this registry: ${supported}`
|
|
5762
|
+
);
|
|
5763
|
+
}
|
|
5764
|
+
return TaskClass;
|
|
5765
|
+
}
|
|
5766
|
+
/**
|
|
5767
|
+
* Dispatcher used by `send-task` / programmatic enqueuers: pick `name`
|
|
5768
|
+
* from `overrides` or `context.params`, look up the class, and delegate
|
|
5769
|
+
* to its static {@link AbstractTask.resolveParams} with `name` seeded into
|
|
5770
|
+
* the overrides. The returned object is shaped for {@link enqueueTask}.
|
|
5771
|
+
*
|
|
5772
|
+
* Validation failures (unknown task, missing required custom params, etc.)
|
|
5773
|
+
* surface as {@link ParamError} so the caller aborts cleanly before any
|
|
5774
|
+
* row is inserted.
|
|
5775
|
+
*
|
|
5776
|
+
* @param {object} context
|
|
5777
|
+
* @param {Record<string, unknown>} [overrides]
|
|
5778
|
+
* @returns {Promise<object>}
|
|
5779
|
+
*/
|
|
5780
|
+
async resolveTaskParams(context, overrides = {}) {
|
|
5781
|
+
const overrideName = typeof overrides.name === "string" ? overrides.name.trim() : overrides.name;
|
|
5782
|
+
const fromCli = context.params.get("name", "string");
|
|
5783
|
+
const cliName = typeof fromCli === "string" ? fromCli.trim() : fromCli;
|
|
5784
|
+
const name = overrideName || cliName;
|
|
5785
|
+
if (!name) {
|
|
5786
|
+
throw new ParamError("Task --name is required (e.g. ping, stop, dummyHarvest)");
|
|
5787
|
+
}
|
|
5788
|
+
const TaskClass = this.requireClass(name);
|
|
5789
|
+
return TaskClass.resolveParams(context, { ...overrides, name });
|
|
5790
|
+
}
|
|
5791
|
+
/**
|
|
5792
|
+
* Names of every registered task, sorted alphabetically (useful for CLI output
|
|
5793
|
+
* and allowlist sanity checks).
|
|
5794
|
+
*
|
|
5795
|
+
* @returns {string[]}
|
|
5796
|
+
*/
|
|
4996
5797
|
listSupportedTasks() {
|
|
4997
5798
|
return Object.keys(this.map).sort();
|
|
4998
5799
|
}
|
|
5800
|
+
/**
|
|
5801
|
+
* Shallow copy of the internal map, for handing to `addMany` on another registry
|
|
5802
|
+
* or for serialization.
|
|
5803
|
+
*
|
|
5804
|
+
* @returns {Record<string, Function>}
|
|
5805
|
+
*/
|
|
4999
5806
|
toObject() {
|
|
5000
5807
|
return { ...this.map };
|
|
5001
5808
|
}
|
|
5002
5809
|
};
|
|
5003
5810
|
|
|
5004
|
-
// src/tasks/
|
|
5811
|
+
// src/tasks/serviceTaskAllowlist.js
|
|
5812
|
+
var SERVICE_TASK_NAMES = [
|
|
5813
|
+
"ping",
|
|
5814
|
+
"stop",
|
|
5815
|
+
"stopRunner",
|
|
5816
|
+
"shellCommand",
|
|
5817
|
+
"systemInfo",
|
|
5818
|
+
"info",
|
|
5819
|
+
"getLogs"
|
|
5820
|
+
];
|
|
5821
|
+
function normalizeAllowedTasks(value) {
|
|
5822
|
+
if (!value) return void 0;
|
|
5823
|
+
if (Array.isArray(value)) {
|
|
5824
|
+
const out2 = value.map((v) => String(v).trim()).filter(Boolean);
|
|
5825
|
+
return out2.length ? out2 : void 0;
|
|
5826
|
+
}
|
|
5827
|
+
const out = String(value).split(",").map((v) => v.trim()).filter(Boolean);
|
|
5828
|
+
return out.length ? out : void 0;
|
|
5829
|
+
}
|
|
5830
|
+
function mergeAllowedTasksWithServiceTasks(names) {
|
|
5831
|
+
const set = /* @__PURE__ */ new Set([...SERVICE_TASK_NAMES, ...names ?? []]);
|
|
5832
|
+
return Array.from(set).sort();
|
|
5833
|
+
}
|
|
5834
|
+
|
|
5835
|
+
// src/tasks/taskScriptRunner.js
|
|
5005
5836
|
var import_node_child_process2 = require("child_process");
|
|
5837
|
+
var MAX_PROGRESS_TEXT_LEN = 4e3;
|
|
5006
5838
|
function toCliArgs(args = []) {
|
|
5007
5839
|
return args.filter((a) => typeof a === "string" && a.length > 0);
|
|
5008
5840
|
}
|
|
5009
5841
|
function formatChildLogPrefix(task) {
|
|
5010
|
-
return `${task.
|
|
5842
|
+
return `${task.name}:${task.id.slice(0, 8)}${task.opid ? `:${task.opid}` : ""}`;
|
|
5011
5843
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5844
|
+
function isProgressPayload(payload) {
|
|
5845
|
+
if (!payload || typeof payload !== "object") return false;
|
|
5846
|
+
if (payload.level !== "progress") return false;
|
|
5847
|
+
const count = Number(payload.count);
|
|
5848
|
+
const total = Number(payload.total);
|
|
5849
|
+
return Number.isFinite(count) && Number.isFinite(total) && total > 0;
|
|
5850
|
+
}
|
|
5851
|
+
function formatProgressText(payload, fallbackPrefix) {
|
|
5852
|
+
const pfx = payload.prefix ? `${payload.prefix} ` : fallbackPrefix ? `${fallbackPrefix} ` : "";
|
|
5853
|
+
const label = typeof payload.message === "string" && payload.message ? `${payload.message} ` : "";
|
|
5854
|
+
return `${pfx}${label}${payload.count}/${payload.total}`;
|
|
5855
|
+
}
|
|
5856
|
+
function forwardChildLogToParent(context, prefix, message) {
|
|
5857
|
+
if (!message || typeof message !== "object") return;
|
|
5858
|
+
const text = typeof message.message === "string" ? message.message : null;
|
|
5859
|
+
if (!text) return;
|
|
5860
|
+
const level = typeof message.level === "string" ? message.level.toLowerCase() : "";
|
|
5861
|
+
const line = `[child:${prefix}] ${text}`;
|
|
5862
|
+
const logger = context.logger;
|
|
5863
|
+
switch (level) {
|
|
5864
|
+
case "error":
|
|
5865
|
+
case "fatal":
|
|
5866
|
+
logger.error?.(line);
|
|
5867
|
+
return;
|
|
5868
|
+
case "warn":
|
|
5869
|
+
case "warning":
|
|
5870
|
+
logger.warn?.(line);
|
|
5871
|
+
return;
|
|
5872
|
+
case "debug":
|
|
5873
|
+
logger.debug?.(line);
|
|
5874
|
+
return;
|
|
5875
|
+
case "info":
|
|
5876
|
+
default:
|
|
5877
|
+
logger.info?.(line);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
function buildNodeArgs(scriptPath, cliArgs) {
|
|
5014
5881
|
const inheritedExecArgs = Array.isArray(process.execArgv) ? [...process.execArgv] : [];
|
|
5015
5882
|
const hasTsRuntimeInParent = inheritedExecArgs.some((arg) => /tsx|ts-node/i.test(arg));
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5883
|
+
return hasTsRuntimeInParent ? [...inheritedExecArgs, scriptPath, ...cliArgs] : ["--import", "tsx", scriptPath, ...cliArgs];
|
|
5884
|
+
}
|
|
5885
|
+
function resolveTasksTableName(context) {
|
|
5886
|
+
return context.tasksQueueName || context.params?.get?.("table") || "tasks";
|
|
5887
|
+
}
|
|
5888
|
+
function announceIpcFileLogsTarget(context, options) {
|
|
5889
|
+
if (!options.ipcFileLogs) return;
|
|
5890
|
+
const logsDir = resolveIpcFileLogsDir(context, options.ipcFileLogs);
|
|
5891
|
+
const enabledRaw = context.params?.get?.("tasksLogsEnabled");
|
|
5892
|
+
const logsEnabled = enabledRaw === void 0 ? true : !!enabledRaw;
|
|
5893
|
+
context.logger.info?.(
|
|
5894
|
+
`[tasks] IPC file logs: ${logsDir}` + (logsEnabled ? "" : " (tasksLogsEnabled=false; not persisted)")
|
|
5030
5895
|
);
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
let
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
await db(tasksTable).where({ id: options.task.id }).update({ progress: text.slice(0, 4e3) });
|
|
5044
|
-
}).catch((error) => {
|
|
5045
|
-
context.logger.warn?.(
|
|
5046
|
-
`[tasks] failed to update progress for task=${options.task.id}: ${error?.message ?? String(error)}`
|
|
5047
|
-
);
|
|
5048
|
-
});
|
|
5049
|
-
if (options.onProgress) {
|
|
5050
|
-
progressCallbackChain = progressCallbackChain.then(async () => {
|
|
5051
|
-
await options.onProgress?.(text.slice(0, 4e3));
|
|
5052
|
-
}).catch((error) => {
|
|
5053
|
-
context.logger.warn?.(
|
|
5054
|
-
`[tasks] reportProgress callback failed for task=${options.task.id}: ${error?.message ?? String(error)}`
|
|
5055
|
-
);
|
|
5896
|
+
}
|
|
5897
|
+
function createSerializedQueue() {
|
|
5898
|
+
let chain = Promise.resolve();
|
|
5899
|
+
return {
|
|
5900
|
+
push(fn) {
|
|
5901
|
+
chain = chain.then(fn, () => {
|
|
5902
|
+
}).catch(() => {
|
|
5903
|
+
});
|
|
5904
|
+
return chain;
|
|
5905
|
+
},
|
|
5906
|
+
drain() {
|
|
5907
|
+
return chain.catch(() => {
|
|
5056
5908
|
});
|
|
5057
5909
|
}
|
|
5058
5910
|
};
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5911
|
+
}
|
|
5912
|
+
async function runNodeTaskScript(context, options) {
|
|
5913
|
+
const cliArgs = toCliArgs(["--route=ipc", "--mode=json", ...options.args || []]);
|
|
5914
|
+
const nodeArgs = buildNodeArgs(options.scriptPath, cliArgs);
|
|
5915
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, nodeArgs, {
|
|
5916
|
+
cwd: options.cwd || process.cwd(),
|
|
5917
|
+
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
5918
|
+
env: {
|
|
5919
|
+
...process.env,
|
|
5920
|
+
TASK_ID: options.task.id,
|
|
5921
|
+
TASK_NAME: options.task.name,
|
|
5922
|
+
TASK_OPID: options.task.opid || ""
|
|
5923
|
+
}
|
|
5924
|
+
});
|
|
5925
|
+
announceIpcFileLogsTarget(context, options);
|
|
5926
|
+
const prefix = formatChildLogPrefix(options.task);
|
|
5927
|
+
const tasksTable = resolveTasksTableName(context);
|
|
5928
|
+
const progressQueue = createSerializedQueue();
|
|
5929
|
+
const state = {
|
|
5930
|
+
stdout: "",
|
|
5931
|
+
stderr: "",
|
|
5932
|
+
workerResult: null,
|
|
5933
|
+
hadErrorMessage: false
|
|
5072
5934
|
};
|
|
5073
|
-
|
|
5935
|
+
const writeProgress = (text) => {
|
|
5936
|
+
const trimmed = typeof text === "string" ? text.slice(0, MAX_PROGRESS_TEXT_LEN) : "";
|
|
5937
|
+
if (!trimmed) return;
|
|
5938
|
+
progressQueue.push(async () => {
|
|
5939
|
+
const db = context.db;
|
|
5940
|
+
if (db) {
|
|
5941
|
+
try {
|
|
5942
|
+
await db(tasksTable).where({ id: options.task.id }).update({ progress: trimmed });
|
|
5943
|
+
} catch (error) {
|
|
5944
|
+
context.logger.warn?.(
|
|
5945
|
+
`[tasks] failed to update progress for task=${options.task.id}: ${error?.message ?? String(error)}`
|
|
5946
|
+
);
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
if (options.onProgress) {
|
|
5950
|
+
try {
|
|
5951
|
+
await options.onProgress(trimmed);
|
|
5952
|
+
} catch (error) {
|
|
5953
|
+
context.logger.warn?.(
|
|
5954
|
+
`[tasks] reportProgress callback failed for task=${options.task.id}: ${error?.message ?? String(error)}`
|
|
5955
|
+
);
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
});
|
|
5959
|
+
};
|
|
5960
|
+
child.stdout?.on("data", (chunk) => {
|
|
5074
5961
|
const text = String(chunk);
|
|
5075
|
-
stdout += text;
|
|
5962
|
+
state.stdout += text;
|
|
5076
5963
|
if (text.trim()) {
|
|
5077
5964
|
context.logger.info?.(`[child:${prefix}] ${text.trimEnd()}`);
|
|
5078
|
-
updateProgress(text.trim().replace(/\s+/g, " ").slice(0, 400));
|
|
5079
5965
|
}
|
|
5080
5966
|
});
|
|
5081
|
-
child.stderr
|
|
5967
|
+
child.stderr?.on("data", (chunk) => {
|
|
5082
5968
|
const text = String(chunk);
|
|
5083
|
-
stderr += text;
|
|
5969
|
+
state.stderr += text;
|
|
5084
5970
|
if (text.trim()) {
|
|
5085
5971
|
context.logger.warn?.(`[child:${prefix}] ${text.trimEnd()}`);
|
|
5086
5972
|
}
|
|
5087
5973
|
});
|
|
5088
5974
|
child.on("message", (message) => {
|
|
5089
5975
|
if (message && typeof message === "object" && "__taskWorkerResult" in message) {
|
|
5090
|
-
workerResult = message.__taskWorkerResult;
|
|
5976
|
+
state.workerResult = message.__taskWorkerResult;
|
|
5091
5977
|
return;
|
|
5092
5978
|
}
|
|
5093
5979
|
if (message && typeof message === "object") {
|
|
5094
5980
|
const level = typeof message.level === "string" ? message.level.toLowerCase() : "";
|
|
5095
5981
|
if (level === "error" || level === "fatal") {
|
|
5096
|
-
hadErrorMessage = true;
|
|
5982
|
+
state.hadErrorMessage = true;
|
|
5097
5983
|
}
|
|
5098
5984
|
}
|
|
5099
|
-
appendTaskIpcLog(context, options.task, message);
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
context.logger.info?.(`[child:${prefix}] ${progressText}`);
|
|
5114
|
-
}
|
|
5115
|
-
} else {
|
|
5116
|
-
context.logger.info?.(`[child:${prefix}] ${progressText}`);
|
|
5117
|
-
}
|
|
5985
|
+
appendTaskIpcLog(context, options.task, message, options.ipcFileLogs);
|
|
5986
|
+
try {
|
|
5987
|
+
options.onChildIpcMessage?.(message);
|
|
5988
|
+
} catch (e) {
|
|
5989
|
+
context.logger.warn?.(`[tasks] onChildIpcMessage failed: ${e?.message ?? String(e)}`);
|
|
5990
|
+
}
|
|
5991
|
+
if (isProgressPayload(message)) {
|
|
5992
|
+
context.logger.progress(message.message || "progress", {
|
|
5993
|
+
prefix: message.prefix || prefix,
|
|
5994
|
+
count: Number(message.count),
|
|
5995
|
+
total: Number(message.total)
|
|
5996
|
+
});
|
|
5997
|
+
writeProgress(formatProgressText(message, prefix));
|
|
5998
|
+
return;
|
|
5118
5999
|
}
|
|
6000
|
+
forwardChildLogToParent(context, prefix, message);
|
|
5119
6001
|
});
|
|
5120
6002
|
return await new Promise((resolve2, reject) => {
|
|
5121
6003
|
child.on("error", (error) => reject(error));
|
|
5122
6004
|
child.on("close", (exitCode, signal) => {
|
|
5123
|
-
|
|
6005
|
+
void (async () => {
|
|
6006
|
+
await flushTaskIpcLogs(context);
|
|
6007
|
+
await progressQueue.drain();
|
|
5124
6008
|
resolve2({
|
|
5125
6009
|
exitCode,
|
|
5126
6010
|
signal,
|
|
5127
|
-
stdout: stdout.trim(),
|
|
5128
|
-
stderr: stderr.trim(),
|
|
5129
|
-
workerResult,
|
|
5130
|
-
hadErrorMessage
|
|
6011
|
+
stdout: state.stdout.trim(),
|
|
6012
|
+
stderr: state.stderr.trim(),
|
|
6013
|
+
workerResult: state.workerResult,
|
|
6014
|
+
hadErrorMessage: state.hadErrorMessage
|
|
5131
6015
|
});
|
|
5132
|
-
});
|
|
6016
|
+
})();
|
|
5133
6017
|
});
|
|
5134
6018
|
});
|
|
5135
6019
|
}
|
|
5136
6020
|
|
|
5137
|
-
// src/tasks/index.
|
|
6021
|
+
// src/tasks/index.js
|
|
5138
6022
|
var LOCKED_BY_ERROR_MESSAGE = "locked by error";
|
|
5139
6023
|
var defaultTasksRegistry = TasksRegistry.withCoreTasks();
|
|
5140
6024
|
function getDb3(context) {
|
|
@@ -5149,22 +6033,13 @@ function normalizeRegistry(registry) {
|
|
|
5149
6033
|
if (registry instanceof TasksRegistry) return registry;
|
|
5150
6034
|
return new TasksRegistry().addMany(registry);
|
|
5151
6035
|
}
|
|
5152
|
-
function
|
|
5153
|
-
if (!value) return void 0;
|
|
5154
|
-
if (Array.isArray(value)) {
|
|
5155
|
-
const out2 = value.map((v) => String(v).trim()).filter(Boolean);
|
|
5156
|
-
return out2.length ? out2 : void 0;
|
|
5157
|
-
}
|
|
5158
|
-
const out = String(value).split(",").map((v) => v.trim()).filter(Boolean);
|
|
5159
|
-
return out.length ? out : void 0;
|
|
5160
|
-
}
|
|
5161
|
-
async function enqueueStopTask(context, target, queue = "tasks", allowanceMs = 5e3) {
|
|
6036
|
+
async function enqueueStopTask(context, serviceGroup, queueName = "tasks", allowanceMs = 5e3) {
|
|
5162
6037
|
return enqueueTask(context, {
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
task: "stopRunner",
|
|
6038
|
+
queueName,
|
|
6039
|
+
name: "stopRunner",
|
|
5166
6040
|
params: { allowanceMs },
|
|
5167
|
-
priority:
|
|
6041
|
+
priority: 0,
|
|
6042
|
+
serviceGroup
|
|
5168
6043
|
});
|
|
5169
6044
|
}
|
|
5170
6045
|
async function signalRunningTasksStop(context, runningTaskInstances, allowanceMs) {
|
|
@@ -5182,18 +6057,20 @@ async function signalRunningTasksStop(context, runningTaskInstances, allowanceMs
|
|
|
5182
6057
|
}
|
|
5183
6058
|
async function executeClaimedTask(context, tasksTable, historyTable, row, registry, runningTaskInstances) {
|
|
5184
6059
|
const db = getDb3(context);
|
|
5185
|
-
const taskName = row.
|
|
6060
|
+
const taskName = row.name;
|
|
5186
6061
|
const TaskClass = registry.get(taskName);
|
|
5187
|
-
const { paused_at: _pausedAt, ...rowForHistory } = row;
|
|
5188
6062
|
if (!TaskClass) {
|
|
5189
6063
|
const err = { message: `Unknown task "${taskName}"` };
|
|
5190
|
-
await db(historyTable).insert(
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
6064
|
+
await db(historyTable).insert(
|
|
6065
|
+
taskHistoryInsertFromQueueRow(row, {
|
|
6066
|
+
completed_at: /* @__PURE__ */ new Date(),
|
|
6067
|
+
success: false,
|
|
6068
|
+
status: "failed",
|
|
6069
|
+
status_changed_at: db.fn.now(),
|
|
6070
|
+
params: toJsonColumn(row.params),
|
|
6071
|
+
results: toJsonColumn(err)
|
|
6072
|
+
})
|
|
6073
|
+
);
|
|
5197
6074
|
if (row.schedule) {
|
|
5198
6075
|
await db(tasksTable).where({ id: row.id }).update({
|
|
5199
6076
|
started_at: null,
|
|
@@ -5201,7 +6078,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
5201
6078
|
success: false,
|
|
5202
6079
|
results: toJsonColumn(err),
|
|
5203
6080
|
past_due: null,
|
|
5204
|
-
|
|
6081
|
+
status: "paused",
|
|
6082
|
+
status_changed_at: db.fn.now(),
|
|
5205
6083
|
progress: LOCKED_BY_ERROR_MESSAGE
|
|
5206
6084
|
});
|
|
5207
6085
|
} else {
|
|
@@ -5228,13 +6106,16 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
5228
6106
|
} finally {
|
|
5229
6107
|
runningTaskInstances.delete(row.id);
|
|
5230
6108
|
}
|
|
5231
|
-
await db(historyTable).insert(
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
6109
|
+
await db(historyTable).insert(
|
|
6110
|
+
taskHistoryInsertFromQueueRow(row, {
|
|
6111
|
+
completed_at: /* @__PURE__ */ new Date(),
|
|
6112
|
+
success,
|
|
6113
|
+
status: success ? "completed" : "failed",
|
|
6114
|
+
status_changed_at: db.fn.now(),
|
|
6115
|
+
params: toJsonColumn(row.params),
|
|
6116
|
+
results: toJsonColumn(results)
|
|
6117
|
+
})
|
|
6118
|
+
);
|
|
5238
6119
|
if (!success) {
|
|
5239
6120
|
const dbName = String(context?.params?.get?.("dbName") || "local");
|
|
5240
6121
|
const tableName = String(context?.params?.get?.("table") || "tasks");
|
|
@@ -5249,19 +6130,32 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
5249
6130
|
const rerunCommand = results && typeof results === "object" && results.rerunCommand ? results.rerunCommand : fallbackRecoverCommand;
|
|
5250
6131
|
appendTaskIpcLog(context, row, {
|
|
5251
6132
|
level: "error",
|
|
5252
|
-
message: `[tasks] task failed: ${row.
|
|
6133
|
+
message: `[tasks] task failed: ${row.name} id=${row.id}. Once problem is fixed, re-run: ${rerunCommand}`,
|
|
5253
6134
|
details: results
|
|
5254
6135
|
});
|
|
5255
6136
|
}
|
|
5256
6137
|
if (row.schedule) {
|
|
5257
6138
|
if (success) {
|
|
6139
|
+
let nextRunAt = null;
|
|
6140
|
+
try {
|
|
6141
|
+
nextRunAt = nextTimeMatch(row.schedule, /* @__PURE__ */ new Date());
|
|
6142
|
+
} catch (e) {
|
|
6143
|
+
context.logger?.warn?.(`[tasks] nextTimeMatch after success for task ${row.id}: ${e?.message ?? String(e)}`);
|
|
6144
|
+
}
|
|
5258
6145
|
await db(tasksTable).where({ id: row.id }).update({
|
|
5259
6146
|
started_at: null,
|
|
5260
6147
|
completed_at: /* @__PURE__ */ new Date(),
|
|
5261
6148
|
success,
|
|
5262
6149
|
results: toJsonColumn(results),
|
|
5263
6150
|
progress: null,
|
|
5264
|
-
past_due: null
|
|
6151
|
+
past_due: null,
|
|
6152
|
+
status: "idle",
|
|
6153
|
+
status_changed_at: db.fn.now(),
|
|
6154
|
+
next_run_at: nextRunAt,
|
|
6155
|
+
// Claim overwrites these; clear so idle rows stay “any worker” (see claimNextRunnableTask).
|
|
6156
|
+
service_name: null,
|
|
6157
|
+
server_name: null,
|
|
6158
|
+
instance_number: null
|
|
5265
6159
|
});
|
|
5266
6160
|
} else {
|
|
5267
6161
|
await db(tasksTable).where({ id: row.id }).update({
|
|
@@ -5269,7 +6163,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
5269
6163
|
completed_at: /* @__PURE__ */ new Date(),
|
|
5270
6164
|
success,
|
|
5271
6165
|
results: toJsonColumn(results),
|
|
5272
|
-
|
|
6166
|
+
status: "paused",
|
|
6167
|
+
status_changed_at: db.fn.now(),
|
|
5273
6168
|
progress: LOCKED_BY_ERROR_MESSAGE,
|
|
5274
6169
|
past_due: null
|
|
5275
6170
|
});
|
|
@@ -5281,58 +6176,93 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
5281
6176
|
const stopAllowanceMs = stopRunnerRequested ? Number(results.allowanceMs ?? 5e3) : 0;
|
|
5282
6177
|
return { stopRunnerRequested, stopAllowanceMs };
|
|
5283
6178
|
}
|
|
5284
|
-
|
|
6179
|
+
function shuffleTaskRowsInPlace(rows) {
|
|
6180
|
+
for (let i = rows.length - 1; i > 0; i--) {
|
|
6181
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
6182
|
+
const t = rows[i];
|
|
6183
|
+
rows[i] = rows[j];
|
|
6184
|
+
rows[j] = t;
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry, scanLimit, taskNames, runnerIdentity) {
|
|
5285
6188
|
const db = getDb3(context);
|
|
5286
|
-
let query = db(tasksTable).
|
|
6189
|
+
let query = db(tasksTable).where({ status: "idle" }).where(function() {
|
|
6190
|
+
this.whereNull("service_group").orWhere({ service_group: serviceGroup });
|
|
6191
|
+
}).orderByRaw("CASE WHEN past_due IS NULL THEN 1 ELSE 0 END ASC").orderBy([{ column: "priority", order: "asc" }]).orderByRaw("CASE WHEN completed_at IS NULL THEN 0 ELSE 1 END ASC").orderBy([{ column: "completed_at", order: "asc" }, { column: "created_at", order: "asc" }]).limit(scanLimit);
|
|
5287
6192
|
if (taskNames && taskNames.length > 0) {
|
|
5288
|
-
query = query.whereIn("
|
|
6193
|
+
query = query.whereIn("name", taskNames);
|
|
6194
|
+
}
|
|
6195
|
+
if (runnerIdentity) {
|
|
6196
|
+
query = query.where(function() {
|
|
6197
|
+
this.whereNull("service_name").orWhere({ service_name: runnerIdentity.service_name });
|
|
6198
|
+
}).where(function() {
|
|
6199
|
+
this.whereNull("instance_number").orWhere({ instance_number: runnerIdentity.instance_number });
|
|
6200
|
+
}).where(function() {
|
|
6201
|
+
this.whereNull("server_name").orWhere({ server_name: runnerIdentity.server_name });
|
|
6202
|
+
});
|
|
6203
|
+
} else {
|
|
6204
|
+
query = query.whereNull("service_name").whereNull("instance_number").whereNull("server_name");
|
|
5289
6205
|
}
|
|
5290
6206
|
const candidates = await query;
|
|
6207
|
+
shuffleTaskRowsInPlace(candidates);
|
|
5291
6208
|
for (const row of candidates) {
|
|
5292
6209
|
if (!row.past_due && row.schedule && !timeMatcher(row.schedule)) {
|
|
5293
6210
|
continue;
|
|
5294
6211
|
}
|
|
5295
|
-
const TaskClass = registry.get(row.
|
|
5296
|
-
if (TaskClass) {
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
6212
|
+
const TaskClass = registry.get(row.name);
|
|
6213
|
+
if (!TaskClass) {
|
|
6214
|
+
continue;
|
|
6215
|
+
}
|
|
6216
|
+
const taskInstance = new TaskClass(context, row);
|
|
6217
|
+
const reason = taskInstance.cantRunReason ? await taskInstance.cantRunReason() : null;
|
|
6218
|
+
if (reason) {
|
|
6219
|
+
if (!row.past_due) {
|
|
6220
|
+
await db(tasksTable).where({ id: row.id }).update({
|
|
6221
|
+
past_due: db.fn.now(),
|
|
6222
|
+
progress: String(reason)
|
|
6223
|
+
});
|
|
5307
6224
|
}
|
|
6225
|
+
continue;
|
|
5308
6226
|
}
|
|
5309
|
-
const
|
|
6227
|
+
const claimPatch = {
|
|
6228
|
+
started_at: db.fn.now(),
|
|
6229
|
+
status: "running",
|
|
6230
|
+
status_changed_at: db.fn.now()
|
|
6231
|
+
};
|
|
6232
|
+
if (runnerIdentity) {
|
|
6233
|
+
claimPatch.service_name = runnerIdentity.service_name;
|
|
6234
|
+
claimPatch.server_name = runnerIdentity.server_name;
|
|
6235
|
+
claimPatch.instance_number = runnerIdentity.instance_number;
|
|
6236
|
+
}
|
|
6237
|
+
const updated = await db(tasksTable).where({ id: row.id, status: "idle" }).update(claimPatch).returning("*");
|
|
5310
6238
|
const claimed = Array.isArray(updated) ? updated[0] : null;
|
|
5311
6239
|
if (claimed) return claimed;
|
|
5312
6240
|
}
|
|
5313
6241
|
return null;
|
|
5314
6242
|
}
|
|
5315
6243
|
async function runTasksLoop(context, options) {
|
|
5316
|
-
const
|
|
6244
|
+
const queueName = options.queueName ?? "tasks";
|
|
5317
6245
|
const target = options.target;
|
|
5318
6246
|
const pollMs = options.pollMs ?? 1e3;
|
|
5319
|
-
const
|
|
6247
|
+
const claimJitterMs = options.claimJitterMs ?? 0;
|
|
6248
|
+
const maxParallel = options.maxParallel ?? 32;
|
|
5320
6249
|
const scanLimit = options.scanLimit ?? 100;
|
|
5321
6250
|
const allowedTasks = normalizeAllowedTasks(options.allowedTasks);
|
|
5322
6251
|
const registry = normalizeRegistry(options.registry);
|
|
5323
|
-
const { tasksTable, historyTable } = queueToTableNames(
|
|
6252
|
+
const { tasksTable, historyTable } = queueToTableNames(queueName);
|
|
5324
6253
|
if (!target) throw new Error("runTasksLoop: target is required");
|
|
6254
|
+
context.tasksQueueName = queueName;
|
|
5325
6255
|
const runningPromises = /* @__PURE__ */ new Set();
|
|
5326
6256
|
const runningTaskInstances = /* @__PURE__ */ new Map();
|
|
5327
6257
|
let runningStopControlPromise = null;
|
|
5328
6258
|
let stopRequested = false;
|
|
5329
6259
|
let stopAllowanceMs = 5e3;
|
|
5330
|
-
context.
|
|
6260
|
+
context.tasksRunnerStop = false;
|
|
5331
6261
|
let registryReg = null;
|
|
5332
6262
|
let registryInterval = null;
|
|
6263
|
+
let runnerIdentity = null;
|
|
5333
6264
|
const hbGroup = options.runnerServiceGroup?.trim();
|
|
5334
6265
|
if (hbGroup) {
|
|
5335
|
-
const identityDir = options.runnerIdentityDir ?? "./data/runner-identities";
|
|
5336
6266
|
const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
|
|
5337
6267
|
const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
|
|
5338
6268
|
const defaultMeta = {
|
|
@@ -5340,16 +6270,21 @@ async function runTasksLoop(context, options) {
|
|
|
5340
6270
|
allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all"
|
|
5341
6271
|
};
|
|
5342
6272
|
registryReg = await registerInServicesRegistry(context, {
|
|
5343
|
-
|
|
6273
|
+
queueName,
|
|
5344
6274
|
target,
|
|
5345
6275
|
serviceGroup: hbGroup,
|
|
5346
6276
|
serviceName: options.runnerServiceName,
|
|
5347
|
-
|
|
6277
|
+
instanceNumber: options.runnerInstanceNumber,
|
|
5348
6278
|
staleMs,
|
|
5349
6279
|
groupMaxInstances: options.runnerGroupMaxInstances,
|
|
5350
6280
|
enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
|
|
5351
6281
|
metadata: options.runnerMetadata ?? defaultMeta
|
|
5352
6282
|
});
|
|
6283
|
+
runnerIdentity = {
|
|
6284
|
+
service_name: registryReg.serviceName,
|
|
6285
|
+
server_name: import_node_os3.default.hostname(),
|
|
6286
|
+
instance_number: registryReg.instanceNumber
|
|
6287
|
+
};
|
|
5353
6288
|
registryInterval = setInterval(() => {
|
|
5354
6289
|
void touchServicesRegistry(context, registryReg).catch((err) => {
|
|
5355
6290
|
context.logger.warn?.(`[services-registry] touch failed: ${err?.message ?? String(err)}`);
|
|
@@ -5357,7 +6292,7 @@ async function runTasksLoop(context, options) {
|
|
|
5357
6292
|
}, hbIntervalMs);
|
|
5358
6293
|
}
|
|
5359
6294
|
try {
|
|
5360
|
-
while (!context.isStop() && !stopRequested &&
|
|
6295
|
+
while (!context.isStop() && !stopRequested && context.tasksRunnerStop !== true) {
|
|
5361
6296
|
if (!runningStopControlPromise) {
|
|
5362
6297
|
const claimedStopTask = await claimNextRunnableTask(
|
|
5363
6298
|
context,
|
|
@@ -5365,7 +6300,8 @@ async function runTasksLoop(context, options) {
|
|
|
5365
6300
|
target,
|
|
5366
6301
|
registry,
|
|
5367
6302
|
10,
|
|
5368
|
-
["stopRunner", "stop"]
|
|
6303
|
+
["stopRunner", "stop"],
|
|
6304
|
+
runnerIdentity
|
|
5369
6305
|
);
|
|
5370
6306
|
if (claimedStopTask) {
|
|
5371
6307
|
runningStopControlPromise = executeClaimedTask(
|
|
@@ -5379,7 +6315,7 @@ async function runTasksLoop(context, options) {
|
|
|
5379
6315
|
if (outcome.stopRunnerRequested && !stopRequested) {
|
|
5380
6316
|
stopRequested = true;
|
|
5381
6317
|
stopAllowanceMs = outcome.stopAllowanceMs || 5e3;
|
|
5382
|
-
context.
|
|
6318
|
+
context.tasksRunnerStop = true;
|
|
5383
6319
|
await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
|
|
5384
6320
|
}
|
|
5385
6321
|
}).finally(() => {
|
|
@@ -5387,6 +6323,9 @@ async function runTasksLoop(context, options) {
|
|
|
5387
6323
|
});
|
|
5388
6324
|
}
|
|
5389
6325
|
}
|
|
6326
|
+
if (claimJitterMs > 0) {
|
|
6327
|
+
await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
|
|
6328
|
+
}
|
|
5390
6329
|
while (runningPromises.size < maxParallel) {
|
|
5391
6330
|
const claimed = await claimNextRunnableTask(
|
|
5392
6331
|
context,
|
|
@@ -5394,14 +6333,15 @@ async function runTasksLoop(context, options) {
|
|
|
5394
6333
|
target,
|
|
5395
6334
|
registry,
|
|
5396
6335
|
scanLimit,
|
|
5397
|
-
allowedTasks
|
|
6336
|
+
allowedTasks,
|
|
6337
|
+
runnerIdentity
|
|
5398
6338
|
);
|
|
5399
6339
|
if (!claimed) break;
|
|
5400
6340
|
const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
|
|
5401
6341
|
if (outcome.stopRunnerRequested && !stopRequested) {
|
|
5402
6342
|
stopRequested = true;
|
|
5403
6343
|
stopAllowanceMs = outcome.stopAllowanceMs || 5e3;
|
|
5404
|
-
context.
|
|
6344
|
+
context.tasksRunnerStop = true;
|
|
5405
6345
|
await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
|
|
5406
6346
|
}
|
|
5407
6347
|
}).finally(() => {
|
|
@@ -5409,7 +6349,16 @@ async function runTasksLoop(context, options) {
|
|
|
5409
6349
|
});
|
|
5410
6350
|
runningPromises.add(p);
|
|
5411
6351
|
}
|
|
5412
|
-
|
|
6352
|
+
const wakePromises = [...runningPromises];
|
|
6353
|
+
if (runningStopControlPromise) {
|
|
6354
|
+
wakePromises.push(runningStopControlPromise);
|
|
6355
|
+
}
|
|
6356
|
+
if (wakePromises.length === 0) {
|
|
6357
|
+
await sleepMs(pollMs);
|
|
6358
|
+
} else {
|
|
6359
|
+
const safe = wakePromises.map((p) => p.catch(() => void 0));
|
|
6360
|
+
await Promise.race([sleepMs(pollMs), Promise.race(safe)]);
|
|
6361
|
+
}
|
|
5413
6362
|
}
|
|
5414
6363
|
if (context.isStop() && !stopRequested) {
|
|
5415
6364
|
await signalRunningTasksStop(context, runningTaskInstances, 5e3);
|
|
@@ -5445,89 +6394,130 @@ async function runTasksLoop(context, options) {
|
|
|
5445
6394
|
}
|
|
5446
6395
|
async function waitForTaskResult(context, taskId, options = {}) {
|
|
5447
6396
|
const db = getDb3(context);
|
|
5448
|
-
const
|
|
6397
|
+
const queueName = options.queueName ?? "tasks";
|
|
5449
6398
|
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
5450
6399
|
const pollMs = options.pollMs ?? 500;
|
|
5451
|
-
const { tasksTable, historyTable } = queueToTableNames(
|
|
6400
|
+
const { tasksTable, historyTable } = queueToTableNames(queueName);
|
|
5452
6401
|
const deadline = Date.now() + timeoutMs;
|
|
6402
|
+
const waitStartedAt = /* @__PURE__ */ new Date();
|
|
6403
|
+
let cachedNameOpid = null;
|
|
6404
|
+
async function historySinceWait(name, opid) {
|
|
6405
|
+
let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
|
|
6406
|
+
if (opid == null || opid === "") {
|
|
6407
|
+
q = q.whereNull("opid");
|
|
6408
|
+
} else {
|
|
6409
|
+
q = q.where({ opid });
|
|
6410
|
+
}
|
|
6411
|
+
return await q.orderBy("completed_at", "desc").first();
|
|
6412
|
+
}
|
|
5453
6413
|
while (Date.now() <= deadline) {
|
|
5454
|
-
const
|
|
5455
|
-
if (
|
|
6414
|
+
const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
|
|
6415
|
+
if (legacy) {
|
|
6416
|
+
return legacy;
|
|
6417
|
+
}
|
|
5456
6418
|
const pending = await db(tasksTable).where({ id: taskId }).first();
|
|
5457
|
-
if (
|
|
5458
|
-
|
|
5459
|
-
|
|
6419
|
+
if (pending) {
|
|
6420
|
+
cachedNameOpid = { name: pending.name, opid: pending.opid };
|
|
6421
|
+
const done = await historySinceWait(pending.name, pending.opid);
|
|
6422
|
+
if (done) {
|
|
6423
|
+
return done;
|
|
6424
|
+
}
|
|
6425
|
+
} else if (cachedNameOpid) {
|
|
6426
|
+
const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
|
|
6427
|
+
if (done) {
|
|
6428
|
+
return done;
|
|
6429
|
+
}
|
|
6430
|
+
return null;
|
|
6431
|
+
} else {
|
|
6432
|
+
return null;
|
|
5460
6433
|
}
|
|
5461
6434
|
await sleepMs(pollMs);
|
|
5462
6435
|
}
|
|
5463
6436
|
return null;
|
|
5464
6437
|
}
|
|
5465
6438
|
var TasksManager = class _TasksManager {
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
6439
|
+
/**
|
|
6440
|
+
* @param {object} context
|
|
6441
|
+
* @param {{
|
|
6442
|
+
* queueName?: string,
|
|
6443
|
+
* target?: string,
|
|
6444
|
+
* recreateTaskTables?: boolean,
|
|
6445
|
+
* pollMs?: number,
|
|
6446
|
+
* claimJitterMs?: number,
|
|
6447
|
+
* maxParallel?: number,
|
|
6448
|
+
* scanLimit?: number,
|
|
6449
|
+
* allowedTasks?: string | string[],
|
|
6450
|
+
* registry?: TasksRegistry | Record<string, Function>,
|
|
6451
|
+
* runnerServiceGroup?: string,
|
|
6452
|
+
* runnerServiceName?: string,
|
|
6453
|
+
* runnerInstanceNumber?: number,
|
|
6454
|
+
* runnerHeartbeatIntervalMs?: number,
|
|
6455
|
+
* runnerHeartbeatStaleMs?: number,
|
|
6456
|
+
* runnerGroupMaxInstances?: number,
|
|
6457
|
+
* runnerEnforceMaxInstances?: boolean,
|
|
6458
|
+
* runnerMetadata?: Record<string, unknown>,
|
|
6459
|
+
* }} [options]
|
|
6460
|
+
*/
|
|
5483
6461
|
constructor(context, options = {}) {
|
|
5484
6462
|
this.context = context;
|
|
5485
|
-
this.
|
|
6463
|
+
this.queueName = options.queueName ?? "tasks";
|
|
5486
6464
|
this.target = options.target ?? "localRunner";
|
|
5487
6465
|
this.recreateTaskTables = options.recreateTaskTables ?? false;
|
|
5488
6466
|
this.pollMs = options.pollMs ?? 1e3;
|
|
6467
|
+
this.claimJitterMs = options.claimJitterMs ?? 0;
|
|
5489
6468
|
this.maxParallel = options.maxParallel ?? 1;
|
|
5490
6469
|
this.scanLimit = options.scanLimit ?? 100;
|
|
5491
6470
|
this.allowedTasks = normalizeAllowedTasks(options.allowedTasks);
|
|
5492
6471
|
this.registry = normalizeRegistry(options.registry);
|
|
5493
6472
|
this.runnerServiceGroup = options.runnerServiceGroup;
|
|
5494
6473
|
this.runnerServiceName = options.runnerServiceName;
|
|
5495
|
-
this.
|
|
6474
|
+
this.runnerInstanceNumber = options.runnerInstanceNumber;
|
|
5496
6475
|
this.runnerHeartbeatIntervalMs = options.runnerHeartbeatIntervalMs;
|
|
5497
6476
|
this.runnerHeartbeatStaleMs = options.runnerHeartbeatStaleMs;
|
|
5498
6477
|
this.runnerGroupMaxInstances = options.runnerGroupMaxInstances;
|
|
5499
6478
|
this.runnerEnforceMaxInstances = options.runnerEnforceMaxInstances;
|
|
5500
6479
|
this.runnerMetadata = options.runnerMetadata;
|
|
5501
6480
|
}
|
|
6481
|
+
/**
|
|
6482
|
+
* Preferred factory: reads defaults from `context.params` (module namespace
|
|
6483
|
+
* `"tasks"`), then overlays explicit `options`. Keeps CLI flags, env vars,
|
|
6484
|
+
* and inline options in one consistent resolver.
|
|
6485
|
+
*
|
|
6486
|
+
* @param {object} context
|
|
6487
|
+
* @param {ConstructorParameters<typeof TasksManager>[1]} [options]
|
|
6488
|
+
* @returns {TasksManager}
|
|
6489
|
+
*/
|
|
5502
6490
|
static init(context, options = {}) {
|
|
5503
6491
|
const defs = {
|
|
5504
6492
|
table: "string default tasks",
|
|
5505
6493
|
target: "string default localRunner",
|
|
5506
6494
|
recreateTaskTables: "boolean default false",
|
|
5507
6495
|
pollMs: "number default 1000",
|
|
6496
|
+
claimJitterMs: "number default 0",
|
|
5508
6497
|
maxParallel: "number default 1",
|
|
5509
6498
|
scanLimit: "number default 100",
|
|
5510
6499
|
allowedTasks: "string",
|
|
5511
6500
|
runnerServiceGroup: "string",
|
|
5512
6501
|
runnerServiceName: "string",
|
|
5513
|
-
|
|
6502
|
+
runnerInstanceNumber: "number",
|
|
5514
6503
|
runnerHeartbeatIntervalMs: "number default 10000",
|
|
5515
6504
|
runnerHeartbeatStaleMs: "number default 45000",
|
|
5516
6505
|
runnerGroupMaxInstances: "number",
|
|
5517
6506
|
runnerEnforceMaxInstances: "boolean default true"
|
|
5518
6507
|
};
|
|
5519
|
-
const discovered = context.params.getAllForModule(defs);
|
|
6508
|
+
const discovered = context.params.getAllForModule("tasks", defs);
|
|
5520
6509
|
const resolved = {
|
|
5521
|
-
|
|
6510
|
+
queueName: discovered.table,
|
|
5522
6511
|
target: discovered.target,
|
|
5523
6512
|
recreateTaskTables: discovered.recreateTaskTables,
|
|
5524
6513
|
pollMs: discovered.pollMs,
|
|
6514
|
+
claimJitterMs: discovered.claimJitterMs,
|
|
5525
6515
|
maxParallel: discovered.maxParallel,
|
|
5526
6516
|
scanLimit: discovered.scanLimit,
|
|
5527
6517
|
allowedTasks: discovered.allowedTasks,
|
|
5528
6518
|
runnerServiceGroup: discovered.runnerServiceGroup,
|
|
5529
6519
|
runnerServiceName: discovered.runnerServiceName,
|
|
5530
|
-
|
|
6520
|
+
runnerInstanceNumber: discovered.runnerInstanceNumber,
|
|
5531
6521
|
runnerHeartbeatIntervalMs: discovered.runnerHeartbeatIntervalMs,
|
|
5532
6522
|
runnerHeartbeatStaleMs: discovered.runnerHeartbeatStaleMs,
|
|
5533
6523
|
runnerGroupMaxInstances: discovered.runnerGroupMaxInstances,
|
|
@@ -5536,24 +6526,39 @@ var TasksManager = class _TasksManager {
|
|
|
5536
6526
|
};
|
|
5537
6527
|
return new _TasksManager(context, resolved);
|
|
5538
6528
|
}
|
|
6529
|
+
/**
|
|
6530
|
+
* Idempotently ensure the three backing tables exist for this queue.
|
|
6531
|
+
*
|
|
6532
|
+
* @param {{ recreate?: boolean }} [options]
|
|
6533
|
+
* @returns {Promise<void>}
|
|
6534
|
+
*/
|
|
5539
6535
|
async ensureTaskTables(options = {}) {
|
|
5540
6536
|
await ensureTaskTables(this.context, {
|
|
5541
|
-
|
|
6537
|
+
queueName: this.queueName,
|
|
5542
6538
|
recreate: options.recreate ?? this.recreateTaskTables
|
|
5543
6539
|
});
|
|
5544
6540
|
}
|
|
6541
|
+
/**
|
|
6542
|
+
* Start the runner loop using this manager's resolved config. Per-call
|
|
6543
|
+
* options override the stored defaults, but `runnerMetadata` still falls
|
|
6544
|
+
* through when omitted.
|
|
6545
|
+
*
|
|
6546
|
+
* @param {Partial<ConstructorParameters<typeof TasksManager>[1]>} [options]
|
|
6547
|
+
* @returns {Promise<void>}
|
|
6548
|
+
*/
|
|
5545
6549
|
async runTasksLoop(options = {}) {
|
|
5546
6550
|
await runTasksLoop(this.context, {
|
|
5547
|
-
|
|
6551
|
+
queueName: options.queueName ?? this.queueName,
|
|
5548
6552
|
target: options.target ?? this.target,
|
|
5549
6553
|
pollMs: options.pollMs ?? this.pollMs,
|
|
6554
|
+
claimJitterMs: options.claimJitterMs ?? this.claimJitterMs,
|
|
5550
6555
|
maxParallel: options.maxParallel ?? this.maxParallel,
|
|
5551
6556
|
scanLimit: options.scanLimit ?? this.scanLimit,
|
|
5552
6557
|
allowedTasks: options.allowedTasks ?? this.allowedTasks,
|
|
5553
6558
|
registry: options.registry ?? this.registry,
|
|
5554
6559
|
runnerServiceGroup: options.runnerServiceGroup ?? this.runnerServiceGroup,
|
|
5555
6560
|
runnerServiceName: options.runnerServiceName ?? this.runnerServiceName,
|
|
5556
|
-
|
|
6561
|
+
runnerInstanceNumber: options.runnerInstanceNumber ?? this.runnerInstanceNumber,
|
|
5557
6562
|
runnerHeartbeatIntervalMs: options.runnerHeartbeatIntervalMs ?? this.runnerHeartbeatIntervalMs,
|
|
5558
6563
|
runnerHeartbeatStaleMs: options.runnerHeartbeatStaleMs ?? this.runnerHeartbeatStaleMs,
|
|
5559
6564
|
runnerGroupMaxInstances: options.runnerGroupMaxInstances ?? this.runnerGroupMaxInstances,
|
|
@@ -5564,6 +6569,7 @@ var TasksManager = class _TasksManager {
|
|
|
5564
6569
|
};
|
|
5565
6570
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5566
6571
|
0 && (module.exports = {
|
|
6572
|
+
AbstractTask,
|
|
5567
6573
|
Args,
|
|
5568
6574
|
Box,
|
|
5569
6575
|
Db,
|
|
@@ -5579,13 +6585,15 @@ var TasksManager = class _TasksManager {
|
|
|
5579
6585
|
MultiColumnListWithPreviewComponent,
|
|
5580
6586
|
Params,
|
|
5581
6587
|
React,
|
|
6588
|
+
S3,
|
|
6589
|
+
SERVICE_TASK_NAMES,
|
|
5582
6590
|
ScreenBody,
|
|
5583
6591
|
ScreenContainer,
|
|
5584
6592
|
ScreenDivider,
|
|
5585
6593
|
ScreenFooter,
|
|
5586
6594
|
ScreenRow,
|
|
5587
6595
|
ScreenTitle,
|
|
5588
|
-
|
|
6596
|
+
TaskGetLogs,
|
|
5589
6597
|
TaskPing,
|
|
5590
6598
|
TaskSampleProcess,
|
|
5591
6599
|
TaskShellCommand,
|
|
@@ -5600,17 +6608,17 @@ var TasksManager = class _TasksManager {
|
|
|
5600
6608
|
buildBreadcrumb,
|
|
5601
6609
|
buildDetailBreadcrumb,
|
|
5602
6610
|
buildFooter,
|
|
5603
|
-
|
|
5604
|
-
dbFindAndConnect,
|
|
5605
|
-
dbInit,
|
|
6611
|
+
convertPattern,
|
|
5606
6612
|
defaultFileSynopsisFunction,
|
|
5607
6613
|
defaultTasksRegistry,
|
|
5608
6614
|
defaultVersionSynopsisFunction,
|
|
5609
6615
|
enqueueStopTask,
|
|
5610
6616
|
enqueueTask,
|
|
5611
6617
|
ensureTaskTables,
|
|
6618
|
+
flushTaskIpcLogs,
|
|
5612
6619
|
getArgsInstance,
|
|
5613
6620
|
h,
|
|
6621
|
+
ipcFileLogsTableNameForSourceResource,
|
|
5614
6622
|
joiEdateType,
|
|
5615
6623
|
joiStringArrayType,
|
|
5616
6624
|
listAliveRunnerHeartbeats,
|
|
@@ -5618,14 +6626,22 @@ var TasksManager = class _TasksManager {
|
|
|
5618
6626
|
listSources,
|
|
5619
6627
|
listTables,
|
|
5620
6628
|
load,
|
|
6629
|
+
matchesParsedPattern,
|
|
6630
|
+
memo,
|
|
6631
|
+
mergeAllowedTasksWithServiceTasks,
|
|
6632
|
+
nextTimeMatch,
|
|
6633
|
+
normalizeAllowedTasks,
|
|
5621
6634
|
organizeFooterMessages,
|
|
5622
6635
|
queueToTableNames,
|
|
6636
|
+
readTaskIpcLogsSnapshot,
|
|
5623
6637
|
registerInServicesRegistry,
|
|
5624
6638
|
registerRunnerHeartbeat,
|
|
6639
|
+
resolveAsterisks,
|
|
6640
|
+
resolveIpcFileLogsDir,
|
|
6641
|
+
resolveRanges,
|
|
6642
|
+
resolveSteps,
|
|
5625
6643
|
runNodeTaskScript,
|
|
5626
6644
|
runTasksLoop,
|
|
5627
|
-
runnerHeartbeatsTable,
|
|
5628
|
-
servicesRegistryTable,
|
|
5629
6645
|
setupContext,
|
|
5630
6646
|
showListScreen,
|
|
5631
6647
|
showMenuScreen,
|
|
@@ -5633,6 +6649,8 @@ var TasksManager = class _TasksManager {
|
|
|
5633
6649
|
showMultiColumnListWithPreviewScreen,
|
|
5634
6650
|
showScreen,
|
|
5635
6651
|
showWordGridScreen,
|
|
6652
|
+
taskHistoryInsertFromQueueRow,
|
|
6653
|
+
timeMatcher,
|
|
5636
6654
|
touchRunnerHeartbeat,
|
|
5637
6655
|
touchServicesRegistry,
|
|
5638
6656
|
unregisterRunnerHeartbeat,
|
|
@@ -5642,6 +6660,7 @@ var TasksManager = class _TasksManager {
|
|
|
5642
6660
|
useCallback,
|
|
5643
6661
|
useEffect,
|
|
5644
6662
|
useInput,
|
|
6663
|
+
useLayoutEffect,
|
|
5645
6664
|
useMemo,
|
|
5646
6665
|
useRef,
|
|
5647
6666
|
useState,
|