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