@sanity/workflow-engine 0.10.0 → 0.11.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/define.d.ts CHANGED
@@ -878,6 +878,86 @@ declare const AuthoringStageSchema: v.StrictObjectSchema<
878
878
  >;
879
879
  title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
880
880
  description: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
881
+ /**
882
+ * Advisory BPMN-aligned classification of this task by its executor (see
883
+ * {@link TASK_KINDS}). Optional and derived from the task's shape when
884
+ * omitted; declaring it lets deploy check the shape matches the lane.
885
+ * Changes no gating or resolution — a label for tooling, like `assignees`.
886
+ */
887
+ kind: v.OptionalSchema<
888
+ v.PicklistSchema<
889
+ readonly ["user", "service", "script", "manual", "receive"],
890
+ `Invalid option: expected one of ${string}`
891
+ >,
892
+ undefined
893
+ >;
894
+ /** Deep-link target for a `kind: "manual"` task (off-system work). Render-only
895
+ * metadata; only valid on a manual task (deploy-checked). */
896
+ target: v.OptionalSchema<
897
+ v.VariantSchema<
898
+ "type",
899
+ [
900
+ v.StrictObjectSchema<
901
+ {
902
+ readonly type: v.LiteralSchema<"url", undefined>;
903
+ readonly url: v.SchemaWithPipe<
904
+ readonly [
905
+ v.StringSchema<undefined>,
906
+ v.UrlAction<string, "must be a valid URL">,
907
+ v.CheckAction<string, "must be an http(s) URL">,
908
+ ]
909
+ >;
910
+ },
911
+ undefined
912
+ >,
913
+ v.StrictObjectSchema<
914
+ {
915
+ readonly type: v.LiteralSchema<"field", undefined>;
916
+ readonly field: v.UnionSchema<
917
+ [
918
+ v.SchemaWithPipe<
919
+ readonly [
920
+ v.StringSchema<undefined>,
921
+ v.MinLengthAction<
922
+ string,
923
+ 1,
924
+ "must be a non-empty string"
925
+ >,
926
+ ]
927
+ >,
928
+ v.StrictObjectSchema<
929
+ {
930
+ readonly scope: v.OptionalSchema<
931
+ v.PicklistSchema<
932
+ readonly ["workflow", "stage", "task"],
933
+ `Invalid option: expected one of ${string}`
934
+ >,
935
+ undefined
936
+ >;
937
+ readonly field: v.SchemaWithPipe<
938
+ readonly [
939
+ v.StringSchema<undefined>,
940
+ v.MinLengthAction<
941
+ string,
942
+ 1,
943
+ "must be a non-empty string"
944
+ >,
945
+ ]
946
+ >;
947
+ },
948
+ undefined
949
+ >,
950
+ ],
951
+ undefined
952
+ >;
953
+ },
954
+ undefined
955
+ >,
956
+ ],
957
+ undefined
958
+ >,
959
+ undefined
960
+ >;
881
961
  activation: v.OptionalSchema<
882
962
  v.PicklistSchema<
883
963
  readonly ["auto", "manual"],
@@ -2791,6 +2871,86 @@ declare const AuthoringTaskSchema: v.StrictObjectSchema<
2791
2871
  >;
2792
2872
  title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
2793
2873
  description: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
2874
+ /**
2875
+ * Advisory BPMN-aligned classification of this task by its executor (see
2876
+ * {@link TASK_KINDS}). Optional and derived from the task's shape when
2877
+ * omitted; declaring it lets deploy check the shape matches the lane.
2878
+ * Changes no gating or resolution — a label for tooling, like `assignees`.
2879
+ */
2880
+ kind: v.OptionalSchema<
2881
+ v.PicklistSchema<
2882
+ readonly ["user", "service", "script", "manual", "receive"],
2883
+ `Invalid option: expected one of ${string}`
2884
+ >,
2885
+ undefined
2886
+ >;
2887
+ /** Deep-link target for a `kind: "manual"` task (off-system work). Render-only
2888
+ * metadata; only valid on a manual task (deploy-checked). */
2889
+ target: v.OptionalSchema<
2890
+ v.VariantSchema<
2891
+ "type",
2892
+ [
2893
+ v.StrictObjectSchema<
2894
+ {
2895
+ readonly type: v.LiteralSchema<"url", undefined>;
2896
+ readonly url: v.SchemaWithPipe<
2897
+ readonly [
2898
+ v.StringSchema<undefined>,
2899
+ v.UrlAction<string, "must be a valid URL">,
2900
+ v.CheckAction<string, "must be an http(s) URL">,
2901
+ ]
2902
+ >;
2903
+ },
2904
+ undefined
2905
+ >,
2906
+ v.StrictObjectSchema<
2907
+ {
2908
+ readonly type: v.LiteralSchema<"field", undefined>;
2909
+ readonly field: v.UnionSchema<
2910
+ [
2911
+ v.SchemaWithPipe<
2912
+ readonly [
2913
+ v.StringSchema<undefined>,
2914
+ v.MinLengthAction<
2915
+ string,
2916
+ 1,
2917
+ "must be a non-empty string"
2918
+ >,
2919
+ ]
2920
+ >,
2921
+ v.StrictObjectSchema<
2922
+ {
2923
+ readonly scope: v.OptionalSchema<
2924
+ v.PicklistSchema<
2925
+ readonly ["workflow", "stage", "task"],
2926
+ `Invalid option: expected one of ${string}`
2927
+ >,
2928
+ undefined
2929
+ >;
2930
+ readonly field: v.SchemaWithPipe<
2931
+ readonly [
2932
+ v.StringSchema<undefined>,
2933
+ v.MinLengthAction<
2934
+ string,
2935
+ 1,
2936
+ "must be a non-empty string"
2937
+ >,
2938
+ ]
2939
+ >;
2940
+ },
2941
+ undefined
2942
+ >,
2943
+ ],
2944
+ undefined
2945
+ >;
2946
+ },
2947
+ undefined
2948
+ >,
2949
+ ],
2950
+ undefined
2951
+ >,
2952
+ undefined
2953
+ >;
2794
2954
  activation: v.OptionalSchema<
