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