@teamkeel/functions-runtime 0.414.4 → 0.414.6

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.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  InlineFile: () => InlineFile,
38
38
  KSUID: () => import_ksuid2.default,
39
39
  ModelAPI: () => ModelAPI,
40
+ NonRetriableError: () => NonRetriableError,
40
41
  PERMISSION_STATE: () => PERMISSION_STATE,
41
42
  Permissions: () => Permissions,
42
43
  RequestHeaders: () => RequestHeaders,
@@ -1977,8 +1978,8 @@ async function handleRequest(request, config) {
1977
1978
  let db = null;
1978
1979
  try {
1979
1980
  const { createContextAPI, functions, permissionFns, actionTypes } = config;
1980
- if (!(request.method in functions)) {
1981
- const message = `no corresponding function found for '${request.method}'`;
1981
+ if (!functions[request.method]) {
1982
+ const message = `function '${request.method}' does not exist or has not been implemented`;
1982
1983
  span.setStatus({
1983
1984
  code: opentelemetry2.SpanStatusCode.ERROR,
1984
1985
  message
@@ -2098,8 +2099,8 @@ async function handleJob(request, config) {
2098
2099
  let db = null;
2099
2100
  try {
2100
2101
  const { createJobContextAPI, jobs } = config;
2101
- if (!(request.method in jobs)) {
2102
- const message = `no corresponding job found for '${request.method}'`;
2102
+ if (!jobs[request.method]) {
2103
+ const message = `job '${request.method}' does not exist or has not been implemented`;
2103
2104
  span.setStatus({
2104
2105
  code: opentelemetry3.SpanStatusCode.ERROR,
2105
2106
  message
@@ -2186,8 +2187,8 @@ async function handleSubscriber(request, config) {
2186
2187
  let db = null;
2187
2188
  try {
2188
2189
  const { createSubscriberContextAPI, subscribers } = config;
2189
- if (!(request.method in subscribers)) {
2190
- const message = `no corresponding subscriber found for '${request.method}'`;
2190
+ if (!subscribers[request.method]) {
2191
+ const message = `subscriber '${request.method}' does not exist or has not been implemented`;
2191
2192
  span.setStatus({
2192
2193
  code: opentelemetry4.SpanStatusCode.ERROR,
2193
2194
  message
@@ -2257,8 +2258,8 @@ async function handleRoute(request, config) {
2257
2258
  let db = null;
2258
2259
  try {
2259
2260
  const { createContextAPI, functions } = config;
2260
- if (!(request.method in functions)) {
2261
- const message = `no route function found for '${request.method}'`;
2261
+ if (!functions[request.method]) {
2262
+ const message = `route function '${request.method}' does not exist or has not been implemented`;
2262
2263
  span.setStatus({
2263
2264
  code: opentelemetry5.SpanStatusCode.ERROR,
2264
2265
  message
@@ -2421,20 +2422,30 @@ var markdown = /* @__PURE__ */ __name((options) => {
2421
2422
 
2422
2423
  // src/flows/ui/elements/display/table.ts
2423
2424
  var table = /* @__PURE__ */ __name((options) => {
2424
- const filteredData = options.columns ? options.data.map((item) => {
2425
- return Object.fromEntries(
2426
- Object.entries(item).filter(
2427
- ([key]) => options.columns?.includes(key)
2428
- )
2429
- );
2430
- }) : options.data;
2425
+ const { data, columns } = processTableData(options.data, options.columns);
2431
2426
  return {
2432
2427
  uiConfig: {
2433
2428
  __type: "ui.display.table",
2434
- data: filteredData || []
2429
+ data: data || [],
2430
+ columns: columns || []
2435
2431
  }
2436
2432
  };
2437
2433
  }, "table");
2434
+ var processTableData = /* @__PURE__ */ __name((data, columnsConfig) => {
2435
+ const filteredData = columnsConfig ? data.map((item) => {
2436
+ return Object.fromEntries(
2437
+ Object.entries(item).filter(
2438
+ ([key]) => columnsConfig?.includes(key)
2439
+ )
2440
+ );
2441
+ }) : data;
2442
+ const cols = Object.keys(filteredData[0] || {});
2443
+ const columns = cols.map((column, index) => ({
2444
+ name: column,
2445
+ index
2446
+ }));
2447
+ return { data: filteredData, columns };
2448
+ }, "processTableData");
2438
2449
 
2439
2450
  // src/flows/ui/elements/select/one.ts
2440
2451
  var selectOne = /* @__PURE__ */ __name((name, options) => {
@@ -2459,30 +2470,41 @@ var selectOne = /* @__PURE__ */ __name((name, options) => {
2459
2470
  async function page(options, data, action) {
2460
2471
  const content = options.content;
2461
2472
  let hasValidationErrors = false;
2462
- if (options.actions) {
2473
+ let validationError;
2474
+ if (options.actions && action !== null) {
2463
2475
  const isValidAction = options.actions.some((a) => {
2464
2476
  if (typeof a === "string") return a === action;
2465
2477
  return a && typeof a === "object" && "value" in a && a.value === action;
2466
2478
  });
2467
- hasValidationErrors = !isValidAction;
2479
+ if (!isValidAction) {
2480
+ hasValidationErrors = true;
2481
+ validationError = "invalid action";
2482
+ }
2468
2483
  }
2469
2484
  const contentUiConfig = await Promise.all(
2470
2485
  content.map(async (c) => {
2471
2486
  const isInput = "__type" in c && c.__type == "input";
2472
2487
  const hasData = data && c.uiConfig.name in data;
2473
2488
  if (isInput && hasData && c.validate) {
2474
- const validationError = await c.validate(data[c.uiConfig.name]);
2475
- if (typeof validationError === "string") {
2489
+ const validationError2 = await c.validate(data[c.uiConfig.name]);
2490
+ if (typeof validationError2 === "string") {
2476
2491
  hasValidationErrors = true;
2477
2492
  return {
2478
2493
  ...c.uiConfig,
2479
- validationError
2494
+ validationError: validationError2
2480
2495
  };
2481
2496
  }
2482
2497
  }
2483
2498
  return c.uiConfig;
2484
2499
  }).filter(Boolean)
2485
2500
  );
2501
+ if (data && options.validate) {
2502
+ const validationResult = await options.validate(data);
2503
+ if (typeof validationResult === "string") {
2504
+ hasValidationErrors = true;
2505
+ validationError = validationResult;
2506
+ }
2507
+ }
2486
2508
  return {
2487
2509
  page: {
2488
2510
  __type: "ui.page",
@@ -2497,7 +2519,9 @@ async function page(options, data, action) {
2497
2519
  a.mode = a.mode || "primary";
2498
2520
  }
2499
2521
  return a;
2500
- })
2522
+ }),
2523
+ hasValidationErrors,
2524
+ validationError
2501
2525
  },
2502
2526
  hasValidationErrors
2503
2527
  };
@@ -2632,6 +2656,89 @@ var keyValue = /* @__PURE__ */ __name((options) => {
2632
2656
  };
2633
2657
  }, "keyValue");
2634
2658
 
2659
+ // src/flows/ui/elements/select/table.ts
2660
+ var selectTable = /* @__PURE__ */ __name((name, options) => {
2661
+ const { data, columns } = processTableData(options.data, options.columns);
2662
+ return {
2663
+ __type: "input",
2664
+ uiConfig: {
2665
+ __type: "ui.select.table",
2666
+ name,
2667
+ data,
2668
+ columns,
2669
+ mode: options?.mode || "multi",
2670
+ optional: options?.optional || false,
2671
+ disabled: options?.disabled || false,
2672
+ helpText: options?.helpText
2673
+ },
2674
+ validate: options?.validate,
2675
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2676
+ };
2677
+ }, "selectTable");
2678
+
2679
+ // src/flows/ui/elements/input/dataGrid.ts
2680
+ var import_change_case2 = require("change-case");
2681
+ var dataGridInput = /* @__PURE__ */ __name((name, options) => {
2682
+ const { data } = processTableData(
2683
+ options.data,
2684
+ options.columns?.map((c) => c.key)
2685
+ );
2686
+ const inferType = /* @__PURE__ */ __name((key) => {
2687
+ const inferredTypeRaw = typeof data[0][key];
2688
+ const inferredTypeMap = {
2689
+ string: "text",
2690
+ number: "number",
2691
+ boolean: "boolean",
2692
+ bigint: "number",
2693
+ symbol: "text",
2694
+ undefined: "text",
2695
+ object: "text",
2696
+ function: "text"
2697
+ };
2698
+ return inferredTypeMap[inferredTypeRaw] ?? "text";
2699
+ }, "inferType");
2700
+ const columns = options.columns ? options.columns?.map((column, idx) => ({
2701
+ key: column.key,
2702
+ label: column.label || column.key,
2703
+ index: idx,
2704
+ type: column.type || inferType(column.key),
2705
+ editable: column.editable === void 0 ? column.type === "id" ? false : true : column.editable
2706
+ })) : Object.keys(data[0]).map((key, idx) => {
2707
+ return {
2708
+ index: idx,
2709
+ key,
2710
+ label: (0, import_change_case2.sentenceCase)(key),
2711
+ type: inferType(key),
2712
+ editable: true
2713
+ };
2714
+ });
2715
+ return {
2716
+ __type: "input",
2717
+ uiConfig: {
2718
+ __type: "ui.input.dataGrid",
2719
+ name,
2720
+ data,
2721
+ columns,
2722
+ helpText: options?.helpText,
2723
+ allowAddRows: options?.allowAddRows ?? false,
2724
+ allowDeleteRows: options?.allowDeleteRows ?? false
2725
+ },
2726
+ validate: options?.validate,
2727
+ // TODO have some built in validation that checks the types of the response
2728
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2729
+ };
2730
+ }, "dataGridInput");
2731
+
2732
+ // src/flows/errors.ts
2733
+ var NonRetriableError = class extends Error {
2734
+ static {
2735
+ __name(this, "NonRetriableError");
2736
+ }
2737
+ constructor(message) {
2738
+ super(message);
2739
+ }
2740
+ };
2741
+
2635
2742
  // src/flows/index.ts
2636
2743
  var STEP_STATUS = /* @__PURE__ */ ((STEP_STATUS2) => {
2637
2744
  STEP_STATUS2["NEW"] = "NEW";
@@ -2649,7 +2756,7 @@ var STEP_TYPE = /* @__PURE__ */ ((STEP_TYPE2) => {
2649
2756
  return STEP_TYPE2;
2650
2757
  })(STEP_TYPE || {});
2651
2758
  var defaultOpts = {
2652
- retries: 5,
2759
+ retries: 4,
2653
2760
  timeout: 6e4
2654
2761
  };
2655
2762
  function createFlowContext(runId, data, action, spanId, ctx) {
@@ -2659,10 +2766,17 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2659
2766
  env: ctx.env,
2660
2767
  now: ctx.now,
2661
2768
  secrets: ctx.secrets,
2662
- complete: /* @__PURE__ */ __name((options) => options, "complete"),
2769
+ complete: /* @__PURE__ */ __name((options) => {
2770
+ return {
2771
+ __type: "ui.complete",
2772
+ ...options
2773
+ };
2774
+ }, "complete"),
2663
2775
  step: /* @__PURE__ */ __name(async (name, optionsOrFn, fn) => {
2664
2776
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
2665
2777
  const actualFn = typeof optionsOrFn === "function" ? optionsOrFn : fn;
2778
+ options.retries = options.retries ?? defaultOpts.retries;
2779
+ options.timeout = options.timeout ?? defaultOpts.timeout;
2666
2780
  const db = useDatabase();
2667
2781
  if (usedNames.has(name)) {
2668
2782
  await db.insertInto("keel.flow_step").values({
@@ -2706,10 +2820,11 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2706
2820
  startTime: /* @__PURE__ */ new Date()
2707
2821
  }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
2708
2822
  try {
2709
- result = await withTimeout(
2710
- actualFn(),
2711
- options.timeout ?? defaultOpts.timeout
2712
- );
2823
+ const stepArgs = {
2824
+ attempt: failedSteps.length + 1,
2825
+ stepOptions: options
2826
+ };
2827
+ result = await withTimeout(actualFn(stepArgs), options.timeout);
2713
2828
  } catch (e) {
2714
2829
  await db.updateTable("keel.flow_step").set({
2715
2830
  status: "FAILED" /* FAILED */,
@@ -2717,7 +2832,10 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2717
2832
  endTime: /* @__PURE__ */ new Date(),
2718
2833
  error: e instanceof Error ? e.message : "An error occurred"
2719
2834
  }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
2720
- if (failedSteps.length + 1 >= (options.retries ?? defaultOpts.retries)) {
2835
+ if (failedSteps.length >= options.retries || e instanceof NonRetriableError) {
2836
+ if (options.onFailure) {
2837
+ await options.onFailure();
2838
+ }
2721
2839
  throw new ExhuastedRetriesDisrupt();
2722
2840
  }
2723
2841
  await db.insertInto("keel.flow_step").values({
@@ -2790,9 +2908,22 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2790
2908
  (await page(options, null, null)).page
2791
2909
  );
2792
2910
  }
2793
- const p = await page(options, data, action);
2794
- if (p.hasValidationErrors) {
2795
- throw new UIRenderDisrupt(step?.id, p.page);
2911
+ try {
2912
+ const p = await page(options, data, action);
2913
+ if (p.hasValidationErrors) {
2914
+ throw new UIRenderDisrupt(step?.id, p.page);
2915
+ }
2916
+ } catch (e) {
2917
+ if (e instanceof UIRenderDisrupt) {
2918
+ throw e;
2919
+ }
2920
+ await db.updateTable("keel.flow_step").set({
2921
+ status: "FAILED" /* FAILED */,
2922
+ spanId,
2923
+ endTime: /* @__PURE__ */ new Date(),
2924
+ error: e instanceof Error ? e.message : "An error occurred"
2925
+ }).where("id", "=", step?.id).returningAll().executeTakeFirst();
2926
+ throw e;
2796
2927
  }
2797
2928
  await db.updateTable("keel.flow_step").set({
2798
2929
  status: "COMPLETED" /* COMPLETED */,
@@ -2806,7 +2937,8 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2806
2937
  inputs: {
2807
2938
  text: textInput,
2808
2939
  number: numberInput,
2809
- boolean: booleanInput
2940
+ boolean: booleanInput,
2941
+ dataGrid: dataGridInput
2810
2942
  },
2811
2943
  display: {
2812
2944
  divider,
@@ -2821,7 +2953,8 @@ function createFlowContext(runId, data, action, spanId, ctx) {
2821
2953
  keyValue
2822
2954
  },
2823
2955
  select: {
2824
- one: selectOne
2956
+ one: selectOne,
2957
+ table: selectTable
2825
2958
  }
2826
2959
  }
2827
2960
  };
@@ -2843,26 +2976,25 @@ __name(withTimeout, "withTimeout");
2843
2976
 
2844
2977
  // src/flows/ui/complete.ts
2845
2978
  async function complete(options) {
2846
- const content = options.content;
2979
+ const content = options.content || [];
2847
2980
  const contentUiConfig = await Promise.all(
2848
2981
  content.map(async (c) => {
2849
2982
  return c.uiConfig;
2850
2983
  })
2851
2984
  );
2852
2985
  return {
2853
- complete: {
2854
- __type: "ui.complete",
2855
- stage: options.stage,
2856
- title: options.title,
2857
- description: options.description,
2858
- content: contentUiConfig
2859
- }
2986
+ __type: "ui.complete",
2987
+ stage: options.stage,
2988
+ title: options.title,
2989
+ description: options.description,
2990
+ content: contentUiConfig || [],
2991
+ autoClose: options.autoClose
2860
2992
  };
2861
2993
  }
2862
2994
  __name(complete, "complete");
2863
2995
 
2864
2996
  // src/handleFlow.ts
2865
- var import_change_case2 = require("change-case");
2997
+ var import_change_case3 = require("change-case");
2866
2998
  async function handleFlow(request, config) {
2867
2999
  const activeContext = opentelemetry6.propagation.extract(
2868
3000
  opentelemetry6.context.active(),
@@ -2878,8 +3010,8 @@ async function handleFlow(request, config) {
2878
3010
  throw new Error("no runId provided");
2879
3011
  }
2880
3012
  const { flows, createFlowContextAPI } = config;
2881
- if (!(request.method in flows)) {
2882
- const message = `no corresponding flow found for '${request.method}'`;
3013
+ if (!flows[request.method]) {
3014
+ const message = `flow '${request.method}' does not exist or has not been implemented`;
2883
3015
  span.setStatus({
2884
3016
  code: opentelemetry6.SpanStatusCode.ERROR,
2885
3017
  message
@@ -2906,7 +3038,7 @@ async function handleFlow(request, config) {
2906
3038
  const rawFlowConfig = flows[request.method].config;
2907
3039
  flowConfig = {
2908
3040
  ...rawFlowConfig,
2909
- title: rawFlowConfig.title || (0, import_change_case2.sentenceCase)(request.method || "flow"),
3041
+ title: rawFlowConfig.title || (0, import_change_case3.sentenceCase)(request.method || "flow"),
2910
3042
  stages: rawFlowConfig.stages?.map((stage) => {
2911
3043
  if (typeof stage === "string") {
2912
3044
  return {
@@ -2961,7 +3093,8 @@ async function handleFlow(request, config) {
2961
3093
  }
2962
3094
  let ui = null;
2963
3095
  let data = null;
2964
- if (response && typeof response == "object" && "content" in response) {
3096
+ if (response && typeof response == "object" && "__type" in response && response.__type === "ui.complete") {
3097
+ ui = await complete(response);
2965
3098
  const completeStep = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("type", "=", "COMPLETE" /* COMPLETE */).selectAll().executeTakeFirst();
2966
3099
  if (!completeStep) {
2967
3100
  await db.insertInto("keel.flow_step").values({
@@ -2971,10 +3104,10 @@ async function handleFlow(request, config) {
2971
3104
  status: "COMPLETED" /* COMPLETED */,
2972
3105
  type: "COMPLETE" /* COMPLETE */,
2973
3106
  startTime: /* @__PURE__ */ new Date(),
2974
- endTime: /* @__PURE__ */ new Date()
3107
+ endTime: /* @__PURE__ */ new Date(),
3108
+ ui: JSON.stringify(ui)
2975
3109
  }).returningAll().executeTakeFirst();
2976
3110
  }
2977
- ui = (await complete(response)).complete;
2978
3111
  data = response.data;
2979
3112
  } else if (response) {
2980
3113
  data = response;
@@ -2983,8 +3116,7 @@ async function handleFlow(request, config) {
2983
3116
  runId,
2984
3117
  runCompleted: true,
2985
3118
  data,
2986
- config: flowConfig,
2987
- ui
3119
+ config: flowConfig
2988
3120
  });
2989
3121
  } catch (e) {
2990
3122
  if (e instanceof Error) {
@@ -3029,6 +3161,7 @@ __name(ksuid, "ksuid");
3029
3161
  InlineFile,
3030
3162
  KSUID,
3031
3163
  ModelAPI,
3164
+ NonRetriableError,
3032
3165
  PERMISSION_STATE,
3033
3166
  Permissions,
3034
3167
  RequestHeaders,