@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.js
CHANGED
|
@@ -295,6 +295,72 @@ var DatabaseTask = class extends DeputyTask {
|
|
|
295
295
|
var isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
296
296
|
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
297
297
|
|
|
298
|
+
// src/utils/inquiry.ts
|
|
299
|
+
var META_INTENT_PREFIX = "meta-";
|
|
300
|
+
var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
|
|
301
|
+
function isPlainObject(value) {
|
|
302
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
303
|
+
}
|
|
304
|
+
function deepMergeDeterministic(left, right) {
|
|
305
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
306
|
+
return [...left, ...right];
|
|
307
|
+
}
|
|
308
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
309
|
+
const merged = { ...left };
|
|
310
|
+
const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])).sort();
|
|
311
|
+
for (const key of keys) {
|
|
312
|
+
if (!(key in left)) {
|
|
313
|
+
merged[key] = right[key];
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (!(key in right)) {
|
|
317
|
+
merged[key] = left[key];
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
merged[key] = deepMergeDeterministic(left[key], right[key]);
|
|
321
|
+
}
|
|
322
|
+
return merged;
|
|
323
|
+
}
|
|
324
|
+
return right;
|
|
325
|
+
}
|
|
326
|
+
function mergeInquiryContexts(contexts) {
|
|
327
|
+
return contexts.reduce((acc, next) => deepMergeDeterministic(acc, next), {});
|
|
328
|
+
}
|
|
329
|
+
function isMetaIntentName(intentName) {
|
|
330
|
+
return intentName.startsWith(META_INTENT_PREFIX);
|
|
331
|
+
}
|
|
332
|
+
function shouldExecuteInquiryResponder(inquiry, responderIsMeta) {
|
|
333
|
+
if (!isMetaIntentName(inquiry)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
return responderIsMeta;
|
|
337
|
+
}
|
|
338
|
+
function compareResponderDescriptors(left, right) {
|
|
339
|
+
if (left.serviceName !== right.serviceName) {
|
|
340
|
+
return left.serviceName.localeCompare(right.serviceName);
|
|
341
|
+
}
|
|
342
|
+
if (left.taskName !== right.taskName) {
|
|
343
|
+
return left.taskName.localeCompare(right.taskName);
|
|
344
|
+
}
|
|
345
|
+
if (left.taskVersion !== right.taskVersion) {
|
|
346
|
+
return left.taskVersion - right.taskVersion;
|
|
347
|
+
}
|
|
348
|
+
return left.localTaskName.localeCompare(right.localTaskName);
|
|
349
|
+
}
|
|
350
|
+
function summarizeResponderStatuses(statuses) {
|
|
351
|
+
let responded = 0;
|
|
352
|
+
let failed = 0;
|
|
353
|
+
let timedOut = 0;
|
|
354
|
+
let pending = 0;
|
|
355
|
+
for (const status of statuses) {
|
|
356
|
+
if (status.status === "fulfilled") responded++;
|
|
357
|
+
if (status.status === "failed") failed++;
|
|
358
|
+
if (status.status === "timed_out") timedOut++;
|
|
359
|
+
}
|
|
360
|
+
pending = Math.max(0, statuses.length - responded - failed - timedOut);
|
|
361
|
+
return { responded, failed, timedOut, pending };
|
|
362
|
+
}
|
|
363
|
+
|
|
298
364
|
// src/registry/ServiceRegistry.ts
|
|
299
365
|
var ServiceRegistry = class _ServiceRegistry {
|
|
300
366
|
/**
|
|
@@ -310,11 +376,47 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
310
376
|
this.instances = /* @__PURE__ */ new Map();
|
|
311
377
|
this.deputies = /* @__PURE__ */ new Map();
|
|
312
378
|
this.remoteSignals = /* @__PURE__ */ new Map();
|
|
379
|
+
this.remoteIntents = /* @__PURE__ */ new Map();
|
|
380
|
+
this.remoteIntentDeputiesByKey = /* @__PURE__ */ new Map();
|
|
381
|
+
this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
|
|
313
382
|
this.serviceName = null;
|
|
314
383
|
this.serviceInstanceId = null;
|
|
315
384
|
this.numberOfRunningGraphs = 0;
|
|
316
385
|
this.useSocket = false;
|
|
317
386
|
this.retryCount = 3;
|
|
387
|
+
CadenzaService.defineIntent({
|
|
388
|
+
name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
|
|
389
|
+
description: "Gather transport diagnostics across all services and communication clients.",
|
|
390
|
+
input: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
detailLevel: {
|
|
394
|
+
type: "string",
|
|
395
|
+
constraints: {
|
|
396
|
+
oneOf: ["summary", "full"]
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
includeErrorHistory: {
|
|
400
|
+
type: "boolean"
|
|
401
|
+
},
|
|
402
|
+
errorHistoryLimit: {
|
|
403
|
+
type: "number",
|
|
404
|
+
constraints: {
|
|
405
|
+
min: 1,
|
|
406
|
+
max: 200
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
output: {
|
|
412
|
+
type: "object",
|
|
413
|
+
properties: {
|
|
414
|
+
transportDiagnostics: {
|
|
415
|
+
type: "object"
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
318
420
|
this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
|
|
319
421
|
"Handle Instance Update",
|
|
320
422
|
(ctx, emit) => {
|
|
@@ -330,10 +432,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
330
432
|
} = serviceInstance;
|
|
331
433
|
if (uuid4 === this.serviceInstanceId) return;
|
|
332
434
|
if (deleted) {
|
|
333
|
-
this.instances.get(serviceName)?.
|
|
334
|
-
|
|
335
|
-
1
|
|
336
|
-
|
|
435
|
+
const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
|
|
436
|
+
if (indexToDelete >= 0) {
|
|
437
|
+
this.instances.get(serviceName)?.splice(indexToDelete, 1);
|
|
438
|
+
}
|
|
337
439
|
if (this.instances.get(serviceName)?.length === 0) {
|
|
338
440
|
this.instances.delete(serviceName);
|
|
339
441
|
} else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
|
|
@@ -354,7 +456,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
354
456
|
if (this.serviceName === serviceName) {
|
|
355
457
|
return false;
|
|
356
458
|
}
|
|
357
|
-
if (!isFrontend && this.deputies.has(serviceName) || this.remoteSignals.has(serviceName)) {
|
|
459
|
+
if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
|
|
358
460
|
const clientCreated = instances?.some(
|
|
359
461
|
(i) => i.address === address && i.port === port && i.clientCreated && i.isActive
|
|
360
462
|
);
|
|
@@ -376,7 +478,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
376
478
|
communicationTypes
|
|
377
479
|
});
|
|
378
480
|
instances?.filter(
|
|
379
|
-
(i) => i.address === address && i.port === port && i.
|
|
481
|
+
(i) => i.address === address && i.port === port && i.isActive
|
|
380
482
|
).forEach((i) => {
|
|
381
483
|
i.clientCreated = true;
|
|
382
484
|
});
|
|
@@ -404,7 +506,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
404
506
|
for (const serviceInstance of ctx.serviceInstances) {
|
|
405
507
|
yield { serviceInstance };
|
|
406
508
|
}
|
|
407
|
-
}).doOn(
|
|
509
|
+
}).doOn(
|
|
510
|
+
"meta.service_registry.registered_global_signals",
|
|
511
|
+
"meta.service_registry.registered_global_intents"
|
|
512
|
+
).then(this.handleInstanceUpdateTask);
|
|
408
513
|
this.handleGlobalSignalRegistrationTask = CadenzaService.createMetaTask(
|
|
409
514
|
"Handle global Signal Registration",
|
|
410
515
|
(ctx) => {
|
|
@@ -445,6 +550,32 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
445
550
|
},
|
|
446
551
|
"Handles registration of remote signals"
|
|
447
552
|
).emits("meta.service_registry.registered_global_signals").doOn("global.meta.cadenza_db.gathered_sync_data");
|
|
553
|
+
this.handleGlobalIntentRegistrationTask = CadenzaService.createMetaTask(
|
|
554
|
+
"Handle global intent registration",
|
|
555
|
+
(ctx) => {
|
|
556
|
+
const intentToTaskMaps = this.normalizeIntentMaps(ctx);
|
|
557
|
+
const sorted = intentToTaskMaps.sort((a, b) => {
|
|
558
|
+
if (a.deleted && !b.deleted) return -1;
|
|
559
|
+
if (!a.deleted && b.deleted) return 1;
|
|
560
|
+
return 0;
|
|
561
|
+
});
|
|
562
|
+
for (const map of sorted) {
|
|
563
|
+
if (map.deleted) {
|
|
564
|
+
this.unregisterRemoteIntentDeputy(map);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
CadenzaService.inquiryBroker.addIntent({
|
|
568
|
+
name: map.intentName
|
|
569
|
+
});
|
|
570
|
+
this.registerRemoteIntentDeputy(map);
|
|
571
|
+
}
|
|
572
|
+
return true;
|
|
573
|
+
},
|
|
574
|
+
"Handles registration of remote inquiry intent responders"
|
|
575
|
+
).emits("meta.service_registry.registered_global_intents").doOn(
|
|
576
|
+
"global.meta.cadenza_db.gathered_sync_data",
|
|
577
|
+
"global.meta.graph_metadata.task_intent_associated"
|
|
578
|
+
);
|
|
448
579
|
this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
|
|
449
580
|
"Handle service not responding",
|
|
450
581
|
(ctx, emit) => {
|
|
@@ -480,7 +611,12 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
480
611
|
return true;
|
|
481
612
|
},
|
|
482
613
|
"Handles service not responding"
|
|
483
|
-
).doOn(
|
|
614
|
+
).doOn(
|
|
615
|
+
"meta.fetch.handshake_failed",
|
|
616
|
+
"meta.fetch.handshake_failed.*",
|
|
617
|
+
"meta.socket_client.disconnected",
|
|
618
|
+
"meta.socket_client.disconnected.*"
|
|
619
|
+
).attachSignal("global.meta.service_registry.service_not_responding");
|
|
484
620
|
this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
|
|
485
621
|
"Handle service handshake",
|
|
486
622
|
(ctx, emit) => {
|
|
@@ -507,7 +643,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
507
643
|
(i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
|
|
508
644
|
);
|
|
509
645
|
for (const i of instancesToDelete ?? []) {
|
|
510
|
-
this.instances.get(serviceName)?.
|
|
646
|
+
const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
|
|
647
|
+
if (indexToDelete >= 0) {
|
|
648
|
+
this.instances.get(serviceName)?.splice(indexToDelete, 1);
|
|
649
|
+
}
|
|
511
650
|
emit("global.meta.service_registry.deleted", {
|
|
512
651
|
data: {
|
|
513
652
|
isActive: false,
|
|
@@ -550,7 +689,10 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
550
689
|
});
|
|
551
690
|
return joinedContext;
|
|
552
691
|
}
|
|
553
|
-
).emits("meta.service_registry.initial_sync_complete").then(
|
|
692
|
+
).emits("meta.service_registry.initial_sync_complete").then(
|
|
693
|
+
this.handleGlobalSignalRegistrationTask,
|
|
694
|
+
this.handleGlobalIntentRegistrationTask
|
|
695
|
+
);
|
|
554
696
|
this.fullSyncTask = CadenzaService.createMetaRoutine("Full sync", [
|
|
555
697
|
CadenzaService.createCadenzaDBQueryTask("signal_to_task_map", {
|
|
556
698
|
filter: {
|
|
@@ -558,6 +700,15 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
558
700
|
},
|
|
559
701
|
fields: ["signal_name", "service_name", "deleted"]
|
|
560
702
|
}).then(mergeSyncDataTask),
|
|
703
|
+
CadenzaService.createCadenzaDBQueryTask("intent_to_task_map", {
|
|
704
|
+
fields: [
|
|
705
|
+
"intent_name",
|
|
706
|
+
"task_name",
|
|
707
|
+
"task_version",
|
|
708
|
+
"service_name",
|
|
709
|
+
"deleted"
|
|
710
|
+
]
|
|
711
|
+
}).then(mergeSyncDataTask),
|
|
561
712
|
CadenzaService.createCadenzaDBQueryTask("service_instance", {
|
|
562
713
|
filter: {
|
|
563
714
|
deleted: false,
|
|
@@ -667,8 +818,9 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
667
818
|
}
|
|
668
819
|
if (__broadcast || instances[0].isFrontend) {
|
|
669
820
|
for (const instance of instances) {
|
|
821
|
+
const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
|
|
670
822
|
emit(
|
|
671
|
-
`meta.service_registry.selected_instance_for_socket:${
|
|
823
|
+
`meta.service_registry.selected_instance_for_socket:${socketKey}`,
|
|
672
824
|
context
|
|
673
825
|
);
|
|
674
826
|
}
|
|
@@ -747,6 +899,25 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
747
899
|
__active: self?.isActive ?? false
|
|
748
900
|
};
|
|
749
901
|
}).doOn("meta.socket.status_check_requested");
|
|
902
|
+
this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
|
|
903
|
+
"Collect transport diagnostics",
|
|
904
|
+
async (ctx) => {
|
|
905
|
+
const inquiryResult = await CadenzaService.inquire(
|
|
906
|
+
META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
|
|
907
|
+
{
|
|
908
|
+
detailLevel: ctx.detailLevel,
|
|
909
|
+
includeErrorHistory: ctx.includeErrorHistory,
|
|
910
|
+
errorHistoryLimit: ctx.errorHistoryLimit
|
|
911
|
+
},
|
|
912
|
+
ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
|
|
913
|
+
);
|
|
914
|
+
return {
|
|
915
|
+
...ctx,
|
|
916
|
+
...inquiryResult
|
|
917
|
+
};
|
|
918
|
+
},
|
|
919
|
+
"Collects distributed transport diagnostics using inquiry responders."
|
|
920
|
+
).doOn("meta.service_registry.transport_diagnostics_requested").emits("meta.service_registry.transport_diagnostics_collected").emitsOnFail("meta.service_registry.transport_diagnostics_failed");
|
|
750
921
|
this.insertServiceTask = CadenzaService.createCadenzaDBInsertTask(
|
|
751
922
|
"service",
|
|
752
923
|
{
|
|
@@ -955,8 +1126,125 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
955
1126
|
if (!this._instance) this._instance = new _ServiceRegistry();
|
|
956
1127
|
return this._instance;
|
|
957
1128
|
}
|
|
1129
|
+
buildRemoteIntentDeputyKey(map) {
|
|
1130
|
+
return `${map.intentName}|${map.serviceName}|${map.taskName}|${map.taskVersion ?? 1}`;
|
|
1131
|
+
}
|
|
1132
|
+
normalizeIntentMaps(ctx) {
|
|
1133
|
+
if (Array.isArray(ctx.intentToTaskMaps)) {
|
|
1134
|
+
return ctx.intentToTaskMaps.map((m) => ({
|
|
1135
|
+
intentName: m.intentName ?? m.intent_name,
|
|
1136
|
+
serviceName: m.serviceName ?? m.service_name,
|
|
1137
|
+
taskName: m.taskName ?? m.task_name,
|
|
1138
|
+
taskVersion: m.taskVersion ?? m.task_version ?? 1,
|
|
1139
|
+
deleted: !!m.deleted
|
|
1140
|
+
})).filter((m) => m.intentName && m.serviceName && m.taskName);
|
|
1141
|
+
}
|
|
1142
|
+
const single = ctx.intentToTaskMap ?? ctx.data ?? (ctx.intentName ? ctx : void 0);
|
|
1143
|
+
if (!single) return [];
|
|
1144
|
+
const normalized = {
|
|
1145
|
+
intentName: single.intentName ?? single.intent_name,
|
|
1146
|
+
serviceName: single.serviceName ?? single.service_name,
|
|
1147
|
+
taskName: single.taskName ?? single.task_name,
|
|
1148
|
+
taskVersion: single.taskVersion ?? single.task_version ?? 1,
|
|
1149
|
+
deleted: !!single.deleted
|
|
1150
|
+
};
|
|
1151
|
+
if (!normalized.intentName || !normalized.serviceName || !normalized.taskName)
|
|
1152
|
+
return [];
|
|
1153
|
+
return [normalized];
|
|
1154
|
+
}
|
|
1155
|
+
registerRemoteIntentDeputy(map) {
|
|
1156
|
+
if (!this.serviceName || map.serviceName === this.serviceName) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const key = this.buildRemoteIntentDeputyKey(map);
|
|
1160
|
+
if (this.remoteIntentDeputiesByKey.has(key)) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const deputyTaskName = `Inquire ${map.intentName} via ${map.serviceName} (${map.taskName} v${map.taskVersion})`;
|
|
1164
|
+
const deputyTask = isMetaIntentName(map.intentName) ? CadenzaService.createMetaDeputyTask(map.taskName, map.serviceName, {
|
|
1165
|
+
register: false,
|
|
1166
|
+
isHidden: true,
|
|
1167
|
+
retryCount: 1,
|
|
1168
|
+
retryDelay: 50,
|
|
1169
|
+
retryDelayFactor: 1.2
|
|
1170
|
+
}) : CadenzaService.createDeputyTask(map.taskName, map.serviceName, {
|
|
1171
|
+
register: false,
|
|
1172
|
+
isHidden: true,
|
|
1173
|
+
retryCount: 1,
|
|
1174
|
+
retryDelay: 50,
|
|
1175
|
+
retryDelayFactor: 1.2
|
|
1176
|
+
});
|
|
1177
|
+
deputyTask.respondsTo(map.intentName);
|
|
1178
|
+
if (!this.remoteIntents.has(map.serviceName)) {
|
|
1179
|
+
this.remoteIntents.set(map.serviceName, /* @__PURE__ */ new Set());
|
|
1180
|
+
}
|
|
1181
|
+
this.remoteIntents.get(map.serviceName).add(map.intentName);
|
|
1182
|
+
const descriptor = {
|
|
1183
|
+
key,
|
|
1184
|
+
intentName: map.intentName,
|
|
1185
|
+
serviceName: map.serviceName,
|
|
1186
|
+
remoteTaskName: map.taskName,
|
|
1187
|
+
remoteTaskVersion: map.taskVersion,
|
|
1188
|
+
localTaskName: deputyTask.name || deputyTaskName,
|
|
1189
|
+
localTask: deputyTask
|
|
1190
|
+
};
|
|
1191
|
+
this.remoteIntentDeputiesByKey.set(key, descriptor);
|
|
1192
|
+
this.remoteIntentDeputiesByTask.set(deputyTask, descriptor);
|
|
1193
|
+
}
|
|
1194
|
+
unregisterRemoteIntentDeputy(map) {
|
|
1195
|
+
const key = this.buildRemoteIntentDeputyKey(map);
|
|
1196
|
+
const descriptor = this.remoteIntentDeputiesByKey.get(key);
|
|
1197
|
+
if (!descriptor) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const task = descriptor.localTask;
|
|
1201
|
+
if (task) {
|
|
1202
|
+
CadenzaService.inquiryBroker.unsubscribe(descriptor.intentName, task);
|
|
1203
|
+
task.destroy();
|
|
1204
|
+
}
|
|
1205
|
+
this.remoteIntentDeputiesByTask.delete(descriptor.localTask);
|
|
1206
|
+
this.remoteIntentDeputiesByKey.delete(key);
|
|
1207
|
+
this.remoteIntents.get(descriptor.serviceName)?.delete(descriptor.intentName);
|
|
1208
|
+
if (!this.remoteIntents.get(descriptor.serviceName)?.size) {
|
|
1209
|
+
this.remoteIntents.delete(descriptor.serviceName);
|
|
1210
|
+
}
|
|
1211
|
+
const deputies = this.deputies.get(descriptor.serviceName);
|
|
1212
|
+
if (deputies) {
|
|
1213
|
+
this.deputies.set(
|
|
1214
|
+
descriptor.serviceName,
|
|
1215
|
+
deputies.filter((d) => d.localTaskName !== descriptor.localTaskName)
|
|
1216
|
+
);
|
|
1217
|
+
if (this.deputies.get(descriptor.serviceName)?.length === 0) {
|
|
1218
|
+
this.deputies.delete(descriptor.serviceName);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
getInquiryResponderDescriptor(task) {
|
|
1223
|
+
const remote = this.remoteIntentDeputiesByTask.get(task);
|
|
1224
|
+
if (remote) {
|
|
1225
|
+
return {
|
|
1226
|
+
isRemote: true,
|
|
1227
|
+
serviceName: remote.serviceName,
|
|
1228
|
+
taskName: remote.remoteTaskName,
|
|
1229
|
+
taskVersion: remote.remoteTaskVersion,
|
|
1230
|
+
localTaskName: remote.localTaskName
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
isRemote: false,
|
|
1235
|
+
serviceName: this.serviceName ?? "UnknownService",
|
|
1236
|
+
taskName: task.name,
|
|
1237
|
+
taskVersion: task.version,
|
|
1238
|
+
localTaskName: task.name
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
958
1241
|
reset() {
|
|
959
1242
|
this.instances.clear();
|
|
1243
|
+
this.deputies.clear();
|
|
1244
|
+
this.remoteSignals.clear();
|
|
1245
|
+
this.remoteIntents.clear();
|
|
1246
|
+
this.remoteIntentDeputiesByKey.clear();
|
|
1247
|
+
this.remoteIntentDeputiesByTask.clear();
|
|
960
1248
|
}
|
|
961
1249
|
};
|
|
962
1250
|
|
|
@@ -1073,6 +1361,8 @@ var RestController = class _RestController {
|
|
|
1073
1361
|
* It initializes and configures the REST server tasks.
|
|
1074
1362
|
*/
|
|
1075
1363
|
constructor() {
|
|
1364
|
+
this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
|
|
1365
|
+
this.diagnosticsErrorHistoryLimit = 100;
|
|
1076
1366
|
/**
|
|
1077
1367
|
* Fetches data from the given URL with a specified timeout. This function performs
|
|
1078
1368
|
* a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
|
|
@@ -1113,6 +1403,11 @@ var RestController = class _RestController {
|
|
|
1113
1403
|
"meta.rest.delegation_requested",
|
|
1114
1404
|
"meta.socket.delegation_requested"
|
|
1115
1405
|
);
|
|
1406
|
+
CadenzaService.createMetaTask(
|
|
1407
|
+
"Collect fetch transport diagnostics",
|
|
1408
|
+
(ctx) => this.collectFetchTransportDiagnostics(ctx),
|
|
1409
|
+
"Responds to distributed transport diagnostics inquiries with REST/fetch client data."
|
|
1410
|
+
).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
|
|
1116
1411
|
CadenzaService.createMetaRoutine(
|
|
1117
1412
|
"RestServer",
|
|
1118
1413
|
[
|
|
@@ -1443,6 +1738,13 @@ var RestController = class _RestController {
|
|
|
1443
1738
|
const port = protocol === "https" ? 443 : servicePort;
|
|
1444
1739
|
const URL = `${protocol}://${serviceAddress}:${port}`;
|
|
1445
1740
|
const fetchId = `${serviceAddress}_${port}`;
|
|
1741
|
+
const fetchDiagnostics = this.ensureFetchClientDiagnostics(
|
|
1742
|
+
fetchId,
|
|
1743
|
+
serviceName,
|
|
1744
|
+
URL
|
|
1745
|
+
);
|
|
1746
|
+
fetchDiagnostics.destroyed = false;
|
|
1747
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1446
1748
|
if (CadenzaService.get(`Send Handshake to ${URL}`)) {
|
|
1447
1749
|
console.error("Fetch client already exists", URL);
|
|
1448
1750
|
return;
|
|
@@ -1464,6 +1766,10 @@ var RestController = class _RestController {
|
|
|
1464
1766
|
);
|
|
1465
1767
|
if (response.__status !== "success") {
|
|
1466
1768
|
const error = response.__error ?? `Failed to connect to service ${serviceName} ${ctx2.serviceInstanceId}`;
|
|
1769
|
+
fetchDiagnostics.connected = false;
|
|
1770
|
+
fetchDiagnostics.lastHandshakeError = error;
|
|
1771
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1772
|
+
this.recordFetchClientError(fetchId, serviceName, URL, error);
|
|
1467
1773
|
CadenzaService.log(
|
|
1468
1774
|
"Fetch handshake failed.",
|
|
1469
1775
|
{ error, serviceName, URL },
|
|
@@ -1473,6 +1779,11 @@ var RestController = class _RestController {
|
|
|
1473
1779
|
return { ...ctx2, __error: error, errored: true };
|
|
1474
1780
|
}
|
|
1475
1781
|
ctx2.serviceInstanceId = response.__serviceInstanceId;
|
|
1782
|
+
fetchDiagnostics.connected = true;
|
|
1783
|
+
fetchDiagnostics.destroyed = false;
|
|
1784
|
+
fetchDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1785
|
+
fetchDiagnostics.lastHandshakeError = null;
|
|
1786
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1476
1787
|
CadenzaService.log("Fetch client connected.", {
|
|
1477
1788
|
response,
|
|
1478
1789
|
serviceName,
|
|
@@ -1488,6 +1799,10 @@ var RestController = class _RestController {
|
|
|
1488
1799
|
});
|
|
1489
1800
|
}
|
|
1490
1801
|
} catch (e) {
|
|
1802
|
+
fetchDiagnostics.connected = false;
|
|
1803
|
+
fetchDiagnostics.lastHandshakeError = this.getErrorMessage(e);
|
|
1804
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1805
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1491
1806
|
CadenzaService.log(
|
|
1492
1807
|
"Error in fetch handshake",
|
|
1493
1808
|
{ error: e, serviceName, URL, ctx: ctx2 },
|
|
@@ -1509,6 +1824,8 @@ var RestController = class _RestController {
|
|
|
1509
1824
|
if (ctx2.__remoteRoutineName === void 0) {
|
|
1510
1825
|
return;
|
|
1511
1826
|
}
|
|
1827
|
+
fetchDiagnostics.delegationRequests++;
|
|
1828
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1512
1829
|
let resultContext;
|
|
1513
1830
|
try {
|
|
1514
1831
|
resultContext = await this.fetchDataWithTimeout(
|
|
@@ -1522,8 +1839,21 @@ var RestController = class _RestController {
|
|
|
1522
1839
|
},
|
|
1523
1840
|
3e4
|
|
1524
1841
|
);
|
|
1842
|
+
if (resultContext?.errored || resultContext?.failed) {
|
|
1843
|
+
fetchDiagnostics.delegationFailures++;
|
|
1844
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1845
|
+
this.recordFetchClientError(
|
|
1846
|
+
fetchId,
|
|
1847
|
+
serviceName,
|
|
1848
|
+
URL,
|
|
1849
|
+
resultContext?.__error ?? resultContext?.error ?? "Delegation failed"
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1525
1852
|
} catch (e) {
|
|
1526
1853
|
console.error("Error in delegation", e);
|
|
1854
|
+
fetchDiagnostics.delegationFailures++;
|
|
1855
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1856
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1527
1857
|
resultContext = {
|
|
1528
1858
|
__error: `Error: ${e}`,
|
|
1529
1859
|
errored: true,
|
|
@@ -1549,6 +1879,8 @@ var RestController = class _RestController {
|
|
|
1549
1879
|
if (ctx2.__signalName === void 0) {
|
|
1550
1880
|
return;
|
|
1551
1881
|
}
|
|
1882
|
+
fetchDiagnostics.signalTransmissions++;
|
|
1883
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1552
1884
|
let response;
|
|
1553
1885
|
try {
|
|
1554
1886
|
response = await this.fetchDataWithTimeout(
|
|
@@ -1565,8 +1897,21 @@ var RestController = class _RestController {
|
|
|
1565
1897
|
if (ctx2.__routineExecId) {
|
|
1566
1898
|
emit(`meta.fetch.transmitted:${ctx2.__routineExecId}`, response);
|
|
1567
1899
|
}
|
|
1900
|
+
if (response?.errored || response?.failed) {
|
|
1901
|
+
fetchDiagnostics.signalFailures++;
|
|
1902
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1903
|
+
this.recordFetchClientError(
|
|
1904
|
+
fetchId,
|
|
1905
|
+
serviceName,
|
|
1906
|
+
URL,
|
|
1907
|
+
response?.__error ?? response?.error ?? "Signal transmission failed"
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1568
1910
|
} catch (e) {
|
|
1569
1911
|
console.error("Error in transmission", e);
|
|
1912
|
+
fetchDiagnostics.signalFailures++;
|
|
1913
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1914
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1570
1915
|
response = {
|
|
1571
1916
|
__error: `Error: ${e}`,
|
|
1572
1917
|
errored: true,
|
|
@@ -1584,6 +1929,8 @@ var RestController = class _RestController {
|
|
|
1584
1929
|
const statusTask = CadenzaService.createMetaTask(
|
|
1585
1930
|
`Request status from ${URL}`,
|
|
1586
1931
|
async (ctx2) => {
|
|
1932
|
+
fetchDiagnostics.statusChecks++;
|
|
1933
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1587
1934
|
let status;
|
|
1588
1935
|
try {
|
|
1589
1936
|
status = await this.fetchDataWithTimeout(
|
|
@@ -1593,7 +1940,20 @@ var RestController = class _RestController {
|
|
|
1593
1940
|
},
|
|
1594
1941
|
1e3
|
|
1595
1942
|
);
|
|
1943
|
+
if (status?.errored || status?.failed) {
|
|
1944
|
+
fetchDiagnostics.statusFailures++;
|
|
1945
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1946
|
+
this.recordFetchClientError(
|
|
1947
|
+
fetchId,
|
|
1948
|
+
serviceName,
|
|
1949
|
+
URL,
|
|
1950
|
+
status?.__error ?? status?.error ?? "Status check failed"
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1596
1953
|
} catch (e) {
|
|
1954
|
+
fetchDiagnostics.statusFailures++;
|
|
1955
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1956
|
+
this.recordFetchClientError(fetchId, serviceName, URL, e);
|
|
1597
1957
|
status = {
|
|
1598
1958
|
__error: `Error: ${e}`,
|
|
1599
1959
|
errored: true,
|
|
@@ -1605,6 +1965,9 @@ var RestController = class _RestController {
|
|
|
1605
1965
|
"Requests status"
|
|
1606
1966
|
).doOn("meta.fetch.status_check_requested").emits("meta.fetch.status_checked").emitsOnFail("meta.fetch.status_check_failed");
|
|
1607
1967
|
CadenzaService.createEphemeralMetaTask("Destroy fetch client", () => {
|
|
1968
|
+
fetchDiagnostics.connected = false;
|
|
1969
|
+
fetchDiagnostics.destroyed = true;
|
|
1970
|
+
fetchDiagnostics.updatedAt = Date.now();
|
|
1608
1971
|
CadenzaService.log("Destroying fetch client", { URL, serviceName });
|
|
1609
1972
|
handshakeTask.destroy();
|
|
1610
1973
|
delegateTask.destroy();
|
|
@@ -1653,17 +2016,191 @@ var RestController = class _RestController {
|
|
|
1653
2016
|
if (!this._instance) this._instance = new _RestController();
|
|
1654
2017
|
return this._instance;
|
|
1655
2018
|
}
|
|
2019
|
+
resolveTransportDiagnosticsOptions(ctx) {
|
|
2020
|
+
const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
|
|
2021
|
+
const includeErrorHistory = Boolean(ctx.includeErrorHistory);
|
|
2022
|
+
const requestedLimit = Number(ctx.errorHistoryLimit);
|
|
2023
|
+
const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
|
|
2024
|
+
return {
|
|
2025
|
+
detailLevel,
|
|
2026
|
+
includeErrorHistory,
|
|
2027
|
+
errorHistoryLimit
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
ensureFetchClientDiagnostics(fetchId, serviceName, url) {
|
|
2031
|
+
let state = this.fetchClientDiagnostics.get(fetchId);
|
|
2032
|
+
if (!state) {
|
|
2033
|
+
state = {
|
|
2034
|
+
fetchId,
|
|
2035
|
+
serviceName,
|
|
2036
|
+
url,
|
|
2037
|
+
connected: false,
|
|
2038
|
+
destroyed: false,
|
|
2039
|
+
lastHandshakeAt: null,
|
|
2040
|
+
lastHandshakeError: null,
|
|
2041
|
+
lastError: null,
|
|
2042
|
+
lastErrorAt: 0,
|
|
2043
|
+
errorHistory: [],
|
|
2044
|
+
delegationRequests: 0,
|
|
2045
|
+
delegationFailures: 0,
|
|
2046
|
+
signalTransmissions: 0,
|
|
2047
|
+
signalFailures: 0,
|
|
2048
|
+
statusChecks: 0,
|
|
2049
|
+
statusFailures: 0,
|
|
2050
|
+
updatedAt: Date.now()
|
|
2051
|
+
};
|
|
2052
|
+
this.fetchClientDiagnostics.set(fetchId, state);
|
|
2053
|
+
} else {
|
|
2054
|
+
state.serviceName = serviceName;
|
|
2055
|
+
state.url = url;
|
|
2056
|
+
}
|
|
2057
|
+
return state;
|
|
2058
|
+
}
|
|
2059
|
+
getErrorMessage(error) {
|
|
2060
|
+
if (error instanceof Error) {
|
|
2061
|
+
return error.message;
|
|
2062
|
+
}
|
|
2063
|
+
if (typeof error === "string") {
|
|
2064
|
+
return error;
|
|
2065
|
+
}
|
|
2066
|
+
try {
|
|
2067
|
+
return JSON.stringify(error);
|
|
2068
|
+
} catch {
|
|
2069
|
+
return String(error);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
recordFetchClientError(fetchId, serviceName, url, error) {
|
|
2073
|
+
const state = this.ensureFetchClientDiagnostics(fetchId, serviceName, url);
|
|
2074
|
+
const message = this.getErrorMessage(error);
|
|
2075
|
+
const now = Date.now();
|
|
2076
|
+
state.lastError = message;
|
|
2077
|
+
state.lastErrorAt = now;
|
|
2078
|
+
state.updatedAt = now;
|
|
2079
|
+
state.errorHistory.push({ at: new Date(now).toISOString(), message });
|
|
2080
|
+
if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
|
|
2081
|
+
state.errorHistory.splice(
|
|
2082
|
+
0,
|
|
2083
|
+
state.errorHistory.length - this.diagnosticsErrorHistoryLimit
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
collectFetchTransportDiagnostics(ctx) {
|
|
2088
|
+
const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
|
|
2089
|
+
const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
|
|
2090
|
+
const states = Array.from(this.fetchClientDiagnostics.values()).sort(
|
|
2091
|
+
(a, b) => a.fetchId.localeCompare(b.fetchId)
|
|
2092
|
+
);
|
|
2093
|
+
const summary = {
|
|
2094
|
+
detailLevel,
|
|
2095
|
+
totalClients: states.length,
|
|
2096
|
+
connectedClients: states.filter((state) => state.connected).length,
|
|
2097
|
+
destroyedClients: states.filter((state) => state.destroyed).length,
|
|
2098
|
+
delegationRequests: states.reduce(
|
|
2099
|
+
(acc, state) => acc + state.delegationRequests,
|
|
2100
|
+
0
|
|
2101
|
+
),
|
|
2102
|
+
delegationFailures: states.reduce(
|
|
2103
|
+
(acc, state) => acc + state.delegationFailures,
|
|
2104
|
+
0
|
|
2105
|
+
),
|
|
2106
|
+
signalTransmissions: states.reduce(
|
|
2107
|
+
(acc, state) => acc + state.signalTransmissions,
|
|
2108
|
+
0
|
|
2109
|
+
),
|
|
2110
|
+
signalFailures: states.reduce((acc, state) => acc + state.signalFailures, 0),
|
|
2111
|
+
statusChecks: states.reduce((acc, state) => acc + state.statusChecks, 0),
|
|
2112
|
+
statusFailures: states.reduce((acc, state) => acc + state.statusFailures, 0),
|
|
2113
|
+
latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
|
|
2114
|
+
};
|
|
2115
|
+
if (detailLevel === "summary") {
|
|
2116
|
+
return {
|
|
2117
|
+
transportDiagnostics: {
|
|
2118
|
+
[serviceName]: {
|
|
2119
|
+
fetchClient: summary
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
const clients = states.map((state) => {
|
|
2125
|
+
const details = {
|
|
2126
|
+
fetchId: state.fetchId,
|
|
2127
|
+
serviceName: state.serviceName,
|
|
2128
|
+
url: state.url,
|
|
2129
|
+
connected: state.connected,
|
|
2130
|
+
destroyed: state.destroyed,
|
|
2131
|
+
lastHandshakeAt: state.lastHandshakeAt,
|
|
2132
|
+
lastHandshakeError: state.lastHandshakeError,
|
|
2133
|
+
latestError: state.lastError,
|
|
2134
|
+
delegationRequests: state.delegationRequests,
|
|
2135
|
+
delegationFailures: state.delegationFailures,
|
|
2136
|
+
signalTransmissions: state.signalTransmissions,
|
|
2137
|
+
signalFailures: state.signalFailures,
|
|
2138
|
+
statusChecks: state.statusChecks,
|
|
2139
|
+
statusFailures: state.statusFailures
|
|
2140
|
+
};
|
|
2141
|
+
if (includeErrorHistory) {
|
|
2142
|
+
details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
|
|
2143
|
+
}
|
|
2144
|
+
return details;
|
|
2145
|
+
});
|
|
2146
|
+
return {
|
|
2147
|
+
transportDiagnostics: {
|
|
2148
|
+
[serviceName]: {
|
|
2149
|
+
fetchClient: {
|
|
2150
|
+
...summary,
|
|
2151
|
+
clients
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1656
2157
|
};
|
|
1657
2158
|
|
|
1658
2159
|
// src/network/SocketController.ts
|
|
1659
2160
|
var import_socket = require("socket.io");
|
|
1660
2161
|
var import_rate_limiter_flexible2 = require("rate-limiter-flexible");
|
|
1661
2162
|
var import_socket2 = require("socket.io-client");
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
2163
|
+
|
|
2164
|
+
// src/network/socketClientUtils.ts
|
|
2165
|
+
var waitForSocketConnection = async (socket, timeoutMs, createError) => {
|
|
2166
|
+
if (!socket) {
|
|
2167
|
+
return { ok: false, error: createError("disconnected") };
|
|
1666
2168
|
}
|
|
2169
|
+
if (socket.connected) {
|
|
2170
|
+
return { ok: true };
|
|
2171
|
+
}
|
|
2172
|
+
return new Promise((resolve) => {
|
|
2173
|
+
let timer = null;
|
|
2174
|
+
let settled = false;
|
|
2175
|
+
const cleanup = () => {
|
|
2176
|
+
if (timer) {
|
|
2177
|
+
clearTimeout(timer);
|
|
2178
|
+
timer = null;
|
|
2179
|
+
}
|
|
2180
|
+
socket.off("connect", onConnect);
|
|
2181
|
+
socket.off("connect_error", onConnectError);
|
|
2182
|
+
socket.off("disconnect", onDisconnect);
|
|
2183
|
+
};
|
|
2184
|
+
const settle = (outcome) => {
|
|
2185
|
+
if (settled) return;
|
|
2186
|
+
settled = true;
|
|
2187
|
+
cleanup();
|
|
2188
|
+
resolve(outcome);
|
|
2189
|
+
};
|
|
2190
|
+
const onConnect = () => settle({ ok: true });
|
|
2191
|
+
const onConnectError = (error) => settle({ ok: false, error: createError("connect_error", error) });
|
|
2192
|
+
const onDisconnect = () => settle({ ok: false, error: createError("disconnected") });
|
|
2193
|
+
socket.once("connect", onConnect);
|
|
2194
|
+
socket.once("connect_error", onConnectError);
|
|
2195
|
+
socket.once("disconnect", onDisconnect);
|
|
2196
|
+
timer = setTimeout(() => {
|
|
2197
|
+
settle({ ok: false, error: createError("connect_timeout") });
|
|
2198
|
+
}, timeoutMs);
|
|
2199
|
+
});
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
// src/network/SocketController.ts
|
|
2203
|
+
var SocketController = class _SocketController {
|
|
1667
2204
|
/**
|
|
1668
2205
|
* Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
|
|
1669
2206
|
* including connection state recovery, rate limiting, CORS handling, and custom event handling.
|
|
@@ -1682,6 +2219,13 @@ var SocketController = class _SocketController {
|
|
|
1682
2219
|
* Initializes the `SocketServer` to be ready for WebSocket communication.
|
|
1683
2220
|
*/
|
|
1684
2221
|
constructor() {
|
|
2222
|
+
this.socketClientDiagnostics = /* @__PURE__ */ new Map();
|
|
2223
|
+
this.diagnosticsErrorHistoryLimit = 100;
|
|
2224
|
+
CadenzaService.createMetaTask(
|
|
2225
|
+
"Collect socket transport diagnostics",
|
|
2226
|
+
(ctx) => this.collectSocketTransportDiagnostics(ctx),
|
|
2227
|
+
"Responds to distributed transport diagnostics inquiries with socket client data."
|
|
2228
|
+
).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
|
|
1685
2229
|
CadenzaService.createMetaRoutine(
|
|
1686
2230
|
"SocketServer",
|
|
1687
2231
|
[
|
|
@@ -1909,6 +2453,13 @@ var SocketController = class _SocketController {
|
|
|
1909
2453
|
const port = protocol === "https" ? 443 : servicePort;
|
|
1910
2454
|
const URL = `${socketProtocol}://${serviceAddress}:${port}`;
|
|
1911
2455
|
const fetchId = `${serviceAddress}_${port}`;
|
|
2456
|
+
const socketDiagnostics = this.ensureSocketClientDiagnostics(
|
|
2457
|
+
fetchId,
|
|
2458
|
+
serviceName,
|
|
2459
|
+
URL
|
|
2460
|
+
);
|
|
2461
|
+
socketDiagnostics.destroyed = false;
|
|
2462
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1912
2463
|
let handshake = false;
|
|
1913
2464
|
let errorCount = 0;
|
|
1914
2465
|
const ERROR_LIMIT = 5;
|
|
@@ -1918,6 +2469,11 @@ var SocketController = class _SocketController {
|
|
|
1918
2469
|
}
|
|
1919
2470
|
const pendingDelegationIds = /* @__PURE__ */ new Set();
|
|
1920
2471
|
const pendingTimers = /* @__PURE__ */ new Set();
|
|
2472
|
+
const syncPendingCounts = () => {
|
|
2473
|
+
socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
|
|
2474
|
+
socketDiagnostics.pendingTimers = pendingTimers.size;
|
|
2475
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2476
|
+
};
|
|
1921
2477
|
let handshakeTask = null;
|
|
1922
2478
|
let emitWhenReady = null;
|
|
1923
2479
|
let transmitTask = null;
|
|
@@ -1934,35 +2490,86 @@ var SocketController = class _SocketController {
|
|
|
1934
2490
|
});
|
|
1935
2491
|
emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
|
|
1936
2492
|
return new Promise((resolve) => {
|
|
1937
|
-
const
|
|
1938
|
-
|
|
1939
|
-
|
|
2493
|
+
const resolveWithError = (errorMessage, fallbackError) => {
|
|
2494
|
+
resolve({
|
|
2495
|
+
...data,
|
|
2496
|
+
errored: true,
|
|
2497
|
+
__error: errorMessage,
|
|
2498
|
+
error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
|
|
2499
|
+
socketId: socket?.id,
|
|
2500
|
+
serviceName,
|
|
2501
|
+
URL
|
|
2502
|
+
});
|
|
2503
|
+
};
|
|
2504
|
+
const tryEmit = async () => {
|
|
2505
|
+
const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
|
|
2506
|
+
const waitResult = await waitForSocketConnection(
|
|
2507
|
+
socket,
|
|
2508
|
+
waitTimeoutMs,
|
|
2509
|
+
(reason, error) => {
|
|
2510
|
+
if (reason === "connect_timeout") {
|
|
2511
|
+
return `Socket connect timed out before '${event}'`;
|
|
2512
|
+
}
|
|
2513
|
+
if (reason === "connect_error") {
|
|
2514
|
+
const errMessage = error instanceof Error ? error.message : String(error);
|
|
2515
|
+
return `Socket connect error before '${event}': ${errMessage}`;
|
|
2516
|
+
}
|
|
2517
|
+
return `Socket disconnected before '${event}'`;
|
|
2518
|
+
}
|
|
2519
|
+
);
|
|
2520
|
+
if (!waitResult.ok) {
|
|
2521
|
+
CadenzaService.log(
|
|
2522
|
+
waitResult.error,
|
|
2523
|
+
{ socketId: socket?.id, serviceName, URL, event },
|
|
2524
|
+
"error"
|
|
2525
|
+
);
|
|
2526
|
+
this.recordSocketClientError(
|
|
2527
|
+
fetchId,
|
|
2528
|
+
serviceName,
|
|
2529
|
+
URL,
|
|
2530
|
+
waitResult.error
|
|
2531
|
+
);
|
|
2532
|
+
resolveWithError(waitResult.error);
|
|
1940
2533
|
return;
|
|
1941
2534
|
}
|
|
1942
|
-
let timer;
|
|
2535
|
+
let timer = null;
|
|
1943
2536
|
if (timeoutMs !== 0) {
|
|
1944
2537
|
timer = setTimeout(() => {
|
|
2538
|
+
if (timer) {
|
|
2539
|
+
pendingTimers.delete(timer);
|
|
2540
|
+
syncPendingCounts();
|
|
2541
|
+
timer = null;
|
|
2542
|
+
}
|
|
1945
2543
|
CadenzaService.log(
|
|
1946
2544
|
`Socket event '${event}' timed out`,
|
|
1947
2545
|
{ socketId: socket?.id, serviceName, URL },
|
|
1948
2546
|
"error"
|
|
1949
2547
|
);
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
errored: true,
|
|
1953
|
-
__error: `Socket event '${event}' timed out`,
|
|
1954
|
-
error: `Socket event '${event}' timed out`,
|
|
1955
|
-
socketId: socket?.id,
|
|
2548
|
+
this.recordSocketClientError(
|
|
2549
|
+
fetchId,
|
|
1956
2550
|
serviceName,
|
|
1957
|
-
URL
|
|
1958
|
-
|
|
2551
|
+
URL,
|
|
2552
|
+
`Socket event '${event}' timed out`
|
|
2553
|
+
);
|
|
2554
|
+
resolveWithError(`Socket event '${event}' timed out`);
|
|
1959
2555
|
}, timeoutMs + 10);
|
|
1960
2556
|
pendingTimers.add(timer);
|
|
2557
|
+
syncPendingCounts();
|
|
1961
2558
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2559
|
+
const connectedSocket = socket;
|
|
2560
|
+
if (!connectedSocket) {
|
|
2561
|
+
resolveWithError(
|
|
2562
|
+
`Socket unavailable before emitting '${event}'`
|
|
2563
|
+
);
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
|
|
2567
|
+
if (timer) {
|
|
2568
|
+
clearTimeout(timer);
|
|
2569
|
+
pendingTimers.delete(timer);
|
|
2570
|
+
syncPendingCounts();
|
|
2571
|
+
timer = null;
|
|
2572
|
+
}
|
|
1966
2573
|
if (err) {
|
|
1967
2574
|
CadenzaService.log(
|
|
1968
2575
|
"Socket timeout.",
|
|
@@ -1974,6 +2581,12 @@ var SocketController = class _SocketController {
|
|
|
1974
2581
|
},
|
|
1975
2582
|
"warning"
|
|
1976
2583
|
);
|
|
2584
|
+
this.recordSocketClientError(
|
|
2585
|
+
fetchId,
|
|
2586
|
+
serviceName,
|
|
2587
|
+
URL,
|
|
2588
|
+
err
|
|
2589
|
+
);
|
|
1977
2590
|
response = {
|
|
1978
2591
|
__error: `Timeout error: ${err}`,
|
|
1979
2592
|
errored: true,
|
|
@@ -1985,15 +2598,15 @@ var SocketController = class _SocketController {
|
|
|
1985
2598
|
resolve(response);
|
|
1986
2599
|
});
|
|
1987
2600
|
};
|
|
1988
|
-
|
|
1989
|
-
tryEmit();
|
|
1990
|
-
} else {
|
|
1991
|
-
socket?.once("connect", tryEmit);
|
|
1992
|
-
}
|
|
2601
|
+
void tryEmit();
|
|
1993
2602
|
});
|
|
1994
2603
|
};
|
|
1995
2604
|
socket.on("connect", () => {
|
|
1996
2605
|
if (handshake) return;
|
|
2606
|
+
socketDiagnostics.connected = true;
|
|
2607
|
+
socketDiagnostics.destroyed = false;
|
|
2608
|
+
socketDiagnostics.socketId = socket?.id ?? null;
|
|
2609
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
1997
2610
|
CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
|
|
1998
2611
|
});
|
|
1999
2612
|
socket.on("delegation_progress", (ctx2) => {
|
|
@@ -2012,6 +2625,12 @@ var SocketController = class _SocketController {
|
|
|
2012
2625
|
});
|
|
2013
2626
|
socket.on("connect_error", (err) => {
|
|
2014
2627
|
handshake = false;
|
|
2628
|
+
socketDiagnostics.connected = false;
|
|
2629
|
+
socketDiagnostics.handshake = false;
|
|
2630
|
+
socketDiagnostics.connectErrors++;
|
|
2631
|
+
socketDiagnostics.lastHandshakeError = err.message;
|
|
2632
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2633
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
2015
2634
|
CadenzaService.log(
|
|
2016
2635
|
"Socket connect error",
|
|
2017
2636
|
{ error: err.message, serviceName, socketId: socket?.id, URL },
|
|
@@ -2020,9 +2639,16 @@ var SocketController = class _SocketController {
|
|
|
2020
2639
|
CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
|
|
2021
2640
|
});
|
|
2022
2641
|
socket.on("reconnect_attempt", (attempt) => {
|
|
2642
|
+
socketDiagnostics.reconnectAttempts = Math.max(
|
|
2643
|
+
socketDiagnostics.reconnectAttempts,
|
|
2644
|
+
attempt
|
|
2645
|
+
);
|
|
2646
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2023
2647
|
CadenzaService.log(`Reconnect attempt: ${attempt}`);
|
|
2024
2648
|
});
|
|
2025
2649
|
socket.on("reconnect", (attempt) => {
|
|
2650
|
+
socketDiagnostics.connected = true;
|
|
2651
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2026
2652
|
CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
|
|
2027
2653
|
socketId: socket?.id,
|
|
2028
2654
|
URL,
|
|
@@ -2031,6 +2657,12 @@ var SocketController = class _SocketController {
|
|
|
2031
2657
|
});
|
|
2032
2658
|
socket.on("reconnect_error", (err) => {
|
|
2033
2659
|
handshake = false;
|
|
2660
|
+
socketDiagnostics.connected = false;
|
|
2661
|
+
socketDiagnostics.handshake = false;
|
|
2662
|
+
socketDiagnostics.reconnectErrors++;
|
|
2663
|
+
socketDiagnostics.lastHandshakeError = err.message;
|
|
2664
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2665
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
2034
2666
|
CadenzaService.log(
|
|
2035
2667
|
"Socket reconnect failed.",
|
|
2036
2668
|
{ error: err.message, serviceName, URL, socketId: socket?.id },
|
|
@@ -2039,6 +2671,9 @@ var SocketController = class _SocketController {
|
|
|
2039
2671
|
});
|
|
2040
2672
|
socket.on("error", (err) => {
|
|
2041
2673
|
errorCount++;
|
|
2674
|
+
socketDiagnostics.socketErrors++;
|
|
2675
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2676
|
+
this.recordSocketClientError(fetchId, serviceName, URL, err);
|
|
2042
2677
|
CadenzaService.log(
|
|
2043
2678
|
"Socket error",
|
|
2044
2679
|
{ error: err, socketId: socket?.id, URL, serviceName },
|
|
@@ -2047,6 +2682,11 @@ var SocketController = class _SocketController {
|
|
|
2047
2682
|
CadenzaService.emit("meta.socket_client.error", err);
|
|
2048
2683
|
});
|
|
2049
2684
|
socket.on("disconnect", () => {
|
|
2685
|
+
const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2686
|
+
socketDiagnostics.connected = false;
|
|
2687
|
+
socketDiagnostics.handshake = false;
|
|
2688
|
+
socketDiagnostics.lastDisconnectAt = disconnectedAt;
|
|
2689
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2050
2690
|
CadenzaService.log(
|
|
2051
2691
|
"Socket disconnected.",
|
|
2052
2692
|
{ URL, serviceName, socketId: socket?.id },
|
|
@@ -2065,6 +2705,8 @@ var SocketController = class _SocketController {
|
|
|
2065
2705
|
async (ctx2, emit) => {
|
|
2066
2706
|
if (handshake) return;
|
|
2067
2707
|
handshake = true;
|
|
2708
|
+
socketDiagnostics.handshake = true;
|
|
2709
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2068
2710
|
await emitWhenReady?.(
|
|
2069
2711
|
"handshake",
|
|
2070
2712
|
{
|
|
@@ -2076,6 +2718,11 @@ var SocketController = class _SocketController {
|
|
|
2076
2718
|
1e4,
|
|
2077
2719
|
(result) => {
|
|
2078
2720
|
if (result.status === "success") {
|
|
2721
|
+
socketDiagnostics.connected = true;
|
|
2722
|
+
socketDiagnostics.handshake = true;
|
|
2723
|
+
socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2724
|
+
socketDiagnostics.lastHandshakeError = null;
|
|
2725
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2079
2726
|
CadenzaService.log("Socket client connected", {
|
|
2080
2727
|
result,
|
|
2081
2728
|
serviceName,
|
|
@@ -2083,6 +2730,16 @@ var SocketController = class _SocketController {
|
|
|
2083
2730
|
URL
|
|
2084
2731
|
});
|
|
2085
2732
|
} else {
|
|
2733
|
+
socketDiagnostics.connected = false;
|
|
2734
|
+
socketDiagnostics.handshake = false;
|
|
2735
|
+
socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
|
|
2736
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2737
|
+
this.recordSocketClientError(
|
|
2738
|
+
fetchId,
|
|
2739
|
+
serviceName,
|
|
2740
|
+
URL,
|
|
2741
|
+
socketDiagnostics.lastHandshakeError
|
|
2742
|
+
);
|
|
2086
2743
|
CadenzaService.log(
|
|
2087
2744
|
"Socket handshake failed",
|
|
2088
2745
|
{ result, serviceName, socketId: socket?.id, URL },
|
|
@@ -2105,6 +2762,7 @@ var SocketController = class _SocketController {
|
|
|
2105
2762
|
delete ctx2.__broadcast;
|
|
2106
2763
|
const requestSentAt = Date.now();
|
|
2107
2764
|
pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
|
|
2765
|
+
syncPendingCounts();
|
|
2108
2766
|
emitWhenReady?.(
|
|
2109
2767
|
"delegation",
|
|
2110
2768
|
ctx2,
|
|
@@ -2122,6 +2780,15 @@ var SocketController = class _SocketController {
|
|
|
2122
2780
|
}
|
|
2123
2781
|
);
|
|
2124
2782
|
pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
|
|
2783
|
+
syncPendingCounts();
|
|
2784
|
+
if (resultContext?.errored || resultContext?.failed) {
|
|
2785
|
+
this.recordSocketClientError(
|
|
2786
|
+
fetchId,
|
|
2787
|
+
serviceName,
|
|
2788
|
+
URL,
|
|
2789
|
+
resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2125
2792
|
resolve(resultContext);
|
|
2126
2793
|
}
|
|
2127
2794
|
);
|
|
@@ -2157,6 +2824,10 @@ var SocketController = class _SocketController {
|
|
|
2157
2824
|
`Shutdown SocketClient ${URL}`,
|
|
2158
2825
|
(ctx2, emit) => {
|
|
2159
2826
|
handshake = false;
|
|
2827
|
+
socketDiagnostics.connected = false;
|
|
2828
|
+
socketDiagnostics.handshake = false;
|
|
2829
|
+
socketDiagnostics.destroyed = true;
|
|
2830
|
+
socketDiagnostics.updatedAt = Date.now();
|
|
2160
2831
|
CadenzaService.log("Shutting down socket client", { URL, serviceName });
|
|
2161
2832
|
socket?.close();
|
|
2162
2833
|
handshakeTask?.destroy();
|
|
@@ -2186,10 +2857,12 @@ var SocketController = class _SocketController {
|
|
|
2186
2857
|
});
|
|
2187
2858
|
}
|
|
2188
2859
|
pendingDelegationIds.clear();
|
|
2860
|
+
syncPendingCounts();
|
|
2189
2861
|
for (const timer of pendingTimers) {
|
|
2190
2862
|
clearTimeout(timer);
|
|
2191
2863
|
}
|
|
2192
2864
|
pendingTimers.clear();
|
|
2865
|
+
syncPendingCounts();
|
|
2193
2866
|
},
|
|
2194
2867
|
"Shuts down the socket client"
|
|
2195
2868
|
).doOn(
|
|
@@ -2203,6 +2876,157 @@ var SocketController = class _SocketController {
|
|
|
2203
2876
|
"Connects to a specified socket server"
|
|
2204
2877
|
).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
|
|
2205
2878
|
}
|
|
2879
|
+
static get instance() {
|
|
2880
|
+
if (!this._instance) this._instance = new _SocketController();
|
|
2881
|
+
return this._instance;
|
|
2882
|
+
}
|
|
2883
|
+
resolveTransportDiagnosticsOptions(ctx) {
|
|
2884
|
+
const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
|
|
2885
|
+
const includeErrorHistory = Boolean(ctx.includeErrorHistory);
|
|
2886
|
+
const requestedLimit = Number(ctx.errorHistoryLimit);
|
|
2887
|
+
const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
|
|
2888
|
+
return {
|
|
2889
|
+
detailLevel,
|
|
2890
|
+
includeErrorHistory,
|
|
2891
|
+
errorHistoryLimit
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
ensureSocketClientDiagnostics(fetchId, serviceName, url) {
|
|
2895
|
+
let state = this.socketClientDiagnostics.get(fetchId);
|
|
2896
|
+
if (!state) {
|
|
2897
|
+
state = {
|
|
2898
|
+
fetchId,
|
|
2899
|
+
serviceName,
|
|
2900
|
+
url,
|
|
2901
|
+
socketId: null,
|
|
2902
|
+
connected: false,
|
|
2903
|
+
handshake: false,
|
|
2904
|
+
reconnectAttempts: 0,
|
|
2905
|
+
connectErrors: 0,
|
|
2906
|
+
reconnectErrors: 0,
|
|
2907
|
+
socketErrors: 0,
|
|
2908
|
+
pendingDelegations: 0,
|
|
2909
|
+
pendingTimers: 0,
|
|
2910
|
+
destroyed: false,
|
|
2911
|
+
lastHandshakeAt: null,
|
|
2912
|
+
lastHandshakeError: null,
|
|
2913
|
+
lastDisconnectAt: null,
|
|
2914
|
+
lastError: null,
|
|
2915
|
+
lastErrorAt: 0,
|
|
2916
|
+
errorHistory: [],
|
|
2917
|
+
updatedAt: Date.now()
|
|
2918
|
+
};
|
|
2919
|
+
this.socketClientDiagnostics.set(fetchId, state);
|
|
2920
|
+
} else {
|
|
2921
|
+
state.serviceName = serviceName;
|
|
2922
|
+
state.url = url;
|
|
2923
|
+
}
|
|
2924
|
+
return state;
|
|
2925
|
+
}
|
|
2926
|
+
getErrorMessage(error) {
|
|
2927
|
+
if (error instanceof Error) {
|
|
2928
|
+
return error.message;
|
|
2929
|
+
}
|
|
2930
|
+
if (typeof error === "string") {
|
|
2931
|
+
return error;
|
|
2932
|
+
}
|
|
2933
|
+
try {
|
|
2934
|
+
return JSON.stringify(error);
|
|
2935
|
+
} catch {
|
|
2936
|
+
return String(error);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
recordSocketClientError(fetchId, serviceName, url, error) {
|
|
2940
|
+
const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
|
|
2941
|
+
const message = this.getErrorMessage(error);
|
|
2942
|
+
const now = Date.now();
|
|
2943
|
+
state.lastError = message;
|
|
2944
|
+
state.lastErrorAt = now;
|
|
2945
|
+
state.updatedAt = now;
|
|
2946
|
+
state.errorHistory.push({
|
|
2947
|
+
at: new Date(now).toISOString(),
|
|
2948
|
+
message
|
|
2949
|
+
});
|
|
2950
|
+
if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
|
|
2951
|
+
state.errorHistory.splice(
|
|
2952
|
+
0,
|
|
2953
|
+
state.errorHistory.length - this.diagnosticsErrorHistoryLimit
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
collectSocketTransportDiagnostics(ctx) {
|
|
2958
|
+
const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
|
|
2959
|
+
const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
|
|
2960
|
+
const states = Array.from(this.socketClientDiagnostics.values()).sort(
|
|
2961
|
+
(a, b) => a.fetchId.localeCompare(b.fetchId)
|
|
2962
|
+
);
|
|
2963
|
+
const summary = {
|
|
2964
|
+
detailLevel,
|
|
2965
|
+
totalClients: states.length,
|
|
2966
|
+
connectedClients: states.filter((state) => state.connected).length,
|
|
2967
|
+
activeHandshakes: states.filter((state) => state.handshake).length,
|
|
2968
|
+
pendingDelegations: states.reduce(
|
|
2969
|
+
(acc, state) => acc + state.pendingDelegations,
|
|
2970
|
+
0
|
|
2971
|
+
),
|
|
2972
|
+
pendingTimers: states.reduce((acc, state) => acc + state.pendingTimers, 0),
|
|
2973
|
+
reconnectAttempts: states.reduce(
|
|
2974
|
+
(acc, state) => acc + state.reconnectAttempts,
|
|
2975
|
+
0
|
|
2976
|
+
),
|
|
2977
|
+
connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
|
|
2978
|
+
reconnectErrors: states.reduce(
|
|
2979
|
+
(acc, state) => acc + state.reconnectErrors,
|
|
2980
|
+
0
|
|
2981
|
+
),
|
|
2982
|
+
socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
|
|
2983
|
+
latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
|
|
2984
|
+
};
|
|
2985
|
+
if (detailLevel === "summary") {
|
|
2986
|
+
return {
|
|
2987
|
+
transportDiagnostics: {
|
|
2988
|
+
[serviceName]: {
|
|
2989
|
+
socketClient: summary
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
const clients = states.map((state) => {
|
|
2995
|
+
const details = {
|
|
2996
|
+
fetchId: state.fetchId,
|
|
2997
|
+
serviceName: state.serviceName,
|
|
2998
|
+
url: state.url,
|
|
2999
|
+
socketId: state.socketId,
|
|
3000
|
+
connected: state.connected,
|
|
3001
|
+
handshake: state.handshake,
|
|
3002
|
+
reconnectAttempts: state.reconnectAttempts,
|
|
3003
|
+
connectErrors: state.connectErrors,
|
|
3004
|
+
reconnectErrors: state.reconnectErrors,
|
|
3005
|
+
socketErrors: state.socketErrors,
|
|
3006
|
+
pendingDelegations: state.pendingDelegations,
|
|
3007
|
+
pendingTimers: state.pendingTimers,
|
|
3008
|
+
destroyed: state.destroyed,
|
|
3009
|
+
lastHandshakeAt: state.lastHandshakeAt,
|
|
3010
|
+
lastHandshakeError: state.lastHandshakeError,
|
|
3011
|
+
lastDisconnectAt: state.lastDisconnectAt,
|
|
3012
|
+
latestError: state.lastError
|
|
3013
|
+
};
|
|
3014
|
+
if (includeErrorHistory) {
|
|
3015
|
+
details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
|
|
3016
|
+
}
|
|
3017
|
+
return details;
|
|
3018
|
+
});
|
|
3019
|
+
return {
|
|
3020
|
+
transportDiagnostics: {
|
|
3021
|
+
[serviceName]: {
|
|
3022
|
+
socketClient: {
|
|
3023
|
+
...summary,
|
|
3024
|
+
clients
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
2206
3030
|
};
|
|
2207
3031
|
|
|
2208
3032
|
// src/utils/tools.ts
|
|
@@ -2307,12 +3131,6 @@ var GraphMetadataController = class _GraphMetadataController {
|
|
|
2307
3131
|
data: {
|
|
2308
3132
|
...ctx.data,
|
|
2309
3133
|
serviceName: CadenzaService.serviceRegistry.serviceName
|
|
2310
|
-
// input_context_schema_id: ctx.data.inputContextSchema ? { // TODO
|
|
2311
|
-
//
|
|
2312
|
-
// } : null,
|
|
2313
|
-
// output_context_schema_id: ctx.data.outputContextSchema ? {
|
|
2314
|
-
//
|
|
2315
|
-
// } : null,
|
|
2316
3134
|
}
|
|
2317
3135
|
};
|
|
2318
3136
|
}).doOn("meta.task.created").emits("global.meta.graph_metadata.task_created");
|
|
@@ -2520,9 +3338,11 @@ var GraphMetadataController = class _GraphMetadataController {
|
|
|
2520
3338
|
{ concurrency: 100, isSubMeta: true }
|
|
2521
3339
|
).doOn("meta.node.mapped", "meta.node.detected_previous_task_execution").emits("global.meta.graph_metadata.relationship_executed");
|
|
2522
3340
|
CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
|
|
3341
|
+
const intentName = ctx.data?.name;
|
|
2523
3342
|
return {
|
|
2524
3343
|
data: {
|
|
2525
|
-
...ctx.data
|
|
3344
|
+
...ctx.data,
|
|
3345
|
+
isMeta: intentName ? isMetaIntentName(intentName) : false
|
|
2526
3346
|
}
|
|
2527
3347
|
};
|
|
2528
3348
|
}).doOn("meta.inquiry_broker.added").emits("global.meta.graph_metadata.intent_created");
|
|
@@ -2554,6 +3374,52 @@ var SCHEMA_TYPES = [
|
|
|
2554
3374
|
// src/database/DatabaseController.ts
|
|
2555
3375
|
var import_pg = require("pg");
|
|
2556
3376
|
var import_lodash_es = require("lodash-es");
|
|
3377
|
+
function resolveTableQueryIntents(serviceName, tableName, table, defaultInputSchema) {
|
|
3378
|
+
const resolvedServiceName = serviceName ?? "unknown-service";
|
|
3379
|
+
const defaultIntentName = `query-${resolvedServiceName}-${tableName}`;
|
|
3380
|
+
const defaultDescription = `Perform a query operation on the ${tableName} table`;
|
|
3381
|
+
const intents = [
|
|
3382
|
+
{
|
|
3383
|
+
name: defaultIntentName,
|
|
3384
|
+
description: defaultDescription,
|
|
3385
|
+
input: defaultInputSchema
|
|
3386
|
+
}
|
|
3387
|
+
];
|
|
3388
|
+
const warnings = [];
|
|
3389
|
+
const names = /* @__PURE__ */ new Set([defaultIntentName]);
|
|
3390
|
+
for (const customIntent of table.customIntents?.query ?? []) {
|
|
3391
|
+
const name = typeof customIntent === "string" ? customIntent.trim() : customIntent.intent?.trim();
|
|
3392
|
+
if (!name) {
|
|
3393
|
+
warnings.push(`Skipped empty custom query intent for table '${tableName}'.`);
|
|
3394
|
+
continue;
|
|
3395
|
+
}
|
|
3396
|
+
if (name.length > 100) {
|
|
3397
|
+
warnings.push(
|
|
3398
|
+
`Skipped custom query intent '${name}' for table '${tableName}': name must be <= 100 characters.`
|
|
3399
|
+
);
|
|
3400
|
+
continue;
|
|
3401
|
+
}
|
|
3402
|
+
if (name.includes(" ") || name.includes(".") || name.includes("\\")) {
|
|
3403
|
+
warnings.push(
|
|
3404
|
+
`Skipped custom query intent '${name}' for table '${tableName}': name cannot contain spaces, dots or backslashes.`
|
|
3405
|
+
);
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
if (names.has(name)) {
|
|
3409
|
+
warnings.push(
|
|
3410
|
+
`Skipped duplicate custom query intent '${name}' for table '${tableName}'.`
|
|
3411
|
+
);
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
names.add(name);
|
|
3415
|
+
intents.push({
|
|
3416
|
+
name,
|
|
3417
|
+
description: typeof customIntent === "string" ? `Perform a query operation on the ${tableName} table` : customIntent.description ?? defaultDescription,
|
|
3418
|
+
input: typeof customIntent === "string" ? defaultInputSchema : customIntent.input ?? defaultInputSchema
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
return { intents, warnings };
|
|
3422
|
+
}
|
|
2557
3423
|
var DatabaseController = class _DatabaseController {
|
|
2558
3424
|
/**
|
|
2559
3425
|
* Constructor for initializing the `DatabaseService` class.
|
|
@@ -2695,6 +3561,20 @@ var DatabaseController = class _DatabaseController {
|
|
|
2695
3561
|
}
|
|
2696
3562
|
}
|
|
2697
3563
|
}
|
|
3564
|
+
if (table.customIntents?.query) {
|
|
3565
|
+
if (!Array.isArray(table.customIntents.query)) {
|
|
3566
|
+
throw new Error(
|
|
3567
|
+
`Invalid customIntents.query for ${tableName}: expected array`
|
|
3568
|
+
);
|
|
3569
|
+
}
|
|
3570
|
+
for (const customIntent of table.customIntents.query) {
|
|
3571
|
+
if (typeof customIntent !== "string" && (typeof customIntent !== "object" || !customIntent || typeof customIntent.intent !== "string")) {
|
|
3572
|
+
throw new Error(
|
|
3573
|
+
`Invalid custom query intent on ${tableName}: expected string or object with intent`
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
2698
3578
|
}
|
|
2699
3579
|
}
|
|
2700
3580
|
console.log("SCHEMA VALIDATED");
|
|
@@ -3621,9 +4501,9 @@ var DatabaseController = class _DatabaseController {
|
|
|
3621
4501
|
createDatabaseTask(op, tableName, table, queryFunction, options) {
|
|
3622
4502
|
const opAction = op === "query" ? "queried" : op === "insert" ? "inserted" : op === "update" ? "updated" : op === "delete" ? "deleted" : "";
|
|
3623
4503
|
const defaultSignal = `global.${options.isMeta ? "meta." : ""}${tableName}.${opAction}`;
|
|
3624
|
-
const
|
|
3625
|
-
const
|
|
3626
|
-
CadenzaService.createThrottledTask(
|
|
4504
|
+
const taskName = `${op.charAt(0).toUpperCase() + op.slice(1)} ${tableName}`;
|
|
4505
|
+
const schema = this.getInputSchema(op, tableName, table);
|
|
4506
|
+
const task = CadenzaService.createThrottledTask(
|
|
3627
4507
|
taskName,
|
|
3628
4508
|
async (context, emit) => {
|
|
3629
4509
|
for (const action of Object.keys(table.customSignals?.triggers ?? {})) {
|
|
@@ -3718,17 +4598,8 @@ var DatabaseController = class _DatabaseController {
|
|
|
3718
4598
|
{
|
|
3719
4599
|
isMeta: options.isMeta,
|
|
3720
4600
|
isSubMeta: options.isMeta,
|
|
3721
|
-
validateInputContext:
|
|
3722
|
-
|
|
3723
|
-
inputSchema: {
|
|
3724
|
-
// TODO
|
|
3725
|
-
type: "object",
|
|
3726
|
-
properties: {
|
|
3727
|
-
filter: {
|
|
3728
|
-
type: "object"
|
|
3729
|
-
}
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
4601
|
+
validateInputContext: options.securityProfile !== "low",
|
|
4602
|
+
inputSchema: schema
|
|
3732
4603
|
}
|
|
3733
4604
|
).doOn(
|
|
3734
4605
|
...table.customSignals?.triggers?.[op]?.map((signal) => {
|
|
@@ -3739,8 +4610,397 @@ var DatabaseController = class _DatabaseController {
|
|
|
3739
4610
|
return typeof signal === "string" ? signal : signal.signal;
|
|
3740
4611
|
}) ?? []
|
|
3741
4612
|
);
|
|
4613
|
+
if (op === "query") {
|
|
4614
|
+
const { intents, warnings } = resolveTableQueryIntents(
|
|
4615
|
+
CadenzaService.serviceRegistry?.serviceName,
|
|
4616
|
+
tableName,
|
|
4617
|
+
table,
|
|
4618
|
+
schema
|
|
4619
|
+
);
|
|
4620
|
+
for (const warning of warnings) {
|
|
4621
|
+
CadenzaService.log(
|
|
4622
|
+
"Skipped custom query intent registration.",
|
|
4623
|
+
{
|
|
4624
|
+
tableName,
|
|
4625
|
+
warning
|
|
4626
|
+
},
|
|
4627
|
+
"warning"
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
for (const intent of intents) {
|
|
4631
|
+
CadenzaService.defineIntent({
|
|
4632
|
+
name: intent.name,
|
|
4633
|
+
description: intent.description,
|
|
4634
|
+
input: intent.input
|
|
4635
|
+
});
|
|
4636
|
+
}
|
|
4637
|
+
task.respondsTo(...intents.map((intent) => intent.name));
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
getInputSchema(op, tableName, table) {
|
|
4641
|
+
const inputSchema = {
|
|
4642
|
+
type: "object",
|
|
4643
|
+
properties: {
|
|
4644
|
+
queryData: {
|
|
4645
|
+
type: "object",
|
|
4646
|
+
properties: {},
|
|
4647
|
+
strict: true
|
|
4648
|
+
}
|
|
4649
|
+
},
|
|
4650
|
+
strict: true
|
|
4651
|
+
};
|
|
4652
|
+
if (!inputSchema.properties) {
|
|
4653
|
+
return inputSchema;
|
|
4654
|
+
}
|
|
4655
|
+
inputSchema.properties.transaction = getTransactionSchema();
|
|
4656
|
+
inputSchema.properties.queryData.properties.transaction = inputSchema.properties.transaction;
|
|
4657
|
+
switch (op) {
|
|
4658
|
+
case "insert":
|
|
4659
|
+
inputSchema.properties.data = getInsertDataSchemaFromTable(
|
|
4660
|
+
table,
|
|
4661
|
+
tableName
|
|
4662
|
+
);
|
|
4663
|
+
inputSchema.properties.queryData.properties.data = inputSchema.properties.data;
|
|
4664
|
+
inputSchema.properties.batch = getQueryBatchSchemaFromTable();
|
|
4665
|
+
inputSchema.properties.queryData.properties.batch = inputSchema.properties.batch;
|
|
4666
|
+
inputSchema.properties.onConflict = getQueryOnConflictSchemaFromTable(
|
|
4667
|
+
table,
|
|
4668
|
+
tableName
|
|
4669
|
+
);
|
|
4670
|
+
inputSchema.properties.queryData.properties.onConflict = inputSchema.properties.onConflict;
|
|
4671
|
+
break;
|
|
4672
|
+
case "query":
|
|
4673
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4674
|
+
table,
|
|
4675
|
+
tableName
|
|
4676
|
+
);
|
|
4677
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4678
|
+
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
|
|
4679
|
+
table,
|
|
4680
|
+
tableName
|
|
4681
|
+
);
|
|
4682
|
+
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
4683
|
+
inputSchema.properties.joins = getQueryJoinsSchemaFromTable(
|
|
4684
|
+
table,
|
|
4685
|
+
tableName
|
|
4686
|
+
);
|
|
4687
|
+
inputSchema.properties.queryData.properties.joins = inputSchema.properties.joins;
|
|
4688
|
+
inputSchema.properties.sort = getQuerySortSchemaFromTable(
|
|
4689
|
+
table,
|
|
4690
|
+
tableName
|
|
4691
|
+
);
|
|
4692
|
+
inputSchema.properties.queryData.properties.sort = inputSchema.properties.sort;
|
|
4693
|
+
inputSchema.properties.limit = getQueryLimitSchemaFromTable();
|
|
4694
|
+
inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
|
|
4695
|
+
inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
|
|
4696
|
+
inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
|
|
4697
|
+
break;
|
|
4698
|
+
case "update":
|
|
4699
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4700
|
+
table,
|
|
4701
|
+
tableName
|
|
4702
|
+
);
|
|
4703
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4704
|
+
inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
|
|
4705
|
+
table,
|
|
4706
|
+
tableName
|
|
4707
|
+
);
|
|
4708
|
+
inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
|
|
4709
|
+
break;
|
|
4710
|
+
case "delete":
|
|
4711
|
+
inputSchema.properties.filter = getQueryFilterSchemaFromTable(
|
|
4712
|
+
table,
|
|
4713
|
+
tableName
|
|
4714
|
+
);
|
|
4715
|
+
inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
|
|
4716
|
+
break;
|
|
4717
|
+
}
|
|
4718
|
+
return inputSchema;
|
|
3742
4719
|
}
|
|
3743
4720
|
};
|
|
4721
|
+
function getInsertDataSchemaFromTable(table, tableName) {
|
|
4722
|
+
const dataSchema = {
|
|
4723
|
+
type: "object",
|
|
4724
|
+
properties: {
|
|
4725
|
+
...Object.fromEntries(
|
|
4726
|
+
Object.entries(table.fields).map((field) => {
|
|
4727
|
+
return [
|
|
4728
|
+
field[0],
|
|
4729
|
+
{
|
|
4730
|
+
value: {
|
|
4731
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4732
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4733
|
+
},
|
|
4734
|
+
effect: {
|
|
4735
|
+
type: "string",
|
|
4736
|
+
constraints: {
|
|
4737
|
+
oneOf: ["increment", "decrement", "set"]
|
|
4738
|
+
}
|
|
4739
|
+
},
|
|
4740
|
+
subOperation: {
|
|
4741
|
+
type: "object",
|
|
4742
|
+
properties: {
|
|
4743
|
+
subOperation: {
|
|
4744
|
+
type: "string",
|
|
4745
|
+
enum: ["insert", "query"]
|
|
4746
|
+
},
|
|
4747
|
+
table: {
|
|
4748
|
+
type: "string"
|
|
4749
|
+
},
|
|
4750
|
+
data: {
|
|
4751
|
+
single: {
|
|
4752
|
+
type: "object"
|
|
4753
|
+
},
|
|
4754
|
+
batch: {
|
|
4755
|
+
type: "array",
|
|
4756
|
+
items: {
|
|
4757
|
+
type: "object"
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
},
|
|
4761
|
+
filter: {
|
|
4762
|
+
type: "object"
|
|
4763
|
+
},
|
|
4764
|
+
fields: {
|
|
4765
|
+
type: "array",
|
|
4766
|
+
items: {
|
|
4767
|
+
type: "string"
|
|
4768
|
+
}
|
|
4769
|
+
},
|
|
4770
|
+
return: {
|
|
4771
|
+
type: "string"
|
|
4772
|
+
}
|
|
4773
|
+
},
|
|
4774
|
+
required: ["subOperation", "table"]
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
];
|
|
4778
|
+
})
|
|
4779
|
+
)
|
|
4780
|
+
},
|
|
4781
|
+
required: Object.entries(table.fields).filter((field) => field[1].required || field[1].primary).map((field) => field[0]),
|
|
4782
|
+
strict: true
|
|
4783
|
+
};
|
|
4784
|
+
return {
|
|
4785
|
+
single: dataSchema,
|
|
4786
|
+
batch: {
|
|
4787
|
+
type: "array",
|
|
4788
|
+
items: dataSchema
|
|
4789
|
+
}
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4792
|
+
function getQueryFilterSchemaFromTable(table, tableName) {
|
|
4793
|
+
return {
|
|
4794
|
+
type: "object",
|
|
4795
|
+
properties: {
|
|
4796
|
+
...Object.fromEntries(
|
|
4797
|
+
Object.entries(table.fields).map((field) => {
|
|
4798
|
+
return [
|
|
4799
|
+
field[0],
|
|
4800
|
+
{
|
|
4801
|
+
value: {
|
|
4802
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4803
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4804
|
+
},
|
|
4805
|
+
in: {
|
|
4806
|
+
type: "array",
|
|
4807
|
+
items: {
|
|
4808
|
+
type: tableFieldTypeToSchemaType(field[1].type)
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
];
|
|
4813
|
+
})
|
|
4814
|
+
)
|
|
4815
|
+
},
|
|
4816
|
+
strict: true,
|
|
4817
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4818
|
+
};
|
|
4819
|
+
}
|
|
4820
|
+
function getQueryFieldsSchemaFromTable(table, tableName) {
|
|
4821
|
+
return {
|
|
4822
|
+
type: "array",
|
|
4823
|
+
items: {
|
|
4824
|
+
type: "string",
|
|
4825
|
+
constraints: {
|
|
4826
|
+
oneOf: Object.keys(table.fields)
|
|
4827
|
+
}
|
|
4828
|
+
},
|
|
4829
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4830
|
+
};
|
|
4831
|
+
}
|
|
4832
|
+
function getQueryJoinsSchemaFromTable(table, tableName) {
|
|
4833
|
+
return {
|
|
4834
|
+
type: "object",
|
|
4835
|
+
properties: {
|
|
4836
|
+
...Object.fromEntries(
|
|
4837
|
+
Object.entries(table.fields).map((field) => {
|
|
4838
|
+
return [
|
|
4839
|
+
field[0],
|
|
4840
|
+
{
|
|
4841
|
+
type: "object",
|
|
4842
|
+
properties: {
|
|
4843
|
+
on: {
|
|
4844
|
+
type: "string"
|
|
4845
|
+
},
|
|
4846
|
+
fields: {
|
|
4847
|
+
type: "array",
|
|
4848
|
+
items: {
|
|
4849
|
+
type: "string"
|
|
4850
|
+
}
|
|
4851
|
+
},
|
|
4852
|
+
filter: {
|
|
4853
|
+
type: "object"
|
|
4854
|
+
},
|
|
4855
|
+
returnAs: {
|
|
4856
|
+
type: "string",
|
|
4857
|
+
constraints: {
|
|
4858
|
+
oneOf: ["array", "object"]
|
|
4859
|
+
}
|
|
4860
|
+
},
|
|
4861
|
+
alias: {
|
|
4862
|
+
type: "string"
|
|
4863
|
+
},
|
|
4864
|
+
joins: {
|
|
4865
|
+
type: "object"
|
|
4866
|
+
}
|
|
4867
|
+
},
|
|
4868
|
+
required: ["on", "fields"],
|
|
4869
|
+
strict: true
|
|
4870
|
+
}
|
|
4871
|
+
];
|
|
4872
|
+
})
|
|
4873
|
+
)
|
|
4874
|
+
},
|
|
4875
|
+
strict: true,
|
|
4876
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4879
|
+
function getQuerySortSchemaFromTable(table, tableName) {
|
|
4880
|
+
return {
|
|
4881
|
+
type: "object",
|
|
4882
|
+
properties: {
|
|
4883
|
+
...Object.fromEntries(
|
|
4884
|
+
Object.entries(table.fields).map((field) => {
|
|
4885
|
+
return [
|
|
4886
|
+
field[0],
|
|
4887
|
+
{
|
|
4888
|
+
type: "string",
|
|
4889
|
+
constraints: {
|
|
4890
|
+
oneOf: ["asc", "desc"]
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
];
|
|
4894
|
+
})
|
|
4895
|
+
)
|
|
4896
|
+
},
|
|
4897
|
+
strict: true,
|
|
4898
|
+
description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
function getQueryLimitSchemaFromTable() {
|
|
4902
|
+
return {
|
|
4903
|
+
type: "number",
|
|
4904
|
+
constraints: {
|
|
4905
|
+
min: 1
|
|
4906
|
+
},
|
|
4907
|
+
description: "Limit for query results"
|
|
4908
|
+
};
|
|
4909
|
+
}
|
|
4910
|
+
function getQueryOffsetSchemaFromTable() {
|
|
4911
|
+
return {
|
|
4912
|
+
type: "number",
|
|
4913
|
+
constraints: {
|
|
4914
|
+
min: 0
|
|
4915
|
+
},
|
|
4916
|
+
description: "Offset for query results"
|
|
4917
|
+
};
|
|
4918
|
+
}
|
|
4919
|
+
function getTransactionSchema() {
|
|
4920
|
+
return {
|
|
4921
|
+
type: "boolean",
|
|
4922
|
+
description: "Whether to run the query in a transaction"
|
|
4923
|
+
};
|
|
4924
|
+
}
|
|
4925
|
+
function getQueryBatchSchemaFromTable() {
|
|
4926
|
+
return {
|
|
4927
|
+
type: "boolean",
|
|
4928
|
+
description: "Whether to run the query in batch mode"
|
|
4929
|
+
};
|
|
4930
|
+
}
|
|
4931
|
+
function getQueryOnConflictSchemaFromTable(table, tableName) {
|
|
4932
|
+
return {
|
|
4933
|
+
type: "object",
|
|
4934
|
+
properties: {
|
|
4935
|
+
target: {
|
|
4936
|
+
type: "array",
|
|
4937
|
+
items: {
|
|
4938
|
+
type: "string",
|
|
4939
|
+
constraints: {
|
|
4940
|
+
oneOf: Object.keys(table.fields)
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
},
|
|
4944
|
+
action: {
|
|
4945
|
+
type: "object",
|
|
4946
|
+
properties: {
|
|
4947
|
+
do: {
|
|
4948
|
+
type: "string",
|
|
4949
|
+
constraints: {
|
|
4950
|
+
oneOf: ["nothing", "update"]
|
|
4951
|
+
}
|
|
4952
|
+
},
|
|
4953
|
+
set: {
|
|
4954
|
+
type: "object",
|
|
4955
|
+
properties: {
|
|
4956
|
+
...Object.fromEntries(
|
|
4957
|
+
Object.entries(table.fields).map((field) => {
|
|
4958
|
+
return [
|
|
4959
|
+
field[0],
|
|
4960
|
+
{
|
|
4961
|
+
type: tableFieldTypeToSchemaType(field[1].type),
|
|
4962
|
+
description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
|
|
4963
|
+
}
|
|
4964
|
+
];
|
|
4965
|
+
})
|
|
4966
|
+
)
|
|
4967
|
+
}
|
|
4968
|
+
},
|
|
4969
|
+
where: {
|
|
4970
|
+
type: "string"
|
|
4971
|
+
}
|
|
4972
|
+
},
|
|
4973
|
+
required: ["do"]
|
|
4974
|
+
}
|
|
4975
|
+
},
|
|
4976
|
+
required: ["target", "action"],
|
|
4977
|
+
strict: true
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
function tableFieldTypeToSchemaType(type) {
|
|
4981
|
+
switch (type) {
|
|
4982
|
+
case "varchar":
|
|
4983
|
+
case "text":
|
|
4984
|
+
case "jsonb":
|
|
4985
|
+
case "uuid":
|
|
4986
|
+
case "date":
|
|
4987
|
+
case "geo_point":
|
|
4988
|
+
case "bytea":
|
|
4989
|
+
return "string";
|
|
4990
|
+
case "int":
|
|
4991
|
+
case "bigint":
|
|
4992
|
+
case "decimal":
|
|
4993
|
+
case "timestamp":
|
|
4994
|
+
return "number";
|
|
4995
|
+
case "boolean":
|
|
4996
|
+
return "boolean";
|
|
4997
|
+
case "array":
|
|
4998
|
+
return "array";
|
|
4999
|
+
case "object":
|
|
5000
|
+
return "object";
|
|
5001
|
+
}
|
|
5002
|
+
return "any";
|
|
5003
|
+
}
|
|
3744
5004
|
|
|
3745
5005
|
// src/Cadenza.ts
|
|
3746
5006
|
var import_uuid3 = require("uuid");
|
|
@@ -4058,6 +5318,75 @@ var GraphSyncController = class _GraphSyncController {
|
|
|
4058
5318
|
{ concurrency: 30 }
|
|
4059
5319
|
) : CadenzaService.get("dbInsertSignalToTaskMap"))?.then(registerSignalTask)
|
|
4060
5320
|
);
|
|
5321
|
+
const registerIntentTask = CadenzaService.createMetaTask(
|
|
5322
|
+
"Record intent registration",
|
|
5323
|
+
(ctx) => {
|
|
5324
|
+
if (!ctx.__syncing) {
|
|
5325
|
+
return;
|
|
5326
|
+
}
|
|
5327
|
+
CadenzaService.debounce("meta.sync_controller.synced_resource", {
|
|
5328
|
+
delayMs: 3e3
|
|
5329
|
+
});
|
|
5330
|
+
const task = CadenzaService.get(ctx.__taskName);
|
|
5331
|
+
task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
|
|
5332
|
+
task.__registeredIntents.add(ctx.__intent);
|
|
5333
|
+
}
|
|
5334
|
+
);
|
|
5335
|
+
this.registerIntentToTaskMapTask = CadenzaService.createMetaTask(
|
|
5336
|
+
"Split intents of task",
|
|
5337
|
+
function* (ctx) {
|
|
5338
|
+
const task = ctx.task;
|
|
5339
|
+
if (task.hidden || !task.register) return;
|
|
5340
|
+
task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
|
|
5341
|
+
task.__invalidMetaIntentWarnings = task.__invalidMetaIntentWarnings ?? /* @__PURE__ */ new Set();
|
|
5342
|
+
for (const intent of task.handlesIntents) {
|
|
5343
|
+
if (task.__registeredIntents.has(intent)) continue;
|
|
5344
|
+
if (isMetaIntentName(intent) && !task.isMeta) {
|
|
5345
|
+
if (!task.__invalidMetaIntentWarnings.has(intent)) {
|
|
5346
|
+
task.__invalidMetaIntentWarnings.add(intent);
|
|
5347
|
+
CadenzaService.log(
|
|
5348
|
+
"Skipping intent-to-task registration: non-meta task cannot handle meta intent.",
|
|
5349
|
+
{
|
|
5350
|
+
intent,
|
|
5351
|
+
taskName: task.name,
|
|
5352
|
+
taskVersion: task.version
|
|
5353
|
+
},
|
|
5354
|
+
"warning"
|
|
5355
|
+
);
|
|
5356
|
+
}
|
|
5357
|
+
continue;
|
|
5358
|
+
}
|
|
5359
|
+
yield {
|
|
5360
|
+
data: {
|
|
5361
|
+
intentName: intent,
|
|
5362
|
+
taskName: task.name,
|
|
5363
|
+
taskVersion: task.version,
|
|
5364
|
+
serviceName: CadenzaService.serviceRegistry.serviceName
|
|
5365
|
+
},
|
|
5366
|
+
__taskName: task.name,
|
|
5367
|
+
__intent: intent
|
|
5368
|
+
};
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
).then(
|
|
5372
|
+
(this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
|
|
5373
|
+
"intent_to_task_map",
|
|
5374
|
+
{
|
|
5375
|
+
onConflict: {
|
|
5376
|
+
target: [
|
|
5377
|
+
"intent_name",
|
|
5378
|
+
"task_name",
|
|
5379
|
+
"task_version",
|
|
5380
|
+
"service_name"
|
|
5381
|
+
],
|
|
5382
|
+
action: {
|
|
5383
|
+
do: "nothing"
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
},
|
|
5387
|
+
{ concurrency: 30 }
|
|
5388
|
+
) : CadenzaService.get("dbInsertIntentToTaskMap"))?.then(registerIntentTask)
|
|
5389
|
+
);
|
|
4061
5390
|
this.registerTaskMapTask = CadenzaService.createMetaTask(
|
|
4062
5391
|
"Register task map to DB",
|
|
4063
5392
|
function* (ctx) {
|
|
@@ -4180,6 +5509,7 @@ var GraphSyncController = class _GraphSyncController {
|
|
|
4180
5509
|
CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks").then(
|
|
4181
5510
|
this.registerTaskMapTask,
|
|
4182
5511
|
this.registerSignalToTaskMapTask,
|
|
5512
|
+
this.registerIntentToTaskMapTask,
|
|
4183
5513
|
this.registerDeputyRelationshipTask
|
|
4184
5514
|
);
|
|
4185
5515
|
CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_routines").then(this.splitTasksInRoutines);
|
|
@@ -4321,11 +5651,192 @@ var CadenzaService = class {
|
|
|
4321
5651
|
import_core3.default.interval(signal, context, intervalMs, leading, startDateTime);
|
|
4322
5652
|
}
|
|
4323
5653
|
static defineIntent(intent) {
|
|
4324
|
-
this.inquiryBroker?.
|
|
5654
|
+
this.inquiryBroker?.addIntent(intent);
|
|
4325
5655
|
return intent;
|
|
4326
5656
|
}
|
|
4327
|
-
static
|
|
4328
|
-
return this.
|
|
5657
|
+
static getInquiryResponderDescriptor(task) {
|
|
5658
|
+
return this.serviceRegistry.getInquiryResponderDescriptor(task);
|
|
5659
|
+
}
|
|
5660
|
+
static compareInquiryResponders(left, right) {
|
|
5661
|
+
return compareResponderDescriptors(left.descriptor, right.descriptor);
|
|
5662
|
+
}
|
|
5663
|
+
static buildInquirySummary(inquiry, startedAt, statuses, totalResponders) {
|
|
5664
|
+
const counts = summarizeResponderStatuses(statuses);
|
|
5665
|
+
const isMetaInquiry = isMetaIntentName(inquiry);
|
|
5666
|
+
const eligibleResponders = statuses.length;
|
|
5667
|
+
return {
|
|
5668
|
+
inquiry,
|
|
5669
|
+
isMetaInquiry,
|
|
5670
|
+
totalResponders,
|
|
5671
|
+
eligibleResponders,
|
|
5672
|
+
filteredOutResponders: Math.max(0, totalResponders - eligibleResponders),
|
|
5673
|
+
responded: counts.responded,
|
|
5674
|
+
failed: counts.failed,
|
|
5675
|
+
timedOut: counts.timedOut,
|
|
5676
|
+
pending: counts.pending,
|
|
5677
|
+
durationMs: Date.now() - startedAt,
|
|
5678
|
+
responders: statuses
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
static async inquire(inquiry, context, options = {}) {
|
|
5682
|
+
this.bootstrap();
|
|
5683
|
+
const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
|
|
5684
|
+
const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
|
|
5685
|
+
task,
|
|
5686
|
+
descriptor: this.getInquiryResponderDescriptor(task)
|
|
5687
|
+
})) : [];
|
|
5688
|
+
const isMetaInquiry = isMetaIntentName(inquiry);
|
|
5689
|
+
const responders = allResponders.filter(({ task, descriptor }) => {
|
|
5690
|
+
const shouldExecute = shouldExecuteInquiryResponder(inquiry, task.isMeta);
|
|
5691
|
+
if (shouldExecute) {
|
|
5692
|
+
return true;
|
|
5693
|
+
}
|
|
5694
|
+
const warningKey = `${inquiry}|${descriptor.serviceName}|${descriptor.taskName}|${descriptor.taskVersion}|${descriptor.localTaskName}`;
|
|
5695
|
+
if (!this.warnedInvalidMetaIntentResponderKeys.has(warningKey)) {
|
|
5696
|
+
this.warnedInvalidMetaIntentResponderKeys.add(warningKey);
|
|
5697
|
+
this.log(
|
|
5698
|
+
"Skipping non-meta task for meta intent inquiry.",
|
|
5699
|
+
{
|
|
5700
|
+
inquiry,
|
|
5701
|
+
responder: descriptor
|
|
5702
|
+
},
|
|
5703
|
+
"warning",
|
|
5704
|
+
descriptor.serviceName
|
|
5705
|
+
);
|
|
5706
|
+
}
|
|
5707
|
+
return false;
|
|
5708
|
+
});
|
|
5709
|
+
if (responders.length === 0) {
|
|
5710
|
+
return {
|
|
5711
|
+
__inquiryMeta: {
|
|
5712
|
+
inquiry,
|
|
5713
|
+
isMetaInquiry,
|
|
5714
|
+
totalResponders: allResponders.length,
|
|
5715
|
+
eligibleResponders: 0,
|
|
5716
|
+
filteredOutResponders: allResponders.length,
|
|
5717
|
+
responded: 0,
|
|
5718
|
+
failed: 0,
|
|
5719
|
+
timedOut: 0,
|
|
5720
|
+
pending: 0,
|
|
5721
|
+
durationMs: 0,
|
|
5722
|
+
responders: []
|
|
5723
|
+
}
|
|
5724
|
+
};
|
|
5725
|
+
}
|
|
5726
|
+
responders.sort(this.compareInquiryResponders.bind(this));
|
|
5727
|
+
const overallTimeoutMs = options.overallTimeoutMs ?? options.timeout ?? 0;
|
|
5728
|
+
const requireComplete = options.requireComplete ?? false;
|
|
5729
|
+
const perResponderTimeoutMs = options.perResponderTimeoutMs;
|
|
5730
|
+
const startedAt = Date.now();
|
|
5731
|
+
const statuses = [];
|
|
5732
|
+
const statusByTask = /* @__PURE__ */ new Map();
|
|
5733
|
+
for (const responder of responders) {
|
|
5734
|
+
const status = {
|
|
5735
|
+
...responder.descriptor,
|
|
5736
|
+
status: "timed_out",
|
|
5737
|
+
durationMs: 0
|
|
5738
|
+
};
|
|
5739
|
+
statuses.push(status);
|
|
5740
|
+
statusByTask.set(responder.task, status);
|
|
5741
|
+
}
|
|
5742
|
+
const resultsByTask = /* @__PURE__ */ new Map();
|
|
5743
|
+
const resolverTasks = [];
|
|
5744
|
+
const pending = new Set(responders.map((r) => r.task));
|
|
5745
|
+
const startTimeByTask = /* @__PURE__ */ new Map();
|
|
5746
|
+
this.emit("meta.inquiry_broker.inquire", { inquiry, context });
|
|
5747
|
+
return new Promise((resolve, reject) => {
|
|
5748
|
+
let finalized = false;
|
|
5749
|
+
let timeoutId;
|
|
5750
|
+
const finalize = (timedOut) => {
|
|
5751
|
+
if (finalized) return;
|
|
5752
|
+
finalized = true;
|
|
5753
|
+
if (timeoutId) {
|
|
5754
|
+
clearTimeout(timeoutId);
|
|
5755
|
+
timeoutId = void 0;
|
|
5756
|
+
}
|
|
5757
|
+
for (const resolverTask of resolverTasks) {
|
|
5758
|
+
resolverTask.destroy();
|
|
5759
|
+
}
|
|
5760
|
+
if (timedOut && pending.size > 0) {
|
|
5761
|
+
for (const task of pending) {
|
|
5762
|
+
const status = statusByTask.get(task);
|
|
5763
|
+
if (!status) continue;
|
|
5764
|
+
status.status = "timed_out";
|
|
5765
|
+
status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5768
|
+
const fulfilledContexts = responders.filter((responder) => resultsByTask.has(responder.task)).map((responder) => resultsByTask.get(responder.task));
|
|
5769
|
+
const mergedContext = mergeInquiryContexts(fulfilledContexts);
|
|
5770
|
+
const inquiryMeta = this.buildInquirySummary(
|
|
5771
|
+
inquiry,
|
|
5772
|
+
startedAt,
|
|
5773
|
+
statuses,
|
|
5774
|
+
allResponders.length
|
|
5775
|
+
);
|
|
5776
|
+
const responseContext = {
|
|
5777
|
+
...mergedContext,
|
|
5778
|
+
__inquiryMeta: inquiryMeta
|
|
5779
|
+
};
|
|
5780
|
+
if (requireComplete && (timedOut || inquiryMeta.failed > 0 || inquiryMeta.timedOut > 0 || inquiryMeta.pending > 0)) {
|
|
5781
|
+
reject({
|
|
5782
|
+
...responseContext,
|
|
5783
|
+
__error: `Inquiry '${inquiry}' did not complete successfully`,
|
|
5784
|
+
errored: true
|
|
5785
|
+
});
|
|
5786
|
+
return;
|
|
5787
|
+
}
|
|
5788
|
+
resolve(responseContext);
|
|
5789
|
+
};
|
|
5790
|
+
if (overallTimeoutMs > 0) {
|
|
5791
|
+
timeoutId = setTimeout(() => finalize(true), overallTimeoutMs);
|
|
5792
|
+
}
|
|
5793
|
+
for (const responder of responders) {
|
|
5794
|
+
const { task, descriptor } = responder;
|
|
5795
|
+
const inquiryId = (0, import_uuid3.v4)();
|
|
5796
|
+
startTimeByTask.set(task, Date.now());
|
|
5797
|
+
const resolverTask = this.createEphemeralMetaTask(
|
|
5798
|
+
`Resolve inquiry ${inquiry} for ${descriptor.localTaskName}`,
|
|
5799
|
+
(resultCtx) => {
|
|
5800
|
+
if (finalized) {
|
|
5801
|
+
return;
|
|
5802
|
+
}
|
|
5803
|
+
pending.delete(task);
|
|
5804
|
+
const status = statusByTask.get(task);
|
|
5805
|
+
if (status) {
|
|
5806
|
+
status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
|
|
5807
|
+
if (resultCtx?.errored || resultCtx?.failed) {
|
|
5808
|
+
status.status = "failed";
|
|
5809
|
+
status.error = String(
|
|
5810
|
+
resultCtx?.__error ?? resultCtx?.error ?? "Inquiry responder failed"
|
|
5811
|
+
);
|
|
5812
|
+
} else {
|
|
5813
|
+
status.status = "fulfilled";
|
|
5814
|
+
resultsByTask.set(task, resultCtx);
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
if (pending.size === 0) {
|
|
5818
|
+
finalize(false);
|
|
5819
|
+
}
|
|
5820
|
+
},
|
|
5821
|
+
"Resolves distributed inquiry responder result",
|
|
5822
|
+
{ register: false }
|
|
5823
|
+
).doOn(`meta.node.graph_completed:${inquiryId}`);
|
|
5824
|
+
resolverTasks.push(resolverTask);
|
|
5825
|
+
const executionContext = {
|
|
5826
|
+
...context,
|
|
5827
|
+
__routineExecId: inquiryId,
|
|
5828
|
+
__isInquiry: true
|
|
5829
|
+
};
|
|
5830
|
+
if (perResponderTimeoutMs !== void 0) {
|
|
5831
|
+
executionContext.__timeout = perResponderTimeoutMs;
|
|
5832
|
+
}
|
|
5833
|
+
if (task.isMeta) {
|
|
5834
|
+
this.metaRunner?.run(task, executionContext);
|
|
5835
|
+
} else {
|
|
5836
|
+
this.runner?.run(task, executionContext);
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
});
|
|
4329
5840
|
}
|
|
4330
5841
|
/**
|
|
4331
5842
|
* Executes the given task or graph routine within the provided context using the configured runner.
|
|
@@ -4614,10 +6125,9 @@ var CadenzaService = class {
|
|
|
4614
6125
|
this.bootstrap();
|
|
4615
6126
|
this.validateName(tableName);
|
|
4616
6127
|
this.validateName(operation);
|
|
4617
|
-
const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
4618
6128
|
const name = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName} in ${databaseServiceName ?? "default database service"}`;
|
|
4619
6129
|
const description = `Executes a ${operation} on table ${tableName} in ${databaseServiceName ?? "default database service"}`;
|
|
4620
|
-
const taskName =
|
|
6130
|
+
const taskName = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName}`;
|
|
4621
6131
|
options = {
|
|
4622
6132
|
concurrency: 100,
|
|
4623
6133
|
timeout: 0,
|
|
@@ -4879,7 +6389,7 @@ var CadenzaService = class {
|
|
|
4879
6389
|
* This method is not supported in a browser environment and will log a warning if called in such an environment.
|
|
4880
6390
|
*
|
|
4881
6391
|
* @param {string} name - The name of the database service to be created.
|
|
4882
|
-
* @param {
|
|
6392
|
+
* @param {DatabaseSchemaDefinition} schema - The schema definition for the database service.
|
|
4883
6393
|
* @param {string} [description=""] - An optional description of the database service.
|
|
4884
6394
|
* @param {ServerOptions & DatabaseOptions} [options={}] - Optional configuration settings for the database and server.
|
|
4885
6395
|
* @return {void} This method does not return a value.
|
|
@@ -4943,7 +6453,7 @@ var CadenzaService = class {
|
|
|
4943
6453
|
* Creates a meta database service with the specified configuration.
|
|
4944
6454
|
*
|
|
4945
6455
|
* @param {string} name - The name of the database service to be created.
|
|
4946
|
-
* @param {
|
|
6456
|
+
* @param {DatabaseSchemaDefinition} schema - The schema definition for the database.
|
|
4947
6457
|
* @param {string} [description=""] - An optional description of the database service.
|
|
4948
6458
|
* @param {ServerOptions & DatabaseOptions} [options={}] - Optional server and database configuration options. The `isMeta` flag will be automatically set to true.
|
|
4949
6459
|
* @return {void} - This method does not return a value.
|
|
@@ -5363,6 +6873,7 @@ var CadenzaService = class {
|
|
|
5363
6873
|
};
|
|
5364
6874
|
CadenzaService.isBootstrapped = false;
|
|
5365
6875
|
CadenzaService.serviceCreated = false;
|
|
6876
|
+
CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
|
|
5366
6877
|
|
|
5367
6878
|
// src/index.ts
|
|
5368
6879
|
var import_core4 = require("@cadenza.io/core");
|