@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.js
CHANGED
|
@@ -472,6 +472,20 @@ function isRichType(obj) {
|
|
|
472
472
|
}
|
|
473
473
|
__name(isRichType, "isRichType");
|
|
474
474
|
|
|
475
|
+
// src/jsonColumnValue.js
|
|
476
|
+
var JsonColumnValue = class {
|
|
477
|
+
static {
|
|
478
|
+
__name(this, "JsonColumnValue");
|
|
479
|
+
}
|
|
480
|
+
constructor(value) {
|
|
481
|
+
this.value = value;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
function parseJsonColumnValue(value) {
|
|
485
|
+
return new JsonColumnValue(JSON.parse(value));
|
|
486
|
+
}
|
|
487
|
+
__name(parseJsonColumnValue, "parseJsonColumnValue");
|
|
488
|
+
|
|
475
489
|
// src/camelCasePlugin.js
|
|
476
490
|
var KeelCamelCasePlugin = class {
|
|
477
491
|
static {
|
|
@@ -479,41 +493,77 @@ var KeelCamelCasePlugin = class {
|
|
|
479
493
|
}
|
|
480
494
|
constructor(opt) {
|
|
481
495
|
this.opt = opt;
|
|
496
|
+
this.auditQueryIds = /* @__PURE__ */ new WeakSet();
|
|
482
497
|
this.CamelCasePlugin = new CamelCasePlugin({
|
|
483
498
|
...opt,
|
|
484
499
|
underscoreBeforeDigits: true
|
|
485
500
|
});
|
|
486
501
|
}
|
|
487
502
|
transformQuery(args) {
|
|
503
|
+
if (args.queryId && referencesTable(args.node, "keel_audit")) {
|
|
504
|
+
this.auditQueryIds.add(args.queryId);
|
|
505
|
+
}
|
|
488
506
|
return this.CamelCasePlugin.transformQuery(args);
|
|
489
507
|
}
|
|
490
508
|
async transformResult(args) {
|
|
491
509
|
if (args.result.rows && Array.isArray(args.result.rows)) {
|
|
510
|
+
const mapAuditJson = args.queryId && this.auditQueryIds.has(args.queryId);
|
|
492
511
|
return {
|
|
493
512
|
...args.result,
|
|
494
|
-
rows: args.result.rows.map((row) => this.mapRow(row))
|
|
513
|
+
rows: args.result.rows.map((row) => this.mapRow(row, { mapAuditJson }))
|
|
495
514
|
};
|
|
496
515
|
}
|
|
497
516
|
return args.result;
|
|
498
517
|
}
|
|
499
|
-
mapRow(row) {
|
|
518
|
+
mapRow(row, context7 = {}) {
|
|
500
519
|
return Object.keys(row).reduce((obj, key) => {
|
|
501
520
|
if (key.endsWith("__sequence")) {
|
|
502
521
|
return obj;
|
|
503
522
|
}
|
|
504
523
|
let value = row[key];
|
|
524
|
+
if (value instanceof JsonColumnValue) {
|
|
525
|
+
value = shouldMapJsonColumn(row, key, context7) && canMap(value.value, this.opt) ? this.mapRow(value.value, context7) : value.value;
|
|
526
|
+
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
527
|
+
return obj;
|
|
528
|
+
}
|
|
505
529
|
if (Array.isArray(value)) {
|
|
506
530
|
value = value.map(
|
|
507
|
-
(it) => canMap(it, this.opt) ? this.mapRow(it) : it
|
|
531
|
+
(it) => canMap(it, this.opt) ? this.mapRow(it, context7) : it
|
|
508
532
|
);
|
|
509
533
|
} else if (canMap(value, this.opt)) {
|
|
510
|
-
value = this.mapRow(value);
|
|
534
|
+
value = this.mapRow(value, context7);
|
|
511
535
|
}
|
|
512
536
|
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
513
537
|
return obj;
|
|
514
538
|
}, {});
|
|
515
539
|
}
|
|
516
540
|
};
|
|
541
|
+
function shouldMapJsonColumn(row, key, context7) {
|
|
542
|
+
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;
|
|
543
|
+
}
|
|
544
|
+
__name(shouldMapJsonColumn, "shouldMapJsonColumn");
|
|
545
|
+
function referencesTable(node, tableName) {
|
|
546
|
+
if (!node || typeof node !== "object") {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
if (node.kind === "IdentifierNode" && (node.name === tableName || node.name === "keelAudit")) {
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
if (node.kind === "RawNode" && Array.isArray(node.sqlFragments) && node.sqlFragments.some(
|
|
553
|
+
(fragment) => new RegExp(`(^|[^A-Za-z0-9_])${tableName}($|[^A-Za-z0-9_])`, "i").test(
|
|
554
|
+
fragment
|
|
555
|
+
)
|
|
556
|
+
)) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
return Object.values(node).some((value) => {
|
|
560
|
+
if (Array.isArray(value)) {
|
|
561
|
+
return value.some((item) => referencesTable(item, tableName));
|
|
562
|
+
}
|
|
563
|
+
return referencesTable(value, tableName);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
__name(referencesTable, "referencesTable");
|
|
517
567
|
function canMap(obj, opt) {
|
|
518
568
|
return isPlainObject(obj) && !opt?.maintainNestedObjectKeys && !isRichType(obj);
|
|
519
569
|
}
|
|
@@ -914,6 +964,8 @@ function getDialect(connString) {
|
|
|
914
964
|
pgTypes.builtins.INTERVAL,
|
|
915
965
|
(val) => new Duration(val)
|
|
916
966
|
);
|
|
967
|
+
pgTypes.setTypeParser(pgTypes.builtins.JSON, parseJsonColumnValue);
|
|
968
|
+
pgTypes.setTypeParser(pgTypes.builtins.JSONB, parseJsonColumnValue);
|
|
917
969
|
const poolConfig = {
|
|
918
970
|
Client: InstrumentedClient,
|
|
919
971
|
// Increased idle time before closing a connection in the local pool (from 10s default).
|
|
@@ -948,6 +1000,8 @@ function getDialect(connString) {
|
|
|
948
1000
|
pgTypes.builtins.INTERVAL,
|
|
949
1001
|
(val) => new Duration(val)
|
|
950
1002
|
);
|
|
1003
|
+
neon.types.setTypeParser(pgTypes.builtins.JSON, parseJsonColumnValue);
|
|
1004
|
+
neon.types.setTypeParser(pgTypes.builtins.JSONB, parseJsonColumnValue);
|
|
951
1005
|
neon.neonConfig.webSocketConstructor = WebSocket;
|
|
952
1006
|
const pool = new InstrumentedNeonServerlessPool({
|
|
953
1007
|
// If connString is not passed fall back to reading from env var
|
|
@@ -2900,30 +2954,30 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2900
2954
|
}
|
|
2901
2955
|
};
|
|
2902
2956
|
|
|
2903
|
-
// src/
|
|
2957
|
+
// src/auth/serviceToken.ts
|
|
2904
2958
|
import jwt3 from "jsonwebtoken";
|
|
2905
|
-
function
|
|
2906
|
-
const headers = { "Content-Type": "application/json" };
|
|
2959
|
+
function buildServiceAuthHeaders(audience) {
|
|
2907
2960
|
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
2908
2961
|
if (!base64pk) {
|
|
2909
2962
|
throw new Error(
|
|
2910
|
-
|
|
2963
|
+
`KEEL_PRIVATE_KEY is not set; cannot sign the ${audience} service token`
|
|
2911
2964
|
);
|
|
2912
2965
|
}
|
|
2913
2966
|
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
2914
|
-
const
|
|
2915
|
-
headers["Authorization"] = "Bearer " + jwt3.sign({}, privateKey, {
|
|
2967
|
+
const token = jwt3.sign({}, privateKey, {
|
|
2916
2968
|
algorithm: "RS256",
|
|
2917
2969
|
expiresIn: 60 * 60 * 24,
|
|
2918
|
-
|
|
2919
|
-
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
2920
|
-
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
2921
|
-
audience: "integration-proxy",
|
|
2970
|
+
audience,
|
|
2922
2971
|
issuer: "https://keel.so"
|
|
2923
2972
|
});
|
|
2924
|
-
return
|
|
2973
|
+
return {
|
|
2974
|
+
"Content-Type": "application/json",
|
|
2975
|
+
Authorization: `Bearer ${token}`
|
|
2976
|
+
};
|
|
2925
2977
|
}
|
|
2926
|
-
__name(
|
|
2978
|
+
__name(buildServiceAuthHeaders, "buildServiceAuthHeaders");
|
|
2979
|
+
|
|
2980
|
+
// src/integrationServer.js
|
|
2927
2981
|
function getApiUrl3() {
|
|
2928
2982
|
const apiUrl = process.env.KEEL_API_URL;
|
|
2929
2983
|
if (!apiUrl) {
|
|
@@ -2940,7 +2994,7 @@ function createIntegrationServer(name, identity) {
|
|
|
2940
2994
|
)}/proxy`;
|
|
2941
2995
|
const response = await fetch(url, {
|
|
2942
2996
|
method: "POST",
|
|
2943
|
-
headers:
|
|
2997
|
+
headers: buildServiceAuthHeaders("integration-proxy"),
|
|
2944
2998
|
body: JSON.stringify(request ?? {})
|
|
2945
2999
|
});
|
|
2946
3000
|
if (!response.ok) {
|
|
@@ -3723,12 +3777,29 @@ async function applyElementGetData(content, data) {
|
|
|
3723
3777
|
return data;
|
|
3724
3778
|
}
|
|
3725
3779
|
__name(applyElementGetData, "applyElementGetData");
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3780
|
+
var ITERATOR_SCOPED_ELEMENT = /^([^[]+)\[(\d+)\]:(.+)$/;
|
|
3781
|
+
async function callbackFn(elements, elementPath, callbackName, data) {
|
|
3782
|
+
const scoped = ITERATOR_SCOPED_ELEMENT.exec(elementPath);
|
|
3783
|
+
let searchScope = elements;
|
|
3784
|
+
let elementName = elementPath;
|
|
3785
|
+
let iteratorName = null;
|
|
3786
|
+
if (scoped) {
|
|
3787
|
+
iteratorName = scoped[1];
|
|
3788
|
+
elementName = scoped[3];
|
|
3789
|
+
const iter = elements.find(
|
|
3790
|
+
(el) => el?.uiConfig?.__type === "ui.iterator" && el.uiConfig.name === iteratorName
|
|
3791
|
+
);
|
|
3792
|
+
if (!iter) {
|
|
3793
|
+
throw new Error(`Iterator with name ${iteratorName} not found`);
|
|
3794
|
+
}
|
|
3795
|
+
searchScope = iter.uiConfig.content;
|
|
3796
|
+
}
|
|
3797
|
+
const element = searchScope.find(
|
|
3798
|
+
(el) => el?.uiConfig && el.uiConfig.name === elementName
|
|
3729
3799
|
);
|
|
3730
3800
|
if (!element) {
|
|
3731
|
-
|
|
3801
|
+
const where = iteratorName ? ` in iterator ${iteratorName}` : "";
|
|
3802
|
+
throw new Error(`Element with name ${elementName} not found${where}`);
|
|
3732
3803
|
}
|
|
3733
3804
|
const cb = element[callbackName];
|
|
3734
3805
|
if (typeof cb !== "function") {
|
|
@@ -4674,6 +4745,19 @@ var defaultOpts = {
|
|
|
4674
4745
|
retries: 4,
|
|
4675
4746
|
timeout: 6e4
|
|
4676
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");
|
|
4677
4761
|
async function insertNewStep(db, runId, name, stage) {
|
|
4678
4762
|
await db.transaction().execute(async (trx) => {
|
|
4679
4763
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
@@ -4694,6 +4778,30 @@ async function insertNewStep(db, runId, name, stage) {
|
|
|
4694
4778
|
__name(insertNewStep, "insertNewStep");
|
|
4695
4779
|
function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
|
|
4696
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");
|
|
4697
4805
|
const context7 = {
|
|
4698
4806
|
identity: ctx.identity,
|
|
4699
4807
|
env: ctx.env,
|
|
@@ -4733,7 +4841,15 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4733
4841
|
throw new Error(`Duplicate step name: ${name}`);
|
|
4734
4842
|
}
|
|
4735
4843
|
usedNames.add(name);
|
|
4736
|
-
|
|
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
|
+
}
|
|
4737
4853
|
const newSteps = past.filter(
|
|
4738
4854
|
(step) => step.status === "NEW" /* NEW */
|
|
4739
4855
|
);
|
|
@@ -4816,7 +4932,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4816
4932
|
}
|
|
4817
4933
|
await db.updateTable("keel.flow_step").set({
|
|
4818
4934
|
status: "COMPLETED" /* COMPLETED */,
|
|
4819
|
-
value:
|
|
4935
|
+
value: serializeStepValue(span, name, result),
|
|
4820
4936
|
spanId,
|
|
4821
4937
|
endTime: /* @__PURE__ */ new Date()
|
|
4822
4938
|
}).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
|
|
@@ -4854,6 +4970,20 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4854
4970
|
throw new Error(`Duplicate step name: ${name}`);
|
|
4855
4971
|
}
|
|
4856
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");
|
|
4857
4987
|
const { step, inserted } = await db.transaction().execute(async (trx) => {
|
|
4858
4988
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
4859
4989
|
const existing = await trx.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(sql4`value::text`.as("valueRaw")).executeTakeFirst();
|
|
@@ -4872,18 +5002,8 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4872
5002
|
return { step: created, inserted: true };
|
|
4873
5003
|
});
|
|
4874
5004
|
if (step && step.status === "COMPLETED" /* COMPLETED */) {
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
const rawValue = step.valueRaw;
|
|
4878
|
-
const storedData = rawValue == null ? null : JSON.parse(rawValue);
|
|
4879
|
-
const parsedData2 = await applyElementGetData(
|
|
4880
|
-
options.content,
|
|
4881
|
-
transformRichDataTypes(storedData)
|
|
4882
|
-
);
|
|
4883
|
-
if (step.action) {
|
|
4884
|
-
return { data: parsedData2, action: step.action };
|
|
4885
|
-
}
|
|
4886
|
-
return parsedData2;
|
|
5005
|
+
uiBoundaryCrossed = true;
|
|
5006
|
+
return replayCompletedUiStep(step);
|
|
4887
5007
|
}
|
|
4888
5008
|
if (inserted) {
|
|
4889
5009
|
span.setAttribute("rendered", true);
|
|
@@ -4942,12 +5062,13 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4942
5062
|
}
|
|
4943
5063
|
await db.updateTable("keel.flow_step").set({
|
|
4944
5064
|
status: "COMPLETED" /* COMPLETED */,
|
|
4945
|
-
value:
|
|
5065
|
+
value: serializeStepValue(span, name, data),
|
|
4946
5066
|
action,
|
|
4947
5067
|
spanId,
|
|
4948
5068
|
endTime: /* @__PURE__ */ new Date()
|
|
4949
5069
|
}).where("id", "=", step.id).returningAll().executeTakeFirst();
|
|
4950
5070
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
5071
|
+
uiBoundaryCrossed = true;
|
|
4951
5072
|
const parsedData = await applyElementGetData(
|
|
4952
5073
|
options.content,
|
|
4953
5074
|
transformRichDataTypes(data)
|
|
@@ -5361,7 +5482,7 @@ async function notifyEmail(input) {
|
|
|
5361
5482
|
};
|
|
5362
5483
|
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5363
5484
|
method: "POST",
|
|
5364
|
-
headers:
|
|
5485
|
+
headers: buildServiceAuthHeaders("notifications"),
|
|
5365
5486
|
body: JSON.stringify(body)
|
|
5366
5487
|
});
|
|
5367
5488
|
if (!response.ok) {
|