windmill-client 1.772.0 → 1.774.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.js CHANGED
@@ -126,7 +126,7 @@ const OpenAPI = {
126
126
  PASSWORD: void 0,
127
127
  TOKEN: getEnv$1("WM_TOKEN"),
128
128
  USERNAME: void 0,
129
- VERSION: "1.772.0",
129
+ VERSION: "1.774.0",
130
130
  WITH_CREDENTIALS: true,
131
131
  interceptors: {
132
132
  request: new Interceptors(),
@@ -2761,6 +2761,24 @@ var WorkspaceService = class {
2761
2761
  });
2762
2762
  }
2763
2763
  /**
2764
+ * check what the data table's database lets its role do
2765
+ * @param data The data for the request.
2766
+ * @param data.workspace
2767
+ * @param data.datatableName
2768
+ * @returns unknown connection and privilege report
2769
+ * @throws ApiError
2770
+ */
2771
+ static testDataTableConnection(data) {
2772
+ return request(OpenAPI, {
2773
+ method: "GET",
2774
+ url: "/w/{workspace}/workspaces/test_datatable_connection/{datatable_name}",
2775
+ path: {
2776
+ workspace: data.workspace,
2777
+ datatable_name: data.datatableName
2778
+ }
2779
+ });
2780
+ }
2781
+ /**
2764
2782
  * list tables of all connected Datatables
2765
2783
  * @param data The data for the request.
2766
2784
  * @param data.workspace
@@ -7476,6 +7494,7 @@ var ScriptService = class {
7476
7494
  * @param data.search case-insensitive substring match on summary or path
7477
7495
  * @param data.perPage number of items to return for a given page (default 30, max 100)
7478
7496
  * @param data.cursor opaque keyset cursor from a previous page's next_cursor
7497
+ * @param data.includeDraftOnly also list the caller's drafts at paths with no deployed row, sorted and paginated with the deployed ones. Ignored for operators, in the archived view, and under a label filter (a draft carries no labels).
7479
7498
  * @returns unknown a page of merged, ordered runnables
7480
7499
  * @throws ApiError
7481
7500
  */
@@ -7494,7 +7513,8 @@ var ScriptService = class {
7494
7513
  label: data.label,
7495
7514
  search: data.search,
7496
7515
  per_page: data.perPage,
7497
- cursor: data.cursor
7516
+ cursor: data.cursor,
7517
+ include_draft_only: data.includeDraftOnly
7498
7518
  }
7499
7519
  });
7500
7520
  }
@@ -7504,6 +7524,7 @@ var ScriptService = class {
7504
7524
  * @param data.workspace
7505
7525
  * @param data.kinds comma-separated subset of script,flow,app (default all)
7506
7526
  * @param data.includeWithoutMain include library scripts (no runnable main)
7527
+ * @param data.includeDraftOnly also count the caller's drafts at paths with no deployed row, matching the same flag on /runnables/list
7507
7528
  * @returns unknown number of runnables per owner prefix, empty owners omitted. Excludes data-pipeline member scripts, which are listed as their folder's single pipeline entry rather than as runnables.
7508
7529
  * @throws ApiError
7509
7530
  */
@@ -7514,7 +7535,8 @@ var ScriptService = class {
7514
7535
  path: { workspace: data.workspace },
7515
7536
  query: {
7516
7537
  kinds: data.kinds,
7517
- include_without_main: data.includeWithoutMain
7538
+ include_without_main: data.includeWithoutMain,
7539
+ include_draft_only: data.includeDraftOnly
7518
7540
  }
7519
7541
  });
7520
7542
  }
@@ -9578,6 +9600,30 @@ var JobService = class {
9578
9600
  });
9579
9601
  }
