@cadenza.io/service 1.20.8 → 1.21.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 CHANGED
@@ -37,12 +37,16 @@ declare class DeputyTask extends Task {
37
37
  * @param context - The GraphContext containing execution data.
38
38
  * @param emit
39
39
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
40
+ * @param nodeData
40
41
  * @returns A Promise resolving with the task result or rejecting on error.
41
42
  * @emits {meta.deputy.executed} - Emitted with context to initiate delegation.
42
43
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
43
44
  * @note The resolution and progress are managed by ephemeral meta-tasks.
44
45
  */
45
- execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void): TaskResult;
46
+ execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
47
+ nodeId: string;
48
+ routineExecId: string;
49
+ }): TaskResult;
46
50
  }
47
51
 
48
52
  type DbOperationType$1 = "query" | "insert" | "update" | "delete";
@@ -71,9 +75,10 @@ interface OnConflictAction {
71
75
  }
72
76
  type OpEffect = "increment" | "decrement" | "set";
73
77
  type ValueOrSubOp = any | SubOperation | OpEffect;
78
+ type ValueOrList = any | any[];
74
79
  interface DbOperationPayload {
75
80
  data?: Record<string, ValueOrSubOp> | Record<string, ValueOrSubOp>[];
76
- filter?: AnyObject;
81
+ filter?: Record<string, ValueOrList>;
77
82
  fields?: string[];
78
83
  joins?: Record<string, JoinDefinition>;
79
84
  sort?: Record<string, SortDirection>;
@@ -125,12 +130,16 @@ declare class DatabaseTask extends DeputyTask {
125
130
  * @param context - The GraphContext containing execution data.
126
131
  * @param emit
127
132
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
133
+ * @param nodeData
128
134
  * @returns A Promise resolving with the task result or rejecting on error.
129
135
  * @emits {meta.deputy.executed} - Emitted with context including queryData to initiate delegation.
130
136
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
131
137
  * @note The resolution and progress are managed by ephemeral meta-tasks.
132
138
  */
133
- execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void): TaskResult;
139
+ execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
140
+ nodeId: string;
141
+ routineExecId: string;
142
+ }): TaskResult;
134
143
  }
135
144
 
136
145
  interface ServiceInstanceDescriptor {
@@ -146,6 +155,7 @@ interface ServiceInstanceDescriptor {
146
155
  health: AnyObject;
147
156
  exposed: boolean;
148
157
  clientCreated?: boolean;
158
+ isFrontend: boolean;
149
159
  }
150
160
  interface DeputyDescriptor {
151
161
  serviceName: string;
@@ -341,6 +351,8 @@ type ServerOptions = {
341
351
  port?: number;
342
352
  };
343
353
  relatedServices?: string[][];
354
+ isDatabase?: boolean;
355
+ isFrontend?: boolean;
344
356
  };
345
357
  interface DatabaseOptions {
346
358
  databaseType?: "postgres";
package/dist/index.d.ts CHANGED
@@ -37,12 +37,16 @@ declare class DeputyTask extends Task {
37
37
  * @param context - The GraphContext containing execution data.
38
38
  * @param emit
39
39
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
40
+ * @param nodeData
40
41
  * @returns A Promise resolving with the task result or rejecting on error.
41
42
  * @emits {meta.deputy.executed} - Emitted with context to initiate delegation.
42
43
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
43
44
  * @note The resolution and progress are managed by ephemeral meta-tasks.
44
45
  */
45
- execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void): TaskResult;
46
+ execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
47
+ nodeId: string;
48
+ routineExecId: string;
49
+ }): TaskResult;
46
50
  }
47
51
 
48
52
  type DbOperationType$1 = "query" | "insert" | "update" | "delete";
@@ -71,9 +75,10 @@ interface OnConflictAction {
71
75
  }
72
76
  type OpEffect = "increment" | "decrement" | "set";
73
77
  type ValueOrSubOp = any | SubOperation | OpEffect;
