@teamkeel/functions-runtime 0.460.0 → 0.461.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 +158 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +158 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -530,6 +530,20 @@ function isRichType(obj) {
|
|
|
530
530
|
}
|
|
531
531
|
__name(isRichType, "isRichType");
|
|
532
532
|
|
|
533
|
+
// src/jsonColumnValue.js
|
|
534
|
+
var JsonColumnValue = class {
|
|
535
|
+
static {
|
|
536
|
+
__name(this, "JsonColumnValue");
|
|
537
|
+
}
|
|
538
|
+
constructor(value) {
|
|
539
|
+
this.value = value;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
function parseJsonColumnValue(value) {
|
|
543
|
+
return new JsonColumnValue(JSON.parse(value));
|
|
544
|
+
}
|
|
545
|
+
__name(parseJsonColumnValue, "parseJsonColumnValue");
|
|
546
|
+
|
|
533
547
|
// src/camelCasePlugin.js
|
|
534
548
|
var KeelCamelCasePlugin = class {
|
|
535
549
|
static {
|
|
@@ -537,41 +551,77 @@ var KeelCamelCasePlugin = class {
|
|
|
537
551
|
}
|
|
538
552
|
constructor(opt) {
|
|
539
553
|
this.opt = opt;
|
|
554
|
+
this.auditQueryIds = /* @__PURE__ */ new WeakSet();
|
|
540
555
|
this.CamelCasePlugin = new import_kysely2.CamelCasePlugin({
|
|
541
556
|
...opt,
|
|
542
557
|
underscoreBeforeDigits: true
|
|
543
558
|
});
|
|
544
559
|
}
|
|
545
560
|
transformQuery(args) {
|
|
561
|
+
if (args.queryId && referencesTable(args.node, "keel_audit")) {
|
|
562
|
+
this.auditQueryIds.add(args.queryId);
|
|
563
|
+
}
|
|
546
564
|
return this.CamelCasePlugin.transformQuery(args);
|
|
547
565
|
}
|
|
548
566
|
async transformResult(args) {
|
|
549
567
|
if (args.result.rows && Array.isArray(args.result.rows)) {
|
|
568
|
+
const mapAuditJson = args.queryId && this.auditQueryIds.has(args.queryId);
|
|
550
569
|
return {
|
|
551
570
|
...args.result,
|
|
552
|
-
rows: args.result.rows.map((row) => this.mapRow(row))
|
|
571
|
+
rows: args.result.rows.map((row) => this.mapRow(row, { mapAuditJson }))
|
|
553
572
|
};
|
|
554
573
|
}
|
|
555
574
|
return args.result;
|
|
556
575
|
}
|
|
557
|
-
mapRow(row) {
|
|
576
|
+
mapRow(row, context7 = {}) {
|
|
558
577
|
return Object.keys(row).reduce((obj, key) => {
|
|
559
578
|
if (key.endsWith("__sequence")) {
|
|
560
579
|
return obj;
|
|
561
580
|
}
|
|
562
581
|
let value = row[key];
|
|
582
|
+
if (value instanceof JsonColumnValue) {
|
|
583
|
+
value = shouldMapJsonColumn(row, key, context7) && canMap(value.value, this.opt) ? this.mapRow(value.value, context7) : value.value;
|
|
584
|
+
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
585
|
+
return obj;
|
|
586
|
+
}
|
|
563
587
|
if (Array.isArray(value)) {
|
|
564
588
|
value = value.map(
|
|
565
|
-
(it) => canMap(it, this.opt) ? this.mapRow(it) : it
|
|
589
|
+
(it) => canMap(it, this.opt) ? this.mapRow(it, context7) : it
|
|
566
590
|
);
|
|
567
591
|
} else if (canMap(value, this.opt)) {
|
|
568
|
-
value = this.mapRow(value);
|
|
592
|
+
value = this.mapRow(value, context7);
|
|
569
593
|
}
|
|
570
594
|
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
571
595
|
return obj;
|
|
572
596
|
}, {});
|
|
573
597
|
}
|
|
574
598
|
};
|
|
599
|
+
function shouldMapJsonColumn(row, key, context7) {
|
|
600
|
+
return context7.mapAuditJson && key === "data" && "id" in row && "table_name" in row && "op" in row && "identity_id" in row && "trace_id" in row && "created_at" in row && "event_processed_at" in row;
|
|
601
|
+
}
|
|
602
|
+
__name(shouldMapJsonColumn, "shouldMapJsonColumn");
|
|
603
|
+
function referencesTable(node, tableName) {
|
|
604
|
+
if (!node || typeof node !== "object") {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (node.kind === "IdentifierNode" && (node.name === tableName || node.name === "keelAudit")) {
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
if (node.kind === "RawNode" && Array.isArray(node.sqlFragments) && node.sqlFragments.some(
|
|
611
|
+
(fragment) => new RegExp(`(^|[^A-Za-z0-9_])${tableName}($|[^A-Za-z0-9_])`, "i").test(
|
|
612
|
+
fragment
|
|
613
|
+
)
|
|
614
|
+
)) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
return Object.values(node).some((value) => {
|
|
618
|
+
if (Array.isArray(value)) {
|
|
619
|
+
return value.some((item) => referencesTable(item, tableName));
|
|
620
|
+
}
|
|
621
|
+
return referencesTable(value, tableName);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
__name(referencesTable, "referencesTable");
|
|
575
625
|
function canMap(obj, opt) {
|
|
576
626
|
return isPlainObject(obj) && !opt?.maintainNestedObjectKeys && !isRichType(obj);
|
|
577
627
|
}
|
|
@@ -972,6 +1022,8 @@ function getDialect(connString) {
|
|
|
972
1022
|
import_pg.types.builtins.INTERVAL,
|
|
973
1023
|
(val) => new Duration(val)
|
|
974
1024
|
);
|
|
1025
|
+
import_pg.types.setTypeParser(import_pg.types.builtins.JSON, parseJsonColumnValue);
|
|
1026
|
+
import_pg.types.setTypeParser(import_pg.types.builtins.JSONB, parseJsonColumnValue);
|
|
975
1027
|
const poolConfig = {
|
|
976
1028
|
Client: InstrumentedClient,
|
|
977
1029
|
// Increased idle time before closing a connection in the local pool (from 10s default).
|
|
@@ -1006,6 +1058,8 @@ function getDialect(connString) {
|
|
|
1006
1058
|
import_pg.types.builtins.INTERVAL,
|
|
1007
1059
|
(val) => new Duration(val)
|
|
1008
1060
|
);
|
|
1061
|
+
neon.types.setTypeParser(import_pg.types.builtins.JSON, parseJsonColumnValue);
|
|
1062
|
+
neon.types.setTypeParser(import_pg.types.builtins.JSONB, parseJsonColumnValue);
|
|
1009
1063
|
neon.neonConfig.webSocketConstructor = import_ws.default;
|
|
1010
1064
|
const pool = new InstrumentedNeonServerlessPool({
|
|
1011
1065
|
// If connString is not passed fall back to reading from env var
|
|
@@ -2955,30 +3009,30 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2955
3009
|
}
|
|
2956
3010
|
};
|
|
2957
3011
|
|
|
2958
|
-
// src/
|
|
3012
|
+
// src/auth/serviceToken.ts
|
|
2959
3013
|
var import_jsonwebtoken3 = __toESM(require("jsonwebtoken"), 1);
|
|
2960
|
-
function
|
|
2961
|
-
const headers = { "Content-Type": "application/json" };
|
|
3014
|
+
function buildServiceAuthHeaders(audience) {
|
|
2962
3015
|
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
2963
3016
|
if (!base64pk) {
|
|
2964
3017
|
throw new Error(
|
|
2965
|
-
|
|
3018
|
+
`KEEL_PRIVATE_KEY is not set; cannot sign the ${audience} service token`
|
|
2966
3019
|
);
|
|
2967
3020
|
}
|
|
2968
3021
|
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
2969
|
-
const
|
|
2970
|
-
headers["Authorization"] = "Bearer " + import_jsonwebtoken3.default.sign({}, privateKey, {
|
|
3022
|
+
const token = import_jsonwebtoken3.default.sign({}, privateKey, {
|
|
2971
3023
|
algorithm: "RS256",
|
|
2972
3024
|
expiresIn: 60 * 60 * 24,
|
|
2973
|
-
|
|
2974
|
-
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
2975
|
-
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
2976
|
-
audience: "integration-proxy",
|
|
3025
|
+
audience,
|
|
2977
3026
|
issuer: "https://keel.so"
|
|
2978
3027
|
});
|
|
2979
|
-
return
|
|
3028
|
+
return {
|
|
3029
|
+
"Content-Type": "application/json",
|
|
3030
|
+
Authorization: `Bearer ${token}`
|
|
3031
|
+
};
|
|
2980
3032
|
}
|
|
2981
|
-
__name(
|
|
3033
|
+
__name(buildServiceAuthHeaders, "buildServiceAuthHeaders");
|
|
3034
|
+
|
|
3035
|
+
// src/integrationServer.js
|
|
2982
3036
|
function getApiUrl3() {
|
|
2983
3037
|
const apiUrl = process.env.KEEL_API_URL;
|
|
2984
3038
|
if (!apiUrl) {
|
|
@@ -2995,7 +3049,7 @@ function createIntegrationServer(name, identity) {
|
|
|
2995
3049
|
)}/proxy`;
|
|
2996
3050
|
const response = await fetch(url, {
|
|
2997
3051
|
method: "POST",
|
|
2998
|
-
headers:
|
|
3052
|
+
headers: buildServiceAuthHeaders("integration-proxy"),
|
|
2999
3053
|
body: JSON.stringify(request ?? {})
|
|
3000
3054
|
});
|
|
3001
3055
|
if (!response.ok) {
|
|
@@ -3758,12 +3812,29 @@ async function applyElementGetData(content, data) {
|
|
|
3758
3812
|
return data;
|
|
3759
3813
|
}
|
|
3760
3814
|
__name(applyElementGetData, "applyElementGetData");
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3815
|
+
var ITERATOR_SCOPED_ELEMENT = /^([^[]+)\[(\d+)\]:(.+)$/;
|
|
3816
|
+
async function callbackFn(elements, elementPath, callbackName, data) {
|
|
3817
|
+
const scoped = ITERATOR_SCOPED_ELEMENT.exec(elementPath);
|
|
3818
|
+
let searchScope = elements;
|
|
3819
|
+
let elementName = elementPath;
|
|
3820
|
+
let iteratorName = null;
|
|
3821
|
+
if (scoped) {
|
|
3822
|
+
iteratorName = scoped[1];
|
|
3823
|
+
elementName = scoped[3];
|
|
3824
|
+
const iter = elements.find(
|
|
3825
|
+
(el) => el?.uiConfig?.__type === "ui.iterator" && el.uiConfig.name === iteratorName
|
|
3826
|
+
);
|
|
3827
|
+
if (!iter) {
|
|
3828
|
+
throw new Error(`Iterator with name ${iteratorName} not found`);
|
|
3829
|
+
}
|
|
3830
|
+
searchScope = iter.uiConfig.content;
|
|
3831
|
+
}
|
|
3832
|
+
const element = searchScope.find(
|
|
3833
|
+
(el) => el?.uiConfig && el.uiConfig.name === elementName
|
|
3764
3834
|
);
|
|
3765
3835
|
if (!element) {
|
|
3766
|
-
|
|
3836
|
+
const where = iteratorName ? ` in iterator ${iteratorName}` : "";
|
|
3837
|
+
throw new Error(`Element with name ${elementName} not found${where}`);
|
|
3767
3838
|
}
|
|
3768
3839
|
const cb = element[callbackName];
|
|
3769
3840
|
if (typeof cb !== "function") {
|
|
@@ -4704,6 +4775,19 @@ var defaultOpts = {
|
|
|
4704
4775
|
retries: 4,
|
|
4705
4776
|
timeout: 6e4
|
|
4706
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");
|
|
4707
4791
|
async function insertNewStep(db, runId, name, stage) {
|
|
4708
4792
|
await db.transaction().execute(async (trx) => {
|
|
4709
4793
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
@@ -4724,6 +4808,30 @@ async function insertNewStep(db, runId, name, stage) {
|
|
|
4724
4808
|
__name(insertNewStep, "insertNewStep");
|
|
4725
4809
|
function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
|
|
4726
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");
|
|
4727
4835
|
const context7 = {
|
|
4728
4836
|
identity: ctx.identity,
|
|
4729
4837
|
env: ctx.env,
|
|
@@ -4763,7 +4871,15 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4763
4871
|
throw new Error(`Duplicate step name: ${name}`);
|
|
4764
4872
|
}
|
|
4765
4873
|
usedNames.add(name);
|
|
4766
|
-
|
|
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
|
+
}
|
|
4767
4883
|
const newSteps = past.filter(
|
|
4768
4884
|
(step) => step.status === "NEW" /* NEW */
|
|
4769
4885
|
);
|
|
@@ -4846,7 +4962,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4846
4962
|
}
|
|
4847
4963
|
await db.updateTable("keel.flow_step").set({
|
|
4848
4964
|
status: "COMPLETED" /* COMPLETED */,
|
|
4849
|
-
value:
|
|
4965
|
+
value: serializeStepValue(span, name, result),
|
|
4850
4966
|
spanId,
|
|
4851
4967
|
endTime: /* @__PURE__ */ new Date()
|
|
4852
4968
|
}).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
|
|
@@ -4884,6 +5000,20 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4884
5000
|
throw new Error(`Duplicate step name: ${name}`);
|
|
4885
5001
|
}
|
|
4886
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");
|
|
4887
5017
|
const { step, inserted } = await db.transaction().execute(async (trx) => {
|
|
4888
5018
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
4889
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();
|
|
@@ -4902,18 +5032,8 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4902
5032
|
return { step: created, inserted: true };
|
|
4903
5033
|
});
|
|
4904
5034
|
if (step && step.status === "COMPLETED" /* COMPLETED */) {
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
const rawValue = step.valueRaw;
|
|
4908
|
-
const storedData = rawValue == null ? null : JSON.parse(rawValue);
|
|
4909
|
-
const parsedData2 = await applyElementGetData(
|
|
4910
|
-
options.content,
|
|
4911
|
-
transformRichDataTypes(storedData)
|
|
4912
|
-
);
|
|
4913
|
-
if (step.action) {
|
|
4914
|
-
return { data: parsedData2, action: step.action };
|
|
4915
|
-
}
|
|
4916
|
-
return parsedData2;
|
|
5035
|
+
uiBoundaryCrossed = true;
|
|
5036
|
+
return replayCompletedUiStep(step);
|
|
4917
5037
|
}
|
|
4918
5038
|
if (inserted) {
|
|
4919
5039
|
span.setAttribute("rendered", true);
|
|
@@ -4972,12 +5092,13 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4972
5092
|
}
|
|
4973
5093
|
await db.updateTable("keel.flow_step").set({
|
|
4974
5094
|
status: "COMPLETED" /* COMPLETED */,
|
|
4975
|
-
value:
|
|
5095
|
+
value: serializeStepValue(span, name, data),
|
|
4976
5096
|
action,
|
|
4977
5097
|
spanId,
|
|
4978
5098
|
endTime: /* @__PURE__ */ new Date()
|
|
4979
5099
|
}).where("id", "=", step.id).returningAll().executeTakeFirst();
|
|
4980
5100
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
5101
|
+
uiBoundaryCrossed = true;
|
|
4981
5102
|
const parsedData = await applyElementGetData(
|
|
4982
5103
|
options.content,
|
|
4983
5104
|
transformRichDataTypes(data)
|
|
@@ -5382,7 +5503,7 @@ async function notifyEmail(input) {
|
|
|
5382
5503
|
};
|
|
5383
5504
|
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5384
5505
|
method: "POST",
|
|
5385
|
-
headers:
|
|
5506
|
+
headers: buildServiceAuthHeaders("notifications"),
|
|
5386
5507
|
body: JSON.stringify(body)
|
|
5387
5508
|
});
|
|
5388
5509
|
if (!response.ok) {
|