9580
9602
  /**
9603
+ * get statuses and truncated results for all jobs of a flow job's execution tree
9604
+ * @param data The data for the request.
9605
+ * @param data.workspace
9606
+ * @param data.id
9607
+ * @param data.maxResultLen per-entry cap (in characters of JSON text) on result_prefix (default 2000, max 30000)
9608
+ * @param data.step step address to resolve to a single job instead of enumerating the tree: "b", "b/c", "b[12]" (1-based iteration/branch), composable as "b[12]/c"
9609
+ * @returns unknown per-job statuses and truncated results of the flow's execution tree, or the single resolved entry when step is provided
9610
+ * @throws ApiError
9611
+ */
9612
+ static getFlowAllResults(data) {
9613
+ return request(OpenAPI, {
9614
+ method: "GET",
9615
+ url: "/w/{workspace}/jobs_u/get_flow_all_results/{id}",
9616
+ path: {
9617
+ workspace: data.workspace,
9618
+ id: data.id
9619
+ },
9620
+ query: {
9621
+ max_result_len: data.maxResultLen,
9622
+ step: data.step
9623
+ }
9624
+ });
9625
+ }
9626
+ /**
9581
9627
  * get completed job logs tail
9582
9628
  * @param data The data for the request.
9583
9629
  * @param data.workspace
@@ -16449,6 +16495,108 @@ var HubPublishService = class {
16449
16495
  }
16450
16496
  };
16451
16497
 
16498
+ //#endregion
16499
+ //#region src/wacError.ts
16500
+ /** Error properties the executors already report as named fields, so they must
16501
+ * not be repeated inside `extra`. Kept identical to the skip-list in the
16502
+ * generated bun/deno job wrappers. */
16503
+ const SERIALIZED_ERROR_FIELDS = [
16504
+ "line",
16505
+ "name",
16506
+ "stack",
16507
+ "column",
16508
+ "message",
16509
+ "sourceURL",
16510
+ "originalLine",
16511
+ "originalColumn"
16512
+ ];
16513
+ /** Read a property off a value that may fight back: a proxy `get` trap or a
16514
+ * throwing accessor turns an ordinary field read into an exception. Callers
16515
+ * are on the failure-reporting path, where nothing has been checkpointed yet. */
16516
+ function safeRead(o, k) {
16517
+ try {
16518
+ return o?.[k];
16519
+ } catch {
16520
+ return void 0;
16521
+ }
16522
+ }
16523
+ /** @internal
16524
+ * Whether a caught value is the SDK's own suspend signal, which must be
16525
+ * rethrown rather than reported as a step failure.
16526
+ *
16527
+ * Both halves of the check can throw on a hostile value: `instanceof` consults
16528
+ * a proxy's `getPrototypeOf` trap, and reading `.name` its `get` trap. This
16529
+ * runs before anything has been checkpointed, so neither may escape — a value
16530
+ * that fights back is simply not a suspend. */
16531
+ function isSuspendSignal(e, suspendCtor) {
16532
+ try {
16533
+ if (e instanceof suspendCtor) return true;
16534
+ } catch {}
16535
+ return safeRead(e, "name") === "StepSuspend";
16536
+ }
16537
+ /** @internal
16538
+ * Serialize a failed `step()` body into the `__wmill_error` marker that task
16539
+ * failures also use, so it can be stored in `completed_steps`.
16540
+ *
16541
+ * The record's final shape is decided by the backend (`wac_failure_record`),
16542
+ * which normalizes task failures through the same function; what is built here
16543
+ * is the raw material plus the envelope the backend recognizes. */
16544
+ function stepErrorMarker(key, e) {
16545
+ const thrown = e;
16546
+ const rawName = safeRead(thrown, "name");
16547
+ const rawMessage = safeRead(thrown, "message");
16548
+ const rawStack = safeRead(thrown, "stack");
16549
+ const name = typeof rawName === "string" ? rawName : void 0;
16550
+ const message = typeof rawMessage === "string" ? rawMessage : void 0;
16551
+ const stack = typeof rawStack === "string" ? rawStack : void 0;
16552
+ const error = {};
16553
+ if (name) error.name = name;
16554
+ if (message !== void 0) error.message = message;
16555
+ if (stack) error.stack = stack;
16556
+ if (thrown !== null && typeof thrown === "object") {
16557
+ const extra = {};
16558
+ let keys = [];
16559
+ try {
16560
+ keys = Object.getOwnPropertyNames(thrown);
16561
+ } catch {
16562
+ keys = [];
16563
+ }
16564
+ for (const k of keys) {
16565
+ if (SERIALIZED_ERROR_FIELDS.includes(k)) continue;
16566
+ try {
16567
+ extra[k] = JSON.parse(JSON.stringify(thrown[k]));
16568
+ } catch {}
16569
+ }
16570
+ if (Object.keys(extra).length > 0) error.extra = extra;
16571
+ }
16572
+ let fallback;
16573
+ try {
16574
+ fallback = String(e);
16575
+ } catch {
16576
+ fallback = `unrepresentable ${typeof e} thrown`;
16577
+ }
16578
+ return {
16579
+ __wmill_error: true,
16580
+ message: message ?? fallback,
16581
+ step_key: key,
16582
+ result: { error }
16583
+ };
16584
+ }
16585
+ /** @internal
16586
+ * Rebuild the error a failed task or step throws. The run that produced the
16587
+ * failure and every later replay go through here: a catch is control flow,
16588
+ * `workflow()` re-runs its body from the top every round, so a handler that
16589
+ * branches on the failure it caught must be handed the same thing in every
16590
+ * round or it dispatches different tasks on the way back. */
16591
+ function taskErrorFromMarker(marker, fallbackMessage) {
16592
+ const err = new Error(marker?.message || fallbackMessage);
16593
+ err.name = "TaskError";
16594
+ err.result = marker?.result;
16595
+ err.step_key = marker?.step_key;
16596
+ err.child_job_id = marker?.child_job_id;
16597
+ return err;
16598
+ }
16599
+
16452
16600
  //#endregion
