@teamkeel/functions-runtime 0.460.1 → 0.462.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
@@ -2954,30 +2954,30 @@ var FlowsAPI = class _FlowsAPI {
2954
2954
  }
2955
2955
  };
2956
2956
 
2957
- // src/integrationServer.js
2957
+ // src/auth/serviceToken.ts
2958
2958
  import jwt3 from "jsonwebtoken";
2959
- function buildHeaders3(identity) {
2960
- const headers = { "Content-Type": "application/json" };
2959
+ function buildServiceAuthHeaders(audience) {
2961
2960
  const base64pk = process.env.KEEL_PRIVATE_KEY;
2962
2961
  if (!base64pk) {
2963
2962
  throw new Error(
2964
- "KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
2963
+ `KEEL_PRIVATE_KEY is not set; cannot sign the ${audience} service token`
2965
2964
  );
2966
2965
  }
2967
2966
  const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
2968
- const subject = identity && identity.id ? identity.id : "integration-proxy";
2969
- headers["Authorization"] = "Bearer " + jwt3.sign({}, privateKey, {
2967
+ const token = jwt3.sign({}, privateKey, {
2970
2968
  algorithm: "RS256",
2971
2969
  expiresIn: 60 * 60 * 24,
2972
- subject,
2973
- // Scope the token to the integration proxy so it can't be mistaken for an ordinary user
2974
- // access token; the runtime proxy verifies this audience before injecting credentials.
2975
- audience: "integration-proxy",
2970
+ audience,
2976
2971
  issuer: "https://keel.so"
2977
2972
  });
2978
- return headers;
2973
+ return {
2974
+ "Content-Type": "application/json",
2975
+ Authorization: `Bearer ${token}`
2976
+ };
2979
2977
  }
2980
- __name(buildHeaders3, "buildHeaders");
2978
+ __name(buildServiceAuthHeaders, "buildServiceAuthHeaders");
2979
+
2980
+ // src/integrationServer.js
2981
2981
  function getApiUrl3() {
2982
2982
  const apiUrl = process.env.KEEL_API_URL;
2983
2983
  if (!apiUrl) {
@@ -2994,7 +2994,7 @@ function createIntegrationServer(name, identity) {
2994
2994
  )}/proxy`;
2995
2995
  const response = await fetch(url, {
2996
2996
  method: "POST",
2997
- headers: buildHeaders3(identity ?? null),
2997
+ headers: buildServiceAuthHeaders("integration-proxy"),
2998
2998
  body: JSON.stringify(request ?? {})
2999
2999
  });
3000
3000
  if (!response.ok) {
@@ -4745,6 +4745,19 @@ var defaultOpts = {
4745
4745
  retries: 4,
4746
4746
  timeout: 6e4
4747
4747
  };
4748
+ var STEP_VALUE_WARN_BYTES = 1024 * 1024;
4749
+ function serializeStepValue(span, name, value) {
4750
+ const serialized = JSON.stringify(value);
4751
+ const bytes = serialized == null ? 0 : Buffer.byteLength(serialized, "utf8");
4752
+ span.setAttribute("step.value.bytes", bytes);
4753
+ if (bytes > STEP_VALUE_WARN_BYTES) {
4754
+ console.warn(
4755
+ `flow step "${name}" returned a ${Math.round(bytes / 1024)}KB value. Step values are reloaded on every flow invocation during replay, so large values slow the whole run down. Consider storing large data as a file and returning a reference to it instead.`
4756
+ );
4757
+ }
4758
+ return serialized;
4759
+ }
4760
+ __name(serializeStepValue, "serializeStepValue");
4748
4761
  async function insertNewStep(db, runId, name, stage) {
4749
4762
  await db.transaction().execute(async (trx) => {
4750
4763
  await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
@@ -4765,6 +4778,30 @@ async function insertNewStep(db, runId, name, stage) {
4765
4778
  __name(insertNewStep, "insertNewStep");
4766
4779
  function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
4767
4780
  const usedNames = /* @__PURE__ */ new Set();
4781
+ let stepsSnapshot = null;
4782
+ let uiBoundaryCrossed = false;
4783
+ async function loadStepsSnapshot(db) {
4784
+ if (stepsSnapshot) {
4785
+ return stepsSnapshot;
4786
+ }
4787
+ const rows = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).select(["id", "name", "status", "type", "action"]).select(sql4`value::text`.as("valueRaw")).execute();
4788
+ const map = /* @__PURE__ */ new Map();
4789
+ for (const row of rows) {
4790
+ const existing = map.get(row.name);
4791
+ if (existing) {
4792
+ existing.push(row);
4793
+ } else {
4794
+ map.set(row.name, [row]);
4795
+ }
4796
+ }
4797
+ stepsSnapshot = map;
4798
+ return map;
4799
+ }
4800
+ __name(loadStepsSnapshot, "loadStepsSnapshot");
4801
+ async function readStepRows(db, name) {
4802
+ return await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).select(["id", "name", "status", "type", "action"]).select(sql4`value::text`.as("valueRaw")).execute();
4803
+ }
4804
+ __name(readStepRows, "readStepRows");
4768
4805
  const context7 = {
4769
4806
  identity: ctx.identity,
4770
4807
  env: ctx.env,
@@ -4804,7 +4841,15 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4804
4841
  throw new Error(`Duplicate step name: ${name}`);
4805
4842
  }
4806
4843
  usedNames.add(name);
4807
- const past = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(sql4`value::text`.as("valueRaw")).execute();
4844
+ let past;
4845
+ if (uiBoundaryCrossed) {
4846
+ past = await readStepRows(db, name);
4847
+ } else {
4848
+ past = (await loadStepsSnapshot(db)).get(name) ?? [];
4849
+ if (!past.some((step) => step.status === "COMPLETED" /* COMPLETED */)) {
4850
+ past = await readStepRows(db, name);
4851
+ }
4852
+ }
4808
4853
  const newSteps = past.filter(
4809
4854
  (step) => step.status === "NEW" /* NEW */
4810
4855
  );
@@ -4887,7 +4932,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4887
4932
  }
4888
4933
  await db.updateTable("keel.flow_step").set({
4889
4934
  status: "COMPLETED" /* COMPLETED */,
4890
- value: JSON.stringify(result),
4935
+ value: serializeStepValue(span, name, result),
4891
4936
  spanId,
4892
4937
  endTime: /* @__PURE__ */ new Date()
4893
4938
  }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
@@ -4925,6 +4970,20 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4925
4970
  throw new Error(`Duplicate step name: ${name}`);
4926
4971
  }
4927
4972
  usedNames.add(name);
4973
+ const replayCompletedUiStep = /* @__PURE__ */ __name(async (completed) => {
4974
+ span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4975
+ span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
4976
+ const rawValue = completed.valueRaw;
4977
+ const storedData = rawValue == null ? null : JSON.parse(rawValue);
4978
+ const parsedData2 = await applyElementGetData(
4979
+ options.content,
4980
+ transformRichDataTypes(storedData)
4981
+ );
4982
+ if (completed.action) {
4983
+ return { data: parsedData2, action: completed.action };
4984
+ }
4985
+ return parsedData2;
4986
+ }, "replayCompletedUiStep");
4928
4987
  const { step, inserted } = await db.transaction().execute(async (trx) => {
4929
4988
  await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
4930
4989
  const existing = await trx.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(sql4`value::text`.as("valueRaw")).executeTakeFirst();
@@ -4943,18 +5002,8 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4943
5002
  return { step: created, inserted: true };
4944
5003
  });
4945
5004
  if (step && step.status === "COMPLETED" /* COMPLETED */) {
4946
- span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4947
- span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
4948
- const rawValue = step.valueRaw;
4949
- const storedData = rawValue == null ? null : JSON.parse(rawValue);
4950
- const parsedData2 = await applyElementGetData(
4951
- options.content,
4952
- transformRichDataTypes(storedData)
4953
- );
4954
- if (step.action) {
4955
- return { data: parsedData2, action: step.action };
4956
- }
4957
- return parsedData2;
5005
+ uiBoundaryCrossed = true;
5006
+ return replayCompletedUiStep(step);
4958
5007
  }
4959
5008
  if (inserted) {
4960
5009
  span.setAttribute("rendered", true);
@@ -5013,12 +5062,13 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
5013
5062
  }
5014
5063
  await db.updateTable("keel.flow_step").set({
5015
5064
  status: "COMPLETED" /* COMPLETED */,
5016
- value: JSON.stringify(data),
5065
+ value: serializeStepValue(span, name, data),
5017
5066
  action,
5018
5067
  spanId,
5019
5068
  endTime: /* @__PURE__ */ new Date()
5020
5069
  }).where("id", "=", step.id).returningAll().executeTakeFirst();
5021
5070
  span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
5071
+ uiBoundaryCrossed = true;
5022
5072
  const parsedData = await applyElementGetData(
5023
5073
  options.content,
5024
5074
  transformRichDataTypes(data)
@@ -5432,7 +5482,7 @@ async function notifyEmail(input) {
5432
5482
  };
5433
5483
  const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
5434
5484
  method: "POST",
5435
- headers: { "Content-Type": "application/json" },
5485
+ headers: buildServiceAuthHeaders("notifications"),
5436
5486
  body: JSON.stringify(body)
5437
5487
  });
5438
5488
  if (!response.ok) {