2795
2955
  v.PicklistSchema<
2796
2956
  readonly ["auto", "manual"],
@@ -4476,6 +4636,105 @@ declare const AuthoringWorkflowSchema: v.StrictObjectSchema<
4476
4636
  v.StringSchema<undefined>,
4477
4637
  undefined
4478
4638
  >;
4639
+ /**
4640
+ * Advisory BPMN-aligned classification of this task by its executor (see
4641
+ * {@link TASK_KINDS}). Optional and derived from the task's shape when
4642
+ * omitted; declaring it lets deploy check the shape matches the lane.
4643
+ * Changes no gating or resolution — a label for tooling, like `assignees`.
4644
+ */
4645
+ kind: v.OptionalSchema<
4646
+ v.PicklistSchema<
4647
+ readonly [
4648
+ "user",
4649
+ "service",
4650
+ "script",
4651
+ "manual",
4652
+ "receive",
4653
+ ],
4654
+ `Invalid option: expected one of ${string}`
4655
+ >,
4656
+ undefined
4657
+ >;
4658
+ /** Deep-link target for a `kind: "manual"` task (off-system work). Render-only
4659
+ * metadata; only valid on a manual task (deploy-checked). */
4660
+ target: v.OptionalSchema<
4661
+ v.VariantSchema<
4662
+ "type",
4663
+ [
4664
+ v.StrictObjectSchema<
4665
+ {
4666
+ readonly type: v.LiteralSchema<
4667
+ "url",
4668
+ undefined
4669
+ >;
4670
+ readonly url: v.SchemaWithPipe<
4671
+ readonly [
4672
+ v.StringSchema<undefined>,
4673
+ v.UrlAction<string, "must be a valid URL">,
4674
+ v.CheckAction<
4675
+ string,
4676
+ "must be an http(s) URL"
4677
+ >,
4678
+ ]
4679
+ >;
4680
+ },
4681
+ undefined
4682
+ >,
4683
+ v.StrictObjectSchema<
4684
+ {
4685
+ readonly type: v.LiteralSchema<
4686
+ "field",
4687
+ undefined
4688
+ >;
4689
+ readonly field: v.UnionSchema<
4690
+ [
4691
+ v.SchemaWithPipe<
4692
+ readonly [
4693
+ v.StringSchema<undefined>,
4694
+ v.MinLengthAction<
4695
+ string,
4696
+ 1,
4697
+ "must be a non-empty string"
4698
+ >,
4699
+ ]
4700
+ >,
4701
+ v.StrictObjectSchema<
4702
+ {
4703
+ readonly scope: v.OptionalSchema<
4704
+ v.PicklistSchema<
4705
+ readonly [
4706
+ "workflow",
4707
+ "stage",
4708
+ "task",
4709
+ ],
4710
+ `Invalid option: expected one of ${string}`
4711
+ >,
4712
+ undefined
4713
+ >;
4714
+ readonly field: v.SchemaWithPipe<
4715
+ readonly [
4716
+ v.StringSchema<undefined>,
4717
+ v.MinLengthAction<
4718
+ string,
4719
+ 1,
4720
+ "must be a non-empty string"
4721
+ >,
4722
+ ]
4723
+ >;
4724
+ },
4725
+ undefined
4726
+ >,
4727
+ ],
4728
+ undefined
4729
+ >;
4730
+ },
4731
+ undefined
4732
+ >,
4733
+ ],
4734
+ undefined
4735
+ >,
4736
+ undefined
4737
+ >;
4479
4738
  activation: v.OptionalSchema<
4480
4739
  v.PicklistSchema<
4481
4740
  readonly ["auto", "manual"],
@@ -6529,7 +6788,29 @@ declare const AuthoringWorkflowSchema: v.StrictObjectSchema<
6529
6788
  name: string;
6530
6789
  title?: string | undefined;
6531
6790
  description?: string | undefined;
6532
- activation?: "auto" | "manual" | undefined;
6791
+ kind?:
6792
+ | "user"
6793
+ | "service"
6794
+ | "script"
6795
+ | "manual"
6796
+ | "receive"
6797
+ | undefined;
6798
+ target?:
6799
+ | {
6800
+ type: "url";
6801
+ url: string;
6802
+ }
6803
+ | {
6804
+ type: "field";
6805
+ field:
6806
+ | string
6807
+ | {
6808
+ scope?: "workflow" | "stage" | "task" | undefined;
6809
+ field: string;
6810
+ };
6811
+ }
6812
+ | undefined;
6813
+ activation?: "manual" | "auto" | undefined;
6533
6814
  filter?: string | undefined;
6534
6815
  requirements?:
6535
6816
  | {
@@ -7527,6 +7808,83 @@ declare const WorkflowDefinitionSchema: v.StrictObjectSchema<
7527
7808
  v.StringSchema<undefined>,
7528
7809
  undefined
7529
7810
  >;
7811
+ /**
7812
+ * Advisory BPMN-aligned classification of this task by its executor (see
7813
+ * {@link TASK_KINDS}). Optional and derived from the task's shape when
7814
+ * omitted; declaring it lets deploy check the shape matches the lane.
7815
+ * Changes no gating or resolution — a label for tooling, like `assignees`.
7816
+ */
7817
+ kind: v.OptionalSchema<
7818
+ v.PicklistSchema<
7819
+ readonly [
7820
+ "user",
7821
+ "service",
7822
+ "script",
7823
+ "manual",
7824
+ "receive",
7825
+ ],
7826
+ `Invalid option: expected one of ${string}`
7827
+ >,
7828
+ undefined
7829
+ >;
7830
+ /** Deep-link target for a `kind: "manual"` task (off-system work). Render-only
7831
+ * metadata; only valid on a manual task (deploy-checked). */
7832
+ target: v.OptionalSchema<
7833
+ v.VariantSchema<
7834
+ "type",
7835
+ [
7836
+ v.StrictObjectSchema<
7837
+ {
7838
+ readonly type: v.LiteralSchema<
7839
+ "url",
7840
+ undefined
7841
+ >;
7842
+ readonly url: v.SchemaWithPipe<
7843
+ readonly [
7844
+ v.StringSchema<undefined>,
7845
+ v.UrlAction<string, "must be a valid URL">,
7846
+ v.CheckAction<
7847
+ string,
7848
+ "must be an http(s) URL"
7849
+ >,
7850
+ ]
7851
+ >;
7852
+ },
7853
+ undefined
7854
+ >,
7855
+ v.StrictObjectSchema<
7856
+ {
7857
+ readonly type: v.LiteralSchema<
7858
+ "field",
7859
+ undefined
7860
+ >;
7861
+ readonly field: v.StrictObjectSchema<
7862
+ {
7863
+ readonly scope: v.PicklistSchema<
7864
+ readonly ["workflow", "stage", "task"],
7865
+ `Invalid option: expected one of ${string}`
7866
+ >;
7867
+ readonly field: v.SchemaWithPipe<
7868
+ readonly [
7869
+ v.StringSchema<undefined>,
7870
+ v.MinLengthAction<
7871
+ string,
7872
+ 1,
7873
+ "must be a non-empty string"
7874
+ >,
7875
+ ]
7876
+ >;
7877
+ },
7878
+ undefined
7879
+ >;
7880
+ },
7881
+ undefined
7882
+ >,
7883
+ ],
7884
+ undefined
7885
+ >,
7886
+ undefined
7887
+ >;
7530
7888
  activation: v.OptionalSchema<
7531
7889
  v.PicklistSchema<
7532
7890
  readonly ["auto", "manual"],
@@ -8961,7 +9319,27 @@ declare const WorkflowDefinitionSchema: v.StrictObjectSchema<
8961
9319
  name: string;
8962
9320
  title?: string | undefined;
8963
9321
  description?: string | undefined;
8964
- activation?: "auto" | "manual" | undefined;
9322
+ kind?:
9323
+ | "user"
9324
+ | "service"
9325
+ | "script"
9326
+ | "manual"
9327
+ | "receive"
9328
+ | undefined;
9329
+ target?:
9330
+ | {
9331
+ type: "url";
9332
+ url: string;
9333
+ }
9334
+ | {
9335
+ type: "field";
9336
+ field: {
9337
+ scope: "workflow" | "stage" | "task";
9338
+ field: string;
9339
+ };
9340
+ }
9341
+ | undefined;
9342
+ activation?: "manual" | "auto" | undefined;
8965
9343
  filter?: string | undefined;
8966
9344
  requirements?:
8967
9345
  | {
package/dist/define.js CHANGED
@@ -132,12 +132,13 @@ function desugarTask(task, path, stageEnv, ctx) {
132
132
  layers: [{ scope: "task", entries: layerOf(taskFields) }, ...stageEnv.layers]
133
133
  }, ops = desugarOps(task.ops, [...path, "ops"], env, task.name, ctx), actions = (task.actions ?? []).map(
134
134
  (action, a) => desugarAction(action, [...path, "actions", a], env, task.name, ctx)
135
- );
135
+ ), target = desugarTarget(task.target, env, [...path, "target"], ctx);
136
136
  return {
137
137
  ...stripUndefined({
138
138
  name: task.name,
139
139
  title: task.title,
140
140
  description: task.description,
141
+ kind: task.kind,
141
142
  filter: task.filter,
142
143
  requirements: task.requirements,
143
144
  completeWhen: task.completeWhen,
@@ -146,11 +147,21 @@ function desugarTask(task, path, stageEnv, ctx) {
146
147
  subworkflows: task.subworkflows
147
148
  }),
148
149
  activation: task.activation ?? "manual",
150
+ ...target ? { target } : {},
149
151
  ...taskFields ? { fields: taskFields } : {},
150
152
  ...ops ? { ops } : {},
151
153
  ...actions.length > 0 ? { actions } : {}
152
154
  };
153
155
  }
156
+ const TARGET_DOC_KINDS = ["doc.ref", "doc.refs", "release.ref"];
157
+ function desugarTarget(target, env, path, ctx) {
158
+ if (target === void 0 || target.type === "url") return target;
159
+ const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef(ref, env, [...path, "field"], ctx) ?? fallbackRef(ref), entry = entryAt(env, field);
160
+ return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
161
+ path: [...path, "field"],
162
+ message: `manual task target references "${field.field}" of kind "${entry.type}" \u2014 a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
163
+ }), { type: "field", field };
164
+ }
154
165
  function desugarAction(action, path, env, taskName, ctx) {
155
166
  if ("type" in action)
156
167
  return desugarClaimAction(action, path, env, ctx);
@@ -551,9 +562,49 @@ function checkAssigneesEntries(def, issues) {
551
562
  message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
552
563
  });
553
564
  }
565
+ function taskShape(task) {
566
+ return {
567
+ hasActions: (task.actions ?? []).length > 0,
568
+ hasEffects: (task.effects ?? []).length > 0,
569
+ hasCompleteWhen: task.completeWhen !== void 0,
570
+ hasSubworkflows: task.subworkflows !== void 0
571
+ };
572
+ }
573
+ const KIND_SHAPE_RULES = {
574
+ user: (s) => s.hasActions ? [] : ["needs at least one action for a person to fire"],
575
+ service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
576
+ manual: (s) => [
577
+ s.hasActions || s.hasCompleteWhen ? void 0 : "needs a way to complete \u2014 an action to acknowledge it, or a `completeWhen` predicate to verify it (otherwise it auto-resolves on activation)",
578
+ s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own task" : void 0
579
+ ].filter((m) => m !== void 0),
580
+ receive: (s) => [
581
+ s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
582
+ s.hasActions ? "has no actor, so it cannot carry actions" : void 0
583
+ ].filter((m) => m !== void 0),
584
+ script: (s) => [
585
+ s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0,
586
+ s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0,
587
+ s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
588
+ ].filter((m) => m !== void 0)
589
+ };
590
+ function checkTaskKinds(def, issues) {
591
+ for (const [i, stage] of def.stages.entries())
592
+ for (const [j, task] of (stage.tasks ?? []).entries()) {
593
+ const path = ["stages", i, "tasks", j];
594
+ if (task.target !== void 0 && task.kind !== "manual" && issues.push({
595
+ path: [...path, "target"],
596
+ message: `task "${task.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
597
+ }), task.kind !== void 0)
598
+ for (const fault of KIND_SHAPE_RULES[task.kind](taskShape(task)))
599
+ issues.push({
600
+ path: [...path, "kind"],
601
+ message: `task "${task.name}" declares kind "${task.kind}" but ${fault}`
602
+ });
603
+ }
604
+ }
554
605
  function checkWorkflowInvariants(def) {
555
606
  const issues = [], stageNames = checkStages(def, issues);
556
- return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), issues;
607
+ return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), checkTaskKinds(def, issues), issues;
557
608
  }
558
609
  function defineWorkflow(definition) {
559
610
  const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];