windmill-client 1.769.0 → 1.771.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/client.d.ts CHANGED
@@ -448,7 +448,11 @@ export declare let _workflowCtx: WorkflowCtx | null;
448
448
  export declare function setWorkflowCtx(ctx: WorkflowCtx | null): void;
449
449
  export declare class WorkflowCtx {
450
450
  private completed;
451
+ /** Null-prototype: step keys are caller-supplied, and a plain object would
452
+ * resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */
451
453
  private counters;
454
+ /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */
455
+ private _usedKeys;
452
456
  private pending;
453
457
  private _suspended;
454
458
  /** When set, the task matching this key executes its inner function directly */
@@ -464,7 +468,10 @@ export declare class WorkflowCtx {
464
468
  * `completed_steps[key]`. Initialized to a resolved promise. */
465
469
  private _inlineChain;
466
470
  constructor(checkpoint?: Record<string, any>);
467
- /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent. */
471
+ /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent.
472
+ * Suffixing alone can alias — a second `step("x")` and a first `step("x_2")` both
473
+ * want `x_2` — so keep bumping past keys already handed out. Allocation order is
474
+ * fixed by the workflow body, so replays reproduce the same keys. */
468
475
  _allocKey(name: string): string;
469
476
  _nextStep(name: string, script: string, args?: Record<string, any>, dispatch_type?: string, options?: TaskOptions): PromiseLike<any>;
470
477
  /** Return and clear any pending (unawaited) steps. */
@@ -479,6 +486,7 @@ export declare class WorkflowCtx {
479
486
  timeout?: number;
480
487
  form?: object;
481
488
  selfApproval?: boolean;
489
+ key?: string;
482
490
  }): PromiseLike<{
483
491
  value: any;
484
492
  approver: string;
@@ -529,23 +537,51 @@ export declare function workflow<T>(fn: (...args: any[]) => Promise<T>): (...arg
529
537
  /**
530
538
  * Suspend the workflow and wait for an external approval.
531
539
  *
532
- * Use `getResumeUrls()` (wrapped in `step()`) to obtain resume/cancel/approvalPage
533
- * URLs before calling this function.
540
+ * Pass `key` to name the step, then `getApprovalUrls(key)` yields the URLs that
541
+ * resume exactly this approval — route them through your own channel. Without a
542
+ * key the steps are named `approval`, `approval_2`, ...
534
543
  *
535
544
  * @example
536
- * const urls = await step("urls", () => getResumeUrls());
537
- * await step("notify", () => sendEmail(urls.approvalPage));
538
- * const { value, approver } = await waitForApproval({ timeout: 3600 });
545
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
546
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
547
+ * const { value, approver } = await waitForApproval({ key: "manager", timeout: 3600 });
539
548
  */
540
549
  export declare function waitForApproval(options?: {
541
550
  timeout?: number;
542
551
  form?: object;
543
552
  selfApproval?: boolean;
553
+ key?: string;
544
554
  }): PromiseLike<{
545
555
  value: any;
546
556
  approver: string;
547
557
  approved: boolean;
548
558
  }>;
559
+ /**
560
+ * Resume/cancel/approval-page URLs bound to one `waitForApproval` step.
561
+ *
562
+ * Unlike `getResumeUrls()`, which signs a random nonce, these address the very
563
+ * `resume_job` record the step's built-in approval buttons use, so they are
564
+ * stable across replays and safe to embed in a custom notification.
565
+ *
566
+ * `stepKey` must match the `key` given to `waitForApproval`. Keys must be unique
567
+ * within a workflow; reusing one throws rather than silently renaming it. The URL
568
+ * only resumes while that step is awaiting approval; used at any other moment it is
569
+ * rejected rather than banking a row a different approval would consume. Send it
570
+ * ahead of time — approvers just cannot act before the workflow reaches the step.
571
+ *
572
+ * `resume` and `cancel` are step-bound; `approvalPage` is not — it opens the job's
573
+ * approval page, which acts on whichever approval is pending when it is used.
574
+ *
575
+ * @example
576
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
577
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
578
+ * await waitForApproval({ key: "manager" });
579
+ */
580
+ export declare function getApprovalUrls(stepKey?: string, approver?: string): Promise<{
581
+ approvalPage: string;
582
+ resume: string;
583
+ cancel: string;
584
+ }>;
549
585
  /**
550
586
  * Process items in parallel with optional concurrency control.
551
587
  *
package/dist/client.mjs CHANGED
@@ -958,6 +958,13 @@ var StepSuspend = class extends Error {
958
958
  this.name = "StepSuspend";
959
959
  }
960
960
  };
961
+ /** A step key travels as one path segment when its URLs are minted, so it must be
962
+ * non-empty and free of `/` and dot segments — otherwise `waitForApproval` would
963
+ * accept a key `getApprovalUrls` can never address. */
964
+ function assertUsableStepKey(key, what) {
965
+ const k = key.trim();
966
+ if (k === "" || k === "." || k === ".." || key.includes("/") || key.includes("\\")) throw new Error(`${what} must be a non-empty step name without \`/\` or dot segments`);
967
+ }
961
968
  let _workflowCtx = null;
962
969
  function setWorkflowCtx(ctx) {
963
970
  _workflowCtx = ctx;
@@ -965,7 +972,11 @@ function setWorkflowCtx(ctx) {
965
972
  }
966
973
  var WorkflowCtx = class {
967
974
  completed;
968
- counters = {};
975
+ /** Null-prototype: step keys are caller-supplied, and a plain object would
976
+ * resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */
977
+ counters = Object.create(null);
978
+ /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */
979
+ _usedKeys = new Set();
969
980
  pending = [];
970
981
  _suspended = false;
971
982
  /** When set, the task matching this key executes its inner function directly */
@@ -981,14 +992,23 @@ var WorkflowCtx = class {
981
992
  * `completed_steps[key]`. Initialized to a resolved promise. */
982
993
  _inlineChain = Promise.resolve();
983
994
  constructor(checkpoint = {}) {
984
- this.completed = checkpoint?.completed_steps ?? {};
995
+ this.completed = Object.assign(Object.create(null), checkpoint?.completed_steps ?? {});
985
996
  this._executingKey = checkpoint?._executing_key ?? null;
986
997
  }
987
- /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent. */
998
+ /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent.
999
+ * Suffixing alone can alias — a second `step("x")` and a first `step("x_2")` both
1000
+ * want `x_2` — so keep bumping past keys already handed out. Allocation order is
1001
+ * fixed by the workflow body, so replays reproduce the same keys. */
988
1002
  _allocKey(name) {
989
- const n = (this.counters[name] ?? 0) + 1;
1003
+ let n = (this.counters[name] ?? 0) + 1;
1004
+ let key = n === 1 ? name : `${name}_${n}`;
1005
+ while (this._usedKeys.has(key)) {
1006
+ n++;
1007
+ key = `${name}_${n}`;
1008
+ }
990
1009
  this.counters[name] = n;
991
- return n === 1 ? name : `${name}_${n}`;
1010
+ this._usedKeys.add(key);
1011
+ return key;
992
1012
  }
993
1013
  _nextStep(name, script, args = {}, dispatch_type = "inline", options) {
994
1014
  const key = this._allocKey(name || script || "step");
@@ -1048,7 +1068,9 @@ var WorkflowCtx = class {
1048
1068
  return steps;
1049
1069
  }
1050
1070
  _waitForApproval(options) {
1051
- const key = this._allocKey("approval");
1071
+ if (options?.key !== void 0) assertUsableStepKey(options.key, "waitForApproval key");
1072
+ const key = this._allocKey(options?.key || "approval");
1073
+ 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.`);
1052
1074
  if (key in this.completed) {
1053
1075
  const value = this.completed[key];
1054
1076
  return { then: (resolve) => resolve(value) };
@@ -1287,13 +1309,14 @@ function workflow(fn) {
1287
1309
  /**
1288
1310
  * Suspend the workflow and wait for an external approval.
1289
1311
  *
1290
- * Use `getResumeUrls()` (wrapped in `step()`) to obtain resume/cancel/approvalPage
1291
- * URLs before calling this function.
1312
+ * Pass `key` to name the step, then `getApprovalUrls(key)` yields the URLs that
1313
+ * resume exactly this approval — route them through your own channel. Without a
1314
+ * key the steps are named `approval`, `approval_2`, ...
1292
1315
  *
1293
1316
  * @example
1294
- * const urls = await step("urls", () => getResumeUrls());
1295
- * await step("notify", () => sendEmail(urls.approvalPage));
1296
- * const { value, approver } = await waitForApproval({ timeout: 3600 });
1317
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
1318
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
1319
+ * const { value, approver } = await waitForApproval({ key: "manager", timeout: 3600 });
1297
1320
  */
1298
1321
  function waitForApproval(options) {
1299
1322
  const ctx = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
@@ -1301,6 +1324,37 @@ function waitForApproval(options) {
1301
1324
  return ctx._waitForApproval(options);
1302
1325
  }
1303
1326
  /**
1327
+ * Resume/cancel/approval-page URLs bound to one `waitForApproval` step.
1328
+ *
1329
+ * Unlike `getResumeUrls()`, which signs a random nonce, these address the very
1330
+ * `resume_job` record the step's built-in approval buttons use, so they are
1331
+ * stable across replays and safe to embed in a custom notification.
1332
+ *
1333
+ * `stepKey` must match the `key` given to `waitForApproval`. Keys must be unique
1334
+ * within a workflow; reusing one throws rather than silently renaming it. The URL
1335
+ * only resumes while that step is awaiting approval; used at any other moment it is
1336
+ * rejected rather than banking a row a different approval would consume. Send it
1337
+ * ahead of time — approvers just cannot act before the workflow reaches the step.
1338
+ *
1339
+ * `resume` and `cancel` are step-bound; `approvalPage` is not — it opens the job's
1340
+ * approval page, which acts on whichever approval is pending when it is used.
1341
+ *
1342
+ * @example
1343
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
1344
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
1345
+ * await waitForApproval({ key: "manager" });
1346
+ */
1347
+ async function getApprovalUrls(stepKey = "approval", approver) {
1348
+ assertUsableStepKey(stepKey, "getApprovalUrls stepKey");
1349
+ const workspace = getWorkspace();
1350
+ return await JobService.getWacApprovalUrls({
1351
+ workspace,
1352
+ stepKey,
1353
+ approver,
1354
+ id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID"
1355
+ });
1356
+ }
1357
+ /**
1304
1358
  * Process items in parallel with optional concurrency control.
1305
1359
  *
1306
1360
  * Each item is processed by calling `fn(item)`, which should be a task().
@@ -1342,4 +1396,4 @@ async function commitKafkaOffsets(triggerPath, topic, partition, offset) {
1342
1396
  }
1343
1397
 
1344
1398
  //#endregion
1345
- export { SHARED_FOLDER, StepSuspend, WorkflowCtx, _workflowCtx, appendToResultStream, base64ToUint8Array, commitKafkaOffsets, databaseUrlFromResource, deleteS3File, denoS3LightClientSettings, getEnv, getFlowUserState, getIdToken, getInternalState, getPresignedS3PublicUrl, getPresignedS3PublicUrls, getProgress, getResource, getResult, getResultMaybe, getResumeEndpoints, getResumeUrls, getRootJobId, getState, getStatePath, getVariable, getWorkspace, loadS3File, loadS3FileStream, parallel, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, resolveDefaultResource, runFlow, runFlowAsync, runScript, runScriptAsync, runScriptByHash, runScriptByHashAsync, runScriptByPath, runScriptByPathAsync, setClient, setFlowUserState, setInternalState, setProgress, setResource, setState, setVariable, setWorkflowCtx, signS3Object, signS3Objects, sleep, step, streamResult, task, taskFlow, taskScript, uint8ArrayToBase64, usernameToEmail, waitForApproval, waitJob, workerHasInternalServer, workflow, writeS3File };
1399
+ export { SHARED_FOLDER, StepSuspend, WorkflowCtx, _workflowCtx, appendToResultStream, base64ToUint8Array, commitKafkaOffsets, databaseUrlFromResource, deleteS3File, denoS3LightClientSettings, getApprovalUrls, getEnv, getFlowUserState, getIdToken, getInternalState, getPresignedS3PublicUrl, getPresignedS3PublicUrls, getProgress, getResource, getResult, getResultMaybe, getResumeEndpoints, getResumeUrls, getRootJobId, getState, getStatePath, getVariable, getWorkspace, loadS3File, loadS3FileStream, parallel, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, resolveDefaultResource, runFlow, runFlowAsync, runScript, runScriptAsync, runScriptByHash, runScriptByHashAsync, runScriptByPath, runScriptByPathAsync, setClient, setFlowUserState, setInternalState, setProgress, setResource, setState, setVariable, setWorkflowCtx, signS3Object, signS3Objects, sleep, step, streamResult, task, taskFlow, taskScript, uint8ArrayToBase64, usernameToEmail, waitForApproval, waitJob, workerHasInternalServer, workflow, writeS3File };
@@ -29,7 +29,7 @@ const OpenAPI = {
29
29
  PASSWORD: void 0,
30
30
  TOKEN: getEnv("WM_TOKEN"),
31
31
  USERNAME: void 0,
32
- VERSION: "1.769.0",
32
+ VERSION: "1.771.0",
33
33
  WITH_CREDENTIALS: true,
34
34
  interceptors: {
35
35
  request: new Interceptors(),
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
4
4
  export * from './services.gen';
5
5
  export * from './types.gen';
6
6
  export type { DenoS3LightClientSettings } from "./s3Types";
7
- export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, type TaskOptions, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";
8
- import { setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, WorkflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, getWorkspace, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, parseS3Object, commitKafkaOffsets } from "./client";
7
+ export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, type TaskOptions, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";
8
+ import { setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, WorkflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, getWorkspace, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, parseS3Object, commitKafkaOffsets } from "./client";
9
9
  import { AdminService, AuditService, FlowService, GranularAclService, GroupService, JobService, ResourceService, VariableService, ScriptService, ScheduleService, SettingsService, UserService, WorkspaceService, TeamsService } from "./services.gen";
10
10
  declare const wmill: {
11
11
  setClient: typeof setClient;
@@ -36,6 +36,7 @@ declare const wmill: {
36
36
  sleep: typeof sleep;
37
37
  parallel: typeof parallel;
38
38
  waitForApproval: typeof waitForApproval;
39
+ getApprovalUrls: typeof getApprovalUrls;
39
40
  WorkflowCtx: typeof WorkflowCtx;
40
41
  _workflowCtx: WorkflowCtx | null;
41
42
  setWorkflowCtx: typeof setWorkflowCtx;