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