@workglow/util 0.2.5 → 0.2.7

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/node.js CHANGED
@@ -844,22 +844,28 @@ class WorkerManager {
844
844
  workerFunctions = new Map;
845
845
  workerStreamFunctions = new Map;
846
846
  workerReactiveFunctions = new Map;
847
- lazyFactories = new Map;
847
+ workerFactories = new Map;
848
+ idleTimeouts = new Map;
848
849
  lazyInitPromises = new Map;
849
- registerWorker(name, workerOrFactory) {
850
+ activeCallCounts = new Map;
851
+ idleTimers = new Map;
852
+ terminationPromises = new Map;
853
+ registerWorker(name, workerOrFactory, options) {
850
854
  if (this.workers.has(name)) {
851
855
  throw new Error(`Worker ${name} is already registered.`);
852
856
  }
853
- if (this.lazyFactories.has(name)) {
857
+ if (this.workerFactories.has(name)) {
854
858
  throw new Error(`Worker ${name} is already registered.`);
855
859
  }
860
+ this.idleTimeouts.set(name, options?.idleTimeoutMs ?? 0);
856
861
  if (typeof workerOrFactory === "function") {
857
- this.lazyFactories.set(name, workerOrFactory);
862
+ this.workerFactories.set(name, workerOrFactory);
858
863
  } else {
859
864
  this.attachWorkerInstance(name, workerOrFactory);
860
865
  }
861
866
  }
862
867
  attachWorkerInstance(name, worker) {
868
+ this.clearIdleTimer(name);
863
869
  this.workers.set(name, worker);
864
870
  worker.addEventListener("error", (event) => {
865
871
  console.error("Worker Error:", event.message, "at", event.filename, "line:", event.lineno);
@@ -896,23 +902,27 @@ class WorkerManager {
896
902
  this.readyWorkers.set(name, readyPromise);
897
903
  }
898
904
  async ensureWorkerReady(name) {
905
+ await this.terminationPromises.get(name);
899
906
  if (this.workers.has(name)) {
900
907
  await this.readyWorkers.get(name);
901
908
  return;
902
909
  }
903
- const factory = this.lazyFactories.get(name);
910
+ const factory = this.workerFactories.get(name);
904
911
  if (!factory) {
905
912
  throw new Error(`Worker ${name} not found.`);
906
913
  }
907
914
  let init = this.lazyInitPromises.get(name);
908
915
  if (!init) {
909
916
  init = (async () => {
917
+ let worker;
910
918
  try {
911
- const f = this.lazyFactories.get(name);
912
- this.lazyFactories.delete(name);
913
- const worker = f();
919
+ const f = this.workerFactories.get(name);
920
+ worker = f();
914
921
  this.attachWorkerInstance(name, worker);
915
922
  await this.readyWorkers.get(name);
923
+ } catch (error) {
924
+ await this.cleanupFailedInitialization(name, worker);
925
+ throw error;
916
926
  } finally {
917
927
  this.lazyInitPromises.delete(name);
918
928
  }
@@ -927,105 +937,187 @@ class WorkerManager {
927
937
  throw new Error(`Worker ${name} not found.`);
928
938
  return worker;
929
939
  }
940
+ beginWorkerActivity(name) {
941
+ this.clearIdleTimer(name);
942
+ this.activeCallCounts.set(name, (this.activeCallCounts.get(name) ?? 0) + 1);
943
+ }
944
+ endWorkerActivity(name) {
945
+ const nextCount = (this.activeCallCounts.get(name) ?? 0) - 1;
946
+ if (nextCount > 0) {
947
+ this.activeCallCounts.set(name, nextCount);
948
+ return;
949
+ }
950
+ this.activeCallCounts.delete(name);
951
+ this.scheduleIdleTermination(name);
952
+ }
953
+ clearIdleTimer(name) {
954
+ const timer = this.idleTimers.get(name);
955
+ if (timer !== undefined) {
956
+ clearTimeout(timer);
957
+ this.idleTimers.delete(name);
958
+ }
959
+ }
960
+ scheduleIdleTermination(name) {
961
+ this.clearIdleTimer(name);
962
+ const idleTimeoutMs = this.idleTimeouts.get(name) ?? 0;
963
+ if (idleTimeoutMs <= 0 || !this.workerFactories.has(name) || !this.workers.has(name)) {
964
+ return;
965
+ }
966
+ const timer = setTimeout(() => {
967
+ this.idleTimers.delete(name);
968
+ if ((this.activeCallCounts.get(name) ?? 0) > 0 || !this.workers.has(name)) {
969
+ return;
970
+ }
971
+ this.terminateWorkerInstance(name).catch((error) => {
972
+ getLogger().warn(`Worker ${name} idle termination failed.`, { error });
973
+ });
974
+ }, idleTimeoutMs);
975
+ this.idleTimers.set(name, timer);
976
+ }
977
+ async cleanupFailedInitialization(name, worker) {
978
+ this.clearIdleTimer(name);
979
+ if (worker !== undefined && this.workers.get(name) === worker) {
980
+ this.workers.delete(name);
981
+ }
982
+ this.readyWorkers.delete(name);
983
+ this.workerFunctions.delete(name);
984
+ this.workerStreamFunctions.delete(name);
985
+ this.workerReactiveFunctions.delete(name);
986
+ this.activeCallCounts.delete(name);
987
+ if (worker && "terminate" in worker && typeof worker.terminate === "function") {
988
+ try {
989
+ await worker.terminate();
990
+ } catch {}
991
+ }
992
+ }
993
+ async terminateWorkerInstance(name) {
994
+ const existing = this.terminationPromises.get(name);
995
+ if (existing) {
996
+ await existing;
997
+ return;
998
+ }
999
+ const termination = (async () => {
1000
+ this.clearIdleTimer(name);
1001
+ const worker = this.workers.get(name);
1002
+ this.workers.delete(name);
1003
+ this.readyWorkers.delete(name);
1004
+ this.workerFunctions.delete(name);
1005
+ this.workerStreamFunctions.delete(name);
1006
+ this.workerReactiveFunctions.delete(name);
1007
+ this.activeCallCounts.delete(name);
1008
+ try {
1009
+ if (worker && "terminate" in worker && typeof worker.terminate === "function") {
1010
+ await worker.terminate();
1011
+ }
1012
+ } catch {}
1013
+ })();
1014
+ this.terminationPromises.set(name, termination);
1015
+ try {
1016
+ await termination;
1017
+ } finally {
1018
+ this.terminationPromises.delete(name);
1019
+ }
1020
+ }
930
1021
  async callWorkerFunction(workerName, functionName, args, options) {
931
1022
  await this.ensureWorkerReady(workerName);
932
1023
  const worker = this.workers.get(workerName);
933
1024
  if (!worker)
934
1025
  throw new Error(`Worker ${workerName} not found.`);
935
- const knownFunctions = this.workerFunctions.get(workerName);
936
- if (knownFunctions && !knownFunctions.has(functionName)) {
937
- throw new Error(`Function "${functionName}" is not registered on worker "${workerName}".`);
938
- }
939
- return new Promise((resolve, reject) => {
940
- const requestId = crypto.randomUUID();
941
- const handleMessage = (event) => {
942
- const { id, type, data } = event.data;
943
- if (id !== requestId)
944
- return;
945
- if (type === "progress" && options?.onProgress) {
946
- options.onProgress(data.progress, data.message, data.details);
947
- getLogger().debug(`Worker ${workerName} function ${functionName} progress: ${data.progress}, `, { data });
948
- } else if (type === "complete") {
949
- cleanup();
950
- getLogger().debug(`Worker ${workerName} function ${functionName} complete.`, { data });
951
- resolve(data);
952
- } else if (type === "error") {
953
- cleanup();
954
- getLogger().debug(`Worker ${workerName} function ${functionName} error.`, { data });
955
- const err = typeof data === "object" && data !== null ? Object.assign(new Error(data.message ?? String(data)), {
956
- name: data.name ?? "Error"
957
- }) : new Error(String(data));
958
- reject(err);
959
- }
960
- };
961
- const handleAbort = () => {
962
- worker.postMessage({ id: requestId, type: "abort" });
963
- getLogger().info(`Worker ${workerName} function ${functionName} aborted.`);
964
- };
965
- const cleanup = () => {
966
- worker.removeEventListener("message", handleMessage);
967
- options?.signal?.removeEventListener("abort", handleAbort);
968
- };
969
- worker.addEventListener("message", handleMessage);
970
- if (options?.signal) {
971
- options.signal.addEventListener("abort", handleAbort, { once: true });
1026
+ this.beginWorkerActivity(workerName);
1027
+ try {
1028
+ const knownFunctions = this.workerFunctions.get(workerName);
1029
+ if (knownFunctions && !knownFunctions.has(functionName)) {
1030
+ throw new Error(`Function "${functionName}" is not registered on worker "${workerName}".`);
972
1031
  }
973
- const message = { id: requestId, type: "call", functionName, args };
974
- worker.postMessage(message);
975
- getLogger().info(`Worker ${workerName} function ${functionName} called.`);
976
- });
1032
+ return await new Promise((resolve, reject) => {
1033
+ const requestId = crypto.randomUUID();
1034
+ const handleMessage = (event) => {
1035
+ const { id, type, data } = event.data;
1036
+ if (id !== requestId)
1037
+ return;
1038
+ if (type === "progress" && options?.onProgress) {
1039
+ options.onProgress(data.progress, data.message, data.details);
1040
+ getLogger().debug(`Worker ${workerName} function ${functionName} progress: ${data.progress}, `, { data });
1041
+ } else if (type === "complete") {
1042
+ cleanup();
1043
+ getLogger().debug(`Worker ${workerName} function ${functionName} complete.`, { data });
1044
+ resolve(data);
1045
+ } else if (type === "error") {
1046
+ cleanup();
1047
+ getLogger().debug(`Worker ${workerName} function ${functionName} error.`, { data });
1048
+ const err = typeof data === "object" && data !== null ? Object.assign(new Error(data.message ?? String(data)), {
1049
+ name: data.name ?? "Error"
1050
+ }) : new Error(String(data));
1051
+ reject(err);
1052
+ }
1053
+ };
1054
+ const handleAbort = () => {
1055
+ worker.postMessage({ id: requestId, type: "abort" });
1056
+ getLogger().info(`Worker ${workerName} function ${functionName} aborted.`);
1057
+ };
1058
+ const cleanup = () => {
1059
+ worker.removeEventListener("message", handleMessage);
1060
+ options?.signal?.removeEventListener("abort", handleAbort);
1061
+ };
1062
+ worker.addEventListener("message", handleMessage);
1063
+ if (options?.signal) {
1064
+ options.signal.addEventListener("abort", handleAbort, { once: true });
1065
+ }
1066
+ const message = { id: requestId, type: "call", functionName, args };
1067
+ worker.postMessage(message);
1068
+ getLogger().info(`Worker ${workerName} function ${functionName} called.`);
1069
+ });
1070
+ } finally {
1071
+ this.endWorkerActivity(workerName);
1072
+ }
977
1073
  }
978
1074
  async callWorkerReactiveFunction(workerName, functionName, args) {
979
1075
  await this.ensureWorkerReady(workerName);
980
1076
  const worker = this.workers.get(workerName);
981
1077
  if (!worker)
982
1078
  return;
983
- const knownReactive = this.workerReactiveFunctions.get(workerName);
984
- if (knownReactive && !knownReactive.has(functionName))
985
- return;
986
- return new Promise((resolve) => {
987
- const requestId = crypto.randomUUID();
988
- const handleMessage = (event) => {
989
- const { id, type, data } = event.data;
990
- if (id !== requestId)
991
- return;
992
- if (type === "complete") {
993
- cleanup();
994
- resolve(data);
995
- } else if (type === "error") {
996
- cleanup();
997
- getLogger().warn(`Worker ${workerName} reactive function ${functionName} error:`, {
998
- error: data
999
- });
1000
- resolve(undefined);
1001
- }
1002
- };
1003
- const cleanup = () => {
1004
- worker.removeEventListener("message", handleMessage);
1005
- };
1006
- worker.addEventListener("message", handleMessage);
1007
- const message = { id: requestId, type: "call", functionName, args, reactive: true };
1008
- worker.postMessage(message);
1009
- getLogger().info(`Worker ${workerName} reactive function ${functionName} called.`);
1010
- });
1079
+ this.beginWorkerActivity(workerName);
1080
+ try {
1081
+ const knownReactive = this.workerReactiveFunctions.get(workerName);
1082
+ if (knownReactive && !knownReactive.has(functionName))
1083
+ return;
1084
+ return await new Promise((resolve) => {
1085
+ const requestId = crypto.randomUUID();
1086
+ const handleMessage = (event) => {
1087
+ const { id, type, data } = event.data;
1088
+ if (id !== requestId)
1089
+ return;
1090
+ if (type === "complete") {
1091
+ cleanup();
1092
+ resolve(data);
1093
+ } else if (type === "error") {
1094
+ cleanup();
1095
+ getLogger().warn(`Worker ${workerName} reactive function ${functionName} error:`, {
1096
+ error: data
1097
+ });
1098
+ resolve(undefined);
1099
+ }
1100
+ };
1101
+ const cleanup = () => {
1102
+ worker.removeEventListener("message", handleMessage);
1103
+ };
1104
+ worker.addEventListener("message", handleMessage);
1105
+ const message = { id: requestId, type: "call", functionName, args, reactive: true };
1106
+ worker.postMessage(message);
1107
+ getLogger().info(`Worker ${workerName} reactive function ${functionName} called.`);
1108
+ });
1109
+ } finally {
1110
+ this.endWorkerActivity(workerName);
1111
+ }
1011
1112
  }
1012
1113
  async terminateWorker(name) {
1013
- const worker = this.workers.get(name);
1014
- this.workers.delete(name);
1015
- this.readyWorkers.delete(name);
1016
- this.workerFunctions.delete(name);
1017
- this.workerStreamFunctions.delete(name);
1018
- this.workerReactiveFunctions.delete(name);
1019
- this.lazyFactories.delete(name);
1114
+ await this.terminateWorkerInstance(name);
1115
+ this.workerFactories.delete(name);
1116
+ this.idleTimeouts.delete(name);
1020
1117
  this.lazyInitPromises.delete(name);
1021
- try {
1022
- if (worker && "terminate" in worker && typeof worker.terminate === "function") {
1023
- await worker.terminate();
1024
- }
1025
- } catch {}
1026
1118
  }
1027
1119
  async dispose() {
1028
- const names = [...this.workers.keys(), ...this.lazyFactories.keys()];
1120
+ const names = [...new Set([...this.workers.keys(), ...this.workerFactories.keys()])];
1029
1121
  for (const name of names) {
1030
1122
  await this.terminateWorker(name);
1031
1123
  }
@@ -1038,80 +1130,85 @@ class WorkerManager {
1038
1130
  const worker = this.workers.get(workerName);
1039
1131
  if (!worker)
1040
1132
  throw new Error(`Worker ${workerName} not found.`);
1041
- const knownStream = this.workerStreamFunctions.get(workerName);
1042
- const knownFns = this.workerFunctions.get(workerName);
1043
- if (knownStream && knownFns && !knownStream.has(functionName) && !knownFns.has(functionName)) {
1044
- throw new Error(`Function "${functionName}" is not registered on worker "${workerName}".`);
1045
- }
1046
- const requestId = crypto.randomUUID();
1047
- const queue = [];
1048
- let waiting = null;
1049
- const notify = () => {
1050
- if (waiting) {
1051
- const resolve = waiting;
1052
- waiting = null;
1053
- resolve();
1054
- }
1055
- };
1056
- const handleMessage = (event) => {
1057
- const { id, type, data } = event.data;
1058
- if (id !== requestId)
1059
- return;
1060
- if (type === "stream_chunk") {
1061
- queue.push({ kind: "event", data });
1062
- notify();
1063
- } else if (type === "complete") {
1064
- queue.push({ kind: "done" });
1065
- notify();
1066
- } else if (type === "error") {
1067
- queue.push({ kind: "error", error: new Error(data) });
1068
- notify();
1133
+ this.beginWorkerActivity(workerName);
1134
+ try {
1135
+ const knownStream = this.workerStreamFunctions.get(workerName);
1136
+ const knownFns = this.workerFunctions.get(workerName);
1137
+ if (knownStream && knownFns && !knownStream.has(functionName) && !knownFns.has(functionName)) {
1138
+ throw new Error(`Function "${functionName}" is not registered on worker "${workerName}".`);
1069
1139
  }
1070
- };
1071
- const handleAbort = () => {
1072
- worker.postMessage({ id: requestId, type: "abort" });
1073
- getLogger().info(`Worker ${workerName} stream function ${functionName} aborted.`);
1074
- };
1075
- const cleanup = () => {
1076
- worker.removeEventListener("message", handleMessage);
1077
- options?.signal?.removeEventListener("abort", handleAbort);
1078
- };
1079
- worker.addEventListener("message", handleMessage);
1080
- if (options?.signal) {
1081
- if (options.signal.aborted) {
1082
- cleanup();
1083
- throw new Error("Operation aborted");
1140
+ const requestId = crypto.randomUUID();
1141
+ const queue = [];
1142
+ let waiting = null;
1143
+ const notify = () => {
1144
+ if (waiting) {
1145
+ const resolve = waiting;
1146
+ waiting = null;
1147
+ resolve();
1148
+ }
1149
+ };
1150
+ const handleMessage = (event) => {
1151
+ const { id, type, data } = event.data;
1152
+ if (id !== requestId)
1153
+ return;
1154
+ if (type === "stream_chunk") {
1155
+ queue.push({ kind: "event", data });
1156
+ notify();
1157
+ } else if (type === "complete") {
1158
+ queue.push({ kind: "done" });
1159
+ notify();
1160
+ } else if (type === "error") {
1161
+ queue.push({ kind: "error", error: new Error(data) });
1162
+ notify();
1163
+ }
1164
+ };
1165
+ const handleAbort = () => {
1166
+ worker.postMessage({ id: requestId, type: "abort" });
1167
+ getLogger().info(`Worker ${workerName} stream function ${functionName} aborted.`);
1168
+ };
1169
+ const cleanup = () => {
1170
+ worker.removeEventListener("message", handleMessage);
1171
+ options?.signal?.removeEventListener("abort", handleAbort);
1172
+ };
1173
+ worker.addEventListener("message", handleMessage);
1174
+ if (options?.signal) {
1175
+ if (options.signal.aborted) {
1176
+ cleanup();
1177
+ throw new Error("Operation aborted");
1178
+ }
1179
+ options.signal.addEventListener("abort", handleAbort, { once: true });
1084
1180
  }
1085
- options.signal.addEventListener("abort", handleAbort, { once: true });
1086
- }
1087
- const message = { id: requestId, type: "call", functionName, args, stream: true };
1088
- worker.postMessage(message);
1089
- getLogger().info(`Worker ${workerName} stream function ${functionName} called.`);
1090
- let completedNormally = false;
1091
- try {
1092
- while (true) {
1093
- while (queue.length > 0) {
1094
- const item = queue.shift();
1095
- if (item.kind === "event") {
1096
- yield item.data;
1097
- } else if (item.kind === "done") {
1098
- completedNormally = true;
1099
- return;
1100
- } else if (item.kind === "error") {
1101
- completedNormally = true;
1102
- throw item.error;
1181
+ const message = { id: requestId, type: "call", functionName, args, stream: true };
1182
+ worker.postMessage(message);
1183
+ getLogger().info(`Worker ${workerName} stream function ${functionName} called.`);
1184
+ let completedNormally = false;
1185
+ try {
1186
+ while (true) {
1187
+ while (queue.length > 0) {
1188
+ const item = queue.shift();
1189
+ if (item.kind === "event") {
1190
+ yield item.data;
1191
+ } else if (item.kind === "done") {
1192
+ completedNormally = true;
1193
+ return;
1194
+ } else if (item.kind === "error") {
1195
+ completedNormally = true;
1196
+ throw item.error;
1197
+ }
1103
1198
  }
1199
+ await new Promise((resolve) => {
1200
+ waiting = resolve;
1201
+ });
1104
1202
  }
1105
- await new Promise((resolve) => {
1106
- waiting = resolve;
1107
- });
1203
+ } finally {
1204
+ if (!completedNormally) {
1205
+ worker.postMessage({ id: requestId, type: "abort" });
1206
+ getLogger().info(`Worker ${workerName} stream function ${functionName} aborted.`);
1207
+ }
1208
+ cleanup();
1108
1209
  }
1109
1210
  } finally {
1110
- if (!completedNormally) {
1111
- worker.postMessage({ id: requestId, type: "abort" });
1112
- getLogger().info(`Worker ${workerName} stream function ${functionName} aborted.`);
1113
- }
1114
- cleanup();
1211
+ this.endWorkerActivity(workerName);
1115
1212
  }
1116
1213
  }
1117
1214
  }
@@ -1647,6 +1744,39 @@ function getTelemetryProvider() {
1647
1744
  function setTelemetryProvider(provider) {
1648
1745
  globalServiceRegistry.registerInstance(TELEMETRY_PROVIDER, provider);
1649
1746
  }
1747
+ // src/resource/ResourceScope.ts
1748
+ class ResourceScope {
1749
+ disposers = new Map;
1750
+ register(key, disposer) {
1751
+ if (!this.disposers.has(key)) {
1752
+ this.disposers.set(key, disposer);
1753
+ }
1754
+ }
1755
+ async dispose(key) {
1756
+ const disposer = this.disposers.get(key);
1757
+ if (disposer) {
1758
+ this.disposers.delete(key);
1759
+ await disposer();
1760
+ }
1761
+ }
1762
+ async disposeAll() {
1763
+ const fns = [...this.disposers.values()];
1764
+ this.disposers.clear();
1765
+ await Promise.allSettled(fns.map((fn) => fn()));
1766
+ }
1767
+ has(key) {
1768
+ return this.disposers.has(key);
1769
+ }
1770
+ keys() {
1771
+ return this.disposers.keys();
1772
+ }
1773
+ get size() {
1774
+ return this.disposers.size;
1775
+ }
1776
+ async[Symbol.asyncDispose]() {
1777
+ await this.disposeAll();
1778
+ }
1779
+ }
1650
1780
  // src/worker/Worker.node.ts
1651
1781
  import { Worker as NodeWorker, isMainThread, parentPort } from "worker_threads";
1652
1782
  import { pathToFileURL } from "url";
@@ -1929,6 +2059,7 @@ export {
1929
2059
  TELEMETRY_PROVIDER,
1930
2060
  SpanStatusCode,
1931
2061
  ServiceRegistry,
2062
+ ResourceScope,
1932
2063
  OtpPassphraseCache,
1933
2064
  OTelTelemetryProvider,
1934
2065
  NullLogger,
@@ -1951,4 +2082,4 @@ export {
1951
2082
  BaseError
1952
2083
  };
1953
2084
 
1954
- //# debugId=4E655B5EF37DCACC64756E2164756E21
2085
+ //# debugId=F62E769AC494970464756E2164756E21