@teamkeel/functions-runtime 0.411.0 → 0.412.0-next.1
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.d.mts +340 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +3093 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3097 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +23 -5
- package/.env.test +0 -2
- package/compose.yaml +0 -10
- package/src/Duration.js +0 -40
- package/src/Duration.test.js +0 -34
- package/src/File.js +0 -295
- package/src/ModelAPI.js +0 -377
- package/src/ModelAPI.test.js +0 -1428
- package/src/QueryBuilder.js +0 -184
- package/src/QueryContext.js +0 -90
- package/src/RequestHeaders.js +0 -21
- package/src/TimePeriod.js +0 -89
- package/src/TimePeriod.test.js +0 -148
- package/src/applyAdditionalQueryConstraints.js +0 -22
- package/src/applyJoins.js +0 -67
- package/src/applyWhereConditions.js +0 -124
- package/src/auditing.js +0 -110
- package/src/auditing.test.js +0 -330
- package/src/camelCasePlugin.js +0 -52
- package/src/casing.js +0 -54
- package/src/casing.test.js +0 -56
- package/src/consts.js +0 -14
- package/src/database.js +0 -244
- package/src/errors.js +0 -160
- package/src/handleJob.js +0 -110
- package/src/handleJob.test.js +0 -270
- package/src/handleRequest.js +0 -153
- package/src/handleRequest.test.js +0 -463
- package/src/handleRoute.js +0 -112
- package/src/handleSubscriber.js +0 -105
- package/src/index.d.ts +0 -317
- package/src/index.js +0 -38
- package/src/parsing.js +0 -113
- package/src/parsing.test.js +0 -140
- package/src/permissions.js +0 -77
- package/src/permissions.test.js +0 -118
- package/src/tracing.js +0 -184
- package/src/tracing.test.js +0 -147
- package/src/tryExecuteFunction.js +0 -91
- package/src/tryExecuteJob.js +0 -29
- package/src/tryExecuteSubscriber.js +0 -17
- package/src/type-utils.js +0 -18
- package/vite.config.js +0 -7
package/dist/index.js
ADDED
|
@@ -0,0 +1,3093 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
|
|
38
|
+
// src/auditing.js
|
|
39
|
+
var require_auditing = __commonJS({
|
|
40
|
+
"src/auditing.js"(exports2, module2) {
|
|
41
|
+
"use strict";
|
|
42
|
+
var { AsyncLocalStorage } = require("async_hooks");
|
|
43
|
+
var TraceParent = require("traceparent");
|
|
44
|
+
var { sql, SelectionNode } = require("kysely");
|
|
45
|
+
var auditContextStorage = new AsyncLocalStorage();
|
|
46
|
+
async function withAuditContext(request, cb) {
|
|
47
|
+
var _a, _b, _c, _d;
|
|
48
|
+
let audit = {};
|
|
49
|
+
if ((_a = request.meta) == null ? void 0 : _a.identity) {
|
|
50
|
+
audit.identityId = request.meta.identity.id;
|
|
51
|
+
}
|
|
52
|
+
if ((_c = (_b = request.meta) == null ? void 0 : _b.tracing) == null ? void 0 : _c.traceparent) {
|
|
53
|
+
audit.traceId = (_d = TraceParent.fromString(
|
|
54
|
+
request.meta.tracing.traceparent
|
|
55
|
+
)) == null ? void 0 : _d.traceId;
|
|
56
|
+
}
|
|
57
|
+
return await auditContextStorage.run(audit, () => {
|
|
58
|
+
return cb();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
__name(withAuditContext, "withAuditContext");
|
|
62
|
+
function getAuditContext() {
|
|
63
|
+
let auditStore = auditContextStorage.getStore();
|
|
64
|
+
return {
|
|
65
|
+
identityId: auditStore == null ? void 0 : auditStore.identityId,
|
|
66
|
+
traceId: auditStore == null ? void 0 : auditStore.traceId
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
__name(getAuditContext, "getAuditContext");
|
|
70
|
+
var _AuditContextPlugin = class _AuditContextPlugin {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.identityIdAlias = "__keel_identity_id";
|
|
73
|
+
this.traceIdAlias = "__keel_trace_id";
|
|
74
|
+
}
|
|
75
|
+
// Appends set_identity_id() and set_trace_id() function calls to the returning statement
|
|
76
|
+
// of INSERT, UPDATE and DELETE operations.
|
|
77
|
+
transformQuery(args) {
|
|
78
|
+
switch (args.node.kind) {
|
|
79
|
+
case "InsertQueryNode":
|
|
80
|
+
case "UpdateQueryNode":
|
|
81
|
+
case "DeleteQueryNode":
|
|
82
|
+
const returning = {
|
|
83
|
+
kind: "ReturningNode",
|
|
84
|
+
selections: []
|
|
85
|
+
};
|
|
86
|
+
if (args.node.returning) {
|
|
87
|
+
returning.selections.push(...args.node.returning.selections);
|
|
88
|
+
}
|
|
89
|
+
const audit = getAuditContext();
|
|
90
|
+
if (audit.identityId) {
|
|
91
|
+
const rawNode = sql`set_identity_id(${audit.identityId})`.as(this.identityIdAlias).toOperationNode();
|
|
92
|
+
returning.selections.push(SelectionNode.create(rawNode));
|
|
93
|
+
}
|
|
94
|
+
if (audit.traceId) {
|
|
95
|
+
const rawNode = sql`set_trace_id(${audit.traceId})`.as(this.traceIdAlias).toOperationNode();
|
|
96
|
+
returning.selections.push(SelectionNode.create(rawNode));
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...args.node,
|
|
100
|
+
returning
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
...args.node
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Drops the set_identity_id() and set_trace_id() fields from the result.
|
|
108
|
+
transformResult(args) {
|
|
109
|
+
var _a;
|
|
110
|
+
if ((_a = args.result) == null ? void 0 : _a.rows) {
|
|
111
|
+
for (let i = 0; i < args.result.rows.length; i++) {
|
|
112
|
+
delete args.result.rows[i][this.identityIdAlias];
|
|
113
|
+
delete args.result.rows[i][this.traceIdAlias];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return args.result;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
__name(_AuditContextPlugin, "AuditContextPlugin");
|
|
120
|
+
var AuditContextPlugin = _AuditContextPlugin;
|
|
121
|
+
module2.exports.withAuditContext = withAuditContext;
|
|
122
|
+
module2.exports.getAuditContext = getAuditContext;
|
|
123
|
+
module2.exports.AuditContextPlugin = AuditContextPlugin;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// src/Duration.js
|
|
128
|
+
var require_Duration = __commonJS({
|
|
129
|
+
"src/Duration.js"(exports2, module2) {
|
|
130
|
+
"use strict";
|
|
131
|
+
var parseInterval = require("postgres-interval");
|
|
132
|
+
var isoRegex = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
|
|
133
|
+
var _Duration = class _Duration {
|
|
134
|
+
constructor(postgresString) {
|
|
135
|
+
this._typename = "Duration";
|
|
136
|
+
this.pgInterval = postgresString;
|
|
137
|
+
this._interval = parseInterval(postgresString);
|
|
138
|
+
}
|
|
139
|
+
static fromISOString(isoString) {
|
|
140
|
+
const match = isoString.match(isoRegex);
|
|
141
|
+
if (match) {
|
|
142
|
+
let d = new _Duration();
|
|
143
|
+
d._interval.years = match[1];
|
|
144
|
+
d._interval.months = match[2];
|
|
145
|
+
d._interval.days = match[3];
|
|
146
|
+
d._interval.hours = match[4];
|
|
147
|
+
d._interval.minutes = match[5];
|
|
148
|
+
d._interval.seconds = match[6];
|
|
149
|
+
return d;
|
|
150
|
+
}
|
|
151
|
+
return new _Duration();
|
|
152
|
+
}
|
|
153
|
+
toISOString() {
|
|
154
|
+
return this._interval.toISOStringShort();
|
|
155
|
+
}
|
|
156
|
+
toPostgres() {
|
|
157
|
+
return this._interval.toPostgres();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
__name(_Duration, "Duration");
|
|
161
|
+
var Duration = _Duration;
|
|
162
|
+
module2.exports = {
|
|
163
|
+
Duration
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// src/type-utils.js
|
|
169
|
+
var require_type_utils = __commonJS({
|
|
170
|
+
"src/type-utils.js"(exports2, module2) {
|
|
171
|
+
"use strict";
|
|
172
|
+
var { Duration } = require_Duration();
|
|
173
|
+
function isPlainObject(obj) {
|
|
174
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
175
|
+
}
|
|
176
|
+
__name(isPlainObject, "isPlainObject");
|
|
177
|
+
function isRichType(obj) {
|
|
178
|
+
if (!isPlainObject(obj)) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return obj instanceof Duration;
|
|
182
|
+
}
|
|
183
|
+
__name(isRichType, "isRichType");
|
|
184
|
+
module2.exports = {
|
|
185
|
+
isPlainObject,
|
|
186
|
+
isRichType
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// src/camelCasePlugin.js
|
|
192
|
+
var require_camelCasePlugin = __commonJS({
|
|
193
|
+
"src/camelCasePlugin.js"(exports2, module2) {
|
|
194
|
+
"use strict";
|
|
195
|
+
var { CamelCasePlugin } = require("kysely");
|
|
196
|
+
var { isPlainObject, isRichType } = require_type_utils();
|
|
197
|
+
var _KeelCamelCasePlugin = class _KeelCamelCasePlugin {
|
|
198
|
+
constructor(opt) {
|
|
199
|
+
this.opt = opt;
|
|
200
|
+
this.CamelCasePlugin = new CamelCasePlugin(opt);
|
|
201
|
+
}
|
|
202
|
+
transformQuery(args) {
|
|
203
|
+
return this.CamelCasePlugin.transformQuery(args);
|
|
204
|
+
}
|
|
205
|
+
async transformResult(args) {
|
|
206
|
+
if (args.result.rows && Array.isArray(args.result.rows)) {
|
|
207
|
+
return {
|
|
208
|
+
...args.result,
|
|
209
|
+
rows: args.result.rows.map((row) => this.mapRow(row))
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return args.result;
|
|
213
|
+
}
|
|
214
|
+
mapRow(row) {
|
|
215
|
+
return Object.keys(row).reduce((obj, key) => {
|
|
216
|
+
if (key.endsWith("__sequence")) {
|
|
217
|
+
return obj;
|
|
218
|
+
}
|
|
219
|
+
let value = row[key];
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
value = value.map(
|
|
222
|
+
(it) => canMap(it, this.opt) ? this.mapRow(it) : it
|
|
223
|
+
);
|
|
224
|
+
} else if (canMap(value, this.opt)) {
|
|
225
|
+
value = this.mapRow(value);
|
|
226
|
+
}
|
|
227
|
+
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
228
|
+
return obj;
|
|
229
|
+
}, {});
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
__name(_KeelCamelCasePlugin, "KeelCamelCasePlugin");
|
|
233
|
+
var KeelCamelCasePlugin = _KeelCamelCasePlugin;
|
|
234
|
+
function canMap(obj, opt) {
|
|
235
|
+
return isPlainObject(obj) && !(opt == null ? void 0 : opt.maintainNestedObjectKeys) && !isRichType(obj);
|
|
236
|
+
}
|
|
237
|
+
__name(canMap, "canMap");
|
|
238
|
+
module2.exports.KeelCamelCasePlugin = KeelCamelCasePlugin;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// src/tracing.js
|
|
243
|
+
var require_tracing = __commonJS({
|
|
244
|
+
"src/tracing.js"(exports2, module2) {
|
|
245
|
+
"use strict";
|
|
246
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
247
|
+
var { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
|
|
248
|
+
var {
|
|
249
|
+
OTLPTraceExporter
|
|
250
|
+
} = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
251
|
+
var { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
|
|
252
|
+
var { envDetectorSync } = require("@opentelemetry/resources");
|
|
253
|
+
async function withSpan(name, fn) {
|
|
254
|
+
return getTracer().startActiveSpan(name, async (span) => {
|
|
255
|
+
try {
|
|
256
|
+
return await fn(span);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
span.recordException(err);
|
|
259
|
+
span.setStatus({
|
|
260
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
261
|
+
message: err.message
|
|
262
|
+
});
|
|
263
|
+
throw err;
|
|
264
|
+
} finally {
|
|
265
|
+
span.end();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
__name(withSpan, "withSpan");
|
|
270
|
+
function patchFetch() {
|
|
271
|
+
if (!globalThis.fetch.patched) {
|
|
272
|
+
const originalFetch = globalThis.fetch;
|
|
273
|
+
globalThis.fetch = async (...args) => {
|
|
274
|
+
return withSpan("fetch", async (span) => {
|
|
275
|
+
const url = new URL(
|
|
276
|
+
args[0] instanceof Request ? args[0].url : String(args[0])
|
|
277
|
+
);
|
|
278
|
+
span.setAttribute("http.url", url.toString());
|
|
279
|
+
const scheme = url.protocol.replace(":", "");
|
|
280
|
+
span.setAttribute("http.scheme", scheme);
|
|
281
|
+
const options = args[0] instanceof Request ? args[0] : args[1] || {};
|
|
282
|
+
const method = (options.method || "GET").toUpperCase();
|
|
283
|
+
span.setAttribute("http.method", method);
|
|
284
|
+
const res = await originalFetch(...args);
|
|
285
|
+
span.setAttribute("http.status", res.status);
|
|
286
|
+
span.setAttribute("http.status_text", res.statusText);
|
|
287
|
+
return res;
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
globalThis.fetch.patched = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
__name(patchFetch, "patchFetch");
|
|
294
|
+
function patchConsoleLog() {
|
|
295
|
+
if (!console.log.patched) {
|
|
296
|
+
const originalConsoleLog = console.log;
|
|
297
|
+
console.log = (...args) => {
|
|
298
|
+
const span = opentelemetry.trace.getActiveSpan();
|
|
299
|
+
if (span) {
|
|
300
|
+
const output = args.map((arg) => {
|
|
301
|
+
if (arg instanceof Error) {
|
|
302
|
+
return arg.stack;
|
|
303
|
+
}
|
|
304
|
+
if (typeof arg === "object") {
|
|
305
|
+
try {
|
|
306
|
+
return JSON.stringify(arg, getCircularReplacer());
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return "[Object with circular references]";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (typeof arg === "function") {
|
|
312
|
+
return arg() || arg.name || arg.toString();
|
|
313
|
+
}
|
|
314
|
+
return String(arg);
|
|
315
|
+
}).join(" ");
|
|
316
|
+
span.addEvent(output);
|
|
317
|
+
}
|
|
318
|
+
originalConsoleLog(...args);
|
|
319
|
+
};
|
|
320
|
+
console.log.patched = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
__name(patchConsoleLog, "patchConsoleLog");
|
|
324
|
+
function patchConsoleError() {
|
|
325
|
+
if (!console.error.patched) {
|
|
326
|
+
const originalConsoleError = console.error;
|
|
327
|
+
console.error = (...args) => {
|
|
328
|
+
const span = opentelemetry.trace.getActiveSpan();
|
|
329
|
+
if (span) {
|
|
330
|
+
const output = args.map((arg) => {
|
|
331
|
+
if (arg instanceof Error) {
|
|
332
|
+
return arg.stack;
|
|
333
|
+
}
|
|
334
|
+
if (typeof arg === "object") {
|
|
335
|
+
try {
|
|
336
|
+
return JSON.stringify(arg, getCircularReplacer());
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return "[Object with circular references]";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (typeof arg === "function") {
|
|
342
|
+
return arg() || arg.name || arg.toString();
|
|
343
|
+
}
|
|
344
|
+
return String(arg);
|
|
345
|
+
}).join(" ");
|
|
346
|
+
span.setStatus({
|
|
347
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
348
|
+
message: output
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
originalConsoleError(...args);
|
|
352
|
+
};
|
|
353
|
+
console.error.patched = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
__name(patchConsoleError, "patchConsoleError");
|
|
357
|
+
function getCircularReplacer() {
|
|
358
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
359
|
+
return (key, value) => {
|
|
360
|
+
if (typeof value === "object" && value !== null) {
|
|
361
|
+
if (seen.has(value)) {
|
|
362
|
+
return "[Circular]";
|
|
363
|
+
}
|
|
364
|
+
seen.add(value);
|
|
365
|
+
}
|
|
366
|
+
return value;
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
__name(getCircularReplacer, "getCircularReplacer");
|
|
370
|
+
function init() {
|
|
371
|
+
if (process.env.KEEL_TRACING_ENABLED == "true") {
|
|
372
|
+
const exporter = new OTLPTraceExporter();
|
|
373
|
+
const processor = new BatchSpanProcessor(exporter);
|
|
374
|
+
const provider = new NodeTracerProvider({
|
|
375
|
+
resource: envDetectorSync.detect(),
|
|
376
|
+
spanProcessors: [processor]
|
|
377
|
+
});
|
|
378
|
+
provider.register();
|
|
379
|
+
}
|
|
380
|
+
patchFetch();
|
|
381
|
+
patchConsoleLog();
|
|
382
|
+
patchConsoleError();
|
|
383
|
+
}
|
|
384
|
+
__name(init, "init");
|
|
385
|
+
async function forceFlush() {
|
|
386
|
+
const provider = opentelemetry.trace.getTracerProvider().getDelegate();
|
|
387
|
+
if (provider && provider.forceFlush) {
|
|
388
|
+
await provider.forceFlush();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
__name(forceFlush, "forceFlush");
|
|
392
|
+
function getTracer() {
|
|
393
|
+
return opentelemetry.trace.getTracer("functions");
|
|
394
|
+
}
|
|
395
|
+
__name(getTracer, "getTracer");
|
|
396
|
+
function spanNameForModelAPI(modelName, action) {
|
|
397
|
+
return `Database ${modelName}.${action}`;
|
|
398
|
+
}
|
|
399
|
+
__name(spanNameForModelAPI, "spanNameForModelAPI");
|
|
400
|
+
module2.exports = {
|
|
401
|
+
getTracer,
|
|
402
|
+
withSpan,
|
|
403
|
+
init,
|
|
404
|
+
forceFlush,
|
|
405
|
+
spanNameForModelAPI
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// src/database.js
|
|
411
|
+
var require_database = __commonJS({
|
|
412
|
+
"src/database.js"(exports2, module2) {
|
|
413
|
+
"use strict";
|
|
414
|
+
var { Kysely, PostgresDialect } = require("kysely");
|
|
415
|
+
var neonserverless = require("@neondatabase/serverless");
|
|
416
|
+
var { AsyncLocalStorage } = require("async_hooks");
|
|
417
|
+
var { AuditContextPlugin } = require_auditing();
|
|
418
|
+
var { KeelCamelCasePlugin } = require_camelCasePlugin();
|
|
419
|
+
var pg = require("pg");
|
|
420
|
+
var { withSpan } = require_tracing();
|
|
421
|
+
var ws = require("ws");
|
|
422
|
+
var fs = require("fs");
|
|
423
|
+
var { Duration } = require_Duration();
|
|
424
|
+
async function withDatabase(db, requiresTransaction, cb) {
|
|
425
|
+
if (requiresTransaction) {
|
|
426
|
+
return db.transaction().execute(async (transaction) => {
|
|
427
|
+
return dbInstance.run(transaction, async () => {
|
|
428
|
+
return cb({ transaction });
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return db.connection().execute(async (sDb) => {
|
|
433
|
+
return dbInstance.run(sDb, async () => {
|
|
434
|
+
return cb({ sDb });
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
__name(withDatabase, "withDatabase");
|
|
439
|
+
var dbInstance = new AsyncLocalStorage();
|
|
440
|
+
var vitestDb = null;
|
|
441
|
+
function useDatabase2() {
|
|
442
|
+
let fromStore = dbInstance.getStore();
|
|
443
|
+
if (fromStore) {
|
|
444
|
+
return fromStore;
|
|
445
|
+
}
|
|
446
|
+
if ("NODE_ENV" in process.env && process.env.NODE_ENV == "test") {
|
|
447
|
+
if (!vitestDb) {
|
|
448
|
+
vitestDb = createDatabaseClient();
|
|
449
|
+
}
|
|
450
|
+
return vitestDb;
|
|
451
|
+
}
|
|
452
|
+
console.trace();
|
|
453
|
+
throw new Error("useDatabase must be called within a function");
|
|
454
|
+
}
|
|
455
|
+
__name(useDatabase2, "useDatabase");
|
|
456
|
+
function createDatabaseClient({ connString } = {}) {
|
|
457
|
+
const db = new Kysely({
|
|
458
|
+
dialect: getDialect(connString),
|
|
459
|
+
plugins: [
|
|
460
|
+
// ensures that the audit context data is written to Postgres configuration parameters
|
|
461
|
+
new AuditContextPlugin(),
|
|
462
|
+
// allows users to query using camelCased versions of the database column names, which
|
|
463
|
+
// should match the names we use in our schema.
|
|
464
|
+
// We're using an extended version of Kysely's CamelCasePlugin which avoids changing keys of objects that represent
|
|
465
|
+
// rich data formats, specific to Keel (e.g. Duration)
|
|
466
|
+
new KeelCamelCasePlugin()
|
|
467
|
+
],
|
|
468
|
+
log(event) {
|
|
469
|
+
if ("DEBUG" in process.env) {
|
|
470
|
+
if (event.level === "query") {
|
|
471
|
+
console.log(event.query.sql);
|
|
472
|
+
console.log(event.query.parameters);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return db;
|
|
478
|
+
}
|
|
479
|
+
__name(createDatabaseClient, "createDatabaseClient");
|
|
480
|
+
var _InstrumentedPool = class _InstrumentedPool extends pg.Pool {
|
|
481
|
+
async connect(...args) {
|
|
482
|
+
const _super = super.connect.bind(this);
|
|
483
|
+
return withSpan("Database Connect", function(span) {
|
|
484
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
485
|
+
return _super(...args);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
__name(_InstrumentedPool, "InstrumentedPool");
|
|
490
|
+
var InstrumentedPool = _InstrumentedPool;
|
|
491
|
+
var _InstrumentedNeonServerlessPool = class _InstrumentedNeonServerlessPool extends neonserverless.Pool {
|
|
492
|
+
async connect(...args) {
|
|
493
|
+
const _super = super.connect.bind(this);
|
|
494
|
+
return withSpan("Database Connect", function(span) {
|
|
495
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
496
|
+
return _super(...args);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
__name(_InstrumentedNeonServerlessPool, "InstrumentedNeonServerlessPool");
|
|
501
|
+
var InstrumentedNeonServerlessPool = _InstrumentedNeonServerlessPool;
|
|
502
|
+
var txStatements = {
|
|
503
|
+
begin: "Transaction Begin",
|
|
504
|
+
commit: "Transaction Commit",
|
|
505
|
+
rollback: "Transaction Rollback"
|
|
506
|
+
};
|
|
507
|
+
var _InstrumentedClient = class _InstrumentedClient extends pg.Client {
|
|
508
|
+
async query(...args) {
|
|
509
|
+
const _super = super.query.bind(this);
|
|
510
|
+
const sql = args[0];
|
|
511
|
+
let sqlAttribute = false;
|
|
512
|
+
let spanName = txStatements[sql.toLowerCase()];
|
|
513
|
+
if (!spanName) {
|
|
514
|
+
spanName = "Database Query";
|
|
515
|
+
sqlAttribute = true;
|
|
516
|
+
}
|
|
517
|
+
return withSpan(spanName, function(span) {
|
|
518
|
+
if (sqlAttribute) {
|
|
519
|
+
span.setAttribute("sql", args[0]);
|
|
520
|
+
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
521
|
+
}
|
|
522
|
+
return _super(...args);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
__name(_InstrumentedClient, "InstrumentedClient");
|
|
527
|
+
var InstrumentedClient = _InstrumentedClient;
|
|
528
|
+
function getDialect(connString) {
|
|
529
|
+
const dbConnType = process.env.KEEL_DB_CONN_TYPE;
|
|
530
|
+
switch (dbConnType) {
|
|
531
|
+
case "pg":
|
|
532
|
+
pg.types.setTypeParser(pg.types.builtins.NUMERIC, function(val) {
|
|
533
|
+
return parseFloat(val);
|
|
534
|
+
});
|
|
535
|
+
pg.types.setTypeParser(pg.types.builtins.INTERVAL, function(val) {
|
|
536
|
+
return new Duration(val);
|
|
537
|
+
});
|
|
538
|
+
return new PostgresDialect({
|
|
539
|
+
pool: new InstrumentedPool({
|
|
540
|
+
Client: InstrumentedClient,
|
|
541
|
+
// Increased idle time before closing a connection in the local pool (from 10s default).
|
|
542
|
+
// Establising a new connection on (almost) every functions query can be expensive, so this
|
|
543
|
+
// will reduce having to open connections as regularly. https://node-postgres.com/apis/pool
|
|
544
|
+
//
|
|
545
|
+
// NOTE: We should consider setting this to 0 (i.e. never pool locally) and open and close
|
|
546
|
+
// connections with each invocation. This is because the freeze/thaw nature of lambdas can cause problems
|
|
547
|
+
// with long-lived connections - see https://github.com/brianc/node-postgres/issues/2718
|
|
548
|
+
// Once we're "fully regional" this should not be a performance problem anymore.
|
|
549
|
+
//
|
|
550
|
+
// Although I doubt we will run into these freeze/thaw issues if idleTimeoutMillis is always shorter than the
|
|
551
|
+
// time is takes for a lambda to freeze (which is not a constant, but could be as short as several minutes,
|
|
552
|
+
// https://www.pluralsight.com/resources/blog/cloud/how-long-does-aws-lambda-keep-your-idle-functions-around-before-a-cold-start)
|
|
553
|
+
idleTimeoutMillis: 5e4,
|
|
554
|
+
// If connString is not passed fall back to reading from env var
|
|
555
|
+
connectionString: connString || process.env.KEEL_DB_CONN,
|
|
556
|
+
// Allow the setting of a cert (.pem) file. RDS requires this to enforce SSL.
|
|
557
|
+
...process.env.KEEL_DB_CERT ? { ssl: { ca: fs.readFileSync(process.env.KEEL_DB_CERT) } } : void 0
|
|
558
|
+
})
|
|
559
|
+
});
|
|
560
|
+
case "neon":
|
|
561
|
+
neonserverless.types.setTypeParser(
|
|
562
|
+
pg.types.builtins.NUMERIC,
|
|
563
|
+
function(val) {
|
|
564
|
+
return parseFloat(val);
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
neonserverless.types.setTypeParser(
|
|
568
|
+
pg.types.builtins.INTERVAL,
|
|
569
|
+
function(val) {
|
|
570
|
+
return new Duration(val);
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
neonserverless.neonConfig.webSocketConstructor = ws;
|
|
574
|
+
const pool = new InstrumentedNeonServerlessPool({
|
|
575
|
+
// If connString is not passed fall back to reading from env var
|
|
576
|
+
connectionString: connString || process.env.KEEL_DB_CONN
|
|
577
|
+
});
|
|
578
|
+
pool.on("connect", (client) => {
|
|
579
|
+
const originalQuery = client.query;
|
|
580
|
+
client.query = function(...args) {
|
|
581
|
+
const sql = args[0];
|
|
582
|
+
let sqlAttribute = false;
|
|
583
|
+
let spanName = txStatements[sql.toLowerCase()];
|
|
584
|
+
if (!spanName) {
|
|
585
|
+
spanName = "Database Query";
|
|
586
|
+
sqlAttribute = true;
|
|
587
|
+
}
|
|
588
|
+
return withSpan(spanName, function(span) {
|
|
589
|
+
if (sqlAttribute) {
|
|
590
|
+
span.setAttribute("sql", args[0]);
|
|
591
|
+
span.setAttribute("dialect", dbConnType);
|
|
592
|
+
}
|
|
593
|
+
return originalQuery.apply(client, args);
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
return new PostgresDialect({
|
|
598
|
+
pool
|
|
599
|
+
});
|
|
600
|
+
default:
|
|
601
|
+
throw Error("unexpected KEEL_DB_CONN_TYPE: " + dbConnType);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
__name(getDialect, "getDialect");
|
|
605
|
+
module2.exports = {
|
|
606
|
+
createDatabaseClient,
|
|
607
|
+
useDatabase: useDatabase2,
|
|
608
|
+
withDatabase
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// src/errors.js
|
|
614
|
+
var require_errors = __commonJS({
|
|
615
|
+
"src/errors.js"(exports2, module2) {
|
|
616
|
+
"use strict";
|
|
617
|
+
var { createJSONRPCErrorResponse } = require("json-rpc-2.0");
|
|
618
|
+
var RuntimeErrors = {
|
|
619
|
+
// Catchall error type for unhandled execution errors during custom function
|
|
620
|
+
UnknownError: -32001,
|
|
621
|
+
// DatabaseError represents any error at pg level that isn't handled explicitly below
|
|
622
|
+
DatabaseError: -32002,
|
|
623
|
+
// No result returned from custom function by user
|
|
624
|
+
NoResultError: -32003,
|
|
625
|
+
// When trying to delete/update a non existent record in the db
|
|
626
|
+
RecordNotFoundError: -32004,
|
|
627
|
+
ForeignKeyConstraintError: -32005,
|
|
628
|
+
NotNullConstraintError: -32006,
|
|
629
|
+
UniqueConstraintError: -32007,
|
|
630
|
+
PermissionError: -32008,
|
|
631
|
+
BadRequestError: -32009
|
|
632
|
+
};
|
|
633
|
+
var _PermissionError = class _PermissionError extends Error {
|
|
634
|
+
};
|
|
635
|
+
__name(_PermissionError, "PermissionError");
|
|
636
|
+
var PermissionError = _PermissionError;
|
|
637
|
+
var _DatabaseError = class _DatabaseError extends Error {
|
|
638
|
+
constructor(error) {
|
|
639
|
+
super(error.message);
|
|
640
|
+
this.error = error;
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
__name(_DatabaseError, "DatabaseError");
|
|
644
|
+
var DatabaseError = _DatabaseError;
|
|
645
|
+
var _NotFoundError = class _NotFoundError extends Error {
|
|
646
|
+
errorCode = RuntimeErrors.RecordNotFoundError;
|
|
647
|
+
constructor(message) {
|
|
648
|
+
super(message);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
__name(_NotFoundError, "NotFoundError");
|
|
652
|
+
var NotFoundError = _NotFoundError;
|
|
653
|
+
var _BadRequestError = class _BadRequestError extends Error {
|
|
654
|
+
errorCode = RuntimeErrors.BadRequestError;
|
|
655
|
+
constructor(message = "bad request") {
|
|
656
|
+
super(message);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
__name(_BadRequestError, "BadRequestError");
|
|
660
|
+
var BadRequestError = _BadRequestError;
|
|
661
|
+
var _UnknownError = class _UnknownError extends Error {
|
|
662
|
+
errorCode = RuntimeErrors.UnknownError;
|
|
663
|
+
constructor(message = "unknown error") {
|
|
664
|
+
super(message);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
__name(_UnknownError, "UnknownError");
|
|
668
|
+
var UnknownError = _UnknownError;
|
|
669
|
+
var ErrorPresets = {
|
|
670
|
+
NotFound: NotFoundError,
|
|
671
|
+
BadRequest: BadRequestError,
|
|
672
|
+
Unknown: UnknownError
|
|
673
|
+
};
|
|
674
|
+
function errorToJSONRPCResponse(request, e) {
|
|
675
|
+
switch (e.constructor.name) {
|
|
676
|
+
case "PermissionError":
|
|
677
|
+
return createJSONRPCErrorResponse(
|
|
678
|
+
request.id,
|
|
679
|
+
RuntimeErrors.PermissionError,
|
|
680
|
+
e.message
|
|
681
|
+
);
|
|
682
|
+
// Any error thrown in the ModelAPI class is
|
|
683
|
+
// wrapped in a DatabaseError in order to differentiate 'our code' vs the user's own code.
|
|
684
|
+
case "NoResultError":
|
|
685
|
+
return createJSONRPCErrorResponse(
|
|
686
|
+
request.id,
|
|
687
|
+
// to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
|
|
688
|
+
RuntimeErrors.RecordNotFoundError,
|
|
689
|
+
""
|
|
690
|
+
// Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
|
|
691
|
+
);
|
|
692
|
+
case "DatabaseError":
|
|
693
|
+
let err = e;
|
|
694
|
+
if (e instanceof DatabaseError) {
|
|
695
|
+
err = e.error;
|
|
696
|
+
}
|
|
697
|
+
if (err.constructor.name == "NoResultError") {
|
|
698
|
+
return createJSONRPCErrorResponse(
|
|
699
|
+
request.id,
|
|
700
|
+
// to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
|
|
701
|
+
RuntimeErrors.RecordNotFoundError,
|
|
702
|
+
""
|
|
703
|
+
// Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
if ("code" in err) {
|
|
707
|
+
const { code, detail, table: table2 } = err;
|
|
708
|
+
let rpcErrorCode, column, value;
|
|
709
|
+
const [col, val] = parseKeyMessage(err.detail);
|
|
710
|
+
column = col;
|
|
711
|
+
value = val;
|
|
712
|
+
switch (code) {
|
|
713
|
+
case "23502":
|
|
714
|
+
rpcErrorCode = RuntimeErrors.NotNullConstraintError;
|
|
715
|
+
column = err.column;
|
|
716
|
+
break;
|
|
717
|
+
case "23503":
|
|
718
|
+
rpcErrorCode = RuntimeErrors.ForeignKeyConstraintError;
|
|
719
|
+
break;
|
|
720
|
+
case "23505":
|
|
721
|
+
rpcErrorCode = RuntimeErrors.UniqueConstraintError;
|
|
722
|
+
break;
|
|
723
|
+
default:
|
|
724
|
+
rpcErrorCode = RuntimeErrors.DatabaseError;
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
return createJSONRPCErrorResponse(request.id, rpcErrorCode, e.message, {
|
|
728
|
+
table: table2,
|
|
729
|
+
column,
|
|
730
|
+
code,
|
|
731
|
+
detail,
|
|
732
|
+
value
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
return createJSONRPCErrorResponse(
|
|
736
|
+
request.id,
|
|
737
|
+
RuntimeErrors.DatabaseError,
|
|
738
|
+
e.message
|
|
739
|
+
);
|
|
740
|
+
default:
|
|
741
|
+
return createJSONRPCErrorResponse(
|
|
742
|
+
request.id,
|
|
743
|
+
e.errorCode ?? RuntimeErrors.UnknownError,
|
|
744
|
+
e.message
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
__name(errorToJSONRPCResponse, "errorToJSONRPCResponse");
|
|
749
|
+
var keyMessagePattern = /\Key\s[(](.*)[)][=][(](.*)[)]/;
|
|
750
|
+
var parseKeyMessage = /* @__PURE__ */ __name((msg) => {
|
|
751
|
+
const [, col, value] = keyMessagePattern.exec(msg) || [];
|
|
752
|
+
return [col, value];
|
|
753
|
+
}, "parseKeyMessage");
|
|
754
|
+
module2.exports = {
|
|
755
|
+
errorToJSONRPCResponse,
|
|
756
|
+
RuntimeErrors,
|
|
757
|
+
DatabaseError,
|
|
758
|
+
PermissionError,
|
|
759
|
+
ErrorPresets
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// src/File.js
|
|
765
|
+
var require_File = __commonJS({
|
|
766
|
+
"src/File.js"(exports2, module2) {
|
|
767
|
+
"use strict";
|
|
768
|
+
var {
|
|
769
|
+
S3Client,
|
|
770
|
+
PutObjectCommand,
|
|
771
|
+
GetObjectCommand
|
|
772
|
+
} = require("@aws-sdk/client-s3");
|
|
773
|
+
var { fromEnv } = require("@aws-sdk/credential-providers");
|
|
774
|
+
var { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
|
775
|
+
var { useDatabase: useDatabase2 } = require_database();
|
|
776
|
+
var { DatabaseError } = require_errors();
|
|
777
|
+
var KSUID = require("ksuid");
|
|
778
|
+
var s3Client = (() => {
|
|
779
|
+
if (!process.env.KEEL_FILES_BUCKET_NAME) {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
const endpoint = process.env.TEST_AWS_ENDPOINT;
|
|
783
|
+
if (!endpoint) {
|
|
784
|
+
return new S3Client({
|
|
785
|
+
region: process.env.KEEL_REGION,
|
|
786
|
+
credentials: fromEnv()
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return new S3Client({
|
|
790
|
+
region: process.env.KEEL_REGION,
|
|
791
|
+
// If a test endpoint is provided then use some test credentials
|
|
792
|
+
credentials: {
|
|
793
|
+
accessKeyId: "test",
|
|
794
|
+
secretAccessKey: "test"
|
|
795
|
+
},
|
|
796
|
+
// If a custom endpoint is set we need to use a custom resolver. Just setting the base endpoint isn't enough for S3 as it
|
|
797
|
+
// as the default resolver uses the bucket name as a sub-domain, which likely won't work with the custom endpoint.
|
|
798
|
+
// By implementing a full resolver we can force it to be the endpoint we want.
|
|
799
|
+
endpointProvider: /* @__PURE__ */ __name(() => {
|
|
800
|
+
return {
|
|
801
|
+
url: new URL(endpoint)
|
|
802
|
+
};
|
|
803
|
+
}, "endpointProvider")
|
|
804
|
+
});
|
|
805
|
+
})();
|
|
806
|
+
var _InlineFile = class _InlineFile {
|
|
807
|
+
constructor({ filename, contentType }) {
|
|
808
|
+
this._filename = filename;
|
|
809
|
+
this._contentType = contentType;
|
|
810
|
+
this._contents = null;
|
|
811
|
+
}
|
|
812
|
+
static fromDataURL(dataURL) {
|
|
813
|
+
var info = dataURL.split(",")[0].split(":")[1];
|
|
814
|
+
var data = dataURL.split(",")[1];
|
|
815
|
+
var mime = info.split(";")[0];
|
|
816
|
+
var name = info.split(";")[1].split("=")[1];
|
|
817
|
+
var buffer = Buffer.from(data, "base64");
|
|
818
|
+
var file = new _InlineFile({ filename: name, contentType: mime });
|
|
819
|
+
file.write(buffer);
|
|
820
|
+
return file;
|
|
821
|
+
}
|
|
822
|
+
get size() {
|
|
823
|
+
if (this._contents) {
|
|
824
|
+
return this._contents.size;
|
|
825
|
+
} else {
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
get contentType() {
|
|
830
|
+
return this._contentType;
|
|
831
|
+
}
|
|
832
|
+
get filename() {
|
|
833
|
+
return this._filename;
|
|
834
|
+
}
|
|
835
|
+
write(buffer) {
|
|
836
|
+
this._contents = new Blob([buffer]);
|
|
837
|
+
}
|
|
838
|
+
// Read the contents of the file. If URL is set, it will be read from the remote storage, otherwise, if dataURL is set
|
|
839
|
+
// on the instance, it will return a blob with the file contents
|
|
840
|
+
async read() {
|
|
841
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
842
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
843
|
+
return buffer;
|
|
844
|
+
}
|
|
845
|
+
async store(expires = null) {
|
|
846
|
+
const content = await this.read();
|
|
847
|
+
const key = KSUID.randomSync().string;
|
|
848
|
+
await storeFile(
|
|
849
|
+
content,
|
|
850
|
+
key,
|
|
851
|
+
this._filename,
|
|
852
|
+
this._contentType,
|
|
853
|
+
this.size,
|
|
854
|
+
expires
|
|
855
|
+
);
|
|
856
|
+
return new File({
|
|
857
|
+
key,
|
|
858
|
+
size: this.size,
|
|
859
|
+
filename: this.filename,
|
|
860
|
+
contentType: this.contentType
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
__name(_InlineFile, "InlineFile");
|
|
865
|
+
var InlineFile = _InlineFile;
|
|
866
|
+
var _File = class _File extends InlineFile {
|
|
867
|
+
constructor(input) {
|
|
868
|
+
super({ filename: input.filename, contentType: input.contentType });
|
|
869
|
+
this._key = input.key;
|
|
870
|
+
this._size = input.size;
|
|
871
|
+
}
|
|
872
|
+
static fromDbRecord({ key, filename, size, contentType }) {
|
|
873
|
+
return new _File({
|
|
874
|
+
key,
|
|
875
|
+
filename,
|
|
876
|
+
size,
|
|
877
|
+
contentType
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
get size() {
|
|
881
|
+
return this._size;
|
|
882
|
+
}
|
|
883
|
+
get key() {
|
|
884
|
+
return this._key;
|
|
885
|
+
}
|
|
886
|
+
async read() {
|
|
887
|
+
if (this._contents) {
|
|
888
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
889
|
+
return Buffer.from(arrayBuffer);
|
|
890
|
+
}
|
|
891
|
+
if (s3Client) {
|
|
892
|
+
const params = {
|
|
893
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
894
|
+
Key: "files/" + this.key
|
|
895
|
+
};
|
|
896
|
+
const command = new GetObjectCommand(params);
|
|
897
|
+
const response = await s3Client.send(command);
|
|
898
|
+
const blob = await response.Body.transformToByteArray();
|
|
899
|
+
return Buffer.from(blob);
|
|
900
|
+
}
|
|
901
|
+
const db = useDatabase2();
|
|
902
|
+
try {
|
|
903
|
+
let query = db.selectFrom("keel_storage").select("data").where("id", "=", this.key);
|
|
904
|
+
const row = await query.executeTakeFirstOrThrow();
|
|
905
|
+
return row.data;
|
|
906
|
+
} catch (e) {
|
|
907
|
+
throw new DatabaseError(e);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async store(expires = null) {
|
|
911
|
+
if (this._contents) {
|
|
912
|
+
const contents = await this.read();
|
|
913
|
+
await storeFile(
|
|
914
|
+
contents,
|
|
915
|
+
this.key,
|
|
916
|
+
this.filename,
|
|
917
|
+
this.contentType,
|
|
918
|
+
expires
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
return this;
|
|
922
|
+
}
|
|
923
|
+
async getPresignedUrl() {
|
|
924
|
+
if (s3Client) {
|
|
925
|
+
const command = new GetObjectCommand({
|
|
926
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
927
|
+
Key: "files/" + this.key,
|
|
928
|
+
ResponseContentDisposition: "inline"
|
|
929
|
+
});
|
|
930
|
+
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
931
|
+
return new URL(url);
|
|
932
|
+
} else {
|
|
933
|
+
const contents = await this.read();
|
|
934
|
+
const dataurl = `data:${this.contentType};name=${this.filename};base64,${contents.toString("base64")}`;
|
|
935
|
+
return new URL(dataurl);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
toDbRecord() {
|
|
939
|
+
return {
|
|
940
|
+
key: this.key,
|
|
941
|
+
filename: this.filename,
|
|
942
|
+
contentType: this.contentType,
|
|
943
|
+
size: this.size
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
toJSON() {
|
|
947
|
+
return {
|
|
948
|
+
key: this.key,
|
|
949
|
+
filename: this.filename,
|
|
950
|
+
contentType: this.contentType,
|
|
951
|
+
size: this.size
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
__name(_File, "File");
|
|
956
|
+
var File = _File;
|
|
957
|
+
async function storeFile(contents, key, filename, contentType, expires) {
|
|
958
|
+
if (s3Client) {
|
|
959
|
+
const params = {
|
|
960
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
961
|
+
Key: "files/" + key,
|
|
962
|
+
Body: contents,
|
|
963
|
+
ContentType: contentType,
|
|
964
|
+
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
965
|
+
filename
|
|
966
|
+
)}"`,
|
|
967
|
+
Metadata: {
|
|
968
|
+
filename
|
|
969
|
+
},
|
|
970
|
+
ACL: "private"
|
|
971
|
+
};
|
|
972
|
+
if (expires) {
|
|
973
|
+
if (expires instanceof Date) {
|
|
974
|
+
params.Expires = expires;
|
|
975
|
+
} else {
|
|
976
|
+
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const command = new PutObjectCommand(params);
|
|
980
|
+
try {
|
|
981
|
+
await s3Client.send(command);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
console.error("Error uploading file:", error);
|
|
984
|
+
throw error;
|
|
985
|
+
}
|
|
986
|
+
} else {
|
|
987
|
+
const db = useDatabase2();
|
|
988
|
+
try {
|
|
989
|
+
let query = db.insertInto("keel_storage").values({
|
|
990
|
+
id: key,
|
|
991
|
+
filename,
|
|
992
|
+
content_type: contentType,
|
|
993
|
+
data: contents
|
|
994
|
+
}).onConflict(
|
|
995
|
+
(oc) => oc.column("id").doUpdateSet(() => ({
|
|
996
|
+
filename,
|
|
997
|
+
content_type: contentType,
|
|
998
|
+
data: contents
|
|
999
|
+
})).where("keel_storage.id", "=", key)
|
|
1000
|
+
).returningAll();
|
|
1001
|
+
await query.execute();
|
|
1002
|
+
} catch (e) {
|
|
1003
|
+
throw new DatabaseError(e);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
__name(storeFile, "storeFile");
|
|
1008
|
+
module2.exports = {
|
|
1009
|
+
InlineFile,
|
|
1010
|
+
File
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
// src/parsing.js
|
|
1016
|
+
var require_parsing = __commonJS({
|
|
1017
|
+
"src/parsing.js"(exports2, module2) {
|
|
1018
|
+
"use strict";
|
|
1019
|
+
var { Duration } = require_Duration();
|
|
1020
|
+
var { InlineFile, File } = require_File();
|
|
1021
|
+
var { isPlainObject } = require_type_utils();
|
|
1022
|
+
function parseInputs(inputs) {
|
|
1023
|
+
if (inputs != null && typeof inputs === "object") {
|
|
1024
|
+
for (const k of Object.keys(inputs)) {
|
|
1025
|
+
if (inputs[k] !== null && typeof inputs[k] === "object") {
|
|
1026
|
+
if (Array.isArray(inputs[k])) {
|
|
1027
|
+
inputs[k] = inputs[k].map((item) => {
|
|
1028
|
+
if (item && typeof item === "object") {
|
|
1029
|
+
if ("__typename" in item) {
|
|
1030
|
+
return parseComplexInputType(item);
|
|
1031
|
+
}
|
|
1032
|
+
return parseInputs(item);
|
|
1033
|
+
}
|
|
1034
|
+
return item;
|
|
1035
|
+
});
|
|
1036
|
+
} else if ("__typename" in inputs[k]) {
|
|
1037
|
+
inputs[k] = parseComplexInputType(inputs[k]);
|
|
1038
|
+
} else {
|
|
1039
|
+
inputs[k] = parseInputs(inputs[k]);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return inputs;
|
|
1045
|
+
}
|
|
1046
|
+
__name(parseInputs, "parseInputs");
|
|
1047
|
+
function parseComplexInputType(value) {
|
|
1048
|
+
switch (value.__typename) {
|
|
1049
|
+
case "InlineFile":
|
|
1050
|
+
return InlineFile.fromDataURL(value.dataURL);
|
|
1051
|
+
case "Duration":
|
|
1052
|
+
return Duration.fromISOString(value.interval);
|
|
1053
|
+
default:
|
|
1054
|
+
throw new Error("complex type not handled: " + value.__typename);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
__name(parseComplexInputType, "parseComplexInputType");
|
|
1058
|
+
async function parseOutputs(outputs) {
|
|
1059
|
+
if (outputs != null && typeof outputs === "object") {
|
|
1060
|
+
for (const k of Object.keys(outputs)) {
|
|
1061
|
+
if (outputs[k] !== null && typeof outputs[k] === "object") {
|
|
1062
|
+
if (Array.isArray(outputs[k])) {
|
|
1063
|
+
outputs[k] = await Promise.all(
|
|
1064
|
+
outputs[k].map((item) => parseOutputs(item))
|
|
1065
|
+
);
|
|
1066
|
+
} else if (outputs[k] instanceof InlineFile) {
|
|
1067
|
+
const stored = await outputs[k].store();
|
|
1068
|
+
outputs[k] = stored;
|
|
1069
|
+
} else if (outputs[k] instanceof Duration) {
|
|
1070
|
+
outputs[k] = outputs[k].toISOString();
|
|
1071
|
+
} else {
|
|
1072
|
+
outputs[k] = await parseOutputs(outputs[k]);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return outputs;
|
|
1078
|
+
}
|
|
1079
|
+
__name(parseOutputs, "parseOutputs");
|
|
1080
|
+
function transformRichDataTypes(data) {
|
|
1081
|
+
const keys = data ? Object.keys(data) : [];
|
|
1082
|
+
const row = {};
|
|
1083
|
+
for (const key of keys) {
|
|
1084
|
+
const value = data[key];
|
|
1085
|
+
if (Array.isArray(value)) {
|
|
1086
|
+
row[key] = value.map((item) => transformRichDataTypes({ item }).item);
|
|
1087
|
+
} else if (isPlainObject(value)) {
|
|
1088
|
+
if (value._typename == "Duration" && value.pgInterval) {
|
|
1089
|
+
row[key] = new Duration(value.pgInterval);
|
|
1090
|
+
} else if (value.key && value.size && value.filename && value.contentType) {
|
|
1091
|
+
row[key] = File.fromDbRecord(value);
|
|
1092
|
+
} else {
|
|
1093
|
+
row[key] = value;
|
|
1094
|
+
}
|
|
1095
|
+
} else {
|
|
1096
|
+
row[key] = value;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return row;
|
|
1100
|
+
}
|
|
1101
|
+
__name(transformRichDataTypes, "transformRichDataTypes");
|
|
1102
|
+
function isReferencingExistingRecord(value) {
|
|
1103
|
+
return Object.keys(value).length === 1 && value.id;
|
|
1104
|
+
}
|
|
1105
|
+
__name(isReferencingExistingRecord, "isReferencingExistingRecord");
|
|
1106
|
+
module2.exports = {
|
|
1107
|
+
parseInputs,
|
|
1108
|
+
parseOutputs,
|
|
1109
|
+
transformRichDataTypes,
|
|
1110
|
+
isReferencingExistingRecord
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// src/casing.js
|
|
1116
|
+
var require_casing = __commonJS({
|
|
1117
|
+
"src/casing.js"(exports2, module2) {
|
|
1118
|
+
"use strict";
|
|
1119
|
+
var { snakeCase, camelCase } = require("change-case");
|
|
1120
|
+
function camelCaseObject(obj = {}) {
|
|
1121
|
+
const r = {};
|
|
1122
|
+
for (const key of Object.keys(obj)) {
|
|
1123
|
+
r[camelCase(key, {
|
|
1124
|
+
transform: camelCaseTransform,
|
|
1125
|
+
splitRegexp: [
|
|
1126
|
+
/([a-z0-9])([A-Z])/g,
|
|
1127
|
+
/([A-Z])([A-Z][a-z])/g,
|
|
1128
|
+
/([a-zA-Z])([0-9])/g
|
|
1129
|
+
]
|
|
1130
|
+
})] = obj[key];
|
|
1131
|
+
}
|
|
1132
|
+
return r;
|
|
1133
|
+
}
|
|
1134
|
+
__name(camelCaseObject, "camelCaseObject");
|
|
1135
|
+
function snakeCaseObject(obj) {
|
|
1136
|
+
const r = {};
|
|
1137
|
+
for (const key of Object.keys(obj)) {
|
|
1138
|
+
r[snakeCase(key, {
|
|
1139
|
+
splitRegexp: [
|
|
1140
|
+
/([a-z0-9])([A-Z])/g,
|
|
1141
|
+
/([A-Z])([A-Z][a-z])/g,
|
|
1142
|
+
/([a-zA-Z])([0-9])/g
|
|
1143
|
+
]
|
|
1144
|
+
})] = obj[key];
|
|
1145
|
+
}
|
|
1146
|
+
return r;
|
|
1147
|
+
}
|
|
1148
|
+
__name(snakeCaseObject, "snakeCaseObject");
|
|
1149
|
+
function upperCamelCase(s) {
|
|
1150
|
+
s = camelCase(s);
|
|
1151
|
+
return s[0].toUpperCase() + s.substring(1);
|
|
1152
|
+
}
|
|
1153
|
+
__name(upperCamelCase, "upperCamelCase");
|
|
1154
|
+
function camelCaseTransform(input, index) {
|
|
1155
|
+
if (index === 0) return input.toLowerCase();
|
|
1156
|
+
const firstChar = input.charAt(0);
|
|
1157
|
+
const lowerChars = input.substr(1).toLowerCase();
|
|
1158
|
+
return `${firstChar.toUpperCase()}${lowerChars}`;
|
|
1159
|
+
}
|
|
1160
|
+
__name(camelCaseTransform, "camelCaseTransform");
|
|
1161
|
+
module2.exports = {
|
|
1162
|
+
camelCaseObject,
|
|
1163
|
+
snakeCaseObject,
|
|
1164
|
+
snakeCase,
|
|
1165
|
+
camelCase,
|
|
1166
|
+
upperCamelCase
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
// src/TimePeriod.js
|
|
1172
|
+
var require_TimePeriod = __commonJS({
|
|
1173
|
+
"src/TimePeriod.js"(exports2, module2) {
|
|
1174
|
+
"use strict";
|
|
1175
|
+
var _TimePeriod = class _TimePeriod {
|
|
1176
|
+
constructor(period = "", value = 0, offset = 0, complete = false) {
|
|
1177
|
+
this.period = period;
|
|
1178
|
+
this.value = value;
|
|
1179
|
+
this.offset = offset;
|
|
1180
|
+
this.complete = complete;
|
|
1181
|
+
}
|
|
1182
|
+
static fromExpression(expression) {
|
|
1183
|
+
const pattern = /^(this|next|last)?\s*(\d+)?\s*(complete)?\s*(second|minute|hour|day|week|month|year|seconds|minutes|hours|days|weeks|months|years)?$/i;
|
|
1184
|
+
const shorthandPattern = /^(now|today|tomorrow|yesterday)$/i;
|
|
1185
|
+
const shorthandMatch = shorthandPattern.exec(expression.trim());
|
|
1186
|
+
if (shorthandMatch) {
|
|
1187
|
+
const shorthand = shorthandMatch[1].toLowerCase();
|
|
1188
|
+
switch (shorthand) {
|
|
1189
|
+
case "now":
|
|
1190
|
+
return new _TimePeriod();
|
|
1191
|
+
case "today":
|
|
1192
|
+
return _TimePeriod.fromExpression("this day");
|
|
1193
|
+
case "tomorrow":
|
|
1194
|
+
return _TimePeriod.fromExpression("next complete day");
|
|
1195
|
+
case "yesterday":
|
|
1196
|
+
return _TimePeriod.fromExpression("last complete day");
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
const match = pattern.exec(expression.trim());
|
|
1200
|
+
if (!match) {
|
|
1201
|
+
throw new Error("Invalid time period expression");
|
|
1202
|
+
}
|
|
1203
|
+
const [, direction, rawValue, isComplete, rawPeriod] = match;
|
|
1204
|
+
let period = rawPeriod ? rawPeriod.toLowerCase().replace(/s$/, "") : "";
|
|
1205
|
+
let value = rawValue ? parseInt(rawValue, 10) : 1;
|
|
1206
|
+
let complete = Boolean(isComplete);
|
|
1207
|
+
let offset = 0;
|
|
1208
|
+
switch (direction == null ? void 0 : direction.toLowerCase()) {
|
|
1209
|
+
case "this":
|
|
1210
|
+
offset = 0;
|
|
1211
|
+
complete = true;
|
|
1212
|
+
break;
|
|
1213
|
+
case "next":
|
|
1214
|
+
offset = complete ? 1 : 0;
|
|
1215
|
+
break;
|
|
1216
|
+
case "last":
|
|
1217
|
+
offset = -value;
|
|
1218
|
+
break;
|
|
1219
|
+
default:
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
"Time period expression must start with this, next, or last"
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
return new _TimePeriod(period, value, offset, complete);
|
|
1225
|
+
}
|
|
1226
|
+
periodStartSQL() {
|
|
1227
|
+
let sql = "NOW()";
|
|
1228
|
+
if (this.offset !== 0) {
|
|
1229
|
+
sql = `${sql} + INTERVAL '${this.offset} ${this.period}'`;
|
|
1230
|
+
}
|
|
1231
|
+
if (this.complete) {
|
|
1232
|
+
sql = `DATE_TRUNC('${this.period}', ${sql})`;
|
|
1233
|
+
} else {
|
|
1234
|
+
sql = `(${sql})`;
|
|
1235
|
+
}
|
|
1236
|
+
return sql;
|
|
1237
|
+
}
|
|
1238
|
+
periodEndSQL() {
|
|
1239
|
+
let sql = this.periodStartSQL();
|
|
1240
|
+
if (this.value != 0) {
|
|
1241
|
+
sql = `(${sql} + INTERVAL '${this.value} ${this.period}')`;
|
|
1242
|
+
}
|
|
1243
|
+
return sql;
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
__name(_TimePeriod, "TimePeriod");
|
|
1247
|
+
var TimePeriod = _TimePeriod;
|
|
1248
|
+
module2.exports = {
|
|
1249
|
+
TimePeriod
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
// src/applyWhereConditions.js
|
|
1255
|
+
var require_applyWhereConditions = __commonJS({
|
|
1256
|
+
"src/applyWhereConditions.js"(exports2, module2) {
|
|
1257
|
+
"use strict";
|
|
1258
|
+
var { sql, Kysely } = require("kysely");
|
|
1259
|
+
var { snakeCase } = require_casing();
|
|
1260
|
+
var { TimePeriod } = require_TimePeriod();
|
|
1261
|
+
var opMapping = {
|
|
1262
|
+
startsWith: { op: "like", value: /* @__PURE__ */ __name((v) => `${v}%`, "value") },
|
|
1263
|
+
endsWith: { op: "like", value: /* @__PURE__ */ __name((v) => `%${v}`, "value") },
|
|
1264
|
+
contains: { op: "like", value: /* @__PURE__ */ __name((v) => `%${v}%`, "value") },
|
|
1265
|
+
oneOf: { op: "=", value: /* @__PURE__ */ __name((v) => sql`ANY(${v})`, "value") },
|
|
1266
|
+
greaterThan: { op: ">" },
|
|
1267
|
+
greaterThanOrEquals: { op: ">=" },
|
|
1268
|
+
lessThan: { op: "<" },
|
|
1269
|
+
lessThanOrEquals: { op: "<=" },
|
|
1270
|
+
before: { op: "<" },
|
|
1271
|
+
onOrBefore: { op: "<=" },
|
|
1272
|
+
after: { op: ">" },
|
|
1273
|
+
onOrAfter: { op: ">=" },
|
|
1274
|
+
equals: { op: sql`is not distinct from` },
|
|
1275
|
+
notEquals: { op: sql`is distinct from` },
|
|
1276
|
+
equalsRelative: {
|
|
1277
|
+
op: sql`BETWEEN`,
|
|
1278
|
+
value: /* @__PURE__ */ __name((v) => sql`${sql.raw(
|
|
1279
|
+
TimePeriod.fromExpression(v).periodStartSQL()
|
|
1280
|
+
)} AND ${sql.raw(TimePeriod.fromExpression(v).periodEndSQL())}`, "value")
|
|
1281
|
+
},
|
|
1282
|
+
beforeRelative: {
|
|
1283
|
+
op: "<",
|
|
1284
|
+
value: /* @__PURE__ */ __name((v) => sql`${sql.raw(TimePeriod.fromExpression(v).periodStartSQL())}`, "value")
|
|
1285
|
+
},
|
|
1286
|
+
afterRelative: {
|
|
1287
|
+
op: ">=",
|
|
1288
|
+
value: /* @__PURE__ */ __name((v) => sql`${sql.raw(TimePeriod.fromExpression(v).periodEndSQL())}`, "value")
|
|
1289
|
+
},
|
|
1290
|
+
any: {
|
|
1291
|
+
isArrayQuery: true,
|
|
1292
|
+
greaterThan: { op: ">" },
|
|
1293
|
+
greaterThanOrEquals: { op: ">=" },
|
|
1294
|
+
lessThan: { op: "<" },
|
|
1295
|
+
lessThanOrEquals: { op: "<=" },
|
|
1296
|
+
before: { op: "<" },
|
|
1297
|
+
onOrBefore: { op: "<=" },
|
|
1298
|
+
after: { op: ">" },
|
|
1299
|
+
onOrAfter: { op: ">=" },
|
|
1300
|
+
equals: { op: "=" },
|
|
1301
|
+
notEquals: { op: "=", value: /* @__PURE__ */ __name((v) => sql`NOT ${v}`, "value") }
|
|
1302
|
+
},
|
|
1303
|
+
all: {
|
|
1304
|
+
isArrayQuery: true,
|
|
1305
|
+
greaterThan: { op: ">" },
|
|
1306
|
+
greaterThanOrEquals: { op: ">=" },
|
|
1307
|
+
lessThan: { op: "<" },
|
|
1308
|
+
lessThanOrEquals: { op: "<=" },
|
|
1309
|
+
before: { op: "<" },
|
|
1310
|
+
onOrBefore: { op: "<=" },
|
|
1311
|
+
after: { op: ">" },
|
|
1312
|
+
onOrAfter: { op: ">=" },
|
|
1313
|
+
equals: { op: "=" },
|
|
1314
|
+
notEquals: { op: "=", value: /* @__PURE__ */ __name((v) => sql`NOT ${v}`, "value") }
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
function applyWhereConditions(context, qb, where = {}) {
|
|
1318
|
+
const conf = context.tableConfig();
|
|
1319
|
+
for (const key of Object.keys(where)) {
|
|
1320
|
+
const v = where[key];
|
|
1321
|
+
if (conf && conf[snakeCase(key)]) {
|
|
1322
|
+
const rel = conf[snakeCase(key)];
|
|
1323
|
+
context.withJoin(rel.referencesTable, () => {
|
|
1324
|
+
qb = applyWhereConditions(context, qb, v);
|
|
1325
|
+
});
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const fieldName = `${context.tableAlias()}.${snakeCase(key)}`;
|
|
1329
|
+
if (Object.prototype.toString.call(v) !== "[object Object]") {
|
|
1330
|
+
qb = qb.where(fieldName, sql`is not distinct from`, sql`${v}`);
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
for (const op of Object.keys(v)) {
|
|
1334
|
+
const mapping = opMapping[op];
|
|
1335
|
+
if (!mapping) {
|
|
1336
|
+
throw new Error(`invalid where condition: ${op}`);
|
|
1337
|
+
}
|
|
1338
|
+
if (mapping.isArrayQuery) {
|
|
1339
|
+
for (const arrayOp of Object.keys(v[op])) {
|
|
1340
|
+
qb = qb.where(
|
|
1341
|
+
mapping[arrayOp].value ? mapping[arrayOp].value(v[op][arrayOp]) : sql`${v[op][arrayOp]}`,
|
|
1342
|
+
mapping[arrayOp].op,
|
|
1343
|
+
sql`${sql(op)}(${sql.ref(fieldName)})`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
} else {
|
|
1347
|
+
qb = qb.where(
|
|
1348
|
+
fieldName,
|
|
1349
|
+
mapping.op,
|
|
1350
|
+
mapping.value ? mapping.value(v[op]) : sql`${v[op]}`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return qb;
|
|
1356
|
+
}
|
|
1357
|
+
__name(applyWhereConditions, "applyWhereConditions");
|
|
1358
|
+
module2.exports = {
|
|
1359
|
+
applyWhereConditions
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
// src/applyAdditionalQueryConstraints.js
|
|
1365
|
+
var require_applyAdditionalQueryConstraints = __commonJS({
|
|
1366
|
+
"src/applyAdditionalQueryConstraints.js"(exports2, module2) {
|
|
1367
|
+
"use strict";
|
|
1368
|
+
var { snakeCase } = require("change-case");
|
|
1369
|
+
function applyLimit(context, qb, limit) {
|
|
1370
|
+
return qb.limit(limit);
|
|
1371
|
+
}
|
|
1372
|
+
__name(applyLimit, "applyLimit");
|
|
1373
|
+
function applyOffset(context, qb, offset) {
|
|
1374
|
+
return qb.offset(offset);
|
|
1375
|
+
}
|
|
1376
|
+
__name(applyOffset, "applyOffset");
|
|
1377
|
+
function applyOrderBy(context, qb, tableName, orderBy = {}) {
|
|
1378
|
+
Object.entries(orderBy).forEach(([key, sortOrder]) => {
|
|
1379
|
+
qb = qb.orderBy(`${tableName}.${snakeCase(key)}`, sortOrder.toLowerCase());
|
|
1380
|
+
});
|
|
1381
|
+
return qb;
|
|
1382
|
+
}
|
|
1383
|
+
__name(applyOrderBy, "applyOrderBy");
|
|
1384
|
+
module2.exports = {
|
|
1385
|
+
applyLimit,
|
|
1386
|
+
applyOffset,
|
|
1387
|
+
applyOrderBy
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
// src/applyJoins.js
|
|
1393
|
+
var require_applyJoins = __commonJS({
|
|
1394
|
+
"src/applyJoins.js"(exports2, module2) {
|
|
1395
|
+
"use strict";
|
|
1396
|
+
var { snakeCase } = require_casing();
|
|
1397
|
+
function applyJoins(context, qb, where) {
|
|
1398
|
+
const conf = context.tableConfig();
|
|
1399
|
+
if (!conf) {
|
|
1400
|
+
return qb;
|
|
1401
|
+
}
|
|
1402
|
+
const srcTable = context.tableAlias();
|
|
1403
|
+
for (const key of Object.keys(where)) {
|
|
1404
|
+
const rel = conf[snakeCase(key)];
|
|
1405
|
+
if (!rel) {
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
const targetTable = rel.referencesTable;
|
|
1409
|
+
if (context.hasJoin(targetTable)) {
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
context.withJoin(targetTable, () => {
|
|
1413
|
+
switch (rel.relationshipType) {
|
|
1414
|
+
case "hasMany":
|
|
1415
|
+
qb = qb.innerJoin(
|
|
1416
|
+
`${targetTable} as ${context.tableAlias()}`,
|
|
1417
|
+
`${srcTable}.id`,
|
|
1418
|
+
`${context.tableAlias()}.${rel.foreignKey}`
|
|
1419
|
+
);
|
|
1420
|
+
break;
|
|
1421
|
+
case "belongsTo":
|
|
1422
|
+
qb = qb.innerJoin(
|
|
1423
|
+
`${targetTable} as ${context.tableAlias()}`,
|
|
1424
|
+
`${srcTable}.${rel.foreignKey}`,
|
|
1425
|
+
`${context.tableAlias()}.id`
|
|
1426
|
+
);
|
|
1427
|
+
break;
|
|
1428
|
+
default:
|
|
1429
|
+
throw new Error(`unknown relationshipType: ${rel.relationshipType}`);
|
|
1430
|
+
}
|
|
1431
|
+
qb = applyJoins(context, qb, where[key]);
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return qb;
|
|
1435
|
+
}
|
|
1436
|
+
__name(applyJoins, "applyJoins");
|
|
1437
|
+
module2.exports = {
|
|
1438
|
+
applyJoins
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
// src/QueryContext.js
|
|
1444
|
+
var require_QueryContext = __commonJS({
|
|
1445
|
+
"src/QueryContext.js"(exports2, module2) {
|
|
1446
|
+
"use strict";
|
|
1447
|
+
var _QueryContext = class _QueryContext {
|
|
1448
|
+
/**
|
|
1449
|
+
* @param {string[]} tablePath This is the path from the "root" table to the "current table".
|
|
1450
|
+
* @param {import("./ModelAPI").TableConfigMap} tableConfigMap
|
|
1451
|
+
* @param {string[]} joins
|
|
1452
|
+
*/
|
|
1453
|
+
constructor(tablePath, tableConfigMap, joins = []) {
|
|
1454
|
+
this._tablePath = tablePath;
|
|
1455
|
+
this._tableConfigMap = tableConfigMap;
|
|
1456
|
+
this._joins = joins;
|
|
1457
|
+
}
|
|
1458
|
+
clone() {
|
|
1459
|
+
return new _QueryContext([...this._tablePath], this._tableConfigMap, [
|
|
1460
|
+
...this._joins
|
|
1461
|
+
]);
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Returns true if, given the current table path, a join to the given
|
|
1465
|
+
* table has already been added.
|
|
1466
|
+
* @param {string} table
|
|
1467
|
+
* @returns {boolean}
|
|
1468
|
+
*/
|
|
1469
|
+
hasJoin(table2) {
|
|
1470
|
+
const alias = joinAlias([...this._tablePath, table2]);
|
|
1471
|
+
return this._joins.includes(alias);
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Adds table to the QueryContext's path and registers the join,
|
|
1475
|
+
* calls fn, then pops the table off the path.
|
|
1476
|
+
* @param {string} table
|
|
1477
|
+
* @param {Function} fn
|
|
1478
|
+
*/
|
|
1479
|
+
withJoin(table2, fn) {
|
|
1480
|
+
this._tablePath.push(table2);
|
|
1481
|
+
this._joins.push(this.tableAlias());
|
|
1482
|
+
fn();
|
|
1483
|
+
this._tablePath.pop();
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Returns the alias that will be used for the current table
|
|
1487
|
+
* @returns {string}
|
|
1488
|
+
*/
|
|
1489
|
+
tableAlias() {
|
|
1490
|
+
return joinAlias(this._tablePath);
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Returns the current table name
|
|
1494
|
+
* @returns {string}
|
|
1495
|
+
*/
|
|
1496
|
+
tableName() {
|
|
1497
|
+
return this._tablePath[this._tablePath.length - 1];
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Return the TableConfig for the current table
|
|
1501
|
+
* @returns {import("./ModelAPI").TableConfig | undefined}
|
|
1502
|
+
*/
|
|
1503
|
+
tableConfig() {
|
|
1504
|
+
return this._tableConfigMap[this.tableName()];
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
__name(_QueryContext, "QueryContext");
|
|
1508
|
+
var QueryContext = _QueryContext;
|
|
1509
|
+
function joinAlias(tablePath) {
|
|
1510
|
+
return tablePath.join("$");
|
|
1511
|
+
}
|
|
1512
|
+
__name(joinAlias, "joinAlias");
|
|
1513
|
+
module2.exports = {
|
|
1514
|
+
QueryContext
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
// src/QueryBuilder.js
|
|
1520
|
+
var require_QueryBuilder = __commonJS({
|
|
1521
|
+
"src/QueryBuilder.js"(exports2, module2) {
|
|
1522
|
+
"use strict";
|
|
1523
|
+
var { applyWhereConditions } = require_applyWhereConditions();
|
|
1524
|
+
var {
|
|
1525
|
+
applyLimit,
|
|
1526
|
+
applyOffset,
|
|
1527
|
+
applyOrderBy
|
|
1528
|
+
} = require_applyAdditionalQueryConstraints();
|
|
1529
|
+
var { applyJoins } = require_applyJoins();
|
|
1530
|
+
var {
|
|
1531
|
+
camelCaseObject,
|
|
1532
|
+
snakeCaseObject,
|
|
1533
|
+
upperCamelCase
|
|
1534
|
+
} = require_casing();
|
|
1535
|
+
var { useDatabase: useDatabase2 } = require_database();
|
|
1536
|
+
var { transformRichDataTypes } = require_parsing();
|
|
1537
|
+
var { QueryContext } = require_QueryContext();
|
|
1538
|
+
var tracing = require_tracing();
|
|
1539
|
+
var { DatabaseError } = require_errors();
|
|
1540
|
+
var _QueryBuilder = class _QueryBuilder {
|
|
1541
|
+
/**
|
|
1542
|
+
* @param {string} tableName
|
|
1543
|
+
* @param {import("./QueryContext").QueryContext} context
|
|
1544
|
+
* @param {import("kysely").Kysely} db
|
|
1545
|
+
*/
|
|
1546
|
+
constructor(tableName, context, db) {
|
|
1547
|
+
this._tableName = tableName;
|
|
1548
|
+
this._context = context;
|
|
1549
|
+
this._db = db;
|
|
1550
|
+
this._modelName = upperCamelCase(this._tableName);
|
|
1551
|
+
}
|
|
1552
|
+
where(where) {
|
|
1553
|
+
const context = this._context.clone();
|
|
1554
|
+
let builder = applyJoins(context, this._db, where);
|
|
1555
|
+
builder = applyWhereConditions(context, builder, where);
|
|
1556
|
+
return new _QueryBuilder(this._tableName, context, builder);
|
|
1557
|
+
}
|
|
1558
|
+
sql() {
|
|
1559
|
+
return this._db.compile().sql;
|
|
1560
|
+
}
|
|
1561
|
+
async update(values) {
|
|
1562
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "update");
|
|
1563
|
+
const db = useDatabase2();
|
|
1564
|
+
return tracing.withSpan(name, async (span) => {
|
|
1565
|
+
const sub = this._db.clearSelect().select("id");
|
|
1566
|
+
const query = db.updateTable(this._tableName).set(snakeCaseObject(values)).returningAll().where("id", "in", sub);
|
|
1567
|
+
try {
|
|
1568
|
+
const result = await query.execute();
|
|
1569
|
+
const numUpdatedRows = result.length;
|
|
1570
|
+
if (numUpdatedRows == 0) {
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
if (numUpdatedRows > 1) {
|
|
1574
|
+
throw new DatabaseError(
|
|
1575
|
+
new Error(
|
|
1576
|
+
"more than one row matched update constraints - only unique fields should be used when updating."
|
|
1577
|
+
)
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
return transformRichDataTypes(camelCaseObject(result[0]));
|
|
1581
|
+
} catch (e) {
|
|
1582
|
+
throw new DatabaseError(e);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
async delete() {
|
|
1587
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "delete");
|
|
1588
|
+
const db = useDatabase2();
|
|
1589
|
+
return tracing.withSpan(name, async (span) => {
|
|
1590
|
+
const sub = this._db.clearSelect().select("id");
|
|
1591
|
+
let builder = db.deleteFrom(this._tableName).where("id", "in", sub);
|
|
1592
|
+
const query = builder.returning(["id"]);
|
|
1593
|
+
span.setAttribute("sql", query.compile().sql);
|
|
1594
|
+
try {
|
|
1595
|
+
const row = await query.executeTakeFirstOrThrow();
|
|
1596
|
+
return row.id;
|
|
1597
|
+
} catch (e) {
|
|
1598
|
+
throw new DatabaseError(e);
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
async findOne() {
|
|
1603
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
|
|
1604
|
+
const db = useDatabase2();
|
|
1605
|
+
return tracing.withSpan(name, async (span) => {
|
|
1606
|
+
let builder = db.selectFrom((qb) => {
|
|
1607
|
+
return this._db.as(this._tableName);
|
|
1608
|
+
}).selectAll();
|
|
1609
|
+
span.setAttribute("sql", builder.compile().sql);
|
|
1610
|
+
const row = await builder.executeTakeFirst();
|
|
1611
|
+
if (!row) {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
return transformRichDataTypes(camelCaseObject(row));
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
async findMany(params) {
|
|
1618
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
|
|
1619
|
+
const db = useDatabase2();
|
|
1620
|
+
return tracing.withSpan(name, async (span) => {
|
|
1621
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1622
|
+
let builder = db.selectFrom((qb) => {
|
|
1623
|
+
return this._db.as(this._tableName);
|
|
1624
|
+
}).selectAll();
|
|
1625
|
+
if (params == null ? void 0 : params.limit) {
|
|
1626
|
+
builder = applyLimit(context, builder, params.limit);
|
|
1627
|
+
}
|
|
1628
|
+
if (params == null ? void 0 : params.offset) {
|
|
1629
|
+
builder = applyOffset(context, builder, params.offset);
|
|
1630
|
+
}
|
|
1631
|
+
if ((params == null ? void 0 : params.orderBy) !== void 0 && Object.keys(params == null ? void 0 : params.orderBy).length > 0) {
|
|
1632
|
+
builder = applyOrderBy(
|
|
1633
|
+
context,
|
|
1634
|
+
builder,
|
|
1635
|
+
this._tableName,
|
|
1636
|
+
params.orderBy
|
|
1637
|
+
);
|
|
1638
|
+
} else {
|
|
1639
|
+
builder = builder.orderBy(`${this._tableName}.id`);
|
|
1640
|
+
}
|
|
1641
|
+
const query = builder;
|
|
1642
|
+
span.setAttribute("sql", query.compile().sql);
|
|
1643
|
+
const rows = await builder.execute();
|
|
1644
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
__name(_QueryBuilder, "QueryBuilder");
|
|
1649
|
+
var QueryBuilder = _QueryBuilder;
|
|
1650
|
+
module2.exports.QueryBuilder = QueryBuilder;
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
|
|
1654
|
+
// src/ModelAPI.js
|
|
1655
|
+
var require_ModelAPI = __commonJS({
|
|
1656
|
+
"src/ModelAPI.js"(exports2, module2) {
|
|
1657
|
+
"use strict";
|
|
1658
|
+
var { sql } = require("kysely");
|
|
1659
|
+
var { useDatabase: useDatabase2 } = require_database();
|
|
1660
|
+
var {
|
|
1661
|
+
transformRichDataTypes,
|
|
1662
|
+
isReferencingExistingRecord
|
|
1663
|
+
} = require_parsing();
|
|
1664
|
+
var { isPlainObject } = require_type_utils();
|
|
1665
|
+
var { QueryBuilder } = require_QueryBuilder();
|
|
1666
|
+
var { QueryContext } = require_QueryContext();
|
|
1667
|
+
var { applyWhereConditions } = require_applyWhereConditions();
|
|
1668
|
+
var { applyJoins } = require_applyJoins();
|
|
1669
|
+
var { InlineFile, File } = require_File();
|
|
1670
|
+
var { Duration } = require_Duration();
|
|
1671
|
+
var {
|
|
1672
|
+
applyLimit,
|
|
1673
|
+
applyOffset,
|
|
1674
|
+
applyOrderBy
|
|
1675
|
+
} = require_applyAdditionalQueryConstraints();
|
|
1676
|
+
var {
|
|
1677
|
+
camelCaseObject,
|
|
1678
|
+
snakeCaseObject,
|
|
1679
|
+
upperCamelCase
|
|
1680
|
+
} = require_casing();
|
|
1681
|
+
var tracing = require_tracing();
|
|
1682
|
+
var { DatabaseError } = require_errors();
|
|
1683
|
+
var _ModelAPI = class _ModelAPI {
|
|
1684
|
+
/**
|
|
1685
|
+
* @param {string} tableName The name of the table this API is for
|
|
1686
|
+
* @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
|
|
1687
|
+
* @param {TableConfigMap} tableConfigMap
|
|
1688
|
+
*/
|
|
1689
|
+
constructor(tableName, _, tableConfigMap = {}) {
|
|
1690
|
+
this._tableName = tableName;
|
|
1691
|
+
this._tableConfigMap = tableConfigMap;
|
|
1692
|
+
this._modelName = upperCamelCase(this._tableName);
|
|
1693
|
+
}
|
|
1694
|
+
async create(values) {
|
|
1695
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "create");
|
|
1696
|
+
return tracing.withSpan(name, () => {
|
|
1697
|
+
const db = useDatabase2();
|
|
1698
|
+
return create(
|
|
1699
|
+
db,
|
|
1700
|
+
this._tableName,
|
|
1701
|
+
this._tableConfigMap,
|
|
1702
|
+
snakeCaseObject(values)
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
async findOne(where = {}) {
|
|
1707
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "findOne");
|
|
1708
|
+
const db = useDatabase2();
|
|
1709
|
+
return tracing.withSpan(name, async (span) => {
|
|
1710
|
+
let builder = db.selectFrom(this._tableName).distinctOn(`${this._tableName}.id`).selectAll(this._tableName);
|
|
1711
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1712
|
+
builder = applyJoins(context, builder, where);
|
|
1713
|
+
builder = applyWhereConditions(context, builder, where);
|
|
1714
|
+
span.setAttribute("sql", builder.compile().sql);
|
|
1715
|
+
const row = await builder.executeTakeFirst();
|
|
1716
|
+
if (!row) {
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
return transformRichDataTypes(camelCaseObject(row));
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
async findMany(params) {
|
|
1723
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "findMany");
|
|
1724
|
+
const db = useDatabase2();
|
|
1725
|
+
const where = (params == null ? void 0 : params.where) || {};
|
|
1726
|
+
return tracing.withSpan(name, async (span) => {
|
|
1727
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1728
|
+
let builder = db.selectFrom((qb) => {
|
|
1729
|
+
let builder2 = qb.selectFrom(this._tableName).distinctOn(`${this._tableName}.id`).selectAll(this._tableName);
|
|
1730
|
+
builder2 = applyJoins(context, builder2, where);
|
|
1731
|
+
builder2 = applyWhereConditions(context, builder2, where);
|
|
1732
|
+
builder2 = builder2.as(this._tableName);
|
|
1733
|
+
return builder2;
|
|
1734
|
+
}).selectAll();
|
|
1735
|
+
if (params == null ? void 0 : params.limit) {
|
|
1736
|
+
builder = applyLimit(context, builder, params.limit);
|
|
1737
|
+
}
|
|
1738
|
+
if (params == null ? void 0 : params.offset) {
|
|
1739
|
+
builder = applyOffset(context, builder, params.offset);
|
|
1740
|
+
}
|
|
1741
|
+
if ((params == null ? void 0 : params.orderBy) !== void 0 && Object.keys(params == null ? void 0 : params.orderBy).length > 0) {
|
|
1742
|
+
builder = applyOrderBy(
|
|
1743
|
+
context,
|
|
1744
|
+
builder,
|
|
1745
|
+
this._tableName,
|
|
1746
|
+
params.orderBy
|
|
1747
|
+
);
|
|
1748
|
+
} else {
|
|
1749
|
+
builder = builder.orderBy(`${this._tableName}.id`);
|
|
1750
|
+
}
|
|
1751
|
+
const query = builder;
|
|
1752
|
+
span.setAttribute("sql", query.compile().sql);
|
|
1753
|
+
const rows = await builder.execute();
|
|
1754
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
async update(where, values) {
|
|
1758
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "update");
|
|
1759
|
+
const db = useDatabase2();
|
|
1760
|
+
return tracing.withSpan(name, async (span) => {
|
|
1761
|
+
let builder = db.updateTable(this._tableName).returningAll();
|
|
1762
|
+
const keys = values ? Object.keys(values) : [];
|
|
1763
|
+
const row = {};
|
|
1764
|
+
for (const key of keys) {
|
|
1765
|
+
const value = values[key];
|
|
1766
|
+
if (Array.isArray(value)) {
|
|
1767
|
+
row[key] = await Promise.all(
|
|
1768
|
+
value.map(async (item) => {
|
|
1769
|
+
if (item instanceof Duration) {
|
|
1770
|
+
return item.toPostgres();
|
|
1771
|
+
}
|
|
1772
|
+
if (item instanceof InlineFile) {
|
|
1773
|
+
const storedFile = await item.store();
|
|
1774
|
+
return storedFile.toDbRecord();
|
|
1775
|
+
}
|
|
1776
|
+
if (item instanceof File) {
|
|
1777
|
+
return item.toDbRecord();
|
|
1778
|
+
}
|
|
1779
|
+
return item;
|
|
1780
|
+
})
|
|
1781
|
+
);
|
|
1782
|
+
} else if (value instanceof Duration) {
|
|
1783
|
+
row[key] = value.toPostgres();
|
|
1784
|
+
} else if (value instanceof InlineFile) {
|
|
1785
|
+
const storedFile = await value.store();
|
|
1786
|
+
row[key] = storedFile.toDbRecord();
|
|
1787
|
+
} else if (value instanceof File) {
|
|
1788
|
+
row[key] = value.toDbRecord();
|
|
1789
|
+
} else {
|
|
1790
|
+
row[key] = value;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
builder = builder.set(snakeCaseObject(row));
|
|
1794
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1795
|
+
builder = applyWhereConditions(context, builder, where);
|
|
1796
|
+
span.setAttribute("sql", builder.compile().sql);
|
|
1797
|
+
try {
|
|
1798
|
+
const row2 = await builder.executeTakeFirstOrThrow();
|
|
1799
|
+
return transformRichDataTypes(camelCaseObject(row2));
|
|
1800
|
+
} catch (e) {
|
|
1801
|
+
throw new DatabaseError(e);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
async delete(where) {
|
|
1806
|
+
const name = tracing.spanNameForModelAPI(this._modelName, "delete");
|
|
1807
|
+
const db = useDatabase2();
|
|
1808
|
+
return tracing.withSpan(name, async (span) => {
|
|
1809
|
+
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
1810
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1811
|
+
builder = applyWhereConditions(context, builder, where);
|
|
1812
|
+
span.setAttribute("sql", builder.compile().sql);
|
|
1813
|
+
try {
|
|
1814
|
+
const row = await builder.executeTakeFirstOrThrow();
|
|
1815
|
+
return row.id;
|
|
1816
|
+
} catch (e) {
|
|
1817
|
+
throw new DatabaseError(e);
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
where(where) {
|
|
1822
|
+
const db = useDatabase2();
|
|
1823
|
+
let builder = db.selectFrom(this._tableName).distinctOn(`${this._tableName}.id`).selectAll(this._tableName);
|
|
1824
|
+
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1825
|
+
builder = applyJoins(context, builder, where);
|
|
1826
|
+
builder = applyWhereConditions(context, builder, where);
|
|
1827
|
+
return new QueryBuilder(this._tableName, context, builder);
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
__name(_ModelAPI, "ModelAPI");
|
|
1831
|
+
var ModelAPI = _ModelAPI;
|
|
1832
|
+
async function create(conn, tableName, tableConfigs, values) {
|
|
1833
|
+
try {
|
|
1834
|
+
let query = conn.insertInto(tableName);
|
|
1835
|
+
const keys = values ? Object.keys(values) : [];
|
|
1836
|
+
const tableConfig = tableConfigs[tableName] || {};
|
|
1837
|
+
const hasManyRecords = [];
|
|
1838
|
+
if (keys.length === 0) {
|
|
1839
|
+
query = query.expression(sql`default values`);
|
|
1840
|
+
} else {
|
|
1841
|
+
const row = {};
|
|
1842
|
+
for (const key of keys) {
|
|
1843
|
+
const value = values[key];
|
|
1844
|
+
const columnConfig = tableConfig[key];
|
|
1845
|
+
if (!columnConfig) {
|
|
1846
|
+
if (Array.isArray(value)) {
|
|
1847
|
+
row[key] = await Promise.all(
|
|
1848
|
+
value.map(async (item) => {
|
|
1849
|
+
if (item instanceof Duration) {
|
|
1850
|
+
return item.toPostgres();
|
|
1851
|
+
}
|
|
1852
|
+
if (item instanceof InlineFile) {
|
|
1853
|
+
const storedFile = await item.store();
|
|
1854
|
+
return storedFile.toDbRecord();
|
|
1855
|
+
}
|
|
1856
|
+
if (item instanceof File) {
|
|
1857
|
+
return item.toDbRecord();
|
|
1858
|
+
}
|
|
1859
|
+
return item;
|
|
1860
|
+
})
|
|
1861
|
+
);
|
|
1862
|
+
} else if (value instanceof Duration) {
|
|
1863
|
+
row[key] = value.toPostgres();
|
|
1864
|
+
} else if (value instanceof InlineFile) {
|
|
1865
|
+
const storedFile = await value.store();
|
|
1866
|
+
row[key] = storedFile.toDbRecord();
|
|
1867
|
+
} else if (value instanceof File) {
|
|
1868
|
+
row[key] = value.toDbRecord();
|
|
1869
|
+
} else {
|
|
1870
|
+
row[key] = value;
|
|
1871
|
+
}
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
switch (columnConfig.relationshipType) {
|
|
1875
|
+
case "belongsTo":
|
|
1876
|
+
if (!isPlainObject(value)) {
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
`non-object provided for field ${key} of ${tableName}`
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
if (isReferencingExistingRecord(value)) {
|
|
1882
|
+
row[columnConfig.foreignKey] = value.id;
|
|
1883
|
+
break;
|
|
1884
|
+
}
|
|
1885
|
+
const created2 = await create(
|
|
1886
|
+
conn,
|
|
1887
|
+
columnConfig.referencesTable,
|
|
1888
|
+
tableConfigs,
|
|
1889
|
+
value
|
|
1890
|
+
);
|
|
1891
|
+
row[columnConfig.foreignKey] = created2.id;
|
|
1892
|
+
break;
|
|
1893
|
+
case "hasMany":
|
|
1894
|
+
if (!Array.isArray(value)) {
|
|
1895
|
+
throw new Error(
|
|
1896
|
+
`non-array provided for has-many field ${key} of ${tableName}`
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
for (const v of value) {
|
|
1900
|
+
hasManyRecords.push({
|
|
1901
|
+
key,
|
|
1902
|
+
value: v,
|
|
1903
|
+
columnConfig
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
break;
|
|
1907
|
+
default:
|
|
1908
|
+
throw new Error(
|
|
1909
|
+
`unsupported relationship type - ${tableName}.${key} (${columnConfig.relationshipType})`
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
query = query.values(row);
|
|
1914
|
+
}
|
|
1915
|
+
const created = await query.returningAll().executeTakeFirstOrThrow();
|
|
1916
|
+
await Promise.all(
|
|
1917
|
+
hasManyRecords.map(async ({ key, value, columnConfig }) => {
|
|
1918
|
+
if (!isPlainObject(value)) {
|
|
1919
|
+
throw new Error(
|
|
1920
|
+
`non-object provided for field ${key} of ${tableName}`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
if (isReferencingExistingRecord(value)) {
|
|
1924
|
+
throw new Error(
|
|
1925
|
+
`nested update as part of create not supported for ${key} of ${tableConfig}`
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
return create(conn, columnConfig.referencesTable, tableConfigs, {
|
|
1929
|
+
...value,
|
|
1930
|
+
[columnConfig.foreignKey]: created.id
|
|
1931
|
+
});
|
|
1932
|
+
})
|
|
1933
|
+
);
|
|
1934
|
+
return transformRichDataTypes(created);
|
|
1935
|
+
} catch (e) {
|
|
1936
|
+
throw new DatabaseError(e);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
__name(create, "create");
|
|
1940
|
+
module2.exports = {
|
|
1941
|
+
ModelAPI,
|
|
1942
|
+
DatabaseError
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
// src/RequestHeaders.js
|
|
1948
|
+
var require_RequestHeaders = __commonJS({
|
|
1949
|
+
"src/RequestHeaders.js"(exports2, module2) {
|
|
1950
|
+
"use strict";
|
|
1951
|
+
var _RequestHeaders = class _RequestHeaders {
|
|
1952
|
+
/**
|
|
1953
|
+
* @param {{Object.<string, string>}} requestHeaders Map of request headers submitted from the client
|
|
1954
|
+
*/
|
|
1955
|
+
constructor(requestHeaders) {
|
|
1956
|
+
this._headers = new Headers(requestHeaders);
|
|
1957
|
+
}
|
|
1958
|
+
get(key) {
|
|
1959
|
+
return this._headers.get(key);
|
|
1960
|
+
}
|
|
1961
|
+
has(key) {
|
|
1962
|
+
return this._headers.has(key);
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
__name(_RequestHeaders, "RequestHeaders");
|
|
1966
|
+
var RequestHeaders = _RequestHeaders;
|
|
1967
|
+
module2.exports = {
|
|
1968
|
+
RequestHeaders
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
|
|
1973
|
+
// src/permissions.js
|
|
1974
|
+
var require_permissions = __commonJS({
|
|
1975
|
+
"src/permissions.js"(exports2, module2) {
|
|
1976
|
+
"use strict";
|
|
1977
|
+
var { AsyncLocalStorage } = require("async_hooks");
|
|
1978
|
+
var { PermissionError } = require_errors();
|
|
1979
|
+
var PERMISSION_STATE = {
|
|
1980
|
+
UNKNOWN: "unknown",
|
|
1981
|
+
PERMITTED: "permitted",
|
|
1982
|
+
UNPERMITTED: "unpermitted"
|
|
1983
|
+
};
|
|
1984
|
+
var withPermissions = /* @__PURE__ */ __name(async (initialValue, cb) => {
|
|
1985
|
+
const permissions = new Permissions();
|
|
1986
|
+
return await permissionsApiInstance.run({ permitted: initialValue }, () => {
|
|
1987
|
+
return cb({ getPermissionState: permissions.getState });
|
|
1988
|
+
});
|
|
1989
|
+
}, "withPermissions");
|
|
1990
|
+
var permissionsApiInstance = new AsyncLocalStorage();
|
|
1991
|
+
var _Permissions = class _Permissions {
|
|
1992
|
+
// The Go runtime performs role based permission rule checks prior to calling the functions
|
|
1993
|
+
// runtime, so the status could already be granted. If already granted, then we need to inherit that permission state as the state is later used to decide whether to run in process permission checks
|
|
1994
|
+
// TLDR if a role based permission is relevant and it is granted, then it is effectively the same as the end user calling api.permissions.allow() explicitly in terms of behaviour.
|
|
1995
|
+
allow() {
|
|
1996
|
+
const store = permissionsApiInstance.getStore().permitted = true;
|
|
1997
|
+
}
|
|
1998
|
+
deny() {
|
|
1999
|
+
permissionsApiInstance.getStore().permitted = false;
|
|
2000
|
+
throw new PermissionError();
|
|
2001
|
+
}
|
|
2002
|
+
getState() {
|
|
2003
|
+
const permitted = permissionsApiInstance.getStore().permitted;
|
|
2004
|
+
switch (true) {
|
|
2005
|
+
case permitted === false:
|
|
2006
|
+
return PERMISSION_STATE.UNPERMITTED;
|
|
2007
|
+
case permitted === null:
|
|
2008
|
+
return PERMISSION_STATE.UNKNOWN;
|
|
2009
|
+
case permitted === true:
|
|
2010
|
+
return PERMISSION_STATE.PERMITTED;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
__name(_Permissions, "Permissions");
|
|
2015
|
+
var Permissions = _Permissions;
|
|
2016
|
+
var checkBuiltInPermissions = /* @__PURE__ */ __name(async ({
|
|
2017
|
+
rows,
|
|
2018
|
+
permissionFns,
|
|
2019
|
+
ctx,
|
|
2020
|
+
db,
|
|
2021
|
+
functionName
|
|
2022
|
+
}) => {
|
|
2023
|
+
for (const permissionFn of permissionFns) {
|
|
2024
|
+
const result = await permissionFn(rows, ctx, db);
|
|
2025
|
+
if (result) {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
throw new PermissionError(`Not permitted to access ${functionName}`);
|
|
2030
|
+
}, "checkBuiltInPermissions");
|
|
2031
|
+
module2.exports.checkBuiltInPermissions = checkBuiltInPermissions;
|
|
2032
|
+
module2.exports.PermissionError = PermissionError;
|
|
2033
|
+
module2.exports.PERMISSION_STATE = PERMISSION_STATE;
|
|
2034
|
+
module2.exports.Permissions = Permissions;
|
|
2035
|
+
module2.exports.withPermissions = withPermissions;
|
|
2036
|
+
module2.exports.permissionsApiInstance = permissionsApiInstance;
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
// src/consts.js
|
|
2041
|
+
var require_consts = __commonJS({
|
|
2042
|
+
"src/consts.js"(exports2, module2) {
|
|
2043
|
+
"use strict";
|
|
2044
|
+
var PROTO_ACTION_TYPES = {
|
|
2045
|
+
UNKNOWN: "ACTION_TYPE_UNKNOWN",
|
|
2046
|
+
CREATE: "ACTION_TYPE_CREATE",
|
|
2047
|
+
GET: "ACTION_TYPE_GET",
|
|
2048
|
+
LIST: "ACTION_TYPE_LIST",
|
|
2049
|
+
UPDATE: "ACTION_TYPE_UPDATE",
|
|
2050
|
+
DELETE: "ACTION_TYPE_DELETE",
|
|
2051
|
+
READ: "ACTION_TYPE_READ",
|
|
2052
|
+
WRITE: "ACTION_TYPE_WRITE",
|
|
2053
|
+
JOB: "JOB_TYPE",
|
|
2054
|
+
SUBSCRIBER: "SUBSCRIBER_TYPE",
|
|
2055
|
+
FLOW: "FLOW_TYPE"
|
|
2056
|
+
};
|
|
2057
|
+
module2.exports.PROTO_ACTION_TYPES = PROTO_ACTION_TYPES;
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
// src/tryExecuteFunction.js
|
|
2062
|
+
var require_tryExecuteFunction = __commonJS({
|
|
2063
|
+
"src/tryExecuteFunction.js"(exports2, module2) {
|
|
2064
|
+
"use strict";
|
|
2065
|
+
var { withDatabase } = require_database();
|
|
2066
|
+
var { withAuditContext } = require_auditing();
|
|
2067
|
+
var {
|
|
2068
|
+
withPermissions,
|
|
2069
|
+
PERMISSION_STATE,
|
|
2070
|
+
checkBuiltInPermissions
|
|
2071
|
+
} = require_permissions();
|
|
2072
|
+
var { PermissionError } = require_errors();
|
|
2073
|
+
var { PROTO_ACTION_TYPES } = require_consts();
|
|
2074
|
+
function tryExecuteFunction({ request, db, permitted, permissionFns, actionType, ctx, functionConfig }, cb) {
|
|
2075
|
+
return withPermissions(permitted, async ({ getPermissionState }) => {
|
|
2076
|
+
let requiresTransaction = true;
|
|
2077
|
+
switch (actionType) {
|
|
2078
|
+
case PROTO_ACTION_TYPES.GET:
|
|
2079
|
+
case PROTO_ACTION_TYPES.LIST:
|
|
2080
|
+
case PROTO_ACTION_TYPES.READ:
|
|
2081
|
+
requiresTransaction = false;
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2084
|
+
if ((functionConfig == null ? void 0 : functionConfig.dbTransaction) !== void 0) {
|
|
2085
|
+
requiresTransaction = functionConfig.dbTransaction;
|
|
2086
|
+
}
|
|
2087
|
+
return withDatabase(db, requiresTransaction, async ({ transaction }) => {
|
|
2088
|
+
const fnResult = await withAuditContext(request, async () => {
|
|
2089
|
+
return cb();
|
|
2090
|
+
});
|
|
2091
|
+
switch (getPermissionState()) {
|
|
2092
|
+
case PERMISSION_STATE.PERMITTED:
|
|
2093
|
+
return fnResult;
|
|
2094
|
+
case PERMISSION_STATE.UNPERMITTED:
|
|
2095
|
+
throw new PermissionError(
|
|
2096
|
+
`Not permitted to access ${request.method}`
|
|
2097
|
+
);
|
|
2098
|
+
default:
|
|
2099
|
+
const relevantPermissions = permissionFns[request.method];
|
|
2100
|
+
const peakInsideTransaction = actionType === PROTO_ACTION_TYPES.CREATE;
|
|
2101
|
+
let rowsForPermissions = [];
|
|
2102
|
+
if (fnResult != null) {
|
|
2103
|
+
switch (actionType) {
|
|
2104
|
+
case PROTO_ACTION_TYPES.LIST:
|
|
2105
|
+
rowsForPermissions = fnResult;
|
|
2106
|
+
break;
|
|
2107
|
+
case PROTO_ACTION_TYPES.DELETE:
|
|
2108
|
+
rowsForPermissions = [{ id: fnResult }];
|
|
2109
|
+
break;
|
|
2110
|
+
case (PROTO_ACTION_TYPES.GET, PROTO_ACTION_TYPES.CREATE):
|
|
2111
|
+
rowsForPermissions = [fnResult];
|
|
2112
|
+
break;
|
|
2113
|
+
default:
|
|
2114
|
+
rowsForPermissions = [fnResult];
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
await checkBuiltInPermissions({
|
|
2119
|
+
rows: rowsForPermissions,
|
|
2120
|
+
permissionFns: relevantPermissions,
|
|
2121
|
+
// it is important that we pass db here as db represents the connection to the database
|
|
2122
|
+
// *outside* of the current transaction. Given that any changes inside of a transaction
|
|
2123
|
+
// are opaque to the outside, we can utilize this when running permission rules and then deciding to
|
|
2124
|
+
// rollback any changes if they do not pass. However, for creates we need to be able to 'peak' inside the transaction to read the created record, as this won't exist outside of the transaction.
|
|
2125
|
+
db: peakInsideTransaction ? transaction : db,
|
|
2126
|
+
ctx,
|
|
2127
|
+
functionName: request.method
|
|
2128
|
+
});
|
|
2129
|
+
return fnResult;
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
__name(tryExecuteFunction, "tryExecuteFunction");
|
|
2135
|
+
module2.exports.tryExecuteFunction = tryExecuteFunction;
|
|
2136
|
+
}
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
// src/handleRequest.js
|
|
2140
|
+
var require_handleRequest = __commonJS({
|
|
2141
|
+
"src/handleRequest.js"(exports2, module2) {
|
|
2142
|
+
"use strict";
|
|
2143
|
+
var {
|
|
2144
|
+
createJSONRPCErrorResponse,
|
|
2145
|
+
createJSONRPCSuccessResponse,
|
|
2146
|
+
JSONRPCErrorCode
|
|
2147
|
+
} = require("json-rpc-2.0");
|
|
2148
|
+
var { createDatabaseClient } = require_database();
|
|
2149
|
+
var { tryExecuteFunction } = require_tryExecuteFunction();
|
|
2150
|
+
var { errorToJSONRPCResponse, RuntimeErrors } = require_errors();
|
|
2151
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
2152
|
+
var { withSpan } = require_tracing();
|
|
2153
|
+
var { parseInputs, parseOutputs } = require_parsing();
|
|
2154
|
+
async function handleRequest(request, config) {
|
|
2155
|
+
var _a;
|
|
2156
|
+
const activeContext = opentelemetry.propagation.extract(
|
|
2157
|
+
opentelemetry.context.active(),
|
|
2158
|
+
(_a = request.meta) == null ? void 0 : _a.tracing
|
|
2159
|
+
);
|
|
2160
|
+
if (process.env.KEEL_LOG_LEVEL == "debug") {
|
|
2161
|
+
console.log(request);
|
|
2162
|
+
}
|
|
2163
|
+
return opentelemetry.context.with(activeContext, () => {
|
|
2164
|
+
return withSpan(request.method, async (span) => {
|
|
2165
|
+
var _a2, _b;
|
|
2166
|
+
let db = null;
|
|
2167
|
+
try {
|
|
2168
|
+
const { createContextAPI, functions, permissionFns, actionTypes } = config;
|
|
2169
|
+
if (!(request.method in functions)) {
|
|
2170
|
+
const message = `no corresponding function found for '${request.method}'`;
|
|
2171
|
+
span.setStatus({
|
|
2172
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2173
|
+
message
|
|
2174
|
+
});
|
|
2175
|
+
return createJSONRPCErrorResponse(
|
|
2176
|
+
request.id,
|
|
2177
|
+
JSONRPCErrorCode.MethodNotFound,
|
|
2178
|
+
message
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
const headers = new Headers();
|
|
2182
|
+
const ctx = createContextAPI({
|
|
2183
|
+
responseHeaders: headers,
|
|
2184
|
+
meta: request.meta
|
|
2185
|
+
});
|
|
2186
|
+
const permitted = request.meta && request.meta.permissionState.status === "granted" ? true : null;
|
|
2187
|
+
db = createDatabaseClient({
|
|
2188
|
+
connString: (_b = (_a2 = request.meta) == null ? void 0 : _a2.secrets) == null ? void 0 : _b.KEEL_DB_CONN
|
|
2189
|
+
});
|
|
2190
|
+
const customFunction = functions[request.method];
|
|
2191
|
+
const actionType = actionTypes[request.method];
|
|
2192
|
+
const functionConfig = (customFunction == null ? void 0 : customFunction.config) ?? {};
|
|
2193
|
+
const result = await tryExecuteFunction(
|
|
2194
|
+
{
|
|
2195
|
+
request,
|
|
2196
|
+
ctx,
|
|
2197
|
+
permitted,
|
|
2198
|
+
db,
|
|
2199
|
+
permissionFns,
|
|
2200
|
+
actionType,
|
|
2201
|
+
functionConfig
|
|
2202
|
+
},
|
|
2203
|
+
async () => {
|
|
2204
|
+
const inputs = parseInputs(request.params);
|
|
2205
|
+
const result2 = await customFunction(ctx, inputs);
|
|
2206
|
+
return parseOutputs(result2);
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
if (result instanceof Error) {
|
|
2210
|
+
span.recordException(result);
|
|
2211
|
+
span.setStatus({
|
|
2212
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2213
|
+
message: result.message
|
|
2214
|
+
});
|
|
2215
|
+
return errorToJSONRPCResponse(request, result);
|
|
2216
|
+
}
|
|
2217
|
+
const response = createJSONRPCSuccessResponse(request.id, result);
|
|
2218
|
+
const responseHeaders = {};
|
|
2219
|
+
for (const pair of headers.entries()) {
|
|
2220
|
+
responseHeaders[pair[0]] = pair[1].split(", ");
|
|
2221
|
+
}
|
|
2222
|
+
response.meta = {
|
|
2223
|
+
headers: responseHeaders,
|
|
2224
|
+
status: ctx.response.status
|
|
2225
|
+
};
|
|
2226
|
+
return response;
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
if (e instanceof Error) {
|
|
2229
|
+
span.recordException(e);
|
|
2230
|
+
span.setStatus({
|
|
2231
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2232
|
+
message: e.message
|
|
2233
|
+
});
|
|
2234
|
+
return errorToJSONRPCResponse(request, e);
|
|
2235
|
+
}
|
|
2236
|
+
const message = JSON.stringify(e);
|
|
2237
|
+
span.setStatus({
|
|
2238
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2239
|
+
message
|
|
2240
|
+
});
|
|
2241
|
+
return createJSONRPCErrorResponse(
|
|
2242
|
+
request.id,
|
|
2243
|
+
RuntimeErrors.UnknownError,
|
|
2244
|
+
message
|
|
2245
|
+
);
|
|
2246
|
+
} finally {
|
|
2247
|
+
if (db) {
|
|
2248
|
+
await db.destroy();
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
});
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
__name(handleRequest, "handleRequest");
|
|
2255
|
+
module2.exports = {
|
|
2256
|
+
handleRequest,
|
|
2257
|
+
RuntimeErrors
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
// src/tryExecuteJob.js
|
|
2263
|
+
var require_tryExecuteJob = __commonJS({
|
|
2264
|
+
"src/tryExecuteJob.js"(exports2, module2) {
|
|
2265
|
+
"use strict";
|
|
2266
|
+
var { withDatabase } = require_database();
|
|
2267
|
+
var { withAuditContext } = require_auditing();
|
|
2268
|
+
var { withPermissions, PERMISSION_STATE } = require_permissions();
|
|
2269
|
+
var { PermissionError } = require_errors();
|
|
2270
|
+
function tryExecuteJob({ db, permitted, request, functionConfig }, cb) {
|
|
2271
|
+
return withPermissions(permitted, async ({ getPermissionState }) => {
|
|
2272
|
+
let requiresTransaction = false;
|
|
2273
|
+
if ((functionConfig == null ? void 0 : functionConfig.dbTransaction) !== void 0) {
|
|
2274
|
+
requiresTransaction = functionConfig.dbTransaction;
|
|
2275
|
+
}
|
|
2276
|
+
return withDatabase(db, requiresTransaction, async () => {
|
|
2277
|
+
await withAuditContext(request, async () => {
|
|
2278
|
+
return cb();
|
|
2279
|
+
});
|
|
2280
|
+
if (getPermissionState() === PERMISSION_STATE.UNPERMITTED) {
|
|
2281
|
+
throw new PermissionError(`Not permitted to access ${request.method}`);
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
__name(tryExecuteJob, "tryExecuteJob");
|
|
2287
|
+
module2.exports.tryExecuteJob = tryExecuteJob;
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
// src/handleJob.js
|
|
2292
|
+
var require_handleJob = __commonJS({
|
|
2293
|
+
"src/handleJob.js"(exports2, module2) {
|
|
2294
|
+
"use strict";
|
|
2295
|
+
var {
|
|
2296
|
+
createJSONRPCErrorResponse,
|
|
2297
|
+
createJSONRPCSuccessResponse,
|
|
2298
|
+
JSONRPCErrorCode
|
|
2299
|
+
} = require("json-rpc-2.0");
|
|
2300
|
+
var { createDatabaseClient } = require_database();
|
|
2301
|
+
var { errorToJSONRPCResponse, RuntimeErrors } = require_errors();
|
|
2302
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
2303
|
+
var { withSpan } = require_tracing();
|
|
2304
|
+
var { tryExecuteJob } = require_tryExecuteJob();
|
|
2305
|
+
var { parseInputs } = require_parsing();
|
|
2306
|
+
var { PROTO_ACTION_TYPES } = require_consts();
|
|
2307
|
+
async function handleJob(request, config) {
|
|
2308
|
+
var _a;
|
|
2309
|
+
const activeContext = opentelemetry.propagation.extract(
|
|
2310
|
+
opentelemetry.context.active(),
|
|
2311
|
+
(_a = request.meta) == null ? void 0 : _a.tracing
|
|
2312
|
+
);
|
|
2313
|
+
return opentelemetry.context.with(activeContext, () => {
|
|
2314
|
+
return withSpan(request.method, async (span) => {
|
|
2315
|
+
var _a2, _b;
|
|
2316
|
+
let db = null;
|
|
2317
|
+
try {
|
|
2318
|
+
const { createJobContextAPI, jobs } = config;
|
|
2319
|
+
if (!(request.method in jobs)) {
|
|
2320
|
+
const message = `no corresponding job found for '${request.method}'`;
|
|
2321
|
+
span.setStatus({
|
|
2322
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2323
|
+
message
|
|
2324
|
+
});
|
|
2325
|
+
return createJSONRPCErrorResponse(
|
|
2326
|
+
request.id,
|
|
2327
|
+
JSONRPCErrorCode.MethodNotFound,
|
|
2328
|
+
message
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
const ctx = createJobContextAPI({
|
|
2332
|
+
meta: request.meta
|
|
2333
|
+
});
|
|
2334
|
+
const permitted = request.meta && request.meta.permissionState.status === "granted" ? true : null;
|
|
2335
|
+
db = createDatabaseClient({
|
|
2336
|
+
connString: (_b = (_a2 = request.meta) == null ? void 0 : _a2.secrets) == null ? void 0 : _b.KEEL_DB_CONN
|
|
2337
|
+
});
|
|
2338
|
+
const jobFunction = jobs[request.method];
|
|
2339
|
+
const actionType = PROTO_ACTION_TYPES.JOB;
|
|
2340
|
+
const functionConfig = (jobFunction == null ? void 0 : jobFunction.config) ?? {};
|
|
2341
|
+
await tryExecuteJob(
|
|
2342
|
+
{ request, permitted, db, actionType, functionConfig },
|
|
2343
|
+
async () => {
|
|
2344
|
+
const inputs = parseInputs(request.params);
|
|
2345
|
+
return jobFunction(ctx, inputs);
|
|
2346
|
+
}
|
|
2347
|
+
);
|
|
2348
|
+
return createJSONRPCSuccessResponse(request.id, null);
|
|
2349
|
+
} catch (e) {
|
|
2350
|
+
if (e instanceof Error) {
|
|
2351
|
+
span.recordException(e);
|
|
2352
|
+
span.setStatus({
|
|
2353
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2354
|
+
message: e.message
|
|
2355
|
+
});
|
|
2356
|
+
return errorToJSONRPCResponse(request, e);
|
|
2357
|
+
}
|
|
2358
|
+
const message = JSON.stringify(e);
|
|
2359
|
+
span.setStatus({
|
|
2360
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2361
|
+
message
|
|
2362
|
+
});
|
|
2363
|
+
return createJSONRPCErrorResponse(
|
|
2364
|
+
request.id,
|
|
2365
|
+
RuntimeErrors.UnknownError,
|
|
2366
|
+
message
|
|
2367
|
+
);
|
|
2368
|
+
} finally {
|
|
2369
|
+
if (db) {
|
|
2370
|
+
await db.destroy();
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
__name(handleJob, "handleJob");
|
|
2377
|
+
module2.exports = {
|
|
2378
|
+
handleJob,
|
|
2379
|
+
RuntimeErrors
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
|
|
2384
|
+
// src/tryExecuteSubscriber.js
|
|
2385
|
+
var require_tryExecuteSubscriber = __commonJS({
|
|
2386
|
+
"src/tryExecuteSubscriber.js"(exports2, module2) {
|
|
2387
|
+
"use strict";
|
|
2388
|
+
var { withDatabase } = require_database();
|
|
2389
|
+
var { withAuditContext } = require_auditing();
|
|
2390
|
+
function tryExecuteSubscriber({ request, db, functionConfig }, cb) {
|
|
2391
|
+
let requiresTransaction = false;
|
|
2392
|
+
if ((functionConfig == null ? void 0 : functionConfig.dbTransaction) !== void 0) {
|
|
2393
|
+
requiresTransaction = functionConfig.dbTransaction;
|
|
2394
|
+
}
|
|
2395
|
+
return withDatabase(db, requiresTransaction, async () => {
|
|
2396
|
+
await withAuditContext(request, async () => {
|
|
2397
|
+
return cb();
|
|
2398
|
+
});
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
__name(tryExecuteSubscriber, "tryExecuteSubscriber");
|
|
2402
|
+
module2.exports.tryExecuteSubscriber = tryExecuteSubscriber;
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
|
|
2406
|
+
// src/handleSubscriber.js
|
|
2407
|
+
var require_handleSubscriber = __commonJS({
|
|
2408
|
+
"src/handleSubscriber.js"(exports2, module2) {
|
|
2409
|
+
"use strict";
|
|
2410
|
+
var {
|
|
2411
|
+
createJSONRPCErrorResponse,
|
|
2412
|
+
createJSONRPCSuccessResponse,
|
|
2413
|
+
JSONRPCErrorCode
|
|
2414
|
+
} = require("json-rpc-2.0");
|
|
2415
|
+
var { createDatabaseClient } = require_database();
|
|
2416
|
+
var { errorToJSONRPCResponse, RuntimeErrors } = require_errors();
|
|
2417
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
2418
|
+
var { withSpan } = require_tracing();
|
|
2419
|
+
var { PROTO_ACTION_TYPES } = require_consts();
|
|
2420
|
+
var { tryExecuteSubscriber } = require_tryExecuteSubscriber();
|
|
2421
|
+
var { parseInputs } = require_parsing();
|
|
2422
|
+
async function handleSubscriber(request, config) {
|
|
2423
|
+
var _a;
|
|
2424
|
+
const activeContext = opentelemetry.propagation.extract(
|
|
2425
|
+
opentelemetry.context.active(),
|
|
2426
|
+
(_a = request.meta) == null ? void 0 : _a.tracing
|
|
2427
|
+
);
|
|
2428
|
+
return opentelemetry.context.with(activeContext, () => {
|
|
2429
|
+
return withSpan(request.method, async (span) => {
|
|
2430
|
+
var _a2, _b;
|
|
2431
|
+
let db = null;
|
|
2432
|
+
try {
|
|
2433
|
+
const { createSubscriberContextAPI, subscribers } = config;
|
|
2434
|
+
if (!(request.method in subscribers)) {
|
|
2435
|
+
const message = `no corresponding subscriber found for '${request.method}'`;
|
|
2436
|
+
span.setStatus({
|
|
2437
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2438
|
+
message
|
|
2439
|
+
});
|
|
2440
|
+
return createJSONRPCErrorResponse(
|
|
2441
|
+
request.id,
|
|
2442
|
+
JSONRPCErrorCode.MethodNotFound,
|
|
2443
|
+
message
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
const ctx = createSubscriberContextAPI({
|
|
2447
|
+
meta: request.meta
|
|
2448
|
+
});
|
|
2449
|
+
db = createDatabaseClient({
|
|
2450
|
+
connString: (_b = (_a2 = request.meta) == null ? void 0 : _a2.secrets) == null ? void 0 : _b.KEEL_DB_CONN
|
|
2451
|
+
});
|
|
2452
|
+
const subscriberFunction = subscribers[request.method];
|
|
2453
|
+
const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
|
|
2454
|
+
const functionConfig = (subscriberFunction == null ? void 0 : subscriberFunction.config) ?? {};
|
|
2455
|
+
await tryExecuteSubscriber(
|
|
2456
|
+
{ request, db, actionType, functionConfig },
|
|
2457
|
+
async () => {
|
|
2458
|
+
const inputs = parseInputs(request.params);
|
|
2459
|
+
return subscriberFunction(ctx, inputs);
|
|
2460
|
+
}
|
|
2461
|
+
);
|
|
2462
|
+
return createJSONRPCSuccessResponse(request.id, null);
|
|
2463
|
+
} catch (e) {
|
|
2464
|
+
if (e instanceof Error) {
|
|
2465
|
+
span.recordException(e);
|
|
2466
|
+
span.setStatus({
|
|
2467
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2468
|
+
message: e.message
|
|
2469
|
+
});
|
|
2470
|
+
return errorToJSONRPCResponse(request, e);
|
|
2471
|
+
}
|
|
2472
|
+
const message = JSON.stringify(e);
|
|
2473
|
+
span.setStatus({
|
|
2474
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2475
|
+
message
|
|
2476
|
+
});
|
|
2477
|
+
return createJSONRPCErrorResponse(
|
|
2478
|
+
request.id,
|
|
2479
|
+
RuntimeErrors.UnknownError,
|
|
2480
|
+
message
|
|
2481
|
+
);
|
|
2482
|
+
} finally {
|
|
2483
|
+
if (db) {
|
|
2484
|
+
await db.destroy();
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
__name(handleSubscriber, "handleSubscriber");
|
|
2491
|
+
module2.exports = {
|
|
2492
|
+
handleSubscriber,
|
|
2493
|
+
RuntimeErrors
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
|
|
2498
|
+
// src/handleRoute.js
|
|
2499
|
+
var require_handleRoute = __commonJS({
|
|
2500
|
+
"src/handleRoute.js"(exports2, module2) {
|
|
2501
|
+
"use strict";
|
|
2502
|
+
var {
|
|
2503
|
+
createJSONRPCErrorResponse,
|
|
2504
|
+
createJSONRPCSuccessResponse,
|
|
2505
|
+
JSONRPCErrorCode
|
|
2506
|
+
} = require("json-rpc-2.0");
|
|
2507
|
+
var { createDatabaseClient, withDatabase } = require_database();
|
|
2508
|
+
var { withAuditContext } = require_auditing();
|
|
2509
|
+
var { errorToJSONRPCResponse, RuntimeErrors } = require_errors();
|
|
2510
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
2511
|
+
var { withSpan } = require_tracing();
|
|
2512
|
+
async function handleRoute(request, config) {
|
|
2513
|
+
var _a;
|
|
2514
|
+
const activeContext = opentelemetry.propagation.extract(
|
|
2515
|
+
opentelemetry.context.active(),
|
|
2516
|
+
(_a = request.meta) == null ? void 0 : _a.tracing
|
|
2517
|
+
);
|
|
2518
|
+
return opentelemetry.context.with(activeContext, () => {
|
|
2519
|
+
return withSpan(request.method, async (span) => {
|
|
2520
|
+
var _a2, _b;
|
|
2521
|
+
let db = null;
|
|
2522
|
+
try {
|
|
2523
|
+
const { createContextAPI, functions } = config;
|
|
2524
|
+
if (!(request.method in functions)) {
|
|
2525
|
+
const message = `no route function found for '${request.method}'`;
|
|
2526
|
+
span.setStatus({
|
|
2527
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2528
|
+
message
|
|
2529
|
+
});
|
|
2530
|
+
return createJSONRPCErrorResponse(
|
|
2531
|
+
request.id,
|
|
2532
|
+
JSONRPCErrorCode.MethodNotFound,
|
|
2533
|
+
message
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
const {
|
|
2537
|
+
headers,
|
|
2538
|
+
response: __,
|
|
2539
|
+
...ctx
|
|
2540
|
+
} = createContextAPI({
|
|
2541
|
+
responseHeaders: new Headers(),
|
|
2542
|
+
meta: request.meta
|
|
2543
|
+
});
|
|
2544
|
+
request.params.headers = headers;
|
|
2545
|
+
db = createDatabaseClient({
|
|
2546
|
+
connString: (_b = (_a2 = request.meta) == null ? void 0 : _a2.secrets) == null ? void 0 : _b.KEEL_DB_CONN
|
|
2547
|
+
});
|
|
2548
|
+
const routeHandler = functions[request.method];
|
|
2549
|
+
const result = await withDatabase(db, false, () => {
|
|
2550
|
+
return withAuditContext(request, () => {
|
|
2551
|
+
return routeHandler(request.params, ctx);
|
|
2552
|
+
});
|
|
2553
|
+
});
|
|
2554
|
+
if (result instanceof Error) {
|
|
2555
|
+
span.recordException(result);
|
|
2556
|
+
span.setStatus({
|
|
2557
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2558
|
+
message: result.message
|
|
2559
|
+
});
|
|
2560
|
+
return errorToJSONRPCResponse(request, result);
|
|
2561
|
+
}
|
|
2562
|
+
const response = createJSONRPCSuccessResponse(request.id, result);
|
|
2563
|
+
return response;
|
|
2564
|
+
} catch (e) {
|
|
2565
|
+
if (e instanceof Error) {
|
|
2566
|
+
span.recordException(e);
|
|
2567
|
+
span.setStatus({
|
|
2568
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2569
|
+
message: e.message
|
|
2570
|
+
});
|
|
2571
|
+
return errorToJSONRPCResponse(request, e);
|
|
2572
|
+
}
|
|
2573
|
+
const message = JSON.stringify(e);
|
|
2574
|
+
span.setStatus({
|
|
2575
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2576
|
+
message
|
|
2577
|
+
});
|
|
2578
|
+
return createJSONRPCErrorResponse(
|
|
2579
|
+
request.id,
|
|
2580
|
+
RuntimeErrors.UnknownError,
|
|
2581
|
+
message
|
|
2582
|
+
);
|
|
2583
|
+
} finally {
|
|
2584
|
+
if (db) {
|
|
2585
|
+
await db.destroy();
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
__name(handleRoute, "handleRoute");
|
|
2592
|
+
module2.exports = {
|
|
2593
|
+
handleRoute,
|
|
2594
|
+
RuntimeErrors
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
// src/tryExecuteFlow.js
|
|
2600
|
+
var require_tryExecuteFlow = __commonJS({
|
|
2601
|
+
"src/tryExecuteFlow.js"(exports2, module2) {
|
|
2602
|
+
"use strict";
|
|
2603
|
+
var { withDatabase } = require_database();
|
|
2604
|
+
function tryExecuteFlow(db, cb) {
|
|
2605
|
+
return withDatabase(db, false, async () => {
|
|
2606
|
+
return cb();
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
__name(tryExecuteFlow, "tryExecuteFlow");
|
|
2610
|
+
module2.exports.tryExecuteFlow = tryExecuteFlow;
|
|
2611
|
+
}
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2614
|
+
// src/flows/ui/elements/input/text.ts
|
|
2615
|
+
var textInput;
|
|
2616
|
+
var init_text = __esm({
|
|
2617
|
+
"src/flows/ui/elements/input/text.ts"() {
|
|
2618
|
+
"use strict";
|
|
2619
|
+
textInput = /* @__PURE__ */ __name((name, options) => {
|
|
2620
|
+
return {
|
|
2621
|
+
uiConfig: {
|
|
2622
|
+
__type: "ui.input.text",
|
|
2623
|
+
name,
|
|
2624
|
+
label: (options == null ? void 0 : options.label) || name,
|
|
2625
|
+
defaultValue: options == null ? void 0 : options.defaultValue,
|
|
2626
|
+
optional: options == null ? void 0 : options.optional,
|
|
2627
|
+
placeholder: options == null ? void 0 : options.placeholder,
|
|
2628
|
+
multiline: options == null ? void 0 : options.multiline,
|
|
2629
|
+
maxLength: options == null ? void 0 : options.maxLength,
|
|
2630
|
+
minLength: options == null ? void 0 : options.minLength
|
|
2631
|
+
},
|
|
2632
|
+
validate: options == null ? void 0 : options.validate,
|
|
2633
|
+
getData: /* @__PURE__ */ __name((x) => x, "getData")
|
|
2634
|
+
};
|
|
2635
|
+
}, "textInput");
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
// src/flows/ui/elements/input/number.ts
|
|
2640
|
+
var numberInput;
|
|
2641
|
+
var init_number = __esm({
|
|
2642
|
+
"src/flows/ui/elements/input/number.ts"() {
|
|
2643
|
+
"use strict";
|
|
2644
|
+
numberInput = /* @__PURE__ */ __name((name, options) => {
|
|
2645
|
+
return {
|
|
2646
|
+
uiConfig: {
|
|
2647
|
+
__type: "ui.input.number",
|
|
2648
|
+
name,
|
|
2649
|
+
label: (options == null ? void 0 : options.label) || name,
|
|
2650
|
+
defaultValue: options == null ? void 0 : options.defaultValue,
|
|
2651
|
+
optional: options == null ? void 0 : options.optional,
|
|
2652
|
+
placeholder: options == null ? void 0 : options.placeholder,
|
|
2653
|
+
min: options == null ? void 0 : options.min,
|
|
2654
|
+
max: options == null ? void 0 : options.max
|
|
2655
|
+
},
|
|
2656
|
+
validate: options == null ? void 0 : options.validate,
|
|
2657
|
+
getData: /* @__PURE__ */ __name((x) => x, "getData")
|
|
2658
|
+
};
|
|
2659
|
+
}, "numberInput");
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
// src/flows/ui/elements/display/divider.ts
|
|
2664
|
+
var divider;
|
|
2665
|
+
var init_divider = __esm({
|
|
2666
|
+
"src/flows/ui/elements/display/divider.ts"() {
|
|
2667
|
+
"use strict";
|
|
2668
|
+
divider = /* @__PURE__ */ __name((options) => {
|
|
2669
|
+
return {
|
|
2670
|
+
uiConfig: {
|
|
2671
|
+
__type: "ui.display.divider"
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
}, "divider");
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
|
|
2678
|
+
// src/flows/ui/elements/input/boolean.ts
|
|
2679
|
+
var booleanInput;
|
|
2680
|
+
var init_boolean = __esm({
|
|
2681
|
+
"src/flows/ui/elements/input/boolean.ts"() {
|
|
2682
|
+
"use strict";
|
|
2683
|
+
booleanInput = /* @__PURE__ */ __name((name, options) => {
|
|
2684
|
+
return {
|
|
2685
|
+
uiConfig: {
|
|
2686
|
+
__type: "ui.input.boolean",
|
|
2687
|
+
name,
|
|
2688
|
+
label: (options == null ? void 0 : options.label) || name,
|
|
2689
|
+
defaultValue: options == null ? void 0 : options.defaultValue,
|
|
2690
|
+
optional: options == null ? void 0 : options.optional,
|
|
2691
|
+
mode: (options == null ? void 0 : options.mode) || "checkbox"
|
|
2692
|
+
},
|
|
2693
|
+
validate: options == null ? void 0 : options.validate,
|
|
2694
|
+
getData: /* @__PURE__ */ __name((x) => x, "getData")
|
|
2695
|
+
};
|
|
2696
|
+
}, "booleanInput");
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
// src/flows/ui/elements/display/markdown.ts
|
|
2701
|
+
var markdown;
|
|
2702
|
+
var init_markdown = __esm({
|
|
2703
|
+
"src/flows/ui/elements/display/markdown.ts"() {
|
|
2704
|
+
"use strict";
|
|
2705
|
+
markdown = /* @__PURE__ */ __name((options) => {
|
|
2706
|
+
return {
|
|
2707
|
+
uiConfig: {
|
|
2708
|
+
__type: "ui.display.markdown",
|
|
2709
|
+
content: (options == null ? void 0 : options.content) || ""
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
}, "markdown");
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
// src/flows/ui/elements/display/table.ts
|
|
2717
|
+
var table;
|
|
2718
|
+
var init_table = __esm({
|
|
2719
|
+
"src/flows/ui/elements/display/table.ts"() {
|
|
2720
|
+
"use strict";
|
|
2721
|
+
table = /* @__PURE__ */ __name((options) => {
|
|
2722
|
+
return {
|
|
2723
|
+
uiConfig: {
|
|
2724
|
+
__type: "ui.display.table",
|
|
2725
|
+
data: (options == null ? void 0 : options.data) || [],
|
|
2726
|
+
columns: (options == null ? void 0 : options.columns) || []
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
}, "table");
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
|
|
2733
|
+
// src/flows/ui/elements/select/single.ts
|
|
2734
|
+
var selectOne;
|
|
2735
|
+
var init_single = __esm({
|
|
2736
|
+
"src/flows/ui/elements/select/single.ts"() {
|
|
2737
|
+
"use strict";
|
|
2738
|
+
selectOne = /* @__PURE__ */ __name((name, options) => {
|
|
2739
|
+
return {
|
|
2740
|
+
uiConfig: {
|
|
2741
|
+
__type: "ui.select.single",
|
|
2742
|
+
name,
|
|
2743
|
+
label: (options == null ? void 0 : options.label) || name,
|
|
2744
|
+
defaultValue: options == null ? void 0 : options.defaultValue,
|
|
2745
|
+
optional: options == null ? void 0 : options.optional,
|
|
2746
|
+
options: []
|
|
2747
|
+
},
|
|
2748
|
+
validate: options == null ? void 0 : options.validate,
|
|
2749
|
+
getData: /* @__PURE__ */ __name((x) => x, "getData")
|
|
2750
|
+
};
|
|
2751
|
+
}, "selectOne");
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
// src/flows/disrupts.ts
|
|
2756
|
+
var disrupts_exports = {};
|
|
2757
|
+
__export(disrupts_exports, {
|
|
2758
|
+
StepCompletedDisrupt: () => StepCompletedDisrupt,
|
|
2759
|
+
StepErrorDisrupt: () => StepErrorDisrupt,
|
|
2760
|
+
UIRenderDisrupt: () => UIRenderDisrupt
|
|
2761
|
+
});
|
|
2762
|
+
var _FlowDisrupt, FlowDisrupt, _UIRenderDisrupt, UIRenderDisrupt, _StepErrorDisrupt, StepErrorDisrupt, _StepCompletedDisrupt, StepCompletedDisrupt;
|
|
2763
|
+
var init_disrupts = __esm({
|
|
2764
|
+
"src/flows/disrupts.ts"() {
|
|
2765
|
+
"use strict";
|
|
2766
|
+
_FlowDisrupt = class _FlowDisrupt {
|
|
2767
|
+
constructor() {
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
__name(_FlowDisrupt, "FlowDisrupt");
|
|
2771
|
+
FlowDisrupt = _FlowDisrupt;
|
|
2772
|
+
_UIRenderDisrupt = class _UIRenderDisrupt extends FlowDisrupt {
|
|
2773
|
+
constructor(stepId, contents) {
|
|
2774
|
+
super();
|
|
2775
|
+
this.stepId = stepId;
|
|
2776
|
+
this.contents = contents;
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
__name(_UIRenderDisrupt, "UIRenderDisrupt");
|
|
2780
|
+
UIRenderDisrupt = _UIRenderDisrupt;
|
|
2781
|
+
_StepErrorDisrupt = class _StepErrorDisrupt extends FlowDisrupt {
|
|
2782
|
+
constructor(errorMessage) {
|
|
2783
|
+
super();
|
|
2784
|
+
this.errorMessage = errorMessage;
|
|
2785
|
+
}
|
|
2786
|
+
};
|
|
2787
|
+
__name(_StepErrorDisrupt, "StepErrorDisrupt");
|
|
2788
|
+
StepErrorDisrupt = _StepErrorDisrupt;
|
|
2789
|
+
_StepCompletedDisrupt = class _StepCompletedDisrupt extends FlowDisrupt {
|
|
2790
|
+
constructor() {
|
|
2791
|
+
super();
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
__name(_StepCompletedDisrupt, "StepCompletedDisrupt");
|
|
2795
|
+
StepCompletedDisrupt = _StepCompletedDisrupt;
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
// src/flows/index.ts
|
|
2800
|
+
var flows_exports = {};
|
|
2801
|
+
__export(flows_exports, {
|
|
2802
|
+
createStepContext: () => createStepContext
|
|
2803
|
+
});
|
|
2804
|
+
function createStepContext(runId, data, spanId) {
|
|
2805
|
+
return {
|
|
2806
|
+
step: /* @__PURE__ */ __name(async (name, fn, opts) => {
|
|
2807
|
+
const db = (0, import_database.useDatabase)();
|
|
2808
|
+
const completed = await db.selectFrom("keel_flow_step").where("run_id", "=", runId).where("name", "=", name).where("status", "=", "COMPLETED" /* COMPLETED */).selectAll().executeTakeFirst();
|
|
2809
|
+
if (completed) {
|
|
2810
|
+
return completed.value;
|
|
2811
|
+
}
|
|
2812
|
+
const step = await db.insertInto("keel_flow_step").values({
|
|
2813
|
+
run_id: runId,
|
|
2814
|
+
name,
|
|
2815
|
+
status: "NEW" /* NEW */,
|
|
2816
|
+
type: "FUNCTION" /* FUNCTION */,
|
|
2817
|
+
maxRetries: (opts == null ? void 0 : opts.maxRetries) ?? defaultOpts.maxRetries,
|
|
2818
|
+
timeoutInMs: (opts == null ? void 0 : opts.timeoutInMs) ?? defaultOpts.timeoutInMs
|
|
2819
|
+
}).returningAll().executeTakeFirst();
|
|
2820
|
+
let result = null;
|
|
2821
|
+
try {
|
|
2822
|
+
result = await withTimeout(fn(), step.timeoutInMs);
|
|
2823
|
+
} catch (e) {
|
|
2824
|
+
await db.updateTable("keel_flow_step").set({
|
|
2825
|
+
status: "FAILED" /* FAILED */,
|
|
2826
|
+
spanId
|
|
2827
|
+
// TODO: store error message
|
|
2828
|
+
}).where("id", "=", step.id).returningAll().executeTakeFirst();
|
|
2829
|
+
throw new StepErrorDisrupt(
|
|
2830
|
+
e instanceof Error ? e.message : "an error occurred"
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
await db.updateTable("keel_flow_step").set({
|
|
2834
|
+
status: "COMPLETED" /* COMPLETED */,
|
|
2835
|
+
value: JSON.stringify(result),
|
|
2836
|
+
spanId
|
|
2837
|
+
}).where("id", "=", step.id).returningAll().executeTakeFirst();
|
|
2838
|
+
throw new StepCompletedDisrupt();
|
|
2839
|
+
}, "step"),
|
|
2840
|
+
ui: {
|
|
2841
|
+
page: /* @__PURE__ */ __name(async (page) => {
|
|
2842
|
+
const db = (0, import_database.useDatabase)();
|
|
2843
|
+
let step = await db.selectFrom("keel_flow_step").where("run_id", "=", runId).where("name", "=", page.title).selectAll().executeTakeFirst();
|
|
2844
|
+
if (step && step.status === "COMPLETED" /* COMPLETED */) {
|
|
2845
|
+
return step.value;
|
|
2846
|
+
}
|
|
2847
|
+
if (!step) {
|
|
2848
|
+
step = await db.insertInto("keel_flow_step").values({
|
|
2849
|
+
run_id: runId,
|
|
2850
|
+
name: page.title,
|
|
2851
|
+
status: "PENDING" /* PENDING */,
|
|
2852
|
+
type: "UI" /* UI */,
|
|
2853
|
+
maxRetries: 3,
|
|
2854
|
+
timeoutInMs: 1e3
|
|
2855
|
+
}).returningAll().executeTakeFirst();
|
|
2856
|
+
}
|
|
2857
|
+
if (data) {
|
|
2858
|
+
await db.updateTable("keel_flow_step").set({
|
|
2859
|
+
status: "COMPLETED" /* COMPLETED */,
|
|
2860
|
+
value: JSON.stringify(data),
|
|
2861
|
+
spanId
|
|
2862
|
+
}).where("id", "=", step.id).returningAll().executeTakeFirst();
|
|
2863
|
+
return data;
|
|
2864
|
+
} else {
|
|
2865
|
+
throw new UIRenderDisrupt(step.id, page);
|
|
2866
|
+
}
|
|
2867
|
+
}, "page"),
|
|
2868
|
+
inputs: {
|
|
2869
|
+
text: textInput,
|
|
2870
|
+
number: numberInput,
|
|
2871
|
+
boolean: booleanInput
|
|
2872
|
+
},
|
|
2873
|
+
display: {
|
|
2874
|
+
divider,
|
|
2875
|
+
markdown,
|
|
2876
|
+
table
|
|
2877
|
+
},
|
|
2878
|
+
select: {
|
|
2879
|
+
single: selectOne
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
function wait(milliseconds) {
|
|
2885
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
2886
|
+
}
|
|
2887
|
+
function withTimeout(promiseFn, timeout) {
|
|
2888
|
+
return Promise.race([
|
|
2889
|
+
promiseFn,
|
|
2890
|
+
wait(timeout).then(() => {
|
|
2891
|
+
throw new Error(`flow times out after ${timeout}ms`);
|
|
2892
|
+
})
|
|
2893
|
+
]);
|
|
2894
|
+
}
|
|
2895
|
+
var import_database, defaultOpts;
|
|
2896
|
+
var init_flows = __esm({
|
|
2897
|
+
"src/flows/index.ts"() {
|
|
2898
|
+
"use strict";
|
|
2899
|
+
import_database = __toESM(require_database());
|
|
2900
|
+
init_text();
|
|
2901
|
+
init_number();
|
|
2902
|
+
init_divider();
|
|
2903
|
+
init_boolean();
|
|
2904
|
+
init_markdown();
|
|
2905
|
+
init_table();
|
|
2906
|
+
init_single();
|
|
2907
|
+
init_disrupts();
|
|
2908
|
+
defaultOpts = {
|
|
2909
|
+
maxRetries: 5,
|
|
2910
|
+
timeoutInMs: 6e4
|
|
2911
|
+
};
|
|
2912
|
+
__name(createStepContext, "createStepContext");
|
|
2913
|
+
__name(wait, "wait");
|
|
2914
|
+
__name(withTimeout, "withTimeout");
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2918
|
+
// src/handleFlow.js
|
|
2919
|
+
var require_handleFlow = __commonJS({
|
|
2920
|
+
"src/handleFlow.js"(exports2, module2) {
|
|
2921
|
+
"use strict";
|
|
2922
|
+
var {
|
|
2923
|
+
createJSONRPCErrorResponse,
|
|
2924
|
+
createJSONRPCSuccessResponse,
|
|
2925
|
+
JSONRPCErrorCode
|
|
2926
|
+
} = require("json-rpc-2.0");
|
|
2927
|
+
var { createDatabaseClient } = require_database();
|
|
2928
|
+
var { errorToJSONRPCResponse, RuntimeErrors } = require_errors();
|
|
2929
|
+
var opentelemetry = require("@opentelemetry/api");
|
|
2930
|
+
var { withSpan } = require_tracing();
|
|
2931
|
+
var { tryExecuteFlow } = require_tryExecuteFlow();
|
|
2932
|
+
var { parseInputs } = require_parsing();
|
|
2933
|
+
var { createStepContext: createStepContext2 } = (init_flows(), __toCommonJS(flows_exports));
|
|
2934
|
+
var {
|
|
2935
|
+
StepCompletedDisrupt: StepCompletedDisrupt2,
|
|
2936
|
+
StepErrorDisrupt: StepErrorDisrupt2,
|
|
2937
|
+
UIRenderDisrupt: UIRenderDisrupt2
|
|
2938
|
+
} = (init_disrupts(), __toCommonJS(disrupts_exports));
|
|
2939
|
+
async function handleFlow(request, config) {
|
|
2940
|
+
var _a;
|
|
2941
|
+
const activeContext = opentelemetry.propagation.extract(
|
|
2942
|
+
opentelemetry.context.active(),
|
|
2943
|
+
(_a = request.meta) == null ? void 0 : _a.tracing
|
|
2944
|
+
);
|
|
2945
|
+
return opentelemetry.context.with(activeContext, () => {
|
|
2946
|
+
return withSpan(request.method, async (span) => {
|
|
2947
|
+
var _a2, _b, _c;
|
|
2948
|
+
let db = null;
|
|
2949
|
+
let flowConfig = null;
|
|
2950
|
+
const runId = (_a2 = request.meta) == null ? void 0 : _a2.runId;
|
|
2951
|
+
try {
|
|
2952
|
+
if (!runId) {
|
|
2953
|
+
throw new Error("no runId provided");
|
|
2954
|
+
}
|
|
2955
|
+
const { flows } = config;
|
|
2956
|
+
if (!(request.method in flows)) {
|
|
2957
|
+
const message = `no corresponding flow found for '${request.method}'`;
|
|
2958
|
+
span.setStatus({
|
|
2959
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
2960
|
+
message
|
|
2961
|
+
});
|
|
2962
|
+
return createJSONRPCErrorResponse(
|
|
2963
|
+
request.id,
|
|
2964
|
+
JSONRPCErrorCode.MethodNotFound,
|
|
2965
|
+
message
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
db = createDatabaseClient({
|
|
2969
|
+
connString: (_c = (_b = request.meta) == null ? void 0 : _b.secrets) == null ? void 0 : _c.KEEL_DB_CONN
|
|
2970
|
+
});
|
|
2971
|
+
const flowRun = await db.selectFrom("keel_flow_run").where("id", "=", runId).selectAll().executeTakeFirst();
|
|
2972
|
+
if (!flowRun) {
|
|
2973
|
+
throw new Error("no flow run found");
|
|
2974
|
+
}
|
|
2975
|
+
const ctx = createStepContext2(
|
|
2976
|
+
request.meta.runId,
|
|
2977
|
+
request.meta.data,
|
|
2978
|
+
span.spanContext().spanId
|
|
2979
|
+
);
|
|
2980
|
+
const flowFunction = flows[request.method].fn;
|
|
2981
|
+
flowConfig = flows[request.method].config;
|
|
2982
|
+
await tryExecuteFlow(db, async () => {
|
|
2983
|
+
const inputs = parseInputs(flowRun.input);
|
|
2984
|
+
return flowFunction(ctx, inputs);
|
|
2985
|
+
});
|
|
2986
|
+
return createJSONRPCSuccessResponse(request.id, {
|
|
2987
|
+
runId,
|
|
2988
|
+
runCompleted: true,
|
|
2989
|
+
config: flowConfig
|
|
2990
|
+
});
|
|
2991
|
+
} catch (e) {
|
|
2992
|
+
if (e instanceof StepCompletedDisrupt2) {
|
|
2993
|
+
return createJSONRPCSuccessResponse(request.id, {
|
|
2994
|
+
runId,
|
|
2995
|
+
runCompleted: false,
|
|
2996
|
+
config: flowConfig
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
if (e instanceof UIRenderDisrupt2) {
|
|
3000
|
+
return createJSONRPCSuccessResponse(request.id, {
|
|
3001
|
+
runId,
|
|
3002
|
+
stepId: e.stepId,
|
|
3003
|
+
config: flowConfig,
|
|
3004
|
+
ui: e.contents
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
if (e instanceof Error) {
|
|
3008
|
+
span.recordException(e);
|
|
3009
|
+
span.setStatus({
|
|
3010
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
3011
|
+
message: e.message
|
|
3012
|
+
});
|
|
3013
|
+
return errorToJSONRPCResponse(request, e);
|
|
3014
|
+
}
|
|
3015
|
+
const message = JSON.stringify(e);
|
|
3016
|
+
span.setStatus({
|
|
3017
|
+
code: opentelemetry.SpanStatusCode.ERROR,
|
|
3018
|
+
message
|
|
3019
|
+
});
|
|
3020
|
+
return createJSONRPCErrorResponse(
|
|
3021
|
+
request.id,
|
|
3022
|
+
RuntimeErrors.UnknownError,
|
|
3023
|
+
message
|
|
3024
|
+
);
|
|
3025
|
+
} finally {
|
|
3026
|
+
if (db) {
|
|
3027
|
+
await db.destroy();
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
});
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
__name(handleFlow, "handleFlow");
|
|
3034
|
+
module2.exports = {
|
|
3035
|
+
handleFlow,
|
|
3036
|
+
RuntimeErrors
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
|
|
3041
|
+
// src/index.js
|
|
3042
|
+
var require_index = __commonJS({
|
|
3043
|
+
"src/index.js"(exports2, module2) {
|
|
3044
|
+
"use strict";
|
|
3045
|
+
var { ModelAPI } = require_ModelAPI();
|
|
3046
|
+
var { RequestHeaders } = require_RequestHeaders();
|
|
3047
|
+
var { handleRequest } = require_handleRequest();
|
|
3048
|
+
var { handleJob } = require_handleJob();
|
|
3049
|
+
var { handleSubscriber } = require_handleSubscriber();
|
|
3050
|
+
var { handleRoute } = require_handleRoute();
|
|
3051
|
+
var { handleFlow } = require_handleFlow();
|
|
3052
|
+
var KSUID = require("ksuid");
|
|
3053
|
+
var { useDatabase: useDatabase2 } = require_database();
|
|
3054
|
+
var {
|
|
3055
|
+
Permissions,
|
|
3056
|
+
PERMISSION_STATE,
|
|
3057
|
+
checkBuiltInPermissions
|
|
3058
|
+
} = require_permissions();
|
|
3059
|
+
var tracing = require_tracing();
|
|
3060
|
+
var { InlineFile, File } = require_File();
|
|
3061
|
+
var { Duration } = require_Duration();
|
|
3062
|
+
var { ErrorPresets } = require_errors();
|
|
3063
|
+
module2.exports = {
|
|
3064
|
+
ModelAPI,
|
|
3065
|
+
RequestHeaders,
|
|
3066
|
+
handleRequest,
|
|
3067
|
+
handleJob,
|
|
3068
|
+
handleSubscriber,
|
|
3069
|
+
handleRoute,
|
|
3070
|
+
handleFlow,
|
|
3071
|
+
KSUID,
|
|
3072
|
+
useDatabase: useDatabase2,
|
|
3073
|
+
Permissions,
|
|
3074
|
+
PERMISSION_STATE,
|
|
3075
|
+
checkBuiltInPermissions,
|
|
3076
|
+
tracing,
|
|
3077
|
+
InlineFile,
|
|
3078
|
+
File,
|
|
3079
|
+
Duration,
|
|
3080
|
+
ErrorPresets,
|
|
3081
|
+
ksuid() {
|
|
3082
|
+
return KSUID.randomSync().string;
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
});
|
|
3087
|
+
|
|
3088
|
+
// src/index.ts
|
|
3089
|
+
var index_exports = {};
|
|
3090
|
+
module.exports = __toCommonJS(index_exports);
|
|
3091
|
+
__reExport(index_exports, __toESM(require_index()), module.exports);
|
|
3092
|
+
init_flows();
|
|
3093
|
+
//# sourceMappingURL=index.js.map
|