@cadenza.io/service 2.3.17 → 2.5.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/index.d.mts +219 -141
- package/dist/index.d.ts +219 -141
- package/dist/index.js +1575 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1575 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -247,6 +247,72 @@ var DatabaseTask = class extends DeputyTask {
|
|
|
247
247
|
var isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
248
248
|
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
249
249
|
|
|
250
|
+
// src/utils/inquiry.ts
|
|
251
|
+
var META_INTENT_PREFIX = "meta-";
|
|
252
|
+
var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
|
|
253
|
+
function isPlainObject(value) {
|
|
254
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
255
|
+
}
|
|
256
|
+
function deepMergeDeterministic(left, right) {
|
|
257
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
258
|
+
return [...left, ...right];
|
|
259
|
+
}
|
|
260
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
261
|
+
const merged = { ...left };
|
|
262
|
+
const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])).sort();
|
|
263
|
+
for (const key of keys) {
|
|
264
|
+
if (!(key in left)) {
|
|
265
|
+
merged[key] = right[key];
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!(key in right)) {
|
|
269
|
+
merged[key] = left[key];
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
merged[key] = deepMergeDeterministic(left[key], right[key]);
|
|
273
|
+
}
|
|
274
|
+
return merged;
|
|
275
|
+
}
|
|
276
|
+
return right;
|
|
277
|
+
}
|
|
278
|
+
function mergeInquiryContexts(contexts) {
|
|
279
|
+
return contexts.reduce((acc, next) => deepMergeDeterministic(acc, next), {});
|
|
280
|
+
}
|
|
281
|
+
function isMetaIntentName(intentName) {
|
|
282
|
+
return intentName.startsWith(META_INTENT_PREFIX);
|
|
283
|
+
}
|
|
284
|
+
function shouldExecuteInquiryResponder(inquiry, responderIsMeta) {
|
|
285
|
+
if (!isMetaIntentName(inquiry)) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
return responderIsMeta;
|
|
289
|
+
}
|
|
290
|
+
function compareResponderDescriptors(left, right) {
|
|
291
|
+
if (left.serviceName !== right.serviceName) {
|
|
292
|
+
return left.serviceName.localeCompare(right.serviceName);
|
|
293
|
+
}
|
|
294
|
+
if (left.taskName !== right.taskName) {
|
|
295
|
+
return left.taskName.localeCompare(right.taskName);
|
|
296
|
+
}
|
|
297
|
+
if (left.taskVersion !== right.taskVersion) {
|
|
298
|
+
return left.taskVersion - right.taskVersion;
|
|
299
|
+
}
|
|
300
|
+
return left.localTaskName.localeCompare(right.localTaskName);
|
|
301
|
+
}
|
|
302
|
+
function summarizeResponderStatuses(statuses) {
|
|
303
|
+
let responded = 0;
|
|
304
|
+
let failed = 0;
|
|
305
|
+
let timedOut = 0;
|
|
306
|
+
let pending = 0;
|
|
307
|
+
for (const status of statuses) {
|
|
308
|
+
if (status.status === "fulfilled") responded++;
|
|
309
|
+
if (status.status === "failed") failed++;
|
|
310
|
+
if (status.status === "timed_out") timedOut++;
|
|
311
|
+
}
|
|
312
|
+
pending = Math.max(0, statuses.length - responded - failed - timedOut);
|
|
313
|
+
return { responded, failed, timedOut, pending };
|
|
314
|
+
}
|
|
315
|
+
|
|
250
316
|
// src/registry/ServiceRegistry.ts
|
|
251
317
|
var ServiceRegistry = class _ServiceRegistry {
|
|
252
318
|
/**
|
|
@@ -262,11 +328,47 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
262
328
|
this.instances = /* @__PURE__ */ new Map();
|
|
263
329
|
this.deputies = /* @__PURE__ */ new Map();
|
|
264
330
|
this.remoteSignals = /* @__PURE__ */ new Map();
|
|
331
|
+
this.remoteIntents = /* @__PURE__ */ new Map();
|
|
332
|
+
this.remoteIntentDeputiesByKey = /* @__PURE__ */ new Map();
|
|
333
|
+
this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
|
|
265
334
|
this.serviceName = null;
|
|
266
335
|
this.serviceInstanceId = null;
|
|
267
336
|
this.numberOfRunningGraphs = 0;
|
|
268
337
|
this.useSocket = false;
|
|
269
338
|
this.retryCount = 3;
|
|
339
|
+
CadenzaService.defineIntent({
|
|
340
|
+
name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
|
|
341
|
+
description: "Gather transport diagnostics across all services and communication clients.",
|
|
342
|
+
input: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
detailLevel: {
|
|
346
|
+
type: "string",
|
|
347
|
+
constraints: {
|
|
348
|
+
oneOf: ["summary", "full"]
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
includeErrorHistory: {
|
|
352
|
+
type: "boolean"
|
|
353
|
+
},
|
|
354
|
+
errorHistoryLimit: {
|
|
355
|
+
type: "number",
|
|
356
|
+
constraints: {
|
|
357
|
+
min: 1,
|
|
358
|
+
max: 200
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
output: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
transportDiagnostics: {
|
|
367
|
+
type: "object"
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
270
372
|
this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
|
|
271
373
|
"Handle Instance Update",
|
|
272
374
|
(ctx, emit) => {
|
|
@@ -282,10 +384,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
282
384
|
} = serviceInstance;
|
|
283
385
|
if (uuid4 === this.serviceInstanceId) return;
|
|
284
386
|
if (deleted) {
|
|
285
|
-
this.instances.get(serviceName)?.
|
|
286
|
-
|
|
287
|
-
1
|
|
288
|
-
|
|
387
|
+
const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
|
|
388
|
+
if (indexToDelete >= 0) {
|
|
389
|
+
this.instances.get(serviceName)?.splice(indexToDelete, 1);
|
|
390
|
+
}
|
|
289
391
|
if (this.instances.get(serviceName)?.length === 0) {
|
|
290
392
|
this.instances.delete(serviceName);
|
|
291
393
|
} else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
|
|
@@ -306,7 +408,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
306
408
|
if (this.serviceName === serviceName) {
|
|
307
409
|
return false;
|
|
308
410
|
}
|
|
309
|
-
if (!isFrontend && this.deputies.has(serviceName) || this.remoteSignals.has(serviceName)) {
|
|
411
|
+
if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
|
|
310
412
|
const clientCreated = instances?.some(
|
|
311
413
|
(i) => i.address === address && i.port === port && i.clientCreated && i.isActive
|
|
312
414
|
);
|
|
@@ -328,7 +430,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
328
430
|
communicationTypes
|
|
329
431
|
});
|
|
330
432
|
instances?.filter(
|
|
331
|
-
(i) => i.address === address && i.port === port && i.
|
|
433
|
+
(i) => i.address === address && i.port === port && i.isActive
|
|
332
434
|
).forEach((i) => {
|
|
333
435
|
i.clientCreated = true;
|
|
334
436
|
});
|
|
@@ -356,7 +458,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
356
458
|
for (const serviceInstance of ctx.serviceInstances) {
|
|
357
459
|
yield { serviceInstance };
|
|
358
460
|
}
|
|
359
|
-
}).doOn(
|
|
461
|
+
}).doOn(
|
|
462
|
+
"meta.service_registry.registered_global_signals",
|
|
463
|
+
"meta.service_registry.registered_global_intents"
|
|
464
|
+
).then(this.handleInstanceUpdateTask);
|
|
360
465
|
this.handleGlobalSignalRegistrationTask = CadenzaService.createMetaTask(
|
|
361
466
|
"Handle global Signal Registration",
|
|
362
467
|
(ctx) => {
|
|
@@ -397,6 +502,32 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
397
502
|
},
|
|
398
503
|
"Handles registration of remote signals"
|
|
399
504
|
).emits("meta.service_registry.registered_global_signals").doOn("global.meta.cadenza_db.gathered_sync_data");
|
|
505
|
+
this.handleGlobalIntentRegistrationTask = CadenzaService.createMetaTask(
|
|
506
|
+
"Handle global intent registration",
|
|
507
|
+
(ctx) => {
|
|
508
|
+
const intentToTaskMaps = this.normalizeIntentMaps(ctx);
|
|
509
|
+
const sorted = intentToTaskMaps.sort((a, b) => {
|
|
510
|
+
if (a.deleted && !b.deleted) return -1;
|
|
511
|
+
if (!a.deleted && b.deleted) return 1;
|
|
512
|
+
return 0;
|
|
513
|
+
});
|
|
514
|
+
for (const map of sorted) {
|
|
515
|
+
if (map.deleted) {
|
|
516
|
+
this.unregisterRemoteIntentDeputy(map);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
CadenzaService.inquiryBroker.addIntent({
|
|
520
|
+
name: map.intentName
|
|
521
|
+
});
|
|
522
|
+
this.registerRemoteIntentDeputy(map);
|
|
523
|
+
}
|
|
524
|
+
return true;
|
|
525
|
+
},
|
|
526
|
+
"Handles registration of remote inquiry intent responders"
|
|
527
|
+
).emits("meta.service_registry.registered_global_intents").doOn(
|
|
528
|
+
"global.meta.cadenza_db.gathered_sync_data",
|
|
529
|
+
"global.meta.graph_metadata.task_intent_associated"
|
|
530
|
+
);
|
|
400
531
|
this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
|
|
401
532
|
"Handle service not responding",
|
|
402
533
|
(ctx, emit) => {
|
|
@@ -432,7 +563,12 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
432
563
|
return true;
|
|
433
564
|
},
|
|
434
565
|
"Handles service not responding"
|
|
435
|
-
).doOn(
|
|
566
|
+
).doOn(
|
|
567
|
+
"meta.fetch.handshake_failed",
|
|
568
|
+
"meta.fetch.handshake_failed.*",
|
|
569
|
+
"meta.socket_client.disconnected",
|
|
570
|
+
"meta.socket_client.disconnected.*"
|
|
571
|
+
).attachSignal("global.meta.service_registry.service_not_responding");
|
|
436
572
|
this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
|
|
437
573
|
"Handle service handshake",
|
|
438
574
|
(ctx, emit) => {
|
|
@@ -459,7 +595,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
459
595
|
(i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
|
|
460
596
|
);
|
|
461
597
|
for (const i of instancesToDelete ?? []) {
|
|
462
|
-
this.instances.get(serviceName)?.
|
|
598
|
+
const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
|
|
599
|
+
if (indexToDelete >= 0) {
|
|
600
|
+
this.instances.get(serviceName)?.splice(indexToDelete, 1);
|
|
601
|
+
}
|
|
463
602
|
emit("global.meta.service_registry.deleted", {
|
|
464
603
|
data: {
|
|
465
604
|
isActive: false,
|
|
@@ -502,7 +641,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
502
641
|
});
|
|
503
642
|
return joinedContext;
|
|
504
643
|
}
|
|
505
|
-
).emits("meta.service_registry.initial_sync_complete").then(
|
|
644
|
+
).emits("meta.service_registry.initial_sync_complete").then(
|
|
645
|
+
this.handleGlobalSignalRegistrationTask,
|
|
646
|
+
this.handleGlobalIntentRegistrationTask
|
|
647
|
+
);
|
|
506
648
|
this.fullSyncTask = CadenzaService.createMetaRoutine("Full sync", [
|
|
507
649
|
CadenzaService.createCadenzaDBQueryTask("signal_to_task_map", {
|
|
508
650
|
filter: {
|
|
@@ -510,6 +652,15 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
510
652
|
},
|
|
511
653
|
fields: ["signal_name", "service_name", "deleted"]
|
|
512
654
|
}).then(mergeSyncDataTask),
|
|
655
|
+
CadenzaService.createCadenzaDBQueryTask("intent_to_task_map", {
|
|
656
|
+
fields: [
|
|
657
|
+
"intent_name",
|
|
658
|
+
"task_name",
|
|
659
|
+
"task_version",
|
|
660
|
+
"service_name",
|
|
661
|
+
"deleted"
|
|
662
|
+
]
|
|
663
|
+
}).then(mergeSyncDataTask),
|
|
513
664
|
CadenzaService.createCadenzaDBQueryTask("service_instance", {
|
|
514
665
|
filter: {
|
|
515
666
|
deleted: false,
|
|
@@ -619,8 +770,9 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
619
770
|
}
|
|
620
771
|
if (__broadcast || instances[0].isFrontend) {
|
|
621
772
|
for (const instance of instances) {
|
|
773
|
+
const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
|
|
622
774
|
emit(
|
|
623
|
-
`meta.service_registry.selected_instance_for_socket:${
|
|
775
|
+
`meta.service_registry.selected_instance_for_socket:${socketKey}`,
|
|
624
776
|
context
|
|
625
777
|
);
|
|
626
778
|
}
|
|
@@ -699,6 +851,25 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
699
851
|
__active: self?.isActive ?? false
|
|
700
852
|
};
|
|
701
853
|
}).doOn("meta.socket.status_check_requested");
|
|
854
|
+
this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
|
|
855
|
+
"Collect transport diagnostics",
|
|
856
|
+
async (ctx) => {
|
|
857
|
+
const inquiryResult = await CadenzaService.inquire(
|
|
858
|
+
META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
|
|
859
|
+
{
|
|
860
|
+
detailLevel: ctx.detailLevel,
|
|
861
|
+
includeErrorHistory: ctx.includeErrorHistory,
|
|
862
|
+
errorHistoryLimit: ctx.errorHistoryLimit
|
|
863
|
+
},
|
|
864
|
+
ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
|
|
865
|
+
);
|
|
866
|
+
return {
|
|
867
|
+
...ctx,
|
|
868
|
+
...inquiryResult
|
|
869
|
+
};
|
|
870
|
+
},
|
|
871
|
+
"Collects distributed transport diagnostics using inquiry responders."
|
|
872
|
+
).doOn("meta.service_registry.transport_diagnostics_requested").emits("meta.service_registry.transport_diagnostics_collected").emitsOnFail("meta.service_registry.transport_diagnostics_failed");
|
|
702
873
|
this.insertServiceTask = CadenzaService.createCadenzaDBInsertTask(
|
|
703
874
|
"service",
|
|
704
875
|
{
|
|
@@ -907,8 +1078,125 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
907
1078
|
if (!this._instance) this._instance = new _ServiceRegistry();
|
|
908
1079
|
return this._instance;
|
|
909
1080
|
}
|
|
1081
|
+
buildRemoteIntentDeputyKey(map) {
|
|
1082
|
+
return `${map.intentName}|${map.serviceName}|${map.taskName}|${map.taskVersion ?? 1}`;
|
|
1083
|
+
}
|
|
1084
|
+
normalizeIntentMaps(ctx) {
|
|
1085
|
+
if (Array.isArray(ctx.intentToTaskMaps)) {
|
|
1086
|
+
return ctx.intentToTaskMaps.map((m) => ({
|
|
1087
|
+
intentName: m.intentName ?? m.intent_name,
|
|
1088
|
+
serviceName: m.serviceName ?? m.service_name,
|
|
1089
|
+
taskName: m.taskName ?? m.task_name,
|
|
1090
|
+
taskVersion: m.taskVersion ?? m.task_version ?? 1,
|
|
1091
|
+
deleted: !!m.deleted
|
|
1092
|
+
})).filter((m) => m.intentName && m.serviceName && m.taskName);
|
|
1093
|
+
}
|
|
1094
|
+
const single = ctx.intentToTaskMap ?? ctx.data ?? (ctx.intentName ? ctx : void 0);
|
|
1095
|
+
if (!single) return [];
|
|
1096
|
+
const normalized = {
|
|
1097
|
+
intentName: single.intentName ?? single.intent_name,
|
|
1098
|
+
serviceName: single.serviceName ?? single.service_name,
|
|
1099
|
+
taskName: single.taskName ?? single.task_name,
|
|
1100
|
+
taskVersion: single.taskVersion ?? single.task_version ?? 1,
|
|
1101
|
+
deleted: !!single.deleted
|
|
1102
|
+
};
|
|
1103
|
+
if (!normalized.intentName || !normalized.serviceName || !normalized.taskName)
|
|
1104
|
+
return [];
|
|
1105
|
+
return [normalized];
|
|
1106
|
+
}
|
|
1107
|
+
registerRemoteIntentDeputy(map) {
|
|
1108
|
+
if (!this.serviceName || map.serviceName === this.serviceName) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const key = this.buildRemoteIntentDeputyKey(map);
|
|
1112
|
+
if (this.remoteIntentDeputiesByKey.has(key)) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const deputyTaskName = `Inquire ${map.intentName} via ${map.serviceName} (${map.taskName} v${map.taskVersion})`;
|
|
1116
|
+
const deputyTask = isMetaIntentName(map.intentName) ? CadenzaService.createMetaDeputyTask(map.taskName, map.serviceName, {
|
|
1117
|
+
register: false,
|
|
1118
|
+
isHidden: true,
|
|
1119
|
+
retryCount: 1,
|
|
1120
|
+
retryDelay: 50,
|
|
1121
|
+
retryDelayFactor: 1.2
|
|
1122
|
+
}) : CadenzaService.createDeputyTask(map.taskName, map.serviceName, {
|
|
1123
|
+
register: false,
|
|
1124
|
+
isHidden: true,
|
|
1125
|
+
retryCount: 1,
|
|
1126
|
+
retryDelay: 50,
|
|
1127
|
+
retryDelayFactor: 1.2
|
|
1128
|
+
});
|
|
1129
|
+
deputyTask.respondsTo(map.intentName);
|
|
1130
|
+
if (!this.remoteIntents.has(map.serviceName)) {
|
|
1131
|
+
this.remoteIntents.set(map.serviceName, /* @__PURE__ */ new Set());
|
|
1132
|
+
}
|
|
1133
|
+
this.remoteIntents.get(map.serviceName).add(map.intentName);
|
|
1134
|
+
const descriptor = {
|
|
1135
|
+
key,
|
|
1136
|
+
intentName: map.intentName,
|
|
1137
|
+
serviceName: map.serviceName,
|
|
1138
|
+
remoteTaskName: map.taskName,
|
|
1139
|
+
remoteTaskVersion: map.taskVersion,
|
|
1140
|
+
localTaskName: deputyTask.name || deputyTaskName,
|
|
1141
|
+
localTask: deputyTask
|
|
1142
|
+
};
|
|
1143
|
+
this.remoteIntentDeputiesByKey.set(key, descriptor);
|
|
1144
|
+
this.remoteIntentDeputiesByTask.set(deputyTask, descriptor);
|
|
1145
|
+
}
|
|
1146
|
+
unregisterRemoteIntentDeputy(map) {
|
|
1147
|
+
const key = this.buildRemoteIntentDeputyKey(map);
|
|
1148
|
+
const descriptor = this.remoteIntentDeputiesByKey.get(key);
|
|
1149
|
+
if (!descriptor) {
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const task = descriptor.localTask;
|
|
1153
|
+
if (task) {
|
|
1154
|
+
CadenzaService.inquiryBroker.unsubscribe(descriptor.intentName, task);
|
|
1155
|
+
task.destroy();
|
|
1156
|
+
}
|
|
1157
|
+
this.remoteIntentDeputiesByTask.delete(descriptor.localTask);
|
|
1158
|
+
this.remoteIntentDeputiesByKey.delete(key);
|
|
1159
|
+
this.remoteIntents.get(descriptor.serviceName)?.delete(descriptor.intentName);
|
|
1160
|
+
if (!this.remoteIntents.get(descriptor.serviceName)?.size) {
|
|
1161
|
+
this.remoteIntents.delete(descriptor.serviceName);
|
|
1162
|
+
}
|
|
1163
|
+
const deputies = this.deputies.get(descriptor.serviceName);
|
|
1164
|
+
if (deputies) {
|
|
1165
|
+
this.deputies.set(
|
|
1166
|
+
descriptor.serviceName,
|
|
1167
|
+
deputies.filter((d) => d.localTaskName !== descriptor.localTaskName)
|
|
1168
|
+
);
|
|
1169
|
+
if (this.deputies.get(descriptor.serviceName)?.length === 0) {
|
|
1170
|
+
this.deputies.delete(descriptor.serviceName);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
getInquiryResponderDescriptor(task) {
|
|
1175
|
+
const remote = this.remoteIntentDeputiesByTask.get(task);
|
|
1176
|
+
if (remote) {
|
|
1177
|
+
return {
|
|
1178
|
+
isRemote: true,
|
|
1179
|
+
serviceName: remote.serviceName,
|
|
1180
|
+
taskName: remote.remoteTaskName,
|
|
1181
|
+
taskVersion: remote.remoteTaskVersion,
|
|
1182
|
+
localTaskName: remote.localTaskName
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
isRemote: false,
|
|
1187
|
+
serviceName: this.serviceName ?? "UnknownService",
|
|
1188
|
+
taskName: task.name,
|
|
1189
|
+
taskVersion: task.version,
|
|
1190
|
+
localTaskName: task.name
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
910
1193
|
reset() {
|
|
911
1194
|
this.instances.clear();
|
|
1195
|
+
this.deputies.clear();
|
|
1196
|
+
this.remoteSignals.clear();
|
|
1197
|
+
this.remoteIntents.clear();
|
|
1198
|
+
this.remoteIntentDeputiesByKey.clear();
|
|
1199
|
+
this.remoteIntentDeputiesByTask.clear();
|
|
912
1200
|
}
|
|
913
1201
|
};
|
|
914
1202
|
|
|
@@ -1025,6 +1313,8 @@ var RestController = class _RestController {
|
|
|
1025
1313
|
* It initializes and configures the REST server tasks.
|
|
1026
1314
|
*/
|
|
1027
1315
|
constructor() {
|
|
1316
|
+
this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
|
|
1317
|
+
this.diagnosticsErrorHistoryLimit = 100;
|
|
1028
1318
|
/**
|
|
1029
1319
|
* Fetches data from the given URL with a specified timeout. This function performs
|
|
1030
1320
|
* a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
|
|
@@ -1065,6 +1355,11 @@ var RestController = class _RestController {
|
|
|
1065
1355
|
"meta.rest.delegation_requested",
|
|
1066
1356
|
"meta.socket.delegation_requested"
|
|
1067
1357
|
);
|
|
1358
|
+
CadenzaService.createMetaTask(
|
|
1359
|
+
"Collect fetch transport diagnostics",
|
|
1360
|
+
(ctx) => this.collectFetchTransportDiagnostics(ctx),
|
|
1361
|
+
"Responds to distributed transport diagnostics inquiries with REST/fetch client data."
|
|
1362
|
+
).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
|
|
1068
1363
|
CadenzaService.createMetaRoutine(
|
|
1069
1364
|
"RestServer",
|
|
1070
1365
|
[
|
|
@@ -1395,6 +1690,13 @@ var RestController = class _RestController {
|
|
|
1395
1690
|
const port = protocol === "https" ? 443 : servicePort;
|
|
1396
1691
|
const URL = `${protocol}://${serviceAddress}:${port}`;
|
|
1397
1692
|
const fetchId = `${serviceAddress}_${port}`;
|
|
1693
|
+
const fetchDiagnostics = this.ensureFetchClientDiagnostics(
|
|
1694
|
+
fetchId,
|
|
1695
|
+
serviceName,
|
|
1696
|
+
URL
|
|
1697
|
+
);
|
|
1698
|
+
fetchDiagnostics.destroyed = false;
|
|
1699
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1398
1700
|
if (CadenzaService.get(`Send Handshake to ${URL}`)) {
|
|
1399
1701
|
console.error("Fetch client already exists", URL);
|
|
1400
1702
|
return;
|
|
@@ -1416,6 +1718,10 @@ var RestController = class _RestController {
|
|
|
1416
1718
|
);
|
|
1417
1719
|
if (response.__status !== "success") {
|
|
1418
1720
|
const error = response.__error ?? `Failed to connect to service ${serviceName} ${ctx2.serviceInstanceId}`;
|
|
1721
|
+
fetchDiagnostics.connected = false;
|
|
1722
|
+
fetchDiagnostics.lastHandshakeError = error;
|
|
1723
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1724
|
+
this.recordFetchClientError(fetchId, serviceName, URL, error);
|
|
1419
1725
|
CadenzaService.log(
|
|
1420
1726
|
"Fetch handshake failed.",
|
|
1421
1727
|
{ error, serviceName, URL },
|
|
@@ -1425,6 +1731,11 @@ var RestController = class _RestController {
|
|
|
1425
1731
|
return { ...ctx2, __error: error, errored: true };
|
|
1426
1732
|
}
|
|
1427
1733
|
ctx2.serviceInstanceId = response.__serviceInstanceId;
|
|
1734
|
+
fetchDiagnostics.connected = true;
|
|
1735
|
+
fetchDiagnostics.destroyed = false;
|
|
1736
|
+
fetchDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1737
|
+
fetchDiagnostics.lastHandshakeError = null;
|
|
1738
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1428
1739
|
CadenzaService.log("Fetch client connected.", {
|
|
1429
1740
|
response,
|
|
1430
1741
|
serviceName,
|
|
@@ -1440,6 +1751,10 @@ var RestController = class _RestController {
|
|
|
1440
1751
|
});
|
|
1441
1752
|
}
|
|
1442
1753
|
} catch (e) {
|
|
1754
|
+
fetchDiagnostics.connected = false;
|
|
1755
|
+
fetchDiagnostics.lastHandshakeError = this.getErrorMessage(e);
|
|
1756
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1757
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1443
1758
|
CadenzaService.log(
|
|
1444
1759
|
"Error in fetch handshake",
|
|
1445
1760
|
{ error: e, serviceName, URL, ctx: ctx2 },
|
|
@@ -1461,6 +1776,8 @@ var RestController = class _RestController {
|
|
|
1461
1776
|
if (ctx2.__remoteRoutineName === void 0) {
|
|
1462
1777
|
return;
|
|
1463
1778
|
}
|
|
1779
|
+
fetchDiagnostics.delegationRequests++;
|
|
1780
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1464
1781
|
let resultContext;
|
|
1465
1782
|
try {
|
|
1466
1783
|
resultContext = await this.fetchDataWithTimeout(
|
|
@@ -1474,8 +1791,21 @@ var RestController = class _RestController {
|
|
|
1474
1791
|
},
|
|
1475
1792
|
3e4
|
|
1476
1793
|
);
|
|
1794
|
+
if (resultContext?.errored || resultContext?.failed) {
|
|
1795
|
+
fetchDiagnostics.delegationFailures++;
|
|
1796
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1797
|
+
this.recordFetchClientError(
|
|
1798
|
+
fetchId,
|
|
1799
|
+
serviceName,
|
|
1800
|
+
URL,
|
|
1801
|
+
resultContext?.__error ?? resultContext?.error ?? "Delegation failed"
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1477
1804
|
} catch (e) {
|
|
1478
1805
|
console.error("Error in delegation", e);
|
|
1806
|
+
fetchDiagnostics.delegationFailures++;
|
|
1807
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1808
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1479
1809
|
resultContext = {
|
|
1480
1810
|
__error: `Error: ${e}`,
|
|
1481
1811
|
errored: true,
|
|
@@ -1501,6 +1831,8 @@ var RestController = class _RestController {
|
|
|
1501
1831
|
if (ctx2.__signalName === void 0) {
|
|
1502
1832
|
return;
|
|
1503
1833
|
}
|
|
1834
|
+
fetchDiagnostics.signalTransmissions++;
|
|
1835
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1504
1836
|
let response;
|
|
1505
1837
|
try {
|
|
1506
1838
|
response = await this.fetchDataWithTimeout(
|
|
@@ -1517,8 +1849,21 @@ var RestController = class _RestController {
|
|
|
1517
1849
|
if (ctx2.__routineExecId) {
|
|
1518
1850
|
emit(`meta.fetch.transmitted:${ctx2.__routineExecId}`, response);
|
|
1519
1851
|
}
|
|
1852
|
+
if (response?.errored || response?.failed) {
|
|
1853
|
+
fetchDiagnostics.signalFailures++;
|
|
1854
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1855
|
+
this.recordFetchClientError(
|
|
1856
|
+
fetchId,
|
|
1857
|
+
serviceName,
|
|
1858
|
+
URL,
|
|
1859
|
+
response?.__error ?? response?.error ?? "Signal transmission failed"
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1520
1862
|
} catch (e) {
|
|
1521
1863
|
console.error("Error in transmission", e);
|
|
1864
|
+
fetchDiagnostics.signalFailures++;
|
|
1865
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1866
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1522
1867
|
response = {
|
|
1523
1868
|
__error: `Error: ${e}`,
|
|
1524
1869
|
errored: true,
|
|
@@ -1536,6 +1881,8 @@ var RestController = class _RestController {
|
|
|
1536
1881
|
const statusTask = CadenzaService.createMetaTask(
|
|
1537
1882
|
`Request status from ${URL}`,
|
|
1538
1883
|
async (ctx2) => {
|
|
1884
|
+
fetchDiagnostics.statusChecks++;
|
|
1885
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1539
1886
|
let status;
|
|
1540
1887
|
try {
|
|
1541
1888
|
status = await this.fetchDataWithTimeout(
|
|
@@ -1545,7 +1892,20 @@ var RestController = class _RestController {
|
|
|
1545
1892
|
},
|
|
1546
1893
|
1e3
|
|
1547
1894
|
);
|
|
1895
|
+
if (status?.errored || status?.failed) {
|
|
1896
|
+
fetchDiagnostics.statusFailures++;
|
|
1897
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1898
|
+
this.recordFetchClientError(
|
|
1899
|
+
fetchId,
|
|
1900
|
+
serviceName,
|
|
1901
|
+
URL,
|
|
1902
|
+
status?.__error ?? status?.error ?? "Status check failed"
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1548
1905
|
} catch (e) {
|
|
1906
|
+
fetchDiagnostics.statusFailures++;
|
|
1907
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1908
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1549
1909
|
status = {
|
|
1550
1910
|
__error: `Error: ${e}`,
|
|
1551
1911
|
errored: true,
|
|
@@ -1557,6 +1917,9 @@ var RestController = class _RestController {
|
|
|
1557
1917
|
"Requests status"
|
|
1558
1918
|
).doOn("meta.fetch.status_check_requested").emits("meta.fetch.status_checked").emitsOnFail("meta.fetch.status_check_failed");
|
|
1559
1919
|
CadenzaService.createEphemeralMetaTask("Destroy fetch client", () => {
|
|
1920
|
+
fetchDiagnostics.connected = false;
|
|
1921
|
+
fetchDiagnostics.destroyed = true;
|
|
1922
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1560
1923
|
CadenzaService.log("Destroying fetch client", { URL, serviceName });
|
|
1561
1924
|
handshakeTask.destroy();
|
|
1562
1925
|
delegateTask.destroy();
|
|
@@ -1605,17 +1968,191 @@ var RestController = class _RestController {
|
|
|
1605
1968
|
if (!this._instance) this._instance = new _RestController();
|
|
1606
1969
|
return this._instance;
|
|
1607
1970
|
}
|
|
1971
|
+
resolveTransportDiagnosticsOptions(ctx) {
|
|
1972
|
+
const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
|
|
1973
|
+
const includeErrorHistory = Boolean(ctx.includeErrorHistory);
|
|
1974
|
+
const requestedLimit = Number(ctx.errorHistoryLimit);
|
|
1975
|
+
const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
|
|
1976
|
+
return {
|
|
1977
|
+
detailLevel,
|
|
1978
|
+
includeErrorHistory,
|
|
1979
|
+
errorHistoryLimit
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
ensureFetchClientDiagnostics(fetchId, serviceName, url) {
|
|
1983
|
+
let state = this.fetchClientDiagnostics.get(fetchId);
|
|
1984
|
+
if (!state) {
|
|
1985
|
+
state = {
|
|
1986
|
+
fetchId,
|
|
1987
|
+
serviceName,
|
|
1988
|
+
url,
|
|
1989
|
+
connected: false,
|
|
1990
|
+
destroyed: false,
|
|
1991
|
+
lastHandshakeAt: null,
|
|
1992
|
+
lastHandshakeError: null,
|
|
1993
|
+
lastError: null,
|
|
1994
|
+
lastErrorAt: 0,
|
|
1995
|
+
errorHistory: [],
|
|
1996
|
+
delegationRequests: 0,
|
|
1997
|
+
delegationFailures: 0,
|
|
1998
|
+
signalTransmissions: 0,
|
|
1999
|
+
signalFailures: 0,
|
|
2000
|
+
statusChecks: 0,
|
|
2001
|
+
statusFailures: 0,
|
|
2002
|
+
updatedAt: Date.now()
|
|
2003
|
+
};
|
|
2004
|
+
this.fetchClientDiagnostics.set(fetchId, state);
|
|
2005
|
+
} else {
|
|
2006
|
+
state.serviceName = serviceName;
|
|
2007
|
+
state.url = url;
|
|
2008
|
+
}
|
|
2009
|
+
return state;
|
|
2010
|
+
}
|
|
2011
|
+
getErrorMessage(error) {
|
|
2012
|
+
if (error instanceof Error) {
|
|
2013
|
+
return error.message;
|
|
2014
|
+
}
|
|
2015
|
+
if (typeof error === "string") {
|
|
2016
|
+
return error;
|
|
2017
|
+
}
|
|
2018
|
+
try {
|
|
2019
|
+
return JSON.stringify(error);
|
|
2020
|
+
} catch {
|
|
2021
|
+
return String(error);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
recordFetchClientError(fetchId, serviceName, url, error) {
|
|
2025
|
+
const state = this.ensureFetchClientDiagnostics(fetchId, serviceName, url);
|
|
2026
|
+
const message = this.getErrorMessage(error);
|
|
2027
|
+
const now = Date.now();
|
|
2028
|
+
state.lastError = message;
|
|
2029
|
+
state.lastErrorAt = now;
|
|
2030
|
+
state.updatedAt = now;
|
|
2031
|
+
state.errorHistory.push({ at: new Date(now).toISOString(), message });
|
|
2032
|
+
if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
|
|
2033
|
+
state.errorHistory.splice(
|
|
2034
|
+
0,
|
|
2035
|
+
state.errorHistory.length - this.diagnosticsErrorHistoryLimit
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
collectFetchTransportDiagnostics(ctx) {
|
|
2040
|
+
const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
|
|
2041
|
+
const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
|
|
2042
|
+
const states = Array.from(this.fetchClientDiagnostics.values()).sort(
|
|
2043
|
+
(a, b) => a.fetchId.localeCompare(b.fetchId)
|
|
2044
|
+
);
|
|
2045
|
+
const summary = {
|
|
2046
|
+
detailLevel,
|
|
2047
|
+
totalClients: states.length,
|
|
2048
|
+
connectedClients: states.filter((state) => state.connected).length,
|
|
2049
|
+
destroyedClients: states.filter((state) => state.destroyed).length,
|
|
2050
|
+
delegationRequests: states.reduce(
|
|
2051
|
+
(acc, state) => acc + state.delegationRequests,
|
|
2052
|
+
0
|
|
2053
|
+
),
|
|
2054
|
+
delegationFailures: states.reduce(
|
|
2055
|
+
(acc, state) => acc + state.delegationFailures,
|
|
2056
|
+
0
|
|
2057
|
+
),
|
|
2058
|
+
signalTransmissions: states.reduce(
|
|
2059
|
+
(acc, state) => acc + state.signalTransmissions,
|
|
2060
|
+
0
|
|
2061
|
+
),
|
|
2062
|
+
signalFailures: states.reduce((acc, state) => acc + state.signalFailures, 0),
|
|
2063
|
+
statusChecks: states.reduce((acc, state) => acc + state.statusChecks, 0),
|
|
2064
|
+
statusFailures: states.reduce((acc, state) => acc + state.statusFailures, 0),
|
|
2065
|
+
latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
|
|
2066
|
+
};
|
|
2067
|
+
if (detailLevel === "summary") {
|
|
2068
|
+
return {
|
|
2069
|
+
transportDiagnostics: {
|
|
2070
|
+
[serviceName]: {
|
|
2071
|
+
fetchClient: summary
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
const clients = states.map((state) => {
|
|
2077
|
+
const details = {
|
|
2078
|
+
fetchId: state.fetchId,
|
|
2079
|
+
serviceName: state.serviceName,
|
|
2080
|
+
url: state.url,
|
|
2081
|
+
connected: state.connected,
|
|
2082
|
+
destroyed: state.destroyed,
|
|
2083
|
+
lastHandshakeAt: state.lastHandshakeAt,
|
|
2084
|
+
lastHandshakeError: state.lastHandshakeError,
|
|
2085
|
+
latestError: state.lastError,
|
|
2086
|
+
delegationRequests: state.delegationRequests,
|
|
2087
|
+
delegationFailures: state.delegationFailures,
|
|
2088
|
+
signalTransmissions: state.signalTransmissions,
|
|
2089
|
+
signalFailures: state.signalFailures,
|
|
2090
|
+
statusChecks: state.statusChecks,
|
|
2091
|
+
statusFailures: state.statusFailures
|
|
2092
|
+
};
|
|
2093
|
+
if (includeErrorHistory) {
|
|
2094
|
+
details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
|
|
2095
|
+
}
|
|
2096
|
+
return details;
|
|
2097
|
+
});
|
|
2098
|
+
return {
|
|
2099
|
+
transportDiagnostics: {
|
|
2100
|
+
[serviceName]: {
|
|
2101
|
+
fetchClient: {
|
|
2102
|
+
...summary,
|
|
2103
|
+
clients
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
1608
2109
|
};
|
|
1609
2110
|
|
|
1610
2111
|
// src/network/SocketController.ts
|
|
1611
2112
|
import { Server } from "socket.io";
|
|
1612
2113
|
import { RateLimiterMemory as RateLimiterMemory2 } from "rate-limiter-flexible";
|
|
1613
2114
|
import { io } from "socket.io-client";
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2115
|
+
|
|
2116
|
+
// src/network/socketClientUtils.ts
|
|
2117
|
+
var waitForSocketConnection = async (socket, timeoutMs, createError) => {
|
|
2118
|
+
if (!socket) {
|
|
2119
|
+
return { ok: false, error: createError("disconnected") };
|
|
1618
2120
|
}
|
|
2121
|
+
if (socket.connected) {
|
|
2122
|
+
return { ok: true };
|
|
2123
|
+
}
|
|
2124
|
+
return new Promise((resolve) => {
|
|
2125
|
+
let timer = null;
|
|
2126
|
+
let settled = false;
|
|
2127
|
+
const cleanup = () => {
|
|
2128
|
+
if (timer) {
|
|
2129
|
+
clearTimeout(timer);
|
|
2130
|
+
timer = null;
|
|
2131
|
+
}
|
|
2132
|
+
socket.off("connect", onConnect);
|
|
2133
|
+
socket.off("connect_error", onConnectError);
|
|
2134
|
+
socket.off("disconnect", onDisconnect);
|
|
2135
|
+
};
|
|
2136
|
+
const settle = (outcome) => {
|
|
2137
|
+
if (settled) return;
|
|
2138
|
+
settled = true;
|
|
2139
|
+
cleanup();
|
|
2140
|
+
resolve(outcome);
|
|
2141
|
+
};
|
|
2142
|
+
const onConnect = () => settle({ ok: true });
|
|
2143
|
+
const onConnectError = (error) => settle({ ok: false, error: createError("connect_error", error) });
|
|
2144
|
+
const onDisconnect = () => settle({ ok: false, error: createError("disconnected") });
|
|
2145
|
+
socket.once("connect", onConnect);
|
|
2146
|
+
socket.once("connect_error", onConnectError);
|
|
2147
|
+
socket.once("disconnect", onDisconnect);
|
|
2148
|
+
timer = setTimeout(() => {
|
|
2149
|
+
settle({ ok: false, error: createError("connect_timeout") });
|
|
2150
|
+
}, timeoutMs);
|
|
2151
|
+
});
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
// src/network/SocketController.ts
|
|
2155
|
+
var SocketController = class _SocketController {
|
|
1619
2156
|
/**
|
|
1620
2157
|
* Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
|
|
1621
2158
|
* including connection state recovery, rate limiting, CORS handling, and custom event handling.
|
|
@@ -1634,6 +2171,13 @@ var SocketController = class _SocketController {
|
|
|
1634
2171
|
* Initializes the `SocketServer` to be ready for WebSocket communication.
|
|
1635
2172
|
*/
|
|
1636
2173
|
constructor() {
|
|
2174
|
+
this.socketClientDiagnostics = /* @__PURE__ */ new Map();
|
|
2175
|
+
this.diagnosticsErrorHistoryLimit = 100;
|
|
2176
|
+
CadenzaService.createMetaTask(
|
|
2177
|
+
"Collect socket transport diagnostics",
|
|
2178
|
+
(ctx) => this.collectSocketTransportDiagnostics(ctx),
|
|
2179
|
+
"Responds to distributed transport diagnostics inquiries with socket client data."
|
|
2180
|
+
).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
|
|
1637
2181
|
CadenzaService.createMetaRoutine(
|
|
1638
2182
|
"SocketServer",
|
|
1639
2183
|
[
|
|
@@ -1861,6 +2405,13 @@ var SocketController = class _SocketController {
|
|
|
1861
2405
|
const port = protocol === "https" ? 443 : servicePort;
|
|
1862
2406
|
const URL = `${socketProtocol}://${serviceAddress}:${port}`;
|
|
1863
2407
|
const fetchId = `${serviceAddress}_${port}`;
|
|
2408
|
+
const socketDiagnostics = this.ensureSocketClientDiagnostics(
|
|
2409
|
+
fetchId,
|
|
2410
|
+
serviceName,
|
|
2411
|
+
URL
|
|
2412
|
+
);
|
|
2413
|
+
socketDiagnostics.destroyed = false;
|
|
2414
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1864
2415
|
let handshake = false;
|
|
1865
2416
|
let errorCount = 0;
|
|
1866
2417
|
const ERROR_LIMIT = 5;
|
|
@@ -1870,6 +2421,11 @@ var SocketController = class _SocketController {
|
|
|
1870
2421
|
}
|
|
1871
2422
|
const pendingDelegationIds = /* @__PURE__ */ new Set();
|
|
1872
2423
|
const pendingTimers = /* @__PURE__ */ new Set();
|
|
2424
|
+
const syncPendingCounts = () => {
|
|
2425
|
+
socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
|
|
2426
|
+
socketDiagnostics.pendingTimers = pendingTimers.size;
|
|
2427
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2428
|
+
};
|
|
1873
2429
|
let handshakeTask = null;
|
|
1874
2430
|
let emitWhenReady = null;
|
|
1875
2431
|
let transmitTask = null;
|
|
@@ -1886,35 +2442,86 @@ var SocketController = class _SocketController {
|
|
|
1886
2442
|
});
|
|
1887
2443
|
emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
|
|
1888
2444
|
return new Promise((resolve) => {
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
2445
|
+
const resolveWithError = (errorMessage, fallbackError) => {
|
|
2446
|
+
resolve({
|
|
2447
|
+
...data,
|
|
2448
|
+
errored: true,
|
|
2449
|
+
__error: errorMessage,
|
|
2450
|
+
error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
|
|
2451
|
+
socketId: socket?.id,
|
|
2452
|
+
serviceName,
|
|
2453
|
+
URL
|
|
2454
|
+
});
|
|
2455
|
+
};
|
|
2456
|
+
const tryEmit = async () => {
|
|
2457
|
+
const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
|
|
2458
|
+
const waitResult = await waitForSocketConnection(
|
|
2459
|
+
socket,
|
|
2460
|
+
waitTimeoutMs,
|
|
2461
|
+
(reason, error) => {
|
|
2462
|
+
if (reason === "connect_timeout") {
|
|
2463
|
+
return `Socket connect timed out before '${event}'`;
|
|
2464
|
+
}
|
|
2465
|
+
if (reason === "connect_error") {
|
|
2466
|
+
const errMessage = error instanceof Error ? error.message : String(error);
|
|
2467
|
+
return `Socket connect error before '${event}': ${errMessage}`;
|
|
2468
|
+
}
|
|
2469
|
+
return `Socket disconnected before '${event}'`;
|
|
2470
|
+
}
|
|
2471
|
+
);
|
|
2472
|
+
if (!waitResult.ok) {
|
|
2473
|
+
CadenzaService.log(
|
|
2474
|
+
waitResult.error,
|
|
2475
|
+
{ socketId: socket?.id, serviceName, URL, event },
|
|
2476
|
+
"error"
|
|
2477
|
+
);
|
|
2478
|
+
this.recordSocketClientError(
|
|
2479
|
+
fetchId,
|
|
2480
|
+
serviceName,
|
|
2481
|
+
URL,
|
|
2482
|
+
waitResult.error
|
|
2483
|
+
);
|
|
2484
|
+
resolveWithError(waitResult.error);
|
|
1892
2485
|
return;
|
|
1893
2486
|
}
|
|
1894
|
-
let timer;
|
|
2487
|
+
let timer = null;
|
|
1895
2488
|
if (timeoutMs !== 0) {
|
|
1896
2489
|
timer = setTimeout(() => {
|
|
2490
|
+
if (timer) {
|
|
2491
|
+
pendingTimers.delete(timer);
|
|
2492
|
+
syncPendingCounts();
|
|
2493
|
+
timer = null;
|
|
2494
|
+
}
|
|
1897
2495
|
CadenzaService.log(
|
|
1898
2496
|
`Socket event '${event}' timed out`,
|
|
1899
2497
|
{ socketId: socket?.id, serviceName, URL },
|
|
1900
2498
|
"error"
|
|
1901
2499
|
);
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
errored: true,
|
|
1905
|
-
__error: `Socket event '${event}' timed out`,
|
|
1906
|
-
error: `Socket event '${event}' timed out`,
|
|
1907
|
-
socketId: socket?.id,
|
|
2500
|
+
this.recordSocketClientError(
|
|
2501
|
+
fetchId,
|
|
1908
2502
|
serviceName,
|
|
1909
|
-
URL
|
|
1910
|
-
|
|
2503
|
+
URL,
|
|
2504
|
+
`Socket event '${event}' timed out`
|
|
2505
|
+
);
|
|
2506
|
+
resolveWithError(`Socket event '${event}' timed out`);
|
|
1911
2507
|
}, timeoutMs + 10);
|
|
1912
2508
|
pendingTimers.add(timer);
|
|
2509
|
+
syncPendingCounts();
|
|
1913
2510
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2511
|
+
const connectedSocket = socket;
|
|
2512
|
+
if (!connectedSocket) {
|
|
2513
|
+
resolveWithError(
|
|
2514
|
+
`Socket unavailable before emitting '${event}'`
|
|
2515
|
+
);
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
|
|
2519
|
+
if (timer) {
|
|
2520
|
+
clearTimeout(timer);
|
|
2521
|
+
pendingTimers.delete(timer);
|
|
2522
|
+
syncPendingCounts();
|
|
2523
|
+
timer = null;
|
|
2524
|
+
}
|
|
1918
2525
|
if (err) {
|
|
1919
2526
|
CadenzaService.log(
|
|
1920
2527
|
"Socket timeout.",
|
|
@@ -1926,6 +2533,12 @@ var SocketController = class _SocketController {
|
|
|
1926
2533
|
},
|
|
1927
2534
|
"warning"
|
|
1928
2535
|
);
|
|
2536
|
+
this.recordSocketClientError(
|
|
2537
|
+
fetchId,
|
|
2538
|
+
serviceName,
|
|
2539
|
+
URL,
|
|
2540
|
+
err
|
|
2541
|
+
);
|
|
1929
2542
|
response = {
|
|
1930
2543
|
__error: `Timeout error: ${err}`,
|
|
1931
2544
|
errored: true,
|
|
@@ -1937,15 +2550,15 @@ var SocketController = class _SocketController {
|
|
|
1937
2550
|
resolve(response);
|
|
1938
2551
|
});
|
|
1939
2552
|
};
|
|
1940
|
-
|
|
1941
|
-
tryEmit();
|
|
1942
|
-
} else {
|
|
1943
|
-
socket?.once("connect", tryEmit);
|
|
1944
|
-
}
|
|
2553
|
+
void tryEmit();
|
|
1945
2554
|
});
|
|
1946
2555
|
};
|
|
1947
2556
|
socket.on("connect", () => {
|
|
1948
2557
|
if (handshake) return;
|
|
2558
|
+
socketDiagnostics.connected = true;
|
|
2559
|
+
socketDiagnostics.destroyed = false;
|
|
2560
|
+
socketDiagnostics.socketId = socket?.id ?? null;
|
|
2561
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1949
2562
|
CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
|
|
1950
2563
|
});
|
|
1951
2564
|
socket.on("delegation_progress", (ctx2) => {
|
|
@@ -1964,6 +2577,12 @@ var SocketController = class _SocketController {
|
|
|
1964
2577
|
});
|
|
1965
2578
|
socket.on("connect_error", (err) => {
|
|
1966
2579
|
handshake = false;
|
|
2580
|
+
socketDiagnostics.connected = false;
|
|
2581
|
+
socketDiagnostics.handshake = false;
|
|
2582
|
+
socketDiagnostics.connectErrors++;
|
|
2583
|
+
socketDiagnostics.lastHandshakeError = err.message;
|
|
2584
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2585
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
1967
2586
|
CadenzaService.log(
|
|
1968
2587
|
"Socket connect error",
|
|
1969
2588
|
{ error: err.message, serviceName, socketId: socket?.id, URL },
|
|
@@ -1972,9 +2591,16 @@ var SocketController = class _SocketController {
|
|
|
1972
2591
|
CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
|
|
1973
2592
|
});
|
|
1974
2593
|
socket.on("reconnect_attempt", (attempt) => {
|
|
2594
|
+
socketDiagnostics.reconnectAttempts = Math.max(
|
|
2595
|
+
socketDiagnostics.reconnectAttempts,
|
|
2596
|
+
attempt
|
|
2597
|
+
);
|
|
2598
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1975
2599
|
CadenzaService.log(`Reconnect attempt: ${attempt}`);
|
|
1976
2600
|
});
|
|
1977
2601
|
socket.on("reconnect", (attempt) => {
|
|
2602
|
+
socketDiagnostics.connected = true;
|
|
2603
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1978
2604
|
CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
|
|
1979
2605
|
socketId: socket?.id,
|
|
1980
2606
|
URL,
|
|
@@ -1983,6 +2609,12 @@ var SocketController = class _SocketController {
|
|
|
1983
2609
|
});
|
|
1984
2610
|
socket.on("reconnect_error", (err) => {
|
|
1985
2611
|
handshake = false;
|
|
2612
|
+
socketDiagnostics.connected = false;
|
|
2613
|
+
socketDiagnostics.handshake = false;
|
|
2614
|
+
socketDiagnostics.reconnectErrors++;
|
|
2615
|
+
socketDiagnostics.lastHandshakeError = err.message;
|
|
2616
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2617
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
1986
2618
|
CadenzaService.log(
|
|
1987
2619
|
"Socket reconnect failed.",
|
|
1988
2620
|
{ error: err.message, serviceName, URL, socketId: socket?.id },
|
|
@@ -1991,6 +2623,9 @@ var SocketController = class _SocketController {
|
|
|
1991
2623
|
});
|
|
1992
2624
|
socket.on("error", (err) => {
|
|
1993
2625
|
errorCount++;
|
|
2626
|
+
socketDiagnostics.socketErrors++;
|
|
2627
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2628
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
1994
2629
|
CadenzaService.log(
|
|
1995
2630
|
"Socket error",
|
|
1996
2631
|
{ error: err, socketId: socket?.id, URL, serviceName },
|
|
@@ -1999,6 +2634,11 @@ var SocketController = class _SocketController {
|
|
|
1999
2634
|
CadenzaService.emit("meta.socket_client.error", err);
|
|
2000
2635
|
});
|
|
2001
2636
|
socket.on("disconnect", () => {
|
|
2637
|
+
const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2638
|
+
socketDiagnostics.connected = false;
|
|
2639
|
+
socketDiagnostics.handshake = false;
|
|
2640
|
+
socketDiagnostics.lastDisconnectAt = disconnectedAt;
|
|
2641
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2002
2642
|
CadenzaService.log(
|
|
2003
2643
|
"Socket disconnected.",
|
|
2004
2644
|
{ URL, serviceName, socketId: socket?.id },
|
|
@@ -2017,6 +2657,8 @@ var SocketController = class _SocketController {
|
|
|
2017
2657
|
async (ctx2, emit) => {
|
|
2018
2658
|
if (handshake) return;
|
|
2019
2659
|
handshake = true;
|
|
2660
|
+
socketDiagnostics.handshake = true;
|
|
2661
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2020
2662
|
await emitWhenReady?.(
|
|
2021
2663
|
"handshake",
|
|
2022
2664
|
{
|
|
@@ -2028,6 +2670,11 @@ var SocketController = class _SocketController {
|
|
|
2028
2670
|
1e4,
|
|
2029
2671
|
(result) => {
|
|
2030
2672
|
if (result.status === "success") {
|
|
2673
|
+
socketDiagnostics.connected = true;
|
|
2674
|
+
socketDiagnostics.handshake = true;
|
|
2675
|
+
socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2676
|
+
socketDiagnostics.lastHandshakeError = null;
|
|
2677
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2031
2678
|
CadenzaService.log("Socket client connected", {
|
|
2032
2679
|
result,
|
|
2033
2680
|
serviceName,
|
|
@@ -2035,6 +2682,16 @@ var SocketController = class _SocketController {
|
|
|
2035
2682
|
URL
|
|
2036
2683
|
});
|
|
2037
2684
|
} else {
|
|
2685
|
+
socketDiagnostics.connected = false;
|
|
2686
|
+
socketDiagnostics.handshake = false;
|
|
2687
|
+
socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
|
|
2688
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2689
|
+
this.recordSocketClientError(
|
|
2690
|
+
fetchId,
|
|
2691
|
+
serviceName,
|
|
2692
|
+
URL,
|
|
2693
|
+
socketDiagnostics.lastHandshakeError
|
|
2694
|
+
);
|
|
2038
2695
|
CadenzaService.log(
|
|
2039
2696
|
"Socket handshake failed",
|
|
2040
2697
|
{ result, serviceName, socketId: socket?.id, URL },
|
|
@@ -2057,6 +2714,7 @@ var SocketController = class _SocketController {
|
|
|
2057
2714
|
delete ctx2.__broadcast;
|
|
2058
2715
|
const requestSentAt = Date.now();
|
|
2059
2716
|
pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
|
|
2717
|
+
syncPendingCounts();
|
|
2060
2718
|
emitWhenReady?.(
|
|
2061
2719
|
"delegation",
|
|
2062
2720
|
ctx2,
|
|
@@ -2074,6 +2732,15 @@ var SocketController = class _SocketController {
|
|
|
2074
2732
|
}
|
|
2075
2733
|
);
|
|
2076
2734
|
pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
|
|
2735
|
+
syncPendingCounts();
|
|
2736
|
+
if (resultContext?.errored || resultContext?.failed) {
|
|
2737
|
+
this.recordSocketClientError(
|
|
2738
|
+
fetchId,
|
|
2739
|
+
serviceName,
|
|
2740
|
+
URL,
|
|
2741
|
+
resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2077
2744
|
resolve(resultContext);
|
|
2078
2745
|
}
|
|
2079
2746
|
);
|
|
@@ -2109,6 +2776,10 @@ var SocketController = class _SocketController {
|
|
|
2109
2776
|
`Shutdown SocketClient ${URL}`,
|
|
2110
2777
|
(ctx2, emit) => {
|
|
2111
2778
|
handshake = false;
|
|
2779
|
+
socketDiagnostics.connected = false;
|
|
2780
|
+
socketDiagnostics.handshake = false;
|
|
2781
|
+
socketDiagnostics.destroyed = true;
|
|
2782
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2112
2783
|
CadenzaService.log("Shutting down socket client", { URL, serviceName });
|
|
2113
2784
|
socket?.close();
|
|
2114
2785
|
handshakeTask?.destroy();
|
|
@@ -2138,10 +2809,12 @@ var SocketController = class _SocketController {
|
|
|
2138
2809
|
});
|
|
2139
2810
|
}
|
|
2140
2811
|
pendingDelegationIds.clear();
|
|
2812
|
+
syncPendingCounts();
|
|
2141
2813
|
for (const timer of pendingTimers) {
|
|
2142
2814
|
clearTimeout(timer);
|
|
2143
2815
|
}
|
|
2144
2816
|
pendingTimers.clear();
|
|
2817
|
+
syncPendingCounts();
|
|
2145
2818
|
},
|
|
2146
2819
|
"Shuts down the socket client"
|
|
2147
2820
|
).doOn(
|
|
@@ -2155,6 +2828,157 @@ var SocketController = class _SocketController {
|
|
|
2155
2828
|
"Connects to a specified socket server"
|
|
2156
2829
|
).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
|
|
2157
2830
|
}
|
|
2831
|
+
static get instance() {
|
|
2832
|
+
if (!this._instance) this._instance = new _SocketController();
|
|
2833
|
+
return this._instance;
|
|
2834
|
+
}
|
|
2835
|
+
resolveTransportDiagnosticsOptions(ctx) {
|
|
2836
|
+
const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
|
|
2837
|
+
const includeErrorHistory = Boolean(ctx.includeErrorHistory);
|
|
2838
|
+
const requestedLimit = Number(ctx.errorHistoryLimit);
|
|
2839
|
+
const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
|
|
2840
|
+
return {
|
|
2841
|
+
detailLevel,
|
|
2842
|
+
includeErrorHistory,
|
|
2843
|
+
errorHistoryLimit
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
ensureSocketClientDiagnostics(fetchId, serviceName, url) {
|
|
2847
|
+
let state = this.socketClientDiagnostics.get(fetchId);
|
|
2848
|
+
if (!state) {
|
|
2849
|
+
state = {
|
|
2850
|
+
fetchId,
|
|
2851
|
+
serviceName,
|
|
2852
|
+
url,
|
|
2853
|
+
socketId: null,
|
|
2854
|
+
connected: false,
|
|
2855
|
+
handshake: false,
|
|
2856
|
+
reconnectAttempts: 0,
|
|
2857
|
+
connectErrors: 0,
|
|
2858
|
+
reconnectErrors: 0,
|
|
2859
|
+
socketErrors: 0,
|
|
2860
|
+
pendingDelegations: 0,
|
|
2861
|
+
pendingTimers: 0,
|
|
2862
|
+
destroyed: false,
|
|
2863
|
+
lastHandshakeAt: null,
|
|
2864
|
+
lastHandshakeError: null,
|
|
2865
|
+
lastDisconnectAt: null,
|
|
2866
|
+
lastError: null,
|
|
2867
|
+
lastErrorAt: 0,
|
|
2868
|
+
errorHistory: [],
|
|
2869
|
+
updatedAt: Date.now()
|
|
2870
|
+
};
|
|
2871
|
+
this.socketClientDiagnostics.set(fetchId, state);
|
|
2872
|
+
} else {
|
|
2873
|
+
state.serviceName = serviceName;
|
|
2874
|
+
state.url = url;
|
|
2875
|
+
}
|
|
2876
|
+
return state;
|
|
2877
|
+
}
|
|
2878
|
+
getErrorMessage(error) {
|
|
2879
|
+
if (error instanceof Error) {
|
|
2880
|
+
return error.message;
|
|
2881
|
+
}
|
|
2882
|
+
if (typeof error === "string") {
|
|
2883
|
+
return error;
|
|
2884
|
+
}
|
|
2885
|
+
try {
|
|
2886
|
+
return JSON.stringify(error);
|
|
2887
|
+
} catch {
|
|
2888
|
+
return String(error);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
recordSocketClientError(fetchId, serviceName, url, error) {
|
|
2892
|
+
const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
|
|
2893
|
+
const message = this.getErrorMessage(error);
|
|
2894
|
+
const now = Date.now();
|
|
2895
|
+
state.lastError = message;
|
|
2896
|
+
state.lastErrorAt = now;
|
|
2897
|
+
state.updatedAt = now;
|
|
2898
|
+
state.errorHistory.push({
|
|
2899
|
+
at: new Date(now).toISOString(),
|
|
2900
|
+
message
|
|
2901
|
+
});
|
|
2902
|
+
if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
|
|
2903
|
+
state.errorHistory.splice(
|
|
2904
|
+
0,
|
|
2905
|
+
state.errorHistory.length - this.diagnosticsErrorHistoryLimit
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
collectSocketTransportDiagnostics(ctx) {
|
|
2910
|
+
const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
|
|
2911
|
+
const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
|
|
2912
|
+
const states = Array.from(this.socketClientDiagnostics.values()).sort(
|
|
2913
|
+
(a, b) => a.fetchId.localeCompare(b.fetchId)
|
|
2914
|
+
);
|
|
2915
|
+
const summary = {
|
|
2916
|
+
detailLevel,
|
|
2917
|
+
totalClients: states.length,
|
|
2918
|
+
connectedClients: states.filter((state) => state.connected).length,
|
|
2919
|
+
activeHandshakes: states.filter((state) => state.handshake).length,
|
|
2920
|
+
pendingDelegations: states.reduce(
|
|
2921
|
+
(acc, state) => acc + state.pendingDelegations,
|
|
2922
|
+
0
|
|
2923
|
+
),
|
|
2924
|
+
pendingTimers: states.reduce((acc, state) => acc + state.pendingTimers, 0),
|
|
2925
|
+
reconnectAttempts: states.reduce(
|
|
2926
|
+
(acc, state) => acc + state.reconnectAttempts,
|
|
2927
|
+
0
|
|
2928
|
+
),
|
|
2929
|
+
connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
|
|
2930
|
+
reconnectErrors: states.reduce(
|
|
2931
|
+
(acc, state) => acc + state.reconnectErrors,
|
|
2932
|
+
0
|
|
2933
|
+
),
|
|
2934
|
+
socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
|
|
2935
|
+
latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
|
|
2936
|
+
};
|
|
2937
|
+
if (detailLevel === "summary") {
|
|
2938
|
+
return {
|
|
2939
|
+
transportDiagnostics: {
|
|
2940
|
+
[serviceName]: {
|
|
2941
|
+
socketClient: summary
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
const clients = states.map((state) => {
|
|
2947
|
+
const details = {
|
|
2948
|
+
fetchId: state.fetchId,
|
|
2949
|
+
serviceName: state.serviceName,
|
|
2950
|
+
url: state.url,
|
|
2951
|
+
socketId: state.socketId,
|
|
2952
|
+
connected: state.connected,
|
|
2953
|
+
handshake: state.handshake,
|
|
2954
|
+
reconnectAttempts: state.reconnectAttempts,
|
|
2955
|
+
connectErrors: state.connectErrors,
|
|
2956
|
+
reconnectErrors: state.reconnectErrors,
|
|
2957
|
+
socketErrors: state.socketErrors,
|
|
2958
|
+
pendingDelegations: state.pendingDelegations,
|
|
2959
|
+
pendingTimers: state.pendingTimers,
|
|
2960
|
+
destroyed: state.destroyed,
|
|
2961
|
+
lastHandshakeAt: state.lastHandshakeAt,
|
|
2962
|
+
lastHandshakeError: state.lastHandshakeError,
|
|
2963
|
+
lastDisconnectAt: state.lastDisconnectAt,
|
|
2964
|
+
latestError: state.lastError
|
|
2965
|
+
};
|
|
2966
|
+
if (includeErrorHistory) {
|
|
2967
|
+
details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
|
|
2968
|
+
}
|
|
2969
|
+
return details;
|
|
2970
|
+
});
|
|
2971
|
+
return {
|
|
2972
|
+
transportDiagnostics: {
|
|
2973
|
+
[serviceName]: {
|
|
2974
|
+
socketClient: {
|
|
2975
|
+
...summary,
|
|
2976
|
+
clients
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2158
2982
|
};
|
|
2159
2983
|
|
|
2160
2984
|
// src/utils/tools.ts
|
|
@@ -2259,12 +3083,6 @@ var GraphMetadataController = class _GraphMetadataController {
|
|
|
2259
3083
|
data: {
|
|
2260
3084
|
...ctx.data,
|
|
2261
3085
|
serviceName: CadenzaService.serviceRegistry.serviceName
|
|
2262
|
-
// input_context_schema_id: ctx.data.inputContextSchema ? { // TODO
|
|
2263
|
-
//
|
|
2264
|
-
// } : null,
|
|
2265
|
-
// output_context_schema_id: ctx.data.outputContextSchema ? {
|
|
2266
|
-
//
|
|
2267
|
-
// } : null,
|
|
2268
3086
|
}
|
|
2269
3087
|
};
|
|
2270
3088
|
}).doOn("meta.task.created").emits("global.meta.graph_metadata.task_created");
|
|
@@ -2472,9 +3290,11 @@ var GraphMetadataController = class _GraphMetadataController {
|
|
|
2472
3290
|
{ concurrency: 100, isSubMeta: true }
|
|
2473
3291
|
).doOn("meta.node.mapped", "meta.node.detected_previous_task_execution").emits("global.meta.graph_metadata.relationship_executed");
|
|
2474
3292
|
CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
|
|
3293
|
+
const intentName = ctx.data?.name;
|
|
2475
3294
|
return {
|
|
2476
3295
|
data: {
|
|
2477
|
-
...ctx.data
|
|
3296
|
+
...ctx.data,
|
|
3297
|
+
isMeta: intentName ? isMetaIntentName(intentName) : false
|
|
2478
3298
|
}
|
|
2479
3299
|
};
|
|
2480
3300
|
}).doOn("meta.inquiry_broker.added").emits("global.meta.graph_metadata.intent_created");
|
|
@@ -2506,6 +3326,52 @@ var SCHEMA_TYPES = [
|
|
|
2506
3326
|
// src/database/DatabaseController.ts
|
|
2507
3327
|
import { Pool } from "pg";
|
|
2508
3328
|
import { camelCase, snakeCase } from "lodash-es";
|
|
3329
|
+
function resolveTableQueryIntents(serviceName, tableName, table, defaultInputSchema) {
|
|
3330
|
+
const resolvedServiceName = serviceName ?? "unknown-service";
|
|
3331
|
+
const defaultIntentName = `query-${resolvedServiceName}-${tableName}`;
|
|
3332
|
+
const defaultDescription = `Perform a query operation on the ${tableName} table`;
|
|
3333
|
+
const intents = [
|
|
3334
|
+
{
|
|
3335
|
+
name: defaultIntentName,
|
|
3336
|
+
description: defaultDescription,
|
|
3337
|
+
input: defaultInputSchema
|
|
3338
|
+
}
|
|
3339
|
+
];
|
|
3340
|
+
const warnings = [];
|
|
3341
|
+
const names = /* @__PURE__ */ new Set([defaultIntentName]);
|
|
3342
|
+
for (const customIntent of table.customIntents?.query ?? []) {
|
|
3343
|
+
const name = typeof customIntent === "string" ? customIntent.trim() : customIntent.intent?.trim();
|
|
3344
|
+
if (!name) {
|
|
3345
|
+
warnings.push(`Skipped empty custom query intent for table '${tableName}'.`);
|
|
3346
|
+
continue;
|
|
3347
|
+
}
|
|
3348
|
+
if (name.length > 100) {
|
|
3349
|
+
warnings.push(
|
|
3350
|
+
`Skipped custom query intent '${name}' for table '${tableName}': name must be <= 100 characters.`
|
|
3351
|
+
);
|
|
3352
|
+
continue;
|
|
3353
|
+
}
|
|
3354
|
+
if (name.includes(" ") || name.includes(".") || name.includes("\\")) {
|
|
3355
|
+
warnings.push(
|
|
3356
|
+
`Skipped custom query intent '${name}' for table '${tableName}': name cannot contain spaces, dots or backslashes.`
|
|
3357
|
+
);
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
if (names.has(name)) {
|
|
3361
|
+
warnings.push(
|
|
3362
|
+
`Skipped duplicate custom query intent '${name}' for table '${tableName}'.`
|
|
3363
|
+
);
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3366
|
+
names.add(name);
|
|
3367
|
+
intents.push({
|
|
3368
|
+
name,
|
|
3369
|
+
description: typeof customIntent === "string" ? `Perform a query operation on the ${tableName} table` : customIntent.description ?? defaultDescription,
|
|
3370
|
+
input: typeof customIntent === "string" ? defaultInputSchema : customIntent.input ?? defaultInputSchema
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
return { intents, warnings };
|
|
3374
|
+
}
|
|
2509
3375
|
var DatabaseController = class _DatabaseController {
|
|
2510
3376
|
/**
|
|
2511
3377
|
* Constructor for initializing the `DatabaseService` class.
|
|
@@ -2647,6 +3513,20 @@ var DatabaseController = class _DatabaseController {
|
|
|
2647
3513
|
}
|
|
2648
3514
|
}
|
|
2649
3515
|
}
|
|
3516
|
+
if (table.customIntents?.query) {
|
|
3517
|
+
if (!Array.isArray(table.customIntents.query)) {
|
|
3518
|
+
throw new Error(
|
|
3519
|
+
`Invalid customIntents.query for ${tableName}: expected array`
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
3522
|
+
for (const customIntent of table.customIntents.query) {
|
|
3523
|
+
if (typeof customIntent !== "string" && (typeof customIntent !== "object" || !customIntent || typeof customIntent.intent !== "string")) {
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
`Invalid custom query intent on ${tableName}: expected string or object with intent`
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
2650
3530
|
}
|
|
2651
3531
|
}
|
|
2652
3532
|
console.log("SCHEMA VALIDATED");
|
|
@@ -3573,9 +4453,9 @@ var DatabaseController = class _DatabaseController {
|
|
|
3573
4453
|
createDatabaseTask(op, tableName, table, queryFunction, options) {
|
|
3574
4454
|
const opAction = op === "query" ? "queried" : op === "insert" ? "inserted" : op === "update" ? "updated" : op === "delete" ? "deleted" : "";
|
|
3575
4455
|
const defaultSignal = `global.${options.isMeta ? "meta." : ""}${tableName}.${opAction}`;
|
|
3576
|
-
const
|
|
3577
|
-
const
|
|
3578
|
-
CadenzaService.createThrottledTask(
|
|
4456
|
+
const taskName = `${op.charAt(0).toUpperCase() + op.slice(1)} ${tableName}`;
|
|
4457
|
+
const schema = this.getInputSchema(op, tableName, table);
|
|
4458
|
+
const task = CadenzaService.createThrottledTask(
|
|
3579
4459
|
taskName,
|
|
3580
4460
|
async (context, emit) => {
|
|
3581
4461
|
for (const action of Object.keys(table.customSignals?.triggers ?? {})) {
|
|
@@ -3670,17 +4550,8 @@ var DatabaseController = class _DatabaseController {
|
|
|
3670
4550
|
{
|
|
3671
4551
|
isMeta: options.isMeta,
|
|
3672
4552
|
isSubMeta: options.isMeta,
|
|
3673
|
-
validateInputContext:
|
|
3674
|
-
|
|
3675
|
-
inputSchema: {
|
|
3676
|
-
// TODO
|
|
3677
|
-
type: "object",
|
|
3678
|
-
properties: {
|
|
3679
|
-
filter: {
|
|
3680
|
-
type: "object"
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
}
|
|
4553
|
+
validateInputContext: options.securityProfile !== "low",
|
|
4554
|
+
inputSchema: schema
|
|
3684
4555
|
}
|
|
3685
4556
|
).doOn(
|
|
3686
4557
|
...table.customSignals?.triggers?.[op]?.map((signal) => {
|
|
@@ -3691,8 +4562,397 @@ var DatabaseController = class _DatabaseController {
|
|
|
3691
4562
|
return typeof signal === "string" ? signal : signal.signal;
|
|
3692
4563
|
}) ?? []
|
|
3693
4564
|
);
|
|
4565
|
+
if (op === "query") {
|
|
4566
|
+
const { intents, warnings } = resolveTableQueryIntents(
|
|
4567
|
+
CadenzaService.serviceRegistry?.serviceName,
|
|
4568
|
+
tableName,
|
|
4569
|
+
table,
|
|
4570
|
+
schema
|
|
4571
|
+
);
|
|
4572
|
+
for (const warning of warnings) {
|
|
4573
|
+
CadenzaService.log(
|
|
4574
|
+
"Skipped custom query intent registration.",
|
|
4575
|
+
{
|
|
4576
|
+
tableName,
|
|
4577
|
+
warning
|
|
4578
|
+
},
|
|
4579
|
+
"warning"
|
|
4580
|
+
);
|
|
4581
|
+
}
|
|
4582
|
+
for (const intent of intents) {
|
|
4583
|
+
CadenzaService.defineIntent({
|
|
4584
|
+
name: intent.name,
|
|
4585
|
+
description: intent.description,
|
|
4586
|
+
input: intent.input
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
task.respondsTo(...intents.map((intent) => intent.name));
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
getInputSchema(op, tableName, table) {
|
|
4593
|
+
const inputSchema = {
|
|
4594
|
+
type: "object",
|
|
4595
|
+
properties: {
|
|
4596
|
+
queryData: {
|
|
4597
|
+
type: "object",
|
|
4598
|
+
properties: {},
|
|
4599
|
+
strict: true
|
|
4600
|
+
}
|
|
4601
|
+
},
|
|
4602
|
+
strict: true
|
|
4603
|
+
};
|
|
4604
|
+
if (!inputSchema.properties) {
|
|
4605
|
+
return inputSchema;
|
|
4606
|
+
}
|
|
4607
|
+
inputSchema.properties.transaction = getTransactionSchema();
|
|
4608
|
+
inputSchema.properties.queryData.properties.transaction = inputSchema.properties.transaction;
|
|
4609
|
+
switch (op) {
|
|
4610
|
+
case "insert":
|
|
4611
|
+
inputSchema.properties.data = getInsertDataSchemaFromTable(
|
|
4612
|
+
table,
|
|
4613
|
+
tableName
|
|
4614
|
+
);
|
|
4615
|
+
inputSchema.properties.queryData.properties.data = inputSchema.properties.data;
|
|
4616
|
+
inputSchema.properties.batch = getQueryBatchSchemaFromTable();
|
|
4617
|
+
inputSchema.properties.queryData.properties.batch = inputSchema.properties.batch;
|
|
4618
|
+
inputSchema.properties.onConflict = getQueryOnConflictSchemaFromTable(
|
|
4619
|
+
table,
|
|
4620
|
+
tableName
|
|
4621
|
+
);
|
|
4622
|
+
inputSchema.properties.queryData.properties.onConflict = inputSchema.properties.onConflict;
|
|
4623
|
+
break;
|
|
4624
|
+
case "query":
|
|
4625
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4626
|
+
table,
|
|
4627
|
+
tableName
|
|
4628
|
+
);
|
|
4629
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4630
|
+
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
|
|
4631
|
+
table,
|
|
4632
|
+
tableName
|
|
4633
|
+
);
|
|
4634
|
+
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
4635
|
+
inputSchema.properties.joins = getQueryJoinsSchemaFromTable(
|
|
4636
|
+
table,
|
|
4637
|
+
tableName
|
|
4638
|
+
);
|
|
4639
|
+
inputSchema.properties.queryData.properties.joins = inputSchema.properties.joins;
|
|
4640
|
+
inputSchema.properties.sort = getQuerySortSchemaFromTable(
|
|
4641
|
+
table,
|
|
4642
|
+
tableName
|
|
4643
|
+
);
|
|
4644
|
+
inputSchema.properties.queryData.properties.sort = inputSchema.properties.sort;
|
|
4645
|
+
inputSchema.properties.limit = getQueryLimitSchemaFromTable();
|
|
4646
|
+
inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
|
|
4647
|
+
inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
|
|
4648
|
+
inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
|
|
4649
|
+
break;
|
|
4650
|
+
case "update":
|
|
4651
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4652
|
+
table,
|
|
4653
|
+
tableName
|
|
4654
|
+
);
|
|
4655
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4656
|
+
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
|
|
4657
|
+
table,
|
|
4658
|
+
tableName
|
|
4659
|
+
);
|
|
4660
|
+
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
4661
|
+
break;
|
|
4662
|
+
case "delete":
|
|
4663
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4664
|
+
table,
|
|
4665
|
+
tableName
|
|
4666
|
+
);
|
|
4667
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4668
|
+
break;
|
|
4669
|
+
}
|
|
4670
|
+
return inputSchema;
|
|
3694
4671
|
}
|
|
3695
4672
|
};
|
|
4673
|
+
function getInsertDataSchemaFromTable(table, tableName) {
|
|
4674
|
+
const dataSchema = {
|
|
4675
|
+
type: "object",
|
|
4676
|
+
properties: {
|
|
4677
|
+
...Object.fromEntries(
|
|
4678
|
+
Object.entries(table.fields).map((field) => {
|
|
4679
|
+
return [
|
|
4680
|
+
field[0],
|
|
4681
|
+
{
|
|
4682
|
+
value: {
|
|
4683
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4684
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4685
|
+
},
|
|
4686
|
+
effect: {
|
|
4687
|
+
type: "string",
|
|
4688
|
+
constraints: {
|
|
4689
|
+
oneOf: ["increment", "decrement", "set"]
|
|
4690
|
+
}
|
|
4691
|
+
},
|
|
4692
|
+
subOperation: {
|
|
4693
|
+
type: "object",
|
|
4694
|
+
properties: {
|
|
4695
|
+
subOperation: {
|
|
4696
|
+
type: "string",
|
|
4697
|
+
enum: ["insert", "query"]
|
|
4698
|
+
},
|
|
4699
|
+
table: {
|
|
4700
|
+
type: "string"
|
|
4701
|
+
},
|
|
4702
|
+
data: {
|
|
4703
|
+
single: {
|
|
4704
|
+
type: "object"
|
|
4705
|
+
},
|
|
4706
|
+
batch: {
|
|
4707
|
+
type: "array",
|
|
4708
|
+
items: {
|
|
4709
|
+
type: "object"
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
},
|
|
4713
|
+
filter: {
|
|
4714
|
+
type: "object"
|
|
4715
|
+
},
|
|
4716
|
+
fields: {
|
|
4717
|
+
type: "array",
|
|
4718
|
+
items: {
|
|
4719
|
+
type: "string"
|
|
4720
|
+
}
|
|
4721
|
+
},
|
|
4722
|
+
return: {
|
|
4723
|
+
type: "string"
|
|
4724
|
+
}
|
|
4725
|
+
},
|
|
4726
|
+
required: ["subOperation", "table"]
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
];
|
|
4730
|
+
})
|
|
4731
|
+
)
|
|
4732
|
+
},
|
|
4733
|
+
required: Object.entries(table.fields).filter((field) => field[1].required || field[1].primary).map((field) => field[0]),
|
|
4734
|
+
strict: true
|
|
4735
|
+
};
|
|
4736
|
+
return {
|
|
4737
|
+
single: dataSchema,
|
|
4738
|
+
batch: {
|
|
4739
|
+
type: "array",
|
|
4740
|
+
items: dataSchema
|
|
4741
|
+
}
|
|
4742
|
+
};
|
|
4743
|
+
}
|
|
4744
|
+
function getQueryFilterSchemaFromTable(table, tableName) {
|
|
4745
|
+
return {
|
|
4746
|
+
type: "object",
|
|
4747
|
+
properties: {
|
|
4748
|
+
...Object.fromEntries(
|
|
4749
|
+
Object.entries(table.fields).map((field) => {
|
|
4750
|
+
return [
|
|
4751
|
+
field[0],
|
|
4752
|
+
{
|
|
4753
|
+
value: {
|
|
4754
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4755
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4756
|
+
},
|
|
4757
|
+
in: {
|
|
4758
|
+
type: "array",
|
|
4759
|
+
items: {
|
|
4760
|
+
type: tableFieldTypeToSchemaType(field[1].type)
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
];
|
|
4765
|
+
})
|
|
4766
|
+
)
|
|
4767
|
+
},
|
|
4768
|
+
strict: true,
|
|
4769
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4770
|
+
};
|
|
4771
|
+
}
|
|
4772
|
+
function getQueryFieldsSchemaFromTable(table, tableName) {
|
|
4773
|
+
return {
|
|
4774
|
+
type: "array",
|
|
4775
|
+
items: {
|
|
4776
|
+
type: "string",
|
|
4777
|
+
constraints: {
|
|
4778
|
+
oneOf: Object.keys(table.fields)
|
|
4779
|
+
}
|
|
4780
|
+
},
|
|
4781
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4784
|
+
function getQueryJoinsSchemaFromTable(table, tableName) {
|
|
4785
|
+
return {
|
|
4786
|
+
type: "object",
|
|
4787
|
+
properties: {
|
|
4788
|
+
...Object.fromEntries(
|
|
4789
|
+
Object.entries(table.fields).map((field) => {
|
|
4790
|
+
return [
|
|
4791
|
+
field[0],
|
|
4792
|
+
{
|
|
4793
|
+
type: "object",
|
|
4794
|
+
properties: {
|
|
4795
|
+
on: {
|
|
4796
|
+
type: "string"
|
|
4797
|
+
},
|
|
4798
|
+
fields: {
|
|
4799
|
+
type: "array",
|
|
4800
|
+
items: {
|
|
4801
|
+
type: "string"
|
|
4802
|
+
}
|
|
4803
|
+
},
|
|
4804
|
+
filter: {
|
|
4805
|
+
type: "object"
|
|
4806
|
+
},
|
|
4807
|
+
returnAs: {
|
|
4808
|
+
type: "string",
|
|
4809
|
+
constraints: {
|
|
4810
|
+
oneOf: ["array", "object"]
|
|
4811
|
+
}
|
|
4812
|
+
},
|
|
4813
|
+
alias: {
|
|
4814
|
+
type: "string"
|
|
4815
|
+
},
|
|
4816
|
+
joins: {
|
|
4817
|
+
type: "object"
|
|
4818
|
+
}
|
|
4819
|
+
},
|
|
4820
|
+
required: ["on", "fields"],
|
|
4821
|
+
strict: true
|
|
4822
|
+
}
|
|
4823
|
+
];
|
|
4824
|
+
})
|
|
4825
|
+
)
|
|
4826
|
+
},
|
|
4827
|
+
strict: true,
|
|
4828
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
function getQuerySortSchemaFromTable(table, tableName) {
|
|
4832
|
+
return {
|
|
4833
|
+
type: "object",
|
|
4834
|
+
properties: {
|
|
4835
|
+
...Object.fromEntries(
|
|
4836
|
+
Object.entries(table.fields).map((field) => {
|
|
4837
|
+
return [
|
|
4838
|
+
field[0],
|
|
4839
|
+
{
|
|
4840
|
+
type: "string",
|
|
4841
|
+
constraints: {
|
|
4842
|
+
oneOf: ["asc", "desc"]
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
];
|
|
4846
|
+
})
|
|
4847
|
+
)
|
|
4848
|
+
},
|
|
4849
|
+
strict: true,
|
|
4850
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
function getQueryLimitSchemaFromTable() {
|
|
4854
|
+
return {
|
|
4855
|
+
type: "number",
|
|
4856
|
+
constraints: {
|
|
4857
|
+
min: 1
|
|
4858
|
+
},
|
|
4859
|
+
description: "Limit for query results"
|
|
4860
|
+
};
|
|
4861
|
+
}
|
|
4862
|
+
function getQueryOffsetSchemaFromTable() {
|
|
4863
|
+
return {
|
|
4864
|
+
type: "number",
|
|
4865
|
+
constraints: {
|
|
4866
|
+
min: 0
|
|
4867
|
+
},
|
|
4868
|
+
description: "Offset for query results"
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
function getTransactionSchema() {
|
|
4872
|
+
return {
|
|
4873
|
+
type: "boolean",
|
|
4874
|
+
description: "Whether to run the query in a transaction"
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
function getQueryBatchSchemaFromTable() {
|
|
4878
|
+
return {
|
|
4879
|
+
type: "boolean",
|
|
4880
|
+
description: "Whether to run the query in batch mode"
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
function getQueryOnConflictSchemaFromTable(table, tableName) {
|
|
4884
|
+
return {
|
|
4885
|
+
type: "object",
|
|
4886
|
+
properties: {
|
|
4887
|
+
target: {
|
|
4888
|
+
type: "array",
|
|
4889
|
+
items: {
|
|
4890
|
+
type: "string",
|
|
4891
|
+
constraints: {
|
|
4892
|
+
oneOf: Object.keys(table.fields)
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
},
|
|
4896
|
+
action: {
|
|
4897
|
+
type: "object",
|
|
4898
|
+
properties: {
|
|
4899
|
+
do: {
|
|
4900
|
+
type: "string",
|
|
4901
|
+
constraints: {
|
|
4902
|
+
oneOf: ["nothing", "update"]
|
|
4903
|
+
}
|
|
4904
|
+
},
|
|
4905
|
+
set: {
|
|
4906
|
+
type: "object",
|
|
4907
|
+
properties: {
|
|
4908
|
+
...Object.fromEntries(
|
|
4909
|
+
Object.entries(table.fields).map((field) => {
|
|
4910
|
+
return [
|
|
4911
|
+
field[0],
|
|
4912
|
+
{
|
|
4913
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4914
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4915
|
+
}
|
|
4916
|
+
];
|
|
4917
|
+
})
|
|
4918
|
+
)
|
|
4919
|
+
}
|
|
4920
|
+
},
|
|
4921
|
+
where: {
|
|
4922
|
+
type: "string"
|
|
4923
|
+
}
|
|
4924
|
+
},
|
|
4925
|
+
required: ["do"]
|
|
4926
|
+
}
|
|
4927
|
+
},
|
|
4928
|
+
required: ["target", "action"],
|
|
4929
|
+
strict: true
|
|
4930
|
+
};
|
|
4931
|
+
}
|
|
4932
|
+
function tableFieldTypeToSchemaType(type) {
|
|
4933
|
+
switch (type) {
|
|
4934
|
+
case "varchar":
|
|
4935
|
+
case "text":
|
|
4936
|
+
case "jsonb":
|
|
4937
|
+
case "uuid":
|
|
4938
|
+
case "date":
|
|
4939
|
+
case "geo_point":
|
|
4940
|
+
case "bytea":
|
|
4941
|
+
return "string";
|
|
4942
|
+
case "int":
|
|
4943
|
+
case "bigint":
|
|
4944
|
+
case "decimal":
|
|
4945
|
+
case "timestamp":
|
|
4946
|
+
return "number";
|
|
4947
|
+
case "boolean":
|
|
4948
|
+
return "boolean";
|
|
4949
|
+
case "array":
|
|
4950
|
+
return "array";
|
|
4951
|
+
case "object":
|
|
4952
|
+
return "object";
|
|
4953
|
+
}
|
|
4954
|
+
return "any";
|
|
4955
|
+
}
|
|
3696
4956
|
|
|
3697
4957
|
// src/Cadenza.ts
|
|
3698
4958
|
import { v4 as uuid3 } from "uuid";
|
|
@@ -4010,6 +5270,75 @@ var GraphSyncController = class _GraphSyncController {
|
|
|
4010
5270
|
{ concurrency: 30 }
|
|
4011
5271
|
) : CadenzaService.get("dbInsertSignalToTaskMap"))?.then(registerSignalTask)
|
|
4012
5272
|
);
|
|
5273
|
+
const registerIntentTask = CadenzaService.createMetaTask(
|
|
5274
|
+
"Record intent registration",
|
|
5275
|
+
(ctx) => {
|
|
5276
|
+
if (!ctx.__syncing) {
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
CadenzaService.debounce("meta.sync_controller.synced_resource", {
|
|
5280
|
+
delayMs: 3e3
|
|
5281
|
+
});
|
|
5282
|
+
const task = CadenzaService.get(ctx.__taskName);
|
|
5283
|
+
task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
|
|
5284
|
+
task.__registeredIntents.add(ctx.__intent);
|
|
5285
|
+
}
|
|
5286
|
+
);
|
|
5287
|
+
this.registerIntentToTaskMapTask = CadenzaService.createMetaTask(
|
|
5288
|
+
"Split intents of task",
|
|
5289
|
+
function* (ctx) {
|
|
5290
|
+
const task = ctx.task;
|
|
5291
|
+
if (task.hidden || !task.register) return;
|
|
5292
|
+
task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
|
|
5293
|
+
task.__invalidMetaIntentWarnings = task.__invalidMetaIntentWarnings ?? /* @__PURE__ */ new Set();
|
|
5294
|
+
for (const intent of task.handlesIntents) {
|
|
5295
|
+
if (task.__registeredIntents.has(intent)) continue;
|
|
5296
|
+
if (isMetaIntentName(intent) && !task.isMeta) {
|
|
5297
|
+
if (!task.__invalidMetaIntentWarnings.has(intent)) {
|
|
5298
|
+
task.__invalidMetaIntentWarnings.add(intent);
|
|
5299
|
+
CadenzaService.log(
|
|
5300
|
+
"Skipping intent-to-task registration: non-meta task cannot handle meta intent.",
|
|
5301
|
+
{
|
|
5302
|
+
intent,
|
|
5303
|
+
taskName: task.name,
|
|
5304
|
+
taskVersion: task.version
|
|
5305
|
+
},
|
|
5306
|
+
"warning"
|
|
5307
|
+
);
|
|
5308
|
+
}
|
|
5309
|
+
continue;
|
|
5310
|
+
}
|
|
5311
|
+
yield {
|
|
5312
|
+
data: {
|
|
5313
|
+
intentName: intent,
|
|
5314
|
+
taskName: task.name,
|
|
5315
|
+
taskVersion: task.version,
|
|
5316
|
+
serviceName: CadenzaService.serviceRegistry.serviceName
|
|
5317
|
+
},
|
|
5318
|
+
__taskName: task.name,
|
|
5319
|
+
__intent: intent
|
|
5320
|
+
};
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
).then(
|
|
5324
|
+
(this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
|
|
5325
|
+
"intent_to_task_map",
|
|
5326
|
+
{
|
|
5327
|
+
onConflict: {
|
|
5328
|
+
target: [
|
|
5329
|
+
"intent_name",
|
|
5330
|
+
"task_name",
|
|
5331
|
+
"task_version",
|
|
5332
|
+
"service_name"
|
|
5333
|
+
],
|
|
5334
|
+
action: {
|
|
5335
|
+
do: "nothing"
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
},
|
|
5339
|
+
{ concurrency: 30 }
|
|
5340
|
+
) : CadenzaService.get("dbInsertIntentToTaskMap"))?.then(registerIntentTask)
|
|
5341
|
+
);
|
|
4013
5342
|
this.registerTaskMapTask = CadenzaService.createMetaTask(
|
|
4014
5343
|
"Register task map to DB",
|
|
4015
5344
|
function* (ctx) {
|
|
@@ -4132,6 +5461,7 @@ var GraphSyncController = class _GraphSyncController {
|
|
|
4132
5461
|
CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks").then(
|
|
4133
5462
|
this.registerTaskMapTask,
|
|
4134
5463
|
this.registerSignalToTaskMapTask,
|
|
5464
|
+
this.registerIntentToTaskMapTask,
|
|
4135
5465
|
this.registerDeputyRelationshipTask
|
|
4136
5466
|
);
|
|
4137
5467
|
CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_routines").then(this.splitTasksInRoutines);
|
|
@@ -4273,11 +5603,192 @@ var CadenzaService = class {
|
|
|
4273
5603
|
Cadenza.interval(signal, context, intervalMs, leading, startDateTime);
|
|
4274
5604
|
}
|
|
4275
5605
|
static defineIntent(intent) {
|
|
4276
|
-
this.inquiryBroker?.
|
|
5606
|
+
this.inquiryBroker?.addIntent(intent);
|
|
4277
5607
|
return intent;
|
|
4278
5608
|
}
|
|
4279
|
-
static
|
|
4280
|
-
return this.
|
|
5609
|
+
static getInquiryResponderDescriptor(task) {
|
|
5610
|
+
return this.serviceRegistry.getInquiryResponderDescriptor(task);
|
|
5611
|
+
}
|
|
5612
|
+
static compareInquiryResponders(left, right) {
|
|
5613
|
+
return compareResponderDescriptors(left.descriptor, right.descriptor);
|
|
5614
|
+
}
|
|
5615
|
+
static buildInquirySummary(inquiry, startedAt, statuses, totalResponders) {
|
|
5616
|
+
const counts = summarizeResponderStatuses(statuses);
|
|
5617
|
+
const isMetaInquiry = isMetaIntentName(inquiry);
|
|
5618
|
+
const eligibleResponders = statuses.length;
|
|
5619
|
+
return {
|
|
5620
|
+
inquiry,
|
|
5621
|
+
isMetaInquiry,
|
|
5622
|
+
totalResponders,
|
|
5623
|
+
eligibleResponders,
|
|
5624
|
+
filteredOutResponders: Math.max(0, totalResponders - eligibleResponders),
|
|
5625
|
+
responded: counts.responded,
|
|
5626
|
+
failed: counts.failed,
|
|
5627
|
+
timedOut: counts.timedOut,
|
|
5628
|
+
pending: counts.pending,
|
|
5629
|
+
durationMs: Date.now() - startedAt,
|
|
5630
|
+
responders: statuses
|
|
5631
|
+
};
|
|
5632
|
+
}
|
|
5633
|
+
static async inquire(inquiry, context, options = {}) {
|
|
5634
|
+
this.bootstrap();
|
|
5635
|
+
const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
|
|
5636
|
+
const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
|
|
5637
|
+
task,
|
|
5638
|
+
descriptor: this.getInquiryResponderDescriptor(task)
|
|
5639
|
+
})) : [];
|
|
5640
|
+
const isMetaInquiry = isMetaIntentName(inquiry);
|
|
5641
|
+
const responders = allResponders.filter(({ task, descriptor }) => {
|
|
5642
|
+
const shouldExecute = shouldExecuteInquiryResponder(inquiry, task.isMeta);
|
|
5643
|
+
if (shouldExecute) {
|
|
5644
|
+
return true;
|
|
5645
|
+
}
|
|
5646
|
+
const warningKey = `${inquiry}|${descriptor.serviceName}|${descriptor.taskName}|${descriptor.taskVersion}|${descriptor.localTaskName}`;
|
|
5647
|
+
if (!this.warnedInvalidMetaIntentResponderKeys.has(warningKey)) {
|
|
5648
|
+
this.warnedInvalidMetaIntentResponderKeys.add(warningKey);
|
|
5649
|
+
this.log(
|
|
5650
|
+
"Skipping non-meta task for meta intent inquiry.",
|
|
5651
|
+
{
|
|
5652
|
+
inquiry,
|
|
5653
|
+
responder: descriptor
|
|
5654
|
+
},
|
|
5655
|
+
"warning",
|
|
5656
|
+
descriptor.serviceName
|
|
5657
|
+
);
|
|
5658
|
+
}
|
|
5659
|
+
return false;
|
|
5660
|
+
});
|
|
5661
|
+
if (responders.length === 0) {
|
|
5662
|
+
return {
|
|
5663
|
+
__inquiryMeta: {
|
|
5664
|
+
inquiry,
|
|
5665
|
+
isMetaInquiry,
|
|
5666
|
+
totalResponders: allResponders.length,
|
|
5667
|
+
eligibleResponders: 0,
|
|
5668
|
+
filteredOutResponders: allResponders.length,
|
|
5669
|
+
responded: 0,
|
|
5670
|
+
failed: 0,
|
|
5671
|
+
timedOut: 0,
|
|
5672
|
+
pending: 0,
|
|
5673
|
+
durationMs: 0,
|
|
5674
|
+
responders: []
|
|
5675
|
+
}
|
|
5676
|
+
};
|
|
5677
|
+
}
|
|
5678
|
+
responders.sort(this.compareInquiryResponders.bind(this));
|
|
5679
|
+
const overallTimeoutMs = options.overallTimeoutMs ?? options.timeout ?? 0;
|
|
5680
|
+
const requireComplete = options.requireComplete ?? false;
|
|
5681
|
+
const perResponderTimeoutMs = options.perResponderTimeoutMs;
|
|
5682
|
+
const startedAt = Date.now();
|
|
5683
|
+
const statuses = [];
|
|
5684
|
+
const statusByTask = /* @__PURE__ */ new Map();
|
|
5685
|
+
for (const responder of responders) {
|
|
5686
|
+
const status = {
|
|
5687
|
+
...responder.descriptor,
|
|
5688
|
+
status: "timed_out",
|
|
5689
|
+
durationMs: 0
|
|
5690
|
+
};
|
|
5691
|
+
statuses.push(status);
|
|
5692
|
+
statusByTask.set(responder.task, status);
|
|
5693
|
+
}
|
|
5694
|
+
const resultsByTask = /* @__PURE__ */ new Map();
|
|
5695
|
+
const resolverTasks = [];
|
|
5696
|
+
const pending = new Set(responders.map((r) => r.task));
|
|
5697
|
+
const startTimeByTask = /* @__PURE__ */ new Map();
|
|
5698
|
+
this.emit("meta.inquiry_broker.inquire", { inquiry, context });
|
|
5699
|
+
return new Promise((resolve, reject) => {
|
|
5700
|
+
let finalized = false;
|
|
5701
|
+
let timeoutId;
|
|
5702
|
+
const finalize = (timedOut) => {
|
|
5703
|
+
if (finalized) return;
|
|
5704
|
+
finalized = true;
|
|
5705
|
+
if (timeoutId) {
|
|
5706
|
+
clearTimeout(timeoutId);
|
|
5707
|
+
timeoutId = void 0;
|
|
5708
|
+
}
|
|
5709
|
+
for (const resolverTask of resolverTasks) {
|
|
5710
|
+
resolverTask.destroy();
|
|
5711
|
+
}
|
|
5712
|
+
if (timedOut && pending.size > 0) {
|
|
5713
|
+
for (const task of pending) {
|
|
5714
|
+
const status = statusByTask.get(task);
|
|
5715
|
+
if (!status) continue;
|
|
5716
|
+
status.status = "timed_out";
|
|
5717
|
+
status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
const fulfilledContexts = responders.filter((responder) => resultsByTask.has(responder.task)).map((responder) => resultsByTask.get(responder.task));
|
|
5721
|
+
const mergedContext = mergeInquiryContexts(fulfilledContexts);
|
|
5722
|
+
const inquiryMeta = this.buildInquirySummary(
|
|
5723
|
+
inquiry,
|
|
5724
|
+
startedAt,
|
|
5725
|
+
statuses,
|
|
5726
|
+
allResponders.length
|
|
5727
|
+
);
|
|
5728
|
+
const responseContext = {
|
|
5729
|
+
...mergedContext,
|
|
5730
|
+
__inquiryMeta: inquiryMeta
|
|
5731
|
+
};
|
|
5732
|
+
if (requireComplete && (timedOut || inquiryMeta.failed > 0 || inquiryMeta.timedOut > 0 || inquiryMeta.pending > 0)) {
|
|
5733
|
+
reject({
|
|
5734
|
+
...responseContext,
|
|
5735
|
+
__error: `Inquiry '${inquiry}' did not complete successfully`,
|
|
5736
|
+
errored: true
|
|
5737
|
+
});
|
|
5738
|
+
return;
|
|
5739
|
+
}
|
|
5740
|
+
resolve(responseContext);
|
|
5741
|
+
};
|
|
5742
|
+
if (overallTimeoutMs > 0) {
|
|
5743
|
+
timeoutId = setTimeout(() => finalize(true), overallTimeoutMs);
|
|
5744
|
+
}
|
|
5745
|
+
for (const responder of responders) {
|
|
5746
|
+
const { task, descriptor } = responder;
|
|
5747
|
+
const inquiryId = uuid3();
|
|
5748
|
+
startTimeByTask.set(task, Date.now());
|
|
5749
|
+
const resolverTask = this.createEphemeralMetaTask(
|
|
5750
|
+
`Resolve inquiry ${inquiry} for ${descriptor.localTaskName}`,
|
|
5751
|
+
(resultCtx) => {
|
|
5752
|
+
if (finalized) {
|
|
5753
|
+
return;
|
|
5754
|
+
}
|
|
5755
|
+
pending.delete(task);
|
|
5756
|
+
const status = statusByTask.get(task);
|
|
5757
|
+
if (status) {
|
|
5758
|
+
status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
|
|
5759
|
+
if (resultCtx?.errored || resultCtx?.failed) {
|
|
5760
|
+
status.status = "failed";
|
|
5761
|
+
status.error = String(
|
|
5762
|
+
resultCtx?.__error ?? resultCtx?.error ?? "Inquiry responder failed"
|
|
5763
|
+
);
|
|
5764
|
+
} else {
|
|
5765
|
+
status.status = "fulfilled";
|
|
5766
|
+
resultsByTask.set(task, resultCtx);
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
if (pending.size === 0) {
|
|
5770
|
+
finalize(false);
|
|
5771
|
+
}
|
|
5772
|
+
},
|
|
5773
|
+
"Resolves distributed inquiry responder result",
|
|
5774
|
+
{ register: false }
|
|
5775
|
+
).doOn(`meta.node.graph_completed:${inquiryId}`);
|
|
5776
|
+
resolverTasks.push(resolverTask);
|
|
5777
|
+
const executionContext = {
|
|
5778
|
+
...context,
|
|
5779
|
+
__routineExecId: inquiryId,
|
|
5780
|
+
__isInquiry: true
|
|
5781
|
+
};
|
|
5782
|
+
if (perResponderTimeoutMs !== void 0) {
|
|
5783
|
+
executionContext.__timeout = perResponderTimeoutMs;
|
|
5784
|
+
}
|
|
5785
|
+
if (task.isMeta) {
|
|
5786
|
+
this.metaRunner?.run(task, executionContext);
|
|
5787
|
+
} else {
|
|
5788
|
+
this.runner?.run(task, executionContext);
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
});
|
|
4281
5792
|
}
|
|
4282
5793
|
/**
|
|
4283
5794
|
* Executes the given task or graph routine within the provided context using the configured runner.
|
|
@@ -4566,10 +6077,9 @@ var CadenzaService = class {
|
|
|
4566
6077
|
this.bootstrap();
|
|
4567
6078
|
this.validateName(tableName);
|
|
4568
6079
|
this.validateName(operation);
|
|
4569
|
-
const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
4570
6080
|
const name = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName} in ${databaseServiceName ?? "default database service"}`;
|
|
4571
6081
|
const description = `Executes a ${operation} on table ${tableName} in ${databaseServiceName ?? "default database service"}`;
|
|
4572
|
-
const taskName =
|
|
6082
|
+
const taskName = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName}`;
|
|
4573
6083
|
options = {
|
|
4574
6084
|
concurrency: 100,
|
|
4575
6085
|
timeout: 0,
|
|
@@ -4831,7 +6341,7 @@ var CadenzaService = class {
|
|
|
4831
6341
|
* This method is not supported in a browser environment and will log a warning if called in such an environment.
|
|
4832
6342
|
*
|
|
4833
6343
|
* @param {string} name - The name of the database service to be created.
|
|
4834
|
-
* @param {
|
|
6344
|
+
* @param {DatabaseSchemaDefinition} schema - The schema definition for the database service.
|
|
4835
6345
|
* @param {string} [description=""] - An optional description of the database service.
|
|
4836
6346
|
* @param {ServerOptions & DatabaseOptions} [options={}] - Optional configuration settings for the database and server.
|
|
4837
6347
|
* @return {void} This method does not return a value.
|
|
@@ -4895,7 +6405,7 @@ var CadenzaService = class {
|
|
|
4895
6405
|
* Creates a meta database service with the specified configuration.
|
|
4896
6406
|
*
|
|
4897
6407
|
* @param {string} name - The name of the database service to be created.
|
|
4898
|
-
* @param {
|
|
6408
|
+
* @param {DatabaseSchemaDefinition} schema - The schema definition for the database.
|
|
4899
6409
|
* @param {string} [description=""] - An optional description of the database service.
|
|
4900
6410
|
* @param {ServerOptions & DatabaseOptions} [options={}] - Optional server and database configuration options. The `isMeta` flag will be automatically set to true.
|
|
4901
6411
|
* @return {void} - This method does not return a value.
|
|
@@ -5315,6 +6825,7 @@ var CadenzaService = class {
|
|
|
5315
6825
|
};
|
|
5316
6826
|
CadenzaService.isBootstrapped = false;
|
|
5317
6827
|
CadenzaService.serviceCreated = false;
|
|
6828
|
+
CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
|
|
5318
6829
|
|
|
5319
6830
|
// src/index.ts
|
|
5320
6831
|
import {
|