@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.cjs CHANGED
@@ -3009,30 +3009,30 @@ var FlowsAPI = class _FlowsAPI {
3009
3009
  }
3010
3010
  };
3011
3011
 
3012
- // src/integrationServer.js
3012
+ // src/auth/serviceToken.ts
3013
3013
  var import_jsonwebtoken3 = __toESM(require("jsonwebtoken"), 1);
3014
- function buildHeaders3(identity) {
3015
- const headers = { "Content-Type": "application/json" };
3014
+ function buildServiceAuthHeaders(audience) {
3016
3015
  const base64pk = process.env.KEEL_PRIVATE_KEY;
3017
3016
  if (!base64pk) {
3018
3017
  throw new Error(
3019
- "KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
3018
+ `KEEL_PRIVATE_KEY is not set; cannot sign the ${audience} service token`
3020
3019
  );
3021
3020
  }
3022
3021
  const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
3023
- const subject = identity && identity.id ? identity.id : "integration-proxy";
3024
- headers["Authorization"] = "Bearer " + import_jsonwebtoken3.default.sign({}, privateKey, {
3022
+ const token = import_jsonwebtoken3.default.sign({}, privateKey, {
3025
3023
  algorithm: "RS256",
3026
3024
  expiresIn: 60 * 60 * 24,
3027
- subject,
3028
- // Scope the token to the integration proxy so it can't be mistaken for an ordinary user
3029
- // access token; the runtime proxy verifies this audience before injecting credentials.
3030
- audience: "integration-proxy",
3025
+ audience,
3031
3026
  issuer: "https://keel.so"
3032
3027
  });
3033
- return headers;
3028
+ return {
3029
+ "Content-Type": "application/json",
3030
+ Authorization: `Bearer ${token}`
3031
+ };
3034
3032
  }
3035
- __name(buildHeaders3, "buildHeaders");
3033
+ __name(buildServiceAuthHeaders, "buildServiceAuthHeaders");
3034
+
3035
+ // src/integrationServer.js
3036
3036
  function getApiUrl3() {
3037
3037
  const apiUrl = process.env.KEEL_API_URL;
3038
3038
  if (!apiUrl) {
@@ -3049,7 +3049,7 @@ function createIntegrationServer(name, identity) {
3049
3049
  )}/proxy`;
3050
3050
  const response = await fetch(url, {
3051
3051
  method: "POST",
3052
- headers: buildHeaders3(identity ?? null),
3052
+ headers: buildServiceAuthHeaders("integration-proxy"),
3053
3053
  body: JSON.stringify(request ?? {})
3054
3054
  });
3055
3055
  if (!response.ok) {
@@ -4775,6 +4775,19 @@ var defaultOpts = {
4775
4775
  retries: 4,
4776
4776
  timeout: 6e4
4777
4777
  };
4778
+ var STEP_VALUE_WARN_BYTES = 1024 * 1024;
4779
+ function serializeStepValue(span, name, value) {
4780
+ const serialized = JSON.stringify(value);
4781
+ const bytes = serialized == null ? 0 : Buffer.byteLength(serialized, "utf8");
4782
+ span.setAttribute("step.value.bytes", bytes);
4783
+ if (bytes > STEP_VALUE_WARN_BYTES) {
4784
+ console.warn(
4785
+ `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.`
4786
+ );
4787
+ }
4788
+ return serialized;
4789
+ }
4790
+ __name(serializeStepValue, "serializeStepValue");
4778
4791
  async function insertNewStep(db, runId, name, stage) {
4779
4792
  await db.transaction().execute(async (trx) => {
4780
4793
  await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
@@ -4795,6 +4808,30 @@ async function insertNewStep(db, runId, name, stage) {
4795
4808
  __name(insertNewStep, "insertNewStep");
4796
4809
  function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
4797
4810
  const usedNames = /* @__PURE__ */ new Set();
4811
+ let stepsSnapshot = null;
4812
+ let uiBoundaryCrossed = false;
4813
+ async function loadStepsSnapshot(db) {
4814
+ if (stepsSnapshot) {
4815
+ return stepsSnapshot;
4816
+ }
4817
+ const rows = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).select(["id", "name", "status", "type", "action"]).select(import_kysely6.sql`value::text`.as("valueRaw")).execute();
4818
+ const map = /* @__PURE__ */ new Map();
4819
+ for (const row of rows) {
4820
+ const existing = map.get(row.name);
4821
+ if (existing) {
4822
+ existing.push(row);
4823
+ } else {
4824
+ map.set(row.name, [row]);
4825
+ }
4826
+ }
4827
+ stepsSnapshot = map;
4828
+ return map;
4829
+ }
4830
+ __name(loadStepsSnapshot, "loadStepsSnapshot");
4831
+ async function readStepRows(db, name) {
4832
+ return await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).select(["id", "name", "status", "type", "action"]).select(import_kysely6.sql`value::text`.as("valueRaw")).execute();
4833
+ }
4834
+ __name(readStepRows, "readStepRows");
4798
4835
  const context7 = {
4799
4836
  identity: ctx.identity,
4800
4837
  env: ctx.env,
@@ -4834,7 +4871,15 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4834
4871
  throw new Error(`Duplicate step name: ${name}`);
4835
4872
  }
4836
4873
  usedNames.add(name);
4837
- const past = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(import_kysely6.sql`value::text`.as("valueRaw")).execute();
4874
+ let past;
4875
+ if (uiBoundaryCrossed) {
4876
+ past = await readStepRows(db, name);
4877
+ } else {
4878
+ past = (await loadStepsSnapshot(db)).get(name) ?? [];
4879
+ if (!past.some((step) => step.status === "COMPLETED" /* COMPLETED */)) {
4880
+ past = await readStepRows(db, name);
4881
+ }
4882
+ }
4838
4883
  const newSteps = past.filter(
4839
4884
  (step) => step.status === "NEW" /* NEW */
4840
4885
  );
@@ -4917,7 +4962,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4917
4962
  }
4918
4963
  await db.updateTable("keel.flow_step").set({
4919
4964
  status: "COMPLETED" /* COMPLETED */,
4920
- value: JSON.stringify(result),
4965
+ value: serializeStepValue(span, name, result),
4921
4966
  spanId,
4922
4967
  endTime: /* @__PURE__ */ new Date()
4923
4968
  }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
@@ -4955,6 +5000,20 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4955
5000
  throw new Error(`Duplicate step name: ${name}`);
4956
5001
  }
4957
5002
  usedNames.add(name);
5003
+ const replayCompletedUiStep = /* @__PURE__ */ __name(async (completed) => {
5004
+ span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
5005
+ span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
5006
+ const rawValue = completed.valueRaw;
5007
+ const storedData = rawValue == null ? null : JSON.parse(rawValue);
5008
+ const parsedData2 = await applyElementGetData(
5009
+ options.content,
5010
+ transformRichDataTypes(storedData)
5011
+ );
5012
+ if (completed.action) {
5013
+ return { data: parsedData2, action: completed.action };
5014
+ }
5015
+ return parsedData2;
5016
+ }, "replayCompletedUiStep");
4958
5017
  const { step, inserted } = await db.transaction().execute(async (trx) => {
4959
5018
  await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
4960
5019
  const existing = await trx.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(import_kysely6.sql`value::text`.as("valueRaw")).executeTakeFirst();
@@ -4973,18 +5032,8 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4973
5032
  return { step: created, inserted: true };
4974
5033
  });
4975
5034
  if (step && step.status === "COMPLETED" /* COMPLETED */) {
4976
- span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4977
- span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
4978
- const rawValue = step.valueRaw;
4979
- const storedData = rawValue == null ? null : JSON.parse(rawValue);
4980
- const parsedData2 = await applyElementGetData(
4981
- options.content,
4982
- transformRichDataTypes(storedData)
4983
- );
4984
- if (step.action) {
4985
- return { data: parsedData2, action: step.action };
4986
- }
4987
- return parsedData2;
5035
+ uiBoundaryCrossed = true;
5036
+ return replayCompletedUiStep(step);
4988
5037
  }
4989
5038
  if (inserted) {
4990
5039
  span.setAttribute("rendered", true);
@@ -5043,12 +5092,13 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
5043
5092
  }
5044
5093
  await db.updateTable("keel.flow_step").set({
5045
5094
  status: "COMPLETED" /* COMPLETED */,
5046
- value: JSON.stringify(data),
5095
+ value: serializeStepValue(span, name, data),
5047
5096
  action,
5048
5097
  spanId,
5049
5098
  endTime: /* @__PURE__ */ new Date()
5050
5099
  }).where("id", "=", step.id).returningAll().executeTakeFirst();
5051
5100
  span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
5101
+ uiBoundaryCrossed = true;
5052
5102
  const parsedData = await applyElementGetData(
5053
5103
  options.content,
5054
5104
  transformRichDataTypes(data)
@@ -5453,7 +5503,7 @@ async function notifyEmail(input) {
5453
5503
  };
5454
5504
  const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
5455
5505
  method: "POST",
5456
- headers: { "Content-Type": "application/json" },
5506
+ headers: buildServiceAuthHeaders("notifications"),
5457
5507
  body: JSON.stringify(body)
5458
5508
  });
5459
5509
  if (!response.ok) {