16453
16601
  //#region src/s3Types.ts
16454
16602
  /**
@@ -17782,31 +17930,24 @@ var StepSuspend = class extends Error {
17782
17930
  this.name = "StepSuspend";
17783
17931
  }
17784
17932
  };
17785
- /** Serialize a failed `step()` body into the `__wmill_error` marker that task
17786
- * failures also use, so it can be stored in `completed_steps`. */
17787
- function stepErrorMarker(key, e) {
17788
- const message = e instanceof Error ? e.message : String(e);
17789
- const type = e instanceof Error ? e.constructor?.name ?? e.name : typeof e;
17790
- return {
17791
- __wmill_error: true,
17792
- message,
17793
- step_key: key,
17794
- result: {
17795
- error: message,
17796
- type
17797
- }
17798
- };
17933
+ /** Encode a checkpoint payload the way the worker wrapper does on the suspend
17934
+ * path, so both arms record the same value for the same step. `undefined` maps
17935
+ * to null; a bigint to its digits, which the wrapper gets instead from the
17936
+ * `BigInt.prototype.toJSON` it installs and the SDK cannot count on. */
17937
+ function encodeCheckpointPayload(payload) {
17938
+ return JSON.stringify(payload, (_key, value) => typeof value === "undefined" ? null : typeof value === "bigint" ? value.toString() : value);
17799
17939
  }
17800
- /** Rebuild the error a failed step throws. Both the run that produced the
17801
- * failure and every later replay go through here, so a workflow's catch block
17802
- * sees the same shape either way. */
17803
- function stepErrorFromMarker(marker, name) {
17804
- const err = new Error(marker?.message || `Step '${name}' failed`);
17805
- err.name = "TaskError";
17806
- err.result = marker?.result;
17807
- err.step_key = marker?.step_key;
17808
- err.child_job_id = marker?.child_job_id;
17809
- return err;
17940
+ /** A whole result that `JSON.stringify` drops the key for leaves a checkpoint
17941
+ * with no `result`, which neither the endpoint nor `WacOutput` accepts. Only
17942
+ * the top level: nested, a dropped key is what every other bun path does. */
17943
+ function checkpointableResult(value) {
17944
+ return typeof value === "function" || typeof value === "symbol" ? null : value;
17945
+ }
17946
+ /** Put a value through the checkpoint's encoding without checkpointing it, so
17947
+ * the paths that never persist anything still hand back the shape the ones
17948
+ * that do would. */
17949
+ function jsonRoundTrip(value) {
17950
+ return JSON.parse(encodeCheckpointPayload({ value: checkpointableResult(value) })).value;
17810
17951
  }
17811
17952
  /** A step key travels as one path segment when its URLs are minted, so it must be
17812
17953
  * non-empty and free of `/` and dot segments — otherwise `waitForApproval` would
@@ -17834,6 +17975,11 @@ var WorkflowCtx = class {
17834
17975
  * `complete` whose step never reached `completed_steps`. Python is immune:
17835
17976
  * `_StepSuspend` derives from `BaseException`. */
17836
17977
  _pendingSuspend = null;
17978
+ /** The failure raised by the step this child round is executing. That exception
17979
+ * *is* the round's result, so a `catch` in the body must not be able to turn it
17980
+ * into a `complete` — the parent would then record the caught branch's value as
17981
+ * a successful step. Boxed: the thrown value may be any falsy value. */
17982
+ _pendingStepFailure = null;
17837
17983
  /** When set, the task matching this key executes its inner function directly */
17838
17984
  _executingKey;
17839
17985
  /** Serializes fast-path POSTs across concurrent step() calls within one
@@ -17866,16 +18012,12 @@ var WorkflowCtx = class {
17866
18012
  return key;
17867
18013
  }
17868
18014
  _nextStep(name, script, args = {}, dispatch_type = "inline", options) {
17869
- this._rethrowSwallowedSuspend();
18015
+ this._rethrowSwallowed();
17870
18016
  const key = this._allocKey(name || script || "step");
17871
18017
  if (key in this.completed) {
17872
18018
  const value = this.completed[key];
17873
18019
  if (value && typeof value === "object" && value.__wmill_error) {
17874
- const err = new Error(value.message || `Task '${name}' failed`);
17875
- err.name = "TaskError";
17876
- err.result = value.result;
17877
- err.step_key = value.step_key;
17878
- err.child_job_id = value.child_job_id;
18020
+ const err = taskErrorFromMarker(value, `Task '${name}' failed`);
17879
18021
  return { then: (_resolve, reject) => {
17880
18022
  if (reject) reject(err);
17881
18023
  else throw err;
@@ -17925,7 +18067,7 @@ var WorkflowCtx = class {
17925
18067
  return steps;
17926
18068
  }
17927
18069
  _waitForApproval(options) {
17928
- this._rethrowSwallowedSuspend();
18070
+ this._rethrowSwallowed();
17929
18071
  if (options?.key !== void 0) assertUsableStepKey(options.key, "waitForApproval key");
17930
18072
  const key = this._allocKey(options?.key || "approval");
17931
18073
  if (options?.key && key !== options.key) throw new Error(`WAC step key "${options.key}" is already used in this workflow. Give each waitForApproval() its own key so getApprovalUrls() can address it.`);
@@ -17945,7 +18087,7 @@ var WorkflowCtx = class {
17945
18087
  });
17946
18088
  }
17947
18089
  _sleep(seconds) {
17948
- this._rethrowSwallowedSuspend();
18090
+ this._rethrowSwallowed();
17949
18091
  const key = this._allocKey("sleep");
17950
18092
  if (key in this.completed) return { then: (resolve$1) => resolve$1(void 0) };
17951
18093
  if (this._executingKey !== null) return { then: () => new Promise(() => {}) };
@@ -17958,11 +18100,11 @@ var WorkflowCtx = class {
17958
18100
  });
17959
18101
  }
17960
18102
  async _runInlineStep(name, fn) {
17961
- this._rethrowSwallowedSuspend();
18103
+ this._rethrowSwallowed();
17962
18104
  const key = this._allocKey(name || "step");
17963
18105
  if (key in this.completed) {
17964
18106
  const value = this.completed[key];
17965
- if (value && typeof value === "object" && value.__wmill_error) throw stepErrorFromMarker(value, name);
18107
+ if (value && typeof value === "object" && value.__wmill_error) throw taskErrorFromMarker(value, `Step '${name}' failed`);
17966
18108
  return value;
17967
18109
  }
17968
18110
  if (this._executingKey !== null) return new Promise(() => {});
@@ -17974,22 +18116,36 @@ var WorkflowCtx = class {
17974
18116
  })}`);
17975
18117
  const t0 = Date.now();
17976
18118
  let result;
17977
- let stepError;
17978
18119
  let errored = false;
17979
18120
  try {
17980
18121
  result = await fn();
17981
18122
  } catch (e) {
17982
- if (e?.name === "StepSuspend" || e instanceof StepSuspend) throw e;
18123
+ if (isSuspendSignal(e, StepSuspend)) throw e;
17983
18124
  errored = true;
17984
- stepError = e;
17985
18125
  result = stepErrorMarker(key, e);
18126
+ console.log(`--- WAC: ${key} failed ---`);
18127
+ console.log(result?.result?.error?.stack ?? result?.message ?? "step failed");
17986
18128
  }
17987
18129
  const durationMs = Date.now() - t0;
17988
18130
  const fastPathFlagRaw = (getEnv("WM_WAC_INLINE_FAST_PATH") ?? "1").trim().toLowerCase();
17989
18131
  const fastPathEnabled = fastPathFlagRaw !== "0" && fastPathFlagRaw !== "false" && fastPathFlagRaw !== "off" && fastPathFlagRaw !== "no";
17990
18132
  const jobId = getEnv("WM_JOB_ID");
17991
18133
  const workspace = getEnv("WM_WORKSPACE");
17992
- if (fastPathEnabled && jobId && workspace && OpenAPI.BASE && OpenAPI.TOKEN) {
18134
+ let payload;
18135
+ let checkpointed;
18136
+ if (fastPathEnabled && jobId && workspace && OpenAPI.BASE && OpenAPI.TOKEN) try {
18137
+ payload = encodeCheckpointPayload({
18138
+ key,
18139
+ result: checkpointableResult(result),
18140
+ started_at: startedAt,
18141
+ duration_ms: durationMs
18142
+ });
18143
+ checkpointed = JSON.parse(payload).result;
18144
+ } catch (e) {
18145
+ console.log(`WAC v2 inline fast path could not serialize key ${key}, falling back to suspend: ${e}`);
18146
+ }
18147
+ if (payload !== void 0) {
18148
+ const body = payload;
17993
18149
  const chainTail = this._inlineChain.then(async () => {
17994
18150
  const ctrl = new AbortController();
17995
18151
  const t = setTimeout(() => ctrl.abort(), 1e4);
@@ -18000,37 +18156,36 @@ var WorkflowCtx = class {
18000
18156
  "Content-Type": "application/json",
18001
18157
  Authorization: `Bearer ${OpenAPI.TOKEN}`
18002
18158
  },
18003
- body: JSON.stringify({
18004
- key,
18005
- result,
18006
- started_at: startedAt,
18007
- duration_ms: durationMs
18008
- }),
18159
+ body,
18009
18160
  signal: ctrl.signal
18010
18161
  });
18011
18162
  if (!resp.ok) throw new Error(`inline_checkpoint API ${resp.status}`);
18163
+ if (!errored) return void 0;
18164
+ if (!(resp.headers.get("content-type") ?? "").includes("json")) return void 0;
18165
+ return (await resp.json())?.failure;
18012
18166
  } finally {
18013
18167
  clearTimeout(t);
18014
18168
  }
18015
18169
  });
18016
18170
  this._inlineChain = chainTail.catch(() => {});
18017
18171
  let fastPathOk = false;
18172
+ let storedFailure;
18018
18173
  try {
18019
- await chainTail;
18174
+ storedFailure = await chainTail;
18020
18175
  fastPathOk = true;
18021
18176
  } catch (e) {
18022
18177
  console.log(`WAC v2 inline fast path failed for key ${key}, falling back to suspend: ${e}`);
18023
18178
  }
18024
18179
  if (fastPathOk) {
18025
- if (errored) throw Object.assign(stepErrorFromMarker(result, name), { cause: stepError });
18026
- return result;
18180
+ if (errored) throw taskErrorFromMarker(storedFailure ?? checkpointed, `Step '${name}' failed`);
18181
+ return checkpointed;
18027
18182
  }
18028
18183
  }
18029
18184
  this._raiseSuspend({
18030
18185
  mode: "inline_checkpoint",
18031
18186
  steps: [],
18032
18187
  key,
18033
- result,
18188
+ result: checkpointableResult(result),
18034
18189
  started_at: startedAt,
18035
18190
  duration_ms: durationMs
18036
18191
  });
@@ -18042,11 +18197,18 @@ var WorkflowCtx = class {
18042
18197
  this._pendingSuspend = suspend;
18043
18198
  throw suspend;
18044
18199
  }
18045
- /** Re-throw a swallowed suspend at the next SDK call. It happened before
18046
- * whatever the body is doing now, so it wins: the run is unwinding either
18047
- * way and everything after it re-runs on the replay. Left set, so a body
18048
- * that catches in a loop can't swallow it a second time. */
18049
- _rethrowSwallowedSuspend() {
18200
+ /** Park then raise the executing step's failure. Child mode only — in a parent
18201
+ * round a task failure is an ordinary `TaskError` the body may handle. */
18202
+ _raiseStepFailure(error) {
18203
+ this._pendingStepFailure = { error };
18204
+ throw error;
18205
+ }
18206
+ /** Re-throw a swallowed suspend or step failure at the next SDK call. It
18207
+ * happened before whatever the body is doing now, so it wins: the run is
18208
+ * unwinding either way and everything after it re-runs on the replay. Left
18209
+ * set, so a body that catches in a loop can't swallow it a second time. */
18210
+ _rethrowSwallowed() {
18211
+ if (this._pendingStepFailure) throw this._pendingStepFailure.error;
18050
18212
  if (this._pendingSuspend) throw this._pendingSuspend;
18051
18213
  }
18052
18214
  /** Hand the runner a suspend the workflow body caught and swallowed, so it is
@@ -18057,16 +18219,30 @@ var WorkflowCtx = class {
18057
18219
  this._pendingSuspend = null;
18058
18220
  return s;
18059
18221
  }
18222
+ /** Same for the executing step's failure, so the runner can fail the child job. */
18223
+ _takePendingStepFailure() {
18224
+ const f = this._pendingStepFailure;
18225
+ this._pendingStepFailure = null;
18226
+ return f;
18227
+ }
18060
18228
  };
18061
18229
  async function sleep(seconds) {
18062
18230
  const ctx = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
18063
18231
  if (ctx) return ctx._sleep(seconds);
18064
18232
  await new Promise((r) => setTimeout(r, seconds * 1e3));
18065
18233
  }
18234
+ /**
18235
+ * Execute `fn` inline and checkpoint the result. On replay the cached value is
18236
+ * returned without re-executing `fn`.
18237
+ *
18238
+ * `fn`'s result is encoded as JSON and decoded back before it is returned, so
18239
+ * the round that runs the body sees the same types every replay sees: a `Date`
18240
+ * comes back as a string, a `Map` as `{}`. {@link Jsonified} is that shape.
18241
+ */
18066
18242
  async function step(name, fn) {
18067
18243
  const ctx = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
18068
18244
  if (ctx) return ctx._runInlineStep(name, fn);
18069
- return fn();
18245
+ return jsonRoundTrip(await fn());
18070
18246
  }
18071
18247
  /**
18072
18248
  * Wrap an async function as a workflow task.
@@ -18077,6 +18253,10 @@ async function step(name, fn) {
18077
18253
  *
18078
18254
  * Inside a `workflow()`, calling a task dispatches it as a step.
18079
18255
  * Outside a workflow, the function body executes directly.
18256
+ *
18257
+ * A task runs as its own job, so its result is always encoded as JSON and
18258
+ * decoded back before the caller sees it: a `Date` comes back as a string, a
18259
+ * `Map` as `{}`. {@link JsonifiedFn} is that shape.
18080
18260
  */
18081
18261
  function task(fnOrPath, maybeFnOrOptions, maybeOptions) {
18082
18262
  let fn;
@@ -18101,11 +18281,17 @@ function task(fnOrPath, maybeFnOrOptions, maybeOptions) {
18101
18281
  else kwargs[`arg${i}`] = args[i];
18102
18282
  const stepResult = ctx._nextStep(taskName, script, kwargs, "inline", taskOptions);
18103
18283
  if (stepResult?._execute_directly) return (async () => {
18104
- const result = await fn(...args);
18284
+ let result;
18285
+ try {
18286
+ result = await fn(...args);
18287
+ } catch (e) {
18288
+ if (isSuspendSignal(e, StepSuspend)) throw e;
18289
+ ctx._raiseStepFailure(e);
18290
+ }
18105
18291
  ctx._raiseSuspend({
18106
18292
  mode: "step_complete",
18107
18293
  steps: [],
18108
- result
18294
+ result: checkpointableResult(result)
18109
18295
  });
18110
18296
  })();
18111
18297
  return stepResult;
@@ -18127,7 +18313,7 @@ function task(fnOrPath, maybeFnOrOptions, maybeOptions) {
18127
18313
  console.log(`Task ${taskName} (${jobId}) completed`);
18128
18314
  return r;
18129
18315
  })();
18130
- else return fn(...args);
18316
+ else return Promise.resolve(fn(...args)).then(jsonRoundTrip);
18131
18317
  };
18132
18318
  Object.defineProperty(wrapper, "name", { value: taskName });
18133
18319
  wrapper._is_task = true;