78
+ type ValueOrList = any | any[];
74
79
  interface DbOperationPayload {
75
80
  data?: Record<string, ValueOrSubOp> | Record<string, ValueOrSubOp>[];
76
- filter?: AnyObject;
81
+ filter?: Record<string, ValueOrList>;
77
82
  fields?: string[];
78
83
  joins?: Record<string, JoinDefinition>;
79
84
  sort?: Record<string, SortDirection>;
@@ -125,12 +130,16 @@ declare class DatabaseTask extends DeputyTask {
125
130
  * @param context - The GraphContext containing execution data.
126
131
  * @param emit
127
132
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
133
+ * @param nodeData
128
134
  * @returns A Promise resolving with the task result or rejecting on error.
129
135
  * @emits {meta.deputy.executed} - Emitted with context including queryData to initiate delegation.
130
136
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
131
137
  * @note The resolution and progress are managed by ephemeral meta-tasks.
132
138
  */
133
- execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void): TaskResult;
139
+ execute(context: GraphContext, emit: (signal: string, ctx: AnyObject) => void, progressCallback: (progress: number) => void, nodeData: {
140
+ nodeId: string;
141
+ routineExecId: string;
142
+ }): TaskResult;
134
143
  }
135
144
 
136
145
  interface ServiceInstanceDescriptor {
@@ -146,6 +155,7 @@ interface ServiceInstanceDescriptor {
146
155
  health: AnyObject;
147
156
  exposed: boolean;
148
157
  clientCreated?: boolean;
158
+ isFrontend: boolean;
149
159
  }
150
160
  interface DeputyDescriptor {
151
161
  serviceName: string;
@@ -341,6 +351,8 @@ type ServerOptions = {
341
351
  port?: number;
342
352
  };
343
353
  relatedServices?: string[][];
354
+ isDatabase?: boolean;
355
+ isFrontend?: boolean;
344
356
  };
345
357
  interface DatabaseOptions {
346
358
  databaseType?: "postgres";
package/dist/index.js CHANGED
@@ -168,16 +168,20 @@ var DeputyTask = class extends import_core.Task {
168
168
  * @param context - The GraphContext containing execution data.
169
169
  * @param emit
170
170
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
171
+ * @param nodeData
171
172
  * @returns A Promise resolving with the task result or rejecting on error.
172
173
  * @emits {meta.deputy.executed} - Emitted with context to initiate delegation.
173
174
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
174
175
  * @note The resolution and progress are managed by ephemeral meta-tasks.
175
176
  */
176
- execute(context, emit, progressCallback) {
177
+ execute(context, emit, progressCallback, nodeData) {
177
178
  const ctx = context.getContext();
178
179
  const metadata = context.getMetadata();
179
180
  const deputyContext = {
180
181
  __localTaskName: this.name,
182
+ __localTaskVersion: this.version,
183
+ __localServiceName: CadenzaService.serviceRegistry.serviceName,
184
+ __previousTaskExecutionId: nodeData.nodeId,
181
185
  __remoteRoutineName: this.remoteRoutineName,
182
186
  __serviceName: this.serviceName,
183
187
  __localRoutineExecId: metadata.__routineExecId ?? metadata.__metadata?.__routineExecId,
@@ -251,18 +255,22 @@ var DatabaseTask = class extends DeputyTask {
251
255
  * @param context - The GraphContext containing execution data.
252
256
  * @param emit
253
257
  * @param progressCallback - Callback to update progress (invoked by meta-layer).
258
+ * @param nodeData
254
259
  * @returns A Promise resolving with the task result or rejecting on error.
255
260
  * @emits {meta.deputy.executed} - Emitted with context including queryData to initiate delegation.
256
261
  * @edge Engine handles timeout and error, triggering `.doOnFail` if chained.
257
262
  * @note The resolution and progress are managed by ephemeral meta-tasks.
258
263
  */
259
- execute(context, emit, progressCallback) {
264
+ execute(context, emit, progressCallback, nodeData) {
260
265
  const ctx = context.getContext();
261
266
  const metadata = context.getMetadata();
262
267
  const dynamicQueryData = ctx.queryData ?? {};
263
268
  delete ctx.queryData;
264
269
  const deputyContext = {
265
270
  __localTaskName: this.name,
271
+ __localTaskVersion: this.version,
272
+ __localServiceName: CadenzaService.serviceRegistry.serviceName,
273
+ __previousTaskExecutionId: nodeData.nodeId,
266
274
  __remoteRoutineName: this.remoteRoutineName,
267
275
  __serviceName: this.serviceName,
268
276
  __executionTraceId: metadata.__executionTraceId ?? null,
@@ -283,6 +291,10 @@ var DatabaseTask = class extends DeputyTask {
283
291
  }
284
292
  };
285
293
 
294
+ // src/utils/environment.ts
295
+ var isNode = typeof process !== "undefined" && process.versions?.node != null;
296
+ var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
297
+
286
298
  // src/registry/ServiceRegistry.ts
287
299
  var ServiceRegistry = class _ServiceRegistry {
288
300
  constructor() {
@@ -298,7 +310,7 @@ var ServiceRegistry = class _ServiceRegistry {
298
310
  "Handle Instance Update",
299
311
  (ctx, emit) => {
300
312
  const { serviceInstance } = ctx;
301
- const { uuid: uuid4, serviceName, address, port, exposed } = serviceInstance;
313
+ const { uuid: uuid4, serviceName, address, port, exposed, isFrontend } = serviceInstance;
302
314
  if (uuid4 === this.serviceInstanceId) return;
303
315
  if (!this.instances.has(serviceName))
304
316
  this.instances.set(serviceName, []);
@@ -307,7 +319,7 @@ var ServiceRegistry = class _ServiceRegistry {
307
319
  if (existing) {
308
320
  Object.assign(existing, serviceInstance);
309
321
  } else {
310
- if (this.deputies.has(serviceName) || this.remoteSignals.has(serviceName) || this.remoteSignals.has("*") && this.serviceName !== serviceName) {
322
+ if (!isFrontend && this.deputies.has(serviceName) || this.remoteSignals.has(serviceName) || this.remoteSignals.has("*") && this.serviceName !== serviceName) {
311
323
  const clientCreated = instances?.some(
312
324
  (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
313
325
  );
@@ -498,7 +510,8 @@ var ServiceRegistry = class _ServiceRegistry {
498
510
  "is_blocked",
499
511
  "health",
500
512
  "exposed",
501
- "created"
513
+ "created",
514
+ "is_frontend"
502
515
  ]
503
516
  }).doOn("meta.sync_requested").emits("meta.service_registry.synced_instances").then(
504
517
  CadenzaService.createMetaTask(
@@ -549,6 +562,7 @@ var ServiceRegistry = class _ServiceRegistry {
549
562
  localTaskName: ctx.localTaskName,
550
563
  communicationType: ctx.communicationType
551
564
  });
565
+ emit("meta.service_registry.deputy_registered", ctx);
552
566
  }
553
567
  ).doOn("meta.deputy.created");
554
568
  this.getAllInstances = CadenzaService.createMetaTask(
@@ -595,6 +609,15 @@ var ServiceRegistry = class _ServiceRegistry {
595
609
  );
596
610
  return context;
597
611
  }
612
+ if (instances[0].isFrontend) {
613
+ for (const instance of instances) {
614
+ emit(
615
+ `meta.service_registry.selected_instance_for_socket:${instance.address}`,
616
+ context
617
+ );
618
+ }
619
+ return context;
620
+ }
598
621
  let instancesToTry = instances.filter(
599
622
  (i) => !__triedInstances?.includes(i.uuid)
600
623
  );
@@ -791,7 +814,7 @@ var ServiceRegistry = class _ServiceRegistry {
791
814
  retryCount: 5,
792
815
  retryDelay: 1e3
793
816
  }
794
- ).doOn("meta.rest.network_configured").then(
817
+ ).doOn("meta.rest.network_configured", "meta.rest.browser_detected").then(
795
818
  CadenzaService.createMetaTask(
796
819
  "Setup service",
797
820
  (ctx) => {
@@ -816,6 +839,68 @@ var ServiceRegistry = class _ServiceRegistry {
816
839
  ctx.__skipRemoteExecution = true;
817
840
  }
818
841
  console.log("service creation", ctx);
842
+ if (isBrowser) {
843
+ CadenzaService.createMetaTask("Prepare for signal sync", () => {
844
+ return {};
845
+ }).doAfter(this.fullSyncTask).then(
846
+ CadenzaService.createCadenzaDBQueryTask("signal_registry", {
847
+ fields: ["name", "service_name"],
848
+ filter: {
849
+ source_service_name: [ctx.__serviceName, "*"]
850
+ }
851
+ }).then(
852
+ CadenzaService.createMetaTask(
853
+ "Create signal transmission tasks",
854
+ (ctx2, emit) => {
855
+ const signalRegistry = ctx2.signalRegistry;
856
+ for (const signal of signalRegistry) {
857
+ emit("meta.service_registry.foreign_signal_registered", {
858
+ __emitterSignalName: signal.name,
859
+ __listenerServiceName: signal.serviceName
860
+ });
861
+ }
862
+ return true;
863
+ }
864
+ ).then(
865
+ CadenzaService.createMetaTask("Connect to services", (ctx2, emit) => {
866
+ const services = Array.from(
867
+ new Set(
868
+ ctx2.signalRegistry.map((s) => s.serviceName)
869
+ )
870
+ );
871
+ for (const service of services) {
872
+ const instances = this.instances.get(service).filter((i) => i.isActive);
873
+ for (const instance of instances) {
874
+ if (instance.clientCreated) continue;
875
+ const address = instance.address;
876
+ const port = instance.port;
877
+ const clientCreated = instances?.some(
878
+ (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
879
+ );
880
+ if (!clientCreated) {
881
+ emit("meta.service_registry.dependee_registered", {
882
+ serviceName: service,
883
+ serviceInstanceId: instance.uuid,
884
+ serviceAddress: address,
885
+ servicePort: port,
886
+ protocol: instance.exposed ? "https" : "http",
887
+ communicationTypes: ["signal"]
888
+ });
889
+ }
890
+ instance.clientCreated = true;
891
+ instances.forEach((i) => {
892
+ if (i.address === address && i.port === port) {
893
+ i.clientCreated = true;
894
+ }
895
+ });
896
+ }
897
+ }
898
+ return {};
899
+ })
900
+ )
901
+ )
902
+ );
903
+ }
819
904
  return ctx;
820
905
  },
821
906
  "Handles the request to create a service instance"
@@ -985,7 +1070,26 @@ var RestController = class _RestController {
985
1070
  [
986
1071
  CadenzaService.createMetaTask(
987
1072
  "Setup Express app security",
988
- (ctx) => {
1073
+ (ctx, emit) => {
1074
+ if (isBrowser) {
1075
+ emit("meta.rest.browser_detected", {
1076
+ data: {
1077
+ uuid: ctx.__serviceInstanceId,
1078
+ address: `browser:${ctx.__serviceInstanceId}`,
1079
+ port: 0,
1080
+ exposed: false,
1081
+ process_pid: 1,
1082
+ service_name: ctx.__serviceName,
1083
+ is_frontend: true,
1084
+ is_active: true,
1085
+ is_non_responsive: false,
1086
+ is_blocked: false,
1087
+ health: {}
1088
+ },
1089
+ ...ctx
1090
+ });
1091
+ return;
1092
+ }
989
1093
  const app = (0, import_express.default)();
990
1094
  app.use(import_body_parser.default.json());
991
1095
  switch (ctx.__securityProfile) {
@@ -1243,6 +1347,7 @@ var RestController = class _RestController {
1243
1347
  process_pid: process.pid,
1244
1348
  service_name: ctx.__serviceName,
1245
1349
  is_active: true,
1350
+ is_database: ctx.__isDatabase,
1246
1351
  is_non_responsive: false,
1247
1352
  is_blocked: false,
1248
1353
  health: {}
@@ -1522,12 +1627,33 @@ var SocketController = class _SocketController {
1522
1627
  server.on("connection", (ws) => {
1523
1628
  console.log("SocketServer: New connection");
1524
1629
  try {
1630
+ ws.emit("handshake", {
1631
+ serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
1632
+ serviceName: CadenzaService.serviceRegistry.serviceName,
1633
+ __status: "success"
1634
+ });
1525
1635
  ws.on("handshake", (ctx2) => {
1526
1636
  console.log("Socket HANDSHAKE", ctx2.serviceInstanceId);
1527
- ws.emit("handshake", {
1528
- serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
1529
- __status: "success"
1530
- });
1637
+ if (ctx2.isFrontend) {
1638
+ const fetchId = `browser:${ctx2.serviceInstanceId}`;
1639
+ CadenzaService.createMetaTask(
1640
+ `Transmit signal to ${fetchId}`,
1641
+ (ctx3, emit) => {
1642
+ if (ctx3.__signalName === void 0) {
1643
+ return;
1644
+ }
1645
+ ws.emit("signal", ctx3);
1646
+ if (ctx3.__routineExecId) {
1647
+ emit(
1648
+ `meta.socket_client.transmitted:${ctx3.__routineExecId}`,
1649
+ {}
1650
+ );
1651
+ }
1652
+ }
1653
+ ).doOn(
1654
+ `meta.service_registry.selected_instance_for_socket:${fetchId}`
1655
+ );
1656
+ }
1531
1657
  CadenzaService.broker.emit("meta.socket.handshake", ctx2);
1532
1658
  });
1533
1659
  ws.on(
@@ -1647,6 +1773,12 @@ var SocketController = class _SocketController {
1647
1773
  });
1648
1774
  socket.on("handshake", (ctx2) => {
1649
1775
  console.log("Socket client HANDSHAKE", ctx2);
1776
+ socket.emit("handshake", {
1777
+ serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
1778
+ serviceName: CadenzaService.serviceRegistry.serviceName,
1779
+ isFrontend: isBrowser,
1780
+ __status: "success"
1781
+ });
1650
1782
  CadenzaService.broker.emit("meta.socket_client.handshake", ctx2);
1651
1783
  });
1652
1784
  socket.on("delegation_progress", (ctx2) => {
@@ -1655,6 +1787,11 @@ var SocketController = class _SocketController {
1655
1787
  ctx2
1656
1788
  );
1657
1789
  });
1790
+ socket.on("signal", (ctx2) => {
1791
+ if (CadenzaService.broker.listObservedSignals().includes(ctx2.__signalName)) {
1792
+ CadenzaService.broker.emit(ctx2.__signalName, ctx2);
1793
+ }
1794
+ });
1658
1795
  socket.on("status_update", (status) => {
1659
1796
  CadenzaService.broker.emit("meta.socket_client.status_received", status);
1660
1797
  });
@@ -1770,6 +1907,45 @@ var SocketController = class _SocketController {
1770
1907
  }
1771
1908
  };
1772
1909
 
1910
+ // src/utils/tools.ts
1911
+ function formatTimestamp(timestamp) {
1912
+ return new Date(timestamp).toISOString();
1913
+ }
1914
+ function decomposeSignalName(signalName) {
1915
+ const parts = signalName.split(".");
1916
+ const firstChar = signalName.charAt(0);
1917
+ let isMeta = false;
1918
+ let sourceServiceName = null;
1919
+ let domain = parts.length === 2 ? parts[0] : "";
1920
+ if (parts[0] === "meta") {
1921
+ isMeta = true;
1922
+ if (parts.length === 3) {
1923
+ domain = parts[1];
1924
+ } else {
1925
+ domain = "";
1926
+ }
1927
+ } else if (firstChar === "*" || firstChar === firstChar.toUpperCase()) {
1928
+ sourceServiceName = parts[0];
1929
+ if (parts[1] === "meta") {
1930
+ isMeta = true;
1931
+ if (parts.length === 4) {
1932
+ domain = parts[2];
1933
+ } else {
1934
+ domain = "";
1935
+ }
1936
+ } else {
1937
+ domain = parts[1];
1938
+ }
1939
+ }
1940
+ const action = parts[parts.length - 1];
1941
+ return {
1942
+ isMeta,
1943
+ sourceServiceName,
1944
+ domain,
1945
+ action
1946
+ };
1947
+ }
1948
+
1773
1949
  // src/signals/SignalController.ts
1774
1950
  var SignalController = class _SignalController {
1775
1951
  static get instance() {
@@ -1781,15 +1957,14 @@ var SignalController = class _SignalController {
1781
1957
  "Handle Signal Registration",
1782
1958
  (ctx, emit) => {
1783
1959
  const { __signalName } = ctx;
1784
- const parts = __signalName.split(".");
1785
- const domain = parts[0] === "meta" ? parts[1] : parts[0];
1786
- const action = parts[parts.length - 1];
1960
+ const { isMeta, sourceServiceName, domain, action } = decomposeSignalName(__signalName);
1787
1961
  emit("meta.signal_controller.signal_added", {
1788
1962
  data: {
1789
1963
  name: __signalName,
1964
+ sourceServiceName,
1790
1965
  domain,
1791
1966
  action,
1792
- is_meta: parts[0] === "meta",
1967
+ isMeta,
1793
1968
  service_name: CadenzaService.serviceRegistry.serviceName
1794
1969
  }
1795
1970
  });
@@ -1852,7 +2027,10 @@ var SignalController = class _SignalController {
1852
2027
  __listenerServiceName
1853
2028
  ).doOn(__emitterSignalName.split(".").slice(1).join("."));
1854
2029
  return true;
1855
- }).doOn("meta.signal_controller.foreign_signal_registered");
2030
+ }).doOn(
2031
+ "meta.signal_controller.foreign_signal_registered",
2032
+ "meta.service_registry.foreign_signal_registered"
2033
+ );
1856
2034
  CadenzaService.createMetaTask(
1857
2035
  "Add data to signal emission",
1858
2036
  (ctx) => {
@@ -1932,6 +2110,24 @@ var GraphMetadataController = class _GraphMetadataController {
1932
2110
  }
1933
2111
  };
1934
2112
  }).doOn("meta.task.relationship_added").emits("meta.graph_metadata.task_relationship_created");
2113
+ CadenzaService.createMetaTask(
2114
+ "Handle task deputy relationship creation",
2115
+ (ctx) => {
2116
+ if (ctx.signalName) return;
2117
+ return {
2118
+ data: {
2119
+ triggered_task_name: ctx.remoteRoutineName,
2120
+ triggered_task_version: 1,
2121
+ // TODO
2122
+ triggered_service_name: ctx.serviceName,
2123
+ deputy_task_name: ctx.localTaskName,
2124
+ deputy_task_version: 1,
2125
+ // TODO
2126
+ deputy_service_name: CadenzaService.serviceRegistry.serviceName
2127
+ }
2128
+ };
2129
+ }
2130
+ ).doOn("meta.service_registry.deputy_registered").emits("meta.graph_metadata.deputy_relationship_created");
1935
2131
  CadenzaService.createMetaTask("Handle task signal observation", (ctx) => {
1936
2132
  const firstChar = ctx.data.signalName.charAt(0);
1937
2133
  let _signal = ctx.data.signalName;
@@ -2166,7 +2362,8 @@ var GraphMetadataController = class _GraphMetadataController {
2166
2362
  (ctx) => {
2167
2363
  return {
2168
2364
  data: {
2169
- executionCount: "increment"
2365
+ execution_count: "increment",
2366
+ last_executed: formatTimestamp(Date.now())
2170
2367
  },
2171
2368
  filter: {
2172
2369
  ...ctx.filter,
@@ -2177,6 +2374,37 @@ var GraphMetadataController = class _GraphMetadataController {
2177
2374
  "Handles task execution relationship creation",
2178
2375
  { concurrency: 100, isSubMeta: true }
2179
2376
  ).doOn("meta.node.mapped").emits("meta.graph_metadata.relationship_executed");
2377
+ CadenzaService.createMetaTask(
2378
+ "Handle explicit task execution relationship creation",
2379
+ (ctx) => {
2380
+ return {
2381
+ data: {
2382
+ deputy_task_execution_id: ctx.data.previousTaskExecutionId,
2383
+ task_execution_id: ctx.data.taskExecutionId
2384
+ }
2385
+ };
2386
+ }
2387
+ ).doOn("meta.node.detected_previous_task_execution").emits("meta.graph_metadata.explicit_relationship_created");
2388
+ CadenzaService.createMetaTask(
2389
+ "Handle explicit task execution relationship execution",
2390
+ (ctx) => {
2391
+ if (!ctx.__localTaskName) return;
2392
+ return {
2393
+ data: {
2394
+ execution_count: "increment",
2395
+ last_executed: formatTimestamp(Date.now())
2396
+ },
2397
+ filter: {
2398
+ deputy_task_name: ctx.__localTaskName,
2399
+ deputy_task_version: ctx.__localTaskVersion,
2400
+ deputy_service_name: ctx.__localServiceName,
2401
+ triggered_task_name: ctx.filter.taskName,
2402
+ triggered_task_version: ctx.filter.taskVersion,
2403
+ triggered_service_name: CadenzaService.serviceRegistry.serviceName
2404
+ }
2405
+ };
2406
+ }
2407
+ ).doOn("meta.node.detected_previous_task_execution").emits("meta.graph_metadata.explicit_relationship_executed");
2180
2408
  }
2181
2409
  };
2182
2410
 
@@ -3043,8 +3271,18 @@ var DatabaseController = class _DatabaseController {
3043
3271
  const conditions = [];
3044
3272
  for (const [key, value] of Object.entries(filter)) {
3045
3273
  if (value !== void 0) {
3046
- conditions.push(`${(0, import_lodash_es.snakeCase)(key)} = $${params.length + 1}`);
3047
- params.push(value);
3274
+ if (Array.isArray(value)) {
3275
+ conditions.push(
3276
+ `${(0, import_lodash_es.snakeCase)(key)} IN (${value.map((v) => {
3277
+ const val = `$${params.length + 1}`;
3278
+ params.push(v);
3279
+ return val;
3280
+ }).join(", ")})`
3281
+ );
3282
+ } else {
3283
+ conditions.push(`${(0, import_lodash_es.snakeCase)(key)} = $${params.length + 1}`);
3284
+ params.push(value);
3285
+ }
3048
3286
  }
3049
3287
  }
3050
3288
  return conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
@@ -3244,15 +3482,14 @@ var GraphSyncController = class _GraphSyncController {
3244
3482
  (signal) => !signal.data.registered
3245
3483
  ).map((signal) => signal.signal);
3246
3484
  for (const signal of filteredSignals) {
3247
- const parts = signal.split(".");
3248
- const domain = parts[0] === "meta" ? parts[1] : parts[0];
3249
- const action = parts[parts.length - 1];
3485
+ const { isMeta, sourceServiceName, domain, action } = decomposeSignalName(signal);
3250
3486
  emit("meta.sync_controller.signal_added", {
3251
3487
  data: {
3252
3488
  name: signal,
3489
+ sourceServiceName,
3253
3490
  domain,
3254
3491
  action,
3255
- isMeta: parts[0] === "meta",
3492
+ isMeta,
3256
3493
  serviceName: CadenzaService.serviceRegistry.serviceName
3257
3494
  }
3258
3495
  });
@@ -3341,7 +3578,6 @@ var GraphSyncController = class _GraphSyncController {
3341
3578
  }
3342
3579
  for (const task of __tasks) {
3343
3580
  if (task.hidden || !task.register) continue;
3344
- if (task.registered) continue;
3345
3581
  task.mapNext(
3346
3582
  (t) => emit("meta.sync_controller.task_map", {
3347
3583
  data: {
@@ -3353,6 +3589,18 @@ var GraphSyncController = class _GraphSyncController {
3353
3589
  }
3354
3590
  })
3355
3591
  );
3592
+ if (task.isDeputy && !task.signalName) {
3593
+ emit("meta.sync_controller.deputy_relationship_created", {
3594
+ data: {
3595
+ triggered_task_name: task.remoteRoutineName,
3596
+ triggered_task_version: 1,
3597
+ triggered_service_name: task.serviceName,
3598
+ deputy_task_name: task.name,
3599
+ deputy_task_version: task.version,
3600
+ deputy_service_name: CadenzaService.serviceRegistry.serviceName
3601
+ }
3602
+ });
3603
+ }
3356
3604
  }
3357
3605
  return true;
3358
3606
  }).doAfter(CadenzaService.registry.getAllTasks);
@@ -3653,6 +3901,7 @@ var CadenzaService = class {
3653
3901
  relatedServices: process.env.RELATED_SERVICES ? process.env.RELATED_SERVICES.split("|").map(
3654
3902
  (s) => s.trim().split(",")
3655
3903
  ) : [],
3904
+ isFrontend: isBrowser,
3656
3905
  ...options
3657
3906
  };
3658
3907
  if (options.cadenzaDB?.connect) {
@@ -3706,7 +3955,8 @@ var CadenzaService = class {
3706
3955
  __securityProfile: options.securityProfile,
3707
3956
  __networkMode: options.networkMode,
3708
3957
  __retryCount: options.retryCount,
3709
- __cadenzaDBConnect: options.cadenzaDB?.connect
3958
+ __cadenzaDBConnect: options.cadenzaDB?.connect,
3959
+ __isDatabase: options.isDatabase
3710
3960
  };
3711
3961
  if (options.cadenzaDB?.connect) {
3712
3962
  import_core3.default.createEphemeralMetaTask("Create service", async (_, emit) => {
@@ -3735,6 +3985,12 @@ var CadenzaService = class {
3735
3985
  this.createCadenzaService(serviceName, description, options);
3736
3986
  }
3737
3987
  static createDatabaseService(name, schema, description = "", options = {}) {
3988
+ if (isBrowser) {
3989
+ console.warn(
3990
+ "Database service creation is not supported in the browser. Use the CadenzaDB service instead."
3991
+ );
3992
+ return;
3993
+ }
3738
3994
  if (this.serviceCreated) return;
3739
3995
  this.bootstrap();
3740
3996
  this.serviceRegistry.serviceName = name;
@@ -3756,6 +4012,7 @@ var CadenzaService = class {
3756
4012
  databaseType: "postgres",
3757
4013
  databaseName: (0, import_lodash_es2.snakeCase)(name),
3758
4014
  poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
4015
+ isDatabase: true,
3759
4016
  ...options
3760
4017
  };
3761
4018
  import_core3.default.broker.emit("meta.database_init_requested", {
@@ -3767,6 +4024,27 @@ var CadenzaService = class {
3767
4024
  console.log("Database service created");
3768
4025
  this.createCadenzaService(name, description, options);
3769
4026
  }).doOn("meta.database.setup_done");
4027
+ if (options.cadenzaDB?.connect) {
4028
+ import_core3.default.createEphemeralMetaTask("Insert database service", (_, emit) => {
4029
+ emit("meta.created_database_service", {
4030
+ data: {
4031
+ service_name: name,
4032
+ description,
4033
+ schema,
4034
+ is_meta: options.isMeta
4035
+ }
4036
+ });
4037
+ }).doOn("meta.service_registry.service_inserted");
4038
+ } else {
4039
+ import_core3.default.broker.emit("meta.created_database_service", {
4040
+ data: {
4041
+ service_name: name,
4042
+ description,
4043
+ schema,
4044
+ is_meta: options.isMeta
4045
+ }
4046
+ });
4047
+ }
3770
4048
  }
3771
4049
  static createMetaDatabaseService(name, schema, description = "", options = {}) {
3772
4050
  this.bootstrap();