@teamkeel/functions-runtime 0.418.0 → 0.420.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.d.cts CHANGED
@@ -355,6 +355,7 @@ type PageOptions<C extends FlowConfig, A extends PageActions[], T extends UIElem
355
355
  content: T;
356
356
  validate?: ValidateFn<ExtractFormData<T>>;
357
357
  actions?: A;
358
+ allowBack?: boolean;
358
359
  };
359
360
  type UiPage<C extends FlowConfig> = <T extends UIElements, const A extends PageActions[] = []>(name: string, options: PageOptions<C, A, T>) => Promise<A["length"] extends 0 ? ExtractFormData<T> : {
360
361
  data: ExtractFormData<T>;
@@ -639,15 +640,8 @@ interface UiElementPickListApiResponse extends BaseUiMinimalInputResponse<"ui.in
639
640
  data: PickListItem[];
640
641
  }
641
642
 
642
- type UiElementBulkScan = {
643
- <N extends string>(name: N): InputElementResponse<N, {
644
- scans: string[];
645
- }>;
646
- <N extends string, const M extends BulkScanDuplicateMode>(name: N, options?: BulkScanOptions<M>): InputElementResponse<N, {
647
- scans: BulkScanResponseItem<M>[];
648
- }>;
649
- };
650
- type BulkScanResponseItem<M> = M extends "trackQuantity" ? {
643
+ type UiElementScan = <N extends string, const M extends scanMode = "single", const D extends ScanDuplicateMode = "none">(name: N, options?: ScanOptions<M, D>) => InputElementResponse<N, M extends "single" ? string : ScanResponseItem<D>[]>;
644
+ type ScanResponseItem<D> = D extends "trackQuantity" ? {
651
645
  value: string;
652
646
  quantity: number;
653
647
  } : string;
@@ -655,33 +649,56 @@ type BulkScanResponseItem<M> = M extends "trackQuantity" ? {
655
649
  * Defines how duplicate scans should be handled.
656
650
  * @default "none"
657
651
  */
658
- type BulkScanDuplicateMode =
652
+ type ScanDuplicateMode =
659
653
  /** No duplicate handling - all scans are accepted as separate entries */
660
654
  "none"
661
655
  /** Track quantity when duplicates are scanned */
662
656
  | "trackQuantity"
663
657
  /** Reject duplicate scans with an error message */
664
658
  | "rejectDuplicates";
665
- type BulkScanOptions<M> = {
666
- /** The singular unit of the item being scanned. E.g. "box", "bottle", "product" etc */
667
- unit?: string;
659
+ type scanMode = "single" | "multi";
660
+ type ScanOptions<M, D> = {
668
661
  /** The title of the input block
669
662
  * @default "Scan {unit plural | 'items'}"
670
663
  */
671
664
  title?: string;
672
665
  description?: string;
666
+ /** The singular unit of the item being scanned. E.g. "box", "bottle", "product" etc */
667
+ unit?: string;
668
+ /** The mode of the scan input
669
+ * @default "single"
670
+ */
671
+ mode: M | scanMode;
672
+ validate?: ValidateFn<ScanResponseItem<D>>;
673
+ } & (M extends "multi" ? {
673
674
  max?: number;
674
675
  min?: number;
675
- duplicateHandling?: M;
676
- validate?: ValidateFn<BulkScanResponseItem<M>>;
677
- };
678
- interface UiElementBulkScanApiResponse extends BaseUiMinimalInputResponse<"ui.interactive.bulkScan"> {
676
+ duplicateHandling?: D | ScanDuplicateMode;
677
+ } : {
678
+ /** Whether to automatically continue to the next step when the user scans an item
679
+ * @default false
680
+ */
681
+ autoContinue?: boolean;
682
+ });
683
+ interface UiElementInputScanApiResponse extends BaseUiMinimalInputResponse<"ui.input.scan"> {
684
+ mode: "single" | "multi";
679
685
  title?: string;
680
686
  description?: string;
681
687
  unit?: string;
682
688
  max?: number;
683
689
  min?: number;
684
- duplicateHandling: BulkScanDuplicateMode;
690
+ duplicateHandling: ScanDuplicateMode;
691
+ }
692
+
693
+ type UiElementFile = DisplayElementWithRequiredConfig<{
694
+ title?: string;
695
+ file: File;
696
+ }>;
697
+ interface UiElementFileApiResponse extends BaseUiDisplayResponse<"ui.display.file"> {
698
+ title: string;
699
+ file?: FileDbRecord & {
700
+ url: string;
701
+ };
685
702
  }
686
703
 
687
704
  interface UI<C extends FlowConfig> {
@@ -697,6 +714,7 @@ type UiInputsElements = {
697
714
  number: UiElementInputNumber;
698
715
  boolean: UiElementInputBoolean;
699
716
  dataGrid: UiElementInputDataGrid;
717
+ scan: UiElementScan;
700
718
  };
701
719
  type UiSelectElements = {
702
720
  one: UiElementSelectOne;
@@ -713,11 +731,11 @@ type UiDisplayElements = {
713
731
  list: UiElementList;
714
732
  table: UiElementTable;
715
733
  keyValue: UiElementKeyValue;
734
+ file: UiElementFile;
716
735
  };
717
736
  type UiInteractiveElements = {
718
737
  print: UiElementPrint;
719
738
  pickList: UiElementPickList;
720
- bulkScan: UiElementBulkScan;
721
739
  };
722
740
  type InputElement<TValueType, TConfig extends any = never> = <N extends string>(name: N, options?: BaseInputConfig<TValueType> & TConfig) => InputElementResponse<N, TValueType>;
723
741
  type DisplayElement<TConfig extends any = never> = (options?: TConfig) => DisplayElementResponse;
@@ -747,8 +765,10 @@ interface BaseInputConfig<T, O extends boolean = boolean> {
747
765
  optional?: O;
748
766
  disabled?: boolean;
749
767
  validate?: ValidateFn<T>;
768
+ onLeave?: CallbackFn<T, T>;
750
769
  }
751
770
  type ValidateFn<T> = (data: T) => Promise<boolean | string> | boolean | string;
771
+ type CallbackFn<InputT, OutputT> = (data: InputT) => Promise<OutputT> | OutputT;
752
772
  interface BaseUiInputResponse<K, TData> {
753
773
  __type: K;
754
774
  name: string;
@@ -779,12 +799,14 @@ type UIApiResponses = {
779
799
  list: UiElementListApiResponse;
780
800
  table: UiElementTableApiResponse;
781
801
  keyValue: UiElementKeyValueApiResponse;
802
+ file: UiElementFileApiResponse;
782
803
  };
783
804
  input: {
784
805
  text: UiElementInputTextApiResponse;
785
806
  number: UiElementInputNumberApiResponse;
786
807
  boolean: UiElementInputBooleanApiResponse;
787
808
  dataGrid: UiElementInputDataGridApiResponse;
809
+ scan: UiElementInputScanApiResponse;
788
810
  };
789
811
  select: {
790
812
  one: UiElementSelectOneApiResponse;
@@ -794,10 +816,9 @@ type UIApiResponses = {
794
816
  interactive: {
795
817
  print: UiElementPrintApiResponse;
796
818
  pickList: UiElementPickListApiResponse;
797
- bulkScan: UiElementBulkScanApiResponse;
798
819
  };
799
820
  };
800
- type UiElementApiResponse = UiElementDividerApiResponse | UiElementMarkdownApiResponse | UiElementHeaderApiResponse | UiElementBannerApiResponse | UiElementImageApiResponse | UiElementCodeApiResponse | UiElementGridApiResponse | UiElementListApiResponse | UiElementTableApiResponse | UiElementKeyValueApiResponse | UiElementInputTextApiResponse | UiElementInputNumberApiResponse | UiElementInputBooleanApiResponse | UiElementInputDataGridApiResponse | UiElementSelectOneApiResponse | UiElementSelectTableApiResponse | UiElementIteratorApiResponse | UiElementPrintApiResponse | UiElementPickListApiResponse | UiElementBulkScanApiResponse;
821
+ type UiElementApiResponse = UiElementDividerApiResponse | UiElementMarkdownApiResponse | UiElementHeaderApiResponse | UiElementBannerApiResponse | UiElementImageApiResponse | UiElementCodeApiResponse | UiElementGridApiResponse | UiElementListApiResponse | UiElementTableApiResponse | UiElementKeyValueApiResponse | UiElementFileApiResponse | UiElementInputTextApiResponse | UiElementInputNumberApiResponse | UiElementInputBooleanApiResponse | UiElementInputDataGridApiResponse | UiElementInputScanApiResponse | UiElementSelectOneApiResponse | UiElementSelectTableApiResponse | UiElementIteratorApiResponse | UiElementPrintApiResponse | UiElementPickListApiResponse;
801
822
  type UiElementApiResponses = UiElementApiResponse[];
802
823
 
803
824
  declare class NonRetriableError extends Error {
@@ -906,7 +927,7 @@ type StageConfigObject = {
906
927
  initiallyHidden?: boolean;
907
928
  };
908
929
  type StageConfig = string | StageConfigObject;
909
- declare function createFlowContext<C extends FlowConfig, E, S, Id, I>(runId: string, data: any, action: string | null, spanId: string, ctx: {
930
+ declare function createFlowContext<C extends FlowConfig, E, S, Id, I>(runId: string, data: any, action: string | null, callback: string | null, element: string | null, spanId: string, ctx: {
910
931
  env: E;
911
932
  now: Date;
912
933
  secrets: S;
package/dist/index.d.ts CHANGED
@@ -355,6 +355,7 @@ type PageOptions<C extends FlowConfig, A extends PageActions[], T extends UIElem
355
355
  content: T;
356
356
  validate?: ValidateFn<ExtractFormData<T>>;
357
357
  actions?: A;
358
+ allowBack?: boolean;
358
359
  };
359
360
  type UiPage<C extends FlowConfig> = <T extends UIElements, const A extends PageActions[] = []>(name: string, options: PageOptions<C, A, T>) => Promise<A["length"] extends 0 ? ExtractFormData<T> : {
360
361
  data: ExtractFormData<T>;
@@ -639,15 +640,8 @@ interface UiElementPickListApiResponse extends BaseUiMinimalInputResponse<"ui.in
639
640
  data: PickListItem[];
640
641
  }
641
642
 
642
- type UiElementBulkScan = {
643
- <N extends string>(name: N): InputElementResponse<N, {
644
- scans: string[];
645
- }>;
646
- <N extends string, const M extends BulkScanDuplicateMode>(name: N, options?: BulkScanOptions<M>): InputElementResponse<N, {
647
- scans: BulkScanResponseItem<M>[];
648
- }>;
649
- };
650
- type BulkScanResponseItem<M> = M extends "trackQuantity" ? {
643
+ type UiElementScan = <N extends string, const M extends scanMode = "single", const D extends ScanDuplicateMode = "none">(name: N, options?: ScanOptions<M, D>) => InputElementResponse<N, M extends "single" ? string : ScanResponseItem<D>[]>;
644
+ type ScanResponseItem<D> = D extends "trackQuantity" ? {
651
645
  value: string;
652
646
  quantity: number;
653
647
  } : string;
@@ -655,33 +649,56 @@ type BulkScanResponseItem<M> = M extends "trackQuantity" ? {
655
649
  * Defines how duplicate scans should be handled.
656
650
  * @default "none"
657
651
  */
658
- type BulkScanDuplicateMode =
652
+ type ScanDuplicateMode =
659
653
  /** No duplicate handling - all scans are accepted as separate entries */
660
654
  "none"
661
655
  /** Track quantity when duplicates are scanned */
662
656
  | "trackQuantity"
663
657
  /** Reject duplicate scans with an error message */
664
658
  | "rejectDuplicates";
665
- type BulkScanOptions<M> = {
666
- /** The singular unit of the item being scanned. E.g. "box", "bottle", "product" etc */
667
- unit?: string;
659
+ type scanMode = "single" | "multi";
660
+ type ScanOptions<M, D> = {
668
661
  /** The title of the input block
669
662
  * @default "Scan {unit plural | 'items'}"
670
663
  */
671
664
  title?: string;
672
665
  description?: string;
666
+ /** The singular unit of the item being scanned. E.g. "box", "bottle", "product" etc */
667
+ unit?: string;
668
+ /** The mode of the scan input
669
+ * @default "single"
670
+ */
671
+ mode: M | scanMode;
672
+ validate?: ValidateFn<ScanResponseItem<D>>;
673
+ } & (M extends "multi" ? {
673
674
  max?: number;
674
675
  min?: number;
675
- duplicateHandling?: M;
676
- validate?: ValidateFn<BulkScanResponseItem<M>>;
677
- };
678
- interface UiElementBulkScanApiResponse extends BaseUiMinimalInputResponse<"ui.interactive.bulkScan"> {
676
+ duplicateHandling?: D | ScanDuplicateMode;
677
+ } : {
678
+ /** Whether to automatically continue to the next step when the user scans an item
679
+ * @default false
680
+ */
681
+ autoContinue?: boolean;
682
+ });
683
+ interface UiElementInputScanApiResponse extends BaseUiMinimalInputResponse<"ui.input.scan"> {
684
+ mode: "single" | "multi";
679
685
  title?: string;
680
686
  description?: string;
681
687
  unit?: string;
682
688
  max?: number;
683
689
  min?: number;
684
- duplicateHandling: BulkScanDuplicateMode;
690
+ duplicateHandling: ScanDuplicateMode;
691
+ }
692
+
693
+ type UiElementFile = DisplayElementWithRequiredConfig<{
694
+ title?: string;
695
+ file: File;
696
+ }>;
697
+ interface UiElementFileApiResponse extends BaseUiDisplayResponse<"ui.display.file"> {
698
+ title: string;
699
+ file?: FileDbRecord & {
700
+ url: string;
701
+ };
685
702
  }
686
703
 
687
704
  interface UI<C extends FlowConfig> {
@@ -697,6 +714,7 @@ type UiInputsElements = {
697
714
  number: UiElementInputNumber;
698
715
  boolean: UiElementInputBoolean;
699
716
  dataGrid: UiElementInputDataGrid;
717
+ scan: UiElementScan;
700
718
  };
701
719
  type UiSelectElements = {
702
720
  one: UiElementSelectOne;
@@ -713,11 +731,11 @@ type UiDisplayElements = {
713
731
  list: UiElementList;
714
732
  table: UiElementTable;
715
733
  keyValue: UiElementKeyValue;
734
+ file: UiElementFile;
716
735
  };
717
736
  type UiInteractiveElements = {
718
737
  print: UiElementPrint;
719
738
  pickList: UiElementPickList;
720
- bulkScan: UiElementBulkScan;
721
739
  };
722
740
  type InputElement<TValueType, TConfig extends any = never> = <N extends string>(name: N, options?: BaseInputConfig<TValueType> & TConfig) => InputElementResponse<N, TValueType>;
723
741
  type DisplayElement<TConfig extends any = never> = (options?: TConfig) => DisplayElementResponse;
@@ -747,8 +765,10 @@ interface BaseInputConfig<T, O extends boolean = boolean> {
747
765
  optional?: O;
748
766
  disabled?: boolean;
749
767
  validate?: ValidateFn<T>;
768
+ onLeave?: CallbackFn<T, T>;
750
769
  }
751
770
  type ValidateFn<T> = (data: T) => Promise<boolean | string> | boolean | string;
771
+ type CallbackFn<InputT, OutputT> = (data: InputT) => Promise<OutputT> | OutputT;
752
772
  interface BaseUiInputResponse<K, TData> {
753
773
  __type: K;
754
774
  name: string;
@@ -779,12 +799,14 @@ type UIApiResponses = {
779
799
  list: UiElementListApiResponse;
780
800
  table: UiElementTableApiResponse;
781
801
  keyValue: UiElementKeyValueApiResponse;
802
+ file: UiElementFileApiResponse;
782
803
  };
783
804
  input: {
784
805
  text: UiElementInputTextApiResponse;
785
806
  number: UiElementInputNumberApiResponse;
786
807
  boolean: UiElementInputBooleanApiResponse;
787
808
  dataGrid: UiElementInputDataGridApiResponse;
809
+ scan: UiElementInputScanApiResponse;
788
810
  };
789
811
  select: {
790
812
  one: UiElementSelectOneApiResponse;
@@ -794,10 +816,9 @@ type UIApiResponses = {
794
816
  interactive: {
795
817
  print: UiElementPrintApiResponse;
796
818
  pickList: UiElementPickListApiResponse;
797
- bulkScan: UiElementBulkScanApiResponse;
798
819
  };
799
820
  };
800
- type UiElementApiResponse = UiElementDividerApiResponse | UiElementMarkdownApiResponse | UiElementHeaderApiResponse | UiElementBannerApiResponse | UiElementImageApiResponse | UiElementCodeApiResponse | UiElementGridApiResponse | UiElementListApiResponse | UiElementTableApiResponse | UiElementKeyValueApiResponse | UiElementInputTextApiResponse | UiElementInputNumberApiResponse | UiElementInputBooleanApiResponse | UiElementInputDataGridApiResponse | UiElementSelectOneApiResponse | UiElementSelectTableApiResponse | UiElementIteratorApiResponse | UiElementPrintApiResponse | UiElementPickListApiResponse | UiElementBulkScanApiResponse;
821
+ type UiElementApiResponse = UiElementDividerApiResponse | UiElementMarkdownApiResponse | UiElementHeaderApiResponse | UiElementBannerApiResponse | UiElementImageApiResponse | UiElementCodeApiResponse | UiElementGridApiResponse | UiElementListApiResponse | UiElementTableApiResponse | UiElementKeyValueApiResponse | UiElementFileApiResponse | UiElementInputTextApiResponse | UiElementInputNumberApiResponse | UiElementInputBooleanApiResponse | UiElementInputDataGridApiResponse | UiElementInputScanApiResponse | UiElementSelectOneApiResponse | UiElementSelectTableApiResponse | UiElementIteratorApiResponse | UiElementPrintApiResponse | UiElementPickListApiResponse;
801
822
  type UiElementApiResponses = UiElementApiResponse[];
802
823
 
803
824
  declare class NonRetriableError extends Error {
@@ -906,7 +927,7 @@ type StageConfigObject = {
906
927
  initiallyHidden?: boolean;
907
928
  };
908
929
  type StageConfig = string | StageConfigObject;
909
- declare function createFlowContext<C extends FlowConfig, E, S, Id, I>(runId: string, data: any, action: string | null, spanId: string, ctx: {
930
+ declare function createFlowContext<C extends FlowConfig, E, S, Id, I>(runId: string, data: any, action: string | null, callback: string | null, element: string | null, spanId: string, ctx: {
910
931
  env: E;
911
932
  now: Date;
912
933
  secrets: S;
package/dist/index.js CHANGED
@@ -736,9 +736,9 @@ var InlineFile = class _InlineFile {
736
736
  const mime = info.split(";")[0];
737
737
  const name = info.split(";")[1].split("=")[1];
738
738
  const buffer = Buffer.from(data, "base64");
739
- const file = new _InlineFile({ filename: name, contentType: mime });
740
- file.write(buffer);
741
- return file;
739
+ const file2 = new _InlineFile({ filename: name, contentType: mime });
740
+ file2.write(buffer);
741
+ return file2;
742
742
  }
743
743
  // Gets size of the file's contents in bytes
744
744
  get size() {
@@ -2332,6 +2332,7 @@ var textInput = /* @__PURE__ */ __name((name, options) => {
2332
2332
  minLength: options?.minLength
2333
2333
  },
2334
2334
  validate: options?.validate,
2335
+ onLeave: options?.onLeave,
2335
2336
  getData: /* @__PURE__ */ __name((x) => x, "getData")
2336
2337
  };
2337
2338
  }, "textInput");
@@ -2353,6 +2354,7 @@ var numberInput = /* @__PURE__ */ __name((name, options) => {
2353
2354
  max: options?.max
2354
2355
  },
2355
2356
  validate: options?.validate,
2357
+ onLeave: options?.onLeave,
2356
2358
  getData: /* @__PURE__ */ __name((x) => x, "getData")
2357
2359
  };
2358
2360
  }, "numberInput");
@@ -2381,6 +2383,7 @@ var booleanInput = /* @__PURE__ */ __name((name, options) => {
2381
2383
  mode: options?.mode || "checkbox"
2382
2384
  },
2383
2385
  validate: options?.validate,
2386
+ onLeave: options?.onLeave,
2384
2387
  getData: /* @__PURE__ */ __name((x) => x, "getData")
2385
2388
  };
2386
2389
  }, "booleanInput");
@@ -2442,6 +2445,22 @@ var selectOne = /* @__PURE__ */ __name((name, options) => {
2442
2445
  }, "selectOne");
2443
2446
 
2444
2447
  // src/flows/ui/page.ts
2448
+ async function callbackFn(elements, elementName, callbackName, data) {
2449
+ const element = elements.find(
2450
+ (el) => el.uiConfig && el.uiConfig.name === elementName
2451
+ );
2452
+ if (!element) {
2453
+ throw new Error(`Element with name ${elementName} not found`);
2454
+ }
2455
+ const cb = element[callbackName];
2456
+ if (typeof cb !== "function") {
2457
+ throw new Error(
2458
+ `Callback ${callbackName} not found on element ${elementName}`
2459
+ );
2460
+ }
2461
+ return await cb(data);
2462
+ }
2463
+ __name(callbackFn, "callbackFn");
2445
2464
  async function page(options, data, action) {
2446
2465
  const content = options.content;
2447
2466
  let hasValidationErrors = false;
@@ -2491,7 +2510,8 @@ async function page(options, data, action) {
2491
2510
  return a;
2492
2511
  }),
2493
2512
  hasValidationErrors,
2494
- validationError
2513
+ validationError,
2514
+ allowBack: options.allowBack
2495
2515
  },
2496
2516
  hasValidationErrors
2497
2517
  };
@@ -2628,6 +2648,16 @@ var ExhuastedRetriesDisrupt = class extends FlowDisrupt {
2628
2648
  super();
2629
2649
  }
2630
2650
  };
2651
+ var CallbackDisrupt = class extends FlowDisrupt {
2652
+ constructor(response, error) {
2653
+ super();
2654
+ this.response = response;
2655
+ this.error = error;
2656
+ }
2657
+ static {
2658
+ __name(this, "CallbackDisrupt");
2659
+ }
2660
+ };
2631
2661
 
2632
2662
  // src/flows/ui/elements/display/banner.ts
2633
2663
  var banner = /* @__PURE__ */ __name((options) => {
@@ -2883,32 +2913,48 @@ var NonRetriableError = class extends Error {
2883
2913
  }
2884
2914
  };
2885
2915
 
2886
- // src/flows/ui/elements/interactive/bulkScan.ts
2887
- var bulkScan = /* @__PURE__ */ __name((name, options) => {
2916
+ // src/flows/ui/elements/input/scan.ts
2917
+ var isMultiMode = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiMode");
2918
+ var scan = /* @__PURE__ */ __name((name, options) => {
2888
2919
  return {
2889
2920
  __type: "input",
2890
2921
  uiConfig: {
2891
- __type: "ui.interactive.bulkScan",
2922
+ __type: "ui.input.scan",
2892
2923
  name,
2893
2924
  title: options?.title ?? void 0,
2894
2925
  description: options?.description ?? void 0,
2895
2926
  unit: options?.unit ?? void 0,
2896
- max: options?.max ?? void 0,
2897
- min: options?.min ?? void 0,
2898
- duplicateHandling: options?.duplicateHandling ?? "none"
2927
+ mode: options?.mode ?? "single",
2928
+ duplicateHandling: "none",
2929
+ ...isMultiMode(options) ? {
2930
+ max: options.max ?? void 0,
2931
+ min: options.min ?? void 0,
2932
+ duplicateHandling: options.duplicateHandling ?? "none"
2933
+ } : {}
2899
2934
  },
2900
2935
  validate: /* @__PURE__ */ __name(async (data) => {
2901
- if (!("scans" in data)) {
2902
- return "Missing scans in response";
2903
- }
2904
- if (!Array.isArray(data.scans)) {
2905
- return "Scans must be an array";
2906
- }
2907
2936
  return options?.validate?.(data) ?? true;
2908
2937
  }, "validate"),
2909
2938
  getData: /* @__PURE__ */ __name((x) => x, "getData")
2910
2939
  };
2911
- }, "bulkScan");
2940
+ }, "scan");
2941
+
2942
+ // src/flows/ui/elements/display/file.ts
2943
+ var file = /* @__PURE__ */ __name(async (options) => {
2944
+ const title = options.title || options.file.filename;
2945
+ const url = await options.file.getPresignedUrl();
2946
+ const metadata = await options.file.toJSON();
2947
+ return {
2948
+ uiConfig: {
2949
+ __type: "ui.display.file",
2950
+ title,
2951
+ file: metadata ? {
2952
+ url: url?.toString() || "",
2953
+ ...metadata
2954
+ } : void 0
2955
+ }
2956
+ };
2957
+ }, "file");
2912
2958
 
2913
2959
  // src/flows/index.ts
2914
2960
  var STEP_STATUS = /* @__PURE__ */ ((STEP_STATUS2) => {
@@ -2944,7 +2990,7 @@ var defaultOpts = {
2944
2990
  retries: 4,
2945
2991
  timeout: 6e4
2946
2992
  };
2947
- function createFlowContext(runId, data, action, spanId, ctx) {
2993
+ function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
2948
2994
  const usedNames = /* @__PURE__ */ new Set();
2949
2995
  return {
2950
2996
  identity: ctx.identity,
@@ -3056,6 +3102,7 @@ function createFlowContext(runId, data, action, spanId, ctx) {
3056
3102
  ui: {
3057
3103
  page: /* @__PURE__ */ __name(async (name, options) => {
3058
3104
  const db = useDatabase();
3105
+ const isCallback = element && callback;
3059
3106
  if (usedNames.has(name)) {
3060
3107
  await db.insertInto("keel.flow_step").values({
3061
3108
  run_id: runId,
@@ -3091,6 +3138,25 @@ function createFlowContext(runId, data, action, spanId, ctx) {
3091
3138
  (await page(options, null, null)).page
3092
3139
  );
3093
3140
  }
3141
+ if (isCallback) {
3142
+ try {
3143
+ const response = await callbackFn(
3144
+ options.content,
3145
+ element,
3146
+ callback,
3147
+ data
3148
+ );
3149
+ throw new CallbackDisrupt(response, false);
3150
+ } catch (e) {
3151
+ if (e instanceof CallbackDisrupt) {
3152
+ throw e;
3153
+ }
3154
+ throw new CallbackDisrupt(
3155
+ e instanceof Error ? e.message : `An error occurred`,
3156
+ true
3157
+ );
3158
+ }
3159
+ }
3094
3160
  if (!data) {
3095
3161
  throw new UIRenderDisrupt(
3096
3162
  step?.id,
@@ -3127,7 +3193,8 @@ function createFlowContext(runId, data, action, spanId, ctx) {
3127
3193
  text: textInput,
3128
3194
  number: numberInput,
3129
3195
  boolean: booleanInput,
3130
- dataGrid: dataGridInput
3196
+ dataGrid: dataGridInput,
3197
+ scan
3131
3198
  },
3132
3199
  display: {
3133
3200
  divider,
@@ -3139,7 +3206,8 @@ function createFlowContext(runId, data, action, spanId, ctx) {
3139
3206
  code,
3140
3207
  grid,
3141
3208
  list,
3142
- keyValue
3209
+ keyValue,
3210
+ file
3143
3211
  },
3144
3212
  select: {
3145
3213
  one: selectOne,
@@ -3148,8 +3216,7 @@ function createFlowContext(runId, data, action, spanId, ctx) {
3148
3216
  iterator,
3149
3217
  interactive: {
3150
3218
  print,
3151
- pickList,
3152
- bulkScan
3219
+ pickList
3153
3220
  }
3154
3221
  }
3155
3222
  };
@@ -3225,6 +3292,8 @@ async function handleFlow(request, config) {
3225
3292
  request.meta.runId,
3226
3293
  request.meta.data,
3227
3294
  request.meta.action,
3295
+ request.meta.callback,
3296
+ request.meta.element,
3228
3297
  span.spanContext().spanId,
3229
3298
  createFlowContextAPI({
3230
3299
  meta: request.meta
@@ -3260,6 +3329,12 @@ async function handleFlow(request, config) {
3260
3329
  executeAfter: e.executeAfter
3261
3330
  });
3262
3331
  }
3332
+ if (e instanceof CallbackDisrupt) {
3333
+ if (e.error) {
3334
+ return createJSONRPCErrorResponse6(request.id, 500, e.response);
3335
+ }
3336
+ return createJSONRPCSuccessResponse5(request.id, e.response);
3337
+ }
3263
3338
  if (e instanceof UIRenderDisrupt) {
3264
3339
  return createJSONRPCSuccessResponse5(request.id, {
3265
3340
  runId,