@teamkeel/functions-runtime 0.412.1 → 0.413.2-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.
Files changed (49) hide show
  1. package/dist/index.cjs +2842 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +739 -0
  4. package/dist/index.d.ts +739 -0
  5. package/dist/index.js +2817 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +30 -4
  8. package/.env.test +0 -2
  9. package/compose.yaml +0 -10
  10. package/src/Duration.js +0 -40
  11. package/src/Duration.test.js +0 -34
  12. package/src/File.js +0 -295
  13. package/src/ModelAPI.js +0 -377
  14. package/src/ModelAPI.test.js +0 -1428
  15. package/src/QueryBuilder.js +0 -184
  16. package/src/QueryContext.js +0 -90
  17. package/src/RequestHeaders.js +0 -21
  18. package/src/TimePeriod.js +0 -89
  19. package/src/TimePeriod.test.js +0 -148
  20. package/src/applyAdditionalQueryConstraints.js +0 -22
  21. package/src/applyJoins.js +0 -67
  22. package/src/applyWhereConditions.js +0 -124
  23. package/src/auditing.js +0 -110
  24. package/src/auditing.test.js +0 -330
  25. package/src/camelCasePlugin.js +0 -52
  26. package/src/casing.js +0 -54
  27. package/src/casing.test.js +0 -56
  28. package/src/consts.js +0 -14
  29. package/src/database.js +0 -244
  30. package/src/errors.js +0 -160
  31. package/src/handleJob.js +0 -110
  32. package/src/handleJob.test.js +0 -270
  33. package/src/handleRequest.js +0 -153
  34. package/src/handleRequest.test.js +0 -463
  35. package/src/handleRoute.js +0 -112
  36. package/src/handleSubscriber.js +0 -105
  37. package/src/index.d.ts +0 -317
  38. package/src/index.js +0 -38
  39. package/src/parsing.js +0 -113
  40. package/src/parsing.test.js +0 -140
  41. package/src/permissions.js +0 -77
  42. package/src/permissions.test.js +0 -118
  43. package/src/tracing.js +0 -184
  44. package/src/tracing.test.js +0 -147
  45. package/src/tryExecuteFunction.js +0 -91
  46. package/src/tryExecuteJob.js +0 -29
  47. package/src/tryExecuteSubscriber.js +0 -17
  48. package/src/type-utils.js +0 -18
  49. package/vite.config.js +0 -7
package/dist/index.cjs ADDED
@@ -0,0 +1,2842 @@
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
+ __type: "input",
2337
+ uiConfig: {
2338
+ __type: "ui.input.text",
2339
+ name,
2340
+ label: options?.label || name,
2341
+ defaultValue: options?.defaultValue,
2342
+ optional: options?.optional,
2343
+ placeholder: options?.placeholder,
2344
+ multiline: options?.multiline,
2345
+ maxLength: options?.maxLength,
2346
+ minLength: options?.minLength
2347
+ },
2348
+ validate: options?.validate,
2349
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2350
+ };
2351
+ }, "textInput");
2352
+
2353
+ // src/flows/ui/elements/input/number.ts
2354
+ var numberInput = /* @__PURE__ */ __name((name, options) => {
2355
+ return {
2356
+ __type: "input",
2357
+ uiConfig: {
2358
+ __type: "ui.input.number",
2359
+ name,
2360
+ label: options?.label || name,
2361
+ defaultValue: options?.defaultValue,
2362
+ optional: options?.optional,
2363
+ placeholder: options?.placeholder,
2364
+ min: options?.min,
2365
+ max: options?.max
2366
+ },
2367
+ validate: options?.validate,
2368
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2369
+ };
2370
+ }, "numberInput");
2371
+
2372
+ // src/flows/ui/elements/display/divider.ts
2373
+ var divider = /* @__PURE__ */ __name((options) => {
2374
+ return {
2375
+ uiConfig: {
2376
+ __type: "ui.display.divider"
2377
+ }
2378
+ };
2379
+ }, "divider");
2380
+
2381
+ // src/flows/ui/elements/input/boolean.ts
2382
+ var booleanInput = /* @__PURE__ */ __name((name, options) => {
2383
+ return {
2384
+ __type: "input",
2385
+ uiConfig: {
2386
+ __type: "ui.input.boolean",
2387
+ name,
2388
+ label: options?.label || name,
2389
+ defaultValue: options?.defaultValue,
2390
+ optional: options?.optional,
2391
+ mode: options?.mode || "checkbox"
2392
+ },
2393
+ validate: options?.validate,
2394
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2395
+ };
2396
+ }, "booleanInput");
2397
+
2398
+ // src/flows/ui/elements/display/markdown.ts
2399
+ var markdown = /* @__PURE__ */ __name((options) => {
2400
+ return {
2401
+ uiConfig: {
2402
+ __type: "ui.display.markdown",
2403
+ content: options?.content || ""
2404
+ }
2405
+ };
2406
+ }, "markdown");
2407
+
2408
+ // src/flows/ui/elements/display/table.ts
2409
+ var table = /* @__PURE__ */ __name((options) => {
2410
+ const filteredData = options.columns ? options.data.map((item) => {
2411
+ return Object.fromEntries(
2412
+ Object.entries(item).filter(
2413
+ ([key]) => options.columns?.includes(key)
2414
+ )
2415
+ );
2416
+ }) : options.data;
2417
+ return {
2418
+ uiConfig: {
2419
+ __type: "ui.display.table",
2420
+ data: filteredData || [],
2421
+ columns: options.columns
2422
+ }
2423
+ };
2424
+ }, "table");
2425
+
2426
+ // src/flows/ui/elements/select/one.ts
2427
+ var selectOne = /* @__PURE__ */ __name((name, options) => {
2428
+ return {
2429
+ __type: "input",
2430
+ uiConfig: {
2431
+ __type: "ui.select.one",
2432
+ name,
2433
+ label: options?.label || name,
2434
+ defaultValue: options?.defaultValue,
2435
+ optional: options?.optional,
2436
+ options: options?.options || []
2437
+ },
2438
+ validate: options?.validate,
2439
+ getData: /* @__PURE__ */ __name((x) => x, "getData")
2440
+ };
2441
+ }, "selectOne");
2442
+
2443
+ // src/flows/ui/page.ts
2444
+ var page = /* @__PURE__ */ __name((options) => {
2445
+ const content = options.content;
2446
+ const contentUiConfig = content.map((c) => c.uiConfig).filter(Boolean);
2447
+ return {
2448
+ __type: "ui.page",
2449
+ stage: options.stage,
2450
+ title: options.title,
2451
+ description: options.description,
2452
+ content: contentUiConfig,
2453
+ actions: options.actions
2454
+ };
2455
+ }, "page");
2456
+
2457
+ // src/flows/disrupts.ts
2458
+ var FlowDisrupt = class {
2459
+ static {
2460
+ __name(this, "FlowDisrupt");
2461
+ }
2462
+ constructor() {
2463
+ }
2464
+ };
2465
+ var UIRenderDisrupt = class extends FlowDisrupt {
2466
+ constructor(stepId, contents) {
2467
+ super();
2468
+ this.stepId = stepId;
2469
+ this.contents = contents;
2470
+ }
2471
+ static {
2472
+ __name(this, "UIRenderDisrupt");
2473
+ }
2474
+ };
2475
+ var StepCreatedDisrupt = class extends FlowDisrupt {
2476
+ static {
2477
+ __name(this, "StepCreatedDisrupt");
2478
+ }
2479
+ constructor() {
2480
+ super();
2481
+ }
2482
+ };
2483
+ var ExhuastedRetriesDisrupt = class extends FlowDisrupt {
2484
+ static {
2485
+ __name(this, "ExhuastedRetriesDisrupt");
2486
+ }
2487
+ constructor() {
2488
+ super();
2489
+ }
2490
+ };
2491
+
2492
+ // src/flows/ui/elements/display/banner.ts
2493
+ var banner = /* @__PURE__ */ __name((options) => {
2494
+ return {
2495
+ uiConfig: {
2496
+ __type: "ui.display.banner",
2497
+ title: options?.title || "",
2498
+ description: options?.description || "",
2499
+ mode: options?.mode || "info"
2500
+ }
2501
+ };
2502
+ }, "banner");
2503
+
2504
+ // src/flows/ui/elements/display/image.ts
2505
+ var image = /* @__PURE__ */ __name((options) => {
2506
+ return {
2507
+ uiConfig: {
2508
+ __type: "ui.display.image",
2509
+ url: options?.url || "",
2510
+ alt: options?.alt,
2511
+ size: options?.size,
2512
+ title: options?.title
2513
+ }
2514
+ };
2515
+ }, "image");
2516
+
2517
+ // src/flows/ui/elements/display/code.ts
2518
+ var code = /* @__PURE__ */ __name((options) => {
2519
+ return {
2520
+ uiConfig: {
2521
+ __type: "ui.display.code",
2522
+ code: options?.code || "",
2523
+ language: options?.language
2524
+ }
2525
+ };
2526
+ }, "code");
2527
+
2528
+ // src/flows/ui/elements/display/grid.ts
2529
+ var grid = /* @__PURE__ */ __name((options) => {
2530
+ return {
2531
+ uiConfig: {
2532
+ __type: "ui.display.grid",
2533
+ data: options.data.map((item) => {
2534
+ const rendered = options.render(item);
2535
+ return {
2536
+ title: rendered.title,
2537
+ description: rendered.description,
2538
+ image: rendered.image
2539
+ };
2540
+ })
2541
+ }
2542
+ };
2543
+ }, "grid");
2544
+
2545
+ // src/flows/ui/elements/display/list.ts
2546
+ var list = /* @__PURE__ */ __name((options) => {
2547
+ return {
2548
+ uiConfig: {
2549
+ __type: "ui.display.list",
2550
+ data: options.data.map((item) => {
2551
+ const rendered = options.render(item);
2552
+ return {
2553
+ title: rendered.title,
2554
+ description: rendered.description,
2555
+ image: rendered.image
2556
+ };
2557
+ })
2558
+ }
2559
+ };
2560
+ }, "list");
2561
+
2562
+ // src/flows/ui/elements/display/header.ts
2563
+ var header = /* @__PURE__ */ __name((options) => {
2564
+ return {
2565
+ uiConfig: {
2566
+ __type: "ui.display.header",
2567
+ level: options?.level || 1,
2568
+ title: options?.title || "",
2569
+ description: options?.description || ""
2570
+ }
2571
+ };
2572
+ }, "header");
2573
+
2574
+ // src/flows/index.ts
2575
+ var defaultOpts = {
2576
+ maxRetries: 5,
2577
+ timeoutInMs: 6e4
2578
+ };
2579
+ function createFlowContext(runId, data, spanId) {
2580
+ return {
2581
+ step: /* @__PURE__ */ __name(async (name, optionsOrFn, fn) => {
2582
+ const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
2583
+ const actualFn = typeof optionsOrFn === "function" ? optionsOrFn : fn;
2584
+ const db = useDatabase();
2585
+ const past = await db.selectFrom("keel_flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().execute();
2586
+ const newSteps = past.filter((step) => step.status === "NEW" /* NEW */);
2587
+ const completedSteps = past.filter(
2588
+ (step) => step.status === "COMPLETED" /* COMPLETED */
2589
+ );
2590
+ const failedSteps = past.filter(
2591
+ (step) => step.status === "FAILED" /* FAILED */
2592
+ );
2593
+ if (newSteps.length > 1) {
2594
+ throw new Error("Multiple NEW steps found for the same step");
2595
+ }
2596
+ if (completedSteps.length > 1) {
2597
+ throw new Error("Multiple completed steps found for the same step");
2598
+ }
2599
+ if (completedSteps.length > 1 && newSteps.length > 1) {
2600
+ throw new Error(
2601
+ "Multiple completed and new steps found for the same step"
2602
+ );
2603
+ }
2604
+ if (completedSteps.length === 1) {
2605
+ return completedSteps[0].value;
2606
+ }
2607
+ if (newSteps.length === 1) {
2608
+ let result = null;
2609
+ await db.updateTable("keel_flow_step").set({
2610
+ startTime: /* @__PURE__ */ new Date()
2611
+ }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
2612
+ try {
2613
+ result = await withTimeout(
2614
+ actualFn(),
2615
+ options.timeoutInMs ?? defaultOpts.timeoutInMs
2616
+ );
2617
+ } catch (e) {
2618
+ await db.updateTable("keel_flow_step").set({
2619
+ status: "FAILED" /* FAILED */,
2620
+ spanId,
2621
+ endTime: /* @__PURE__ */ new Date(),
2622
+ error: e instanceof Error ? e.message : "An error occurred"
2623
+ }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
2624
+ if (failedSteps.length + 1 >= (options.maxRetries ?? defaultOpts.maxRetries)) {
2625
+ throw new ExhuastedRetriesDisrupt();
2626
+ }
2627
+ await db.insertInto("keel_flow_step").values({
2628
+ run_id: runId,
2629
+ name,
2630
+ stage: options.stage,
2631
+ status: "NEW" /* NEW */,
2632
+ type: "FUNCTION" /* FUNCTION */
2633
+ }).returningAll().executeTakeFirst();
2634
+ throw new StepCreatedDisrupt();
2635
+ }
2636
+ await db.updateTable("keel_flow_step").set({
2637
+ status: "COMPLETED" /* COMPLETED */,
2638
+ value: JSON.stringify(result),
2639
+ spanId,
2640
+ endTime: /* @__PURE__ */ new Date()
2641
+ }).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
2642
+ return result;
2643
+ }
2644
+ await db.insertInto("keel_flow_step").values({
2645
+ run_id: runId,
2646
+ name,
2647
+ stage: options.stage,
2648
+ status: "NEW" /* NEW */,
2649
+ type: "FUNCTION" /* FUNCTION */
2650
+ }).returningAll().executeTakeFirst();
2651
+ throw new StepCreatedDisrupt();
2652
+ }, "step"),
2653
+ ui: {
2654
+ page: /* @__PURE__ */ __name(async (name, options) => {
2655
+ const db = useDatabase();
2656
+ let step = await db.selectFrom("keel_flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().executeTakeFirst();
2657
+ if (step && step.status === "COMPLETED" /* COMPLETED */) {
2658
+ return step.value;
2659
+ }
2660
+ if (!step) {
2661
+ step = await db.insertInto("keel_flow_step").values({
2662
+ run_id: runId,
2663
+ name,
2664
+ stage: options.stage,
2665
+ status: "PENDING" /* PENDING */,
2666
+ type: "UI" /* UI */,
2667
+ startTime: /* @__PURE__ */ new Date()
2668
+ }).returningAll().executeTakeFirst();
2669
+ throw new UIRenderDisrupt(step?.id, page(options));
2670
+ }
2671
+ if (!data) {
2672
+ throw new UIRenderDisrupt(step?.id, page(options));
2673
+ }
2674
+ await db.updateTable("keel_flow_step").set({
2675
+ status: "COMPLETED" /* COMPLETED */,
2676
+ value: JSON.stringify(data),
2677
+ spanId,
2678
+ endTime: /* @__PURE__ */ new Date()
2679
+ }).where("id", "=", step.id).returningAll().executeTakeFirst();
2680
+ return data;
2681
+ }, "page"),
2682
+ inputs: {
2683
+ text: textInput,
2684
+ number: numberInput,
2685
+ boolean: booleanInput
2686
+ },
2687
+ display: {
2688
+ divider,
2689
+ markdown,
2690
+ table,
2691
+ header,
2692
+ banner,
2693
+ image,
2694
+ code,
2695
+ grid,
2696
+ list
2697
+ },
2698
+ select: {
2699
+ one: selectOne
2700
+ }
2701
+ }
2702
+ };
2703
+ }
2704
+ __name(createFlowContext, "createFlowContext");
2705
+ function wait(milliseconds) {
2706
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
2707
+ }
2708
+ __name(wait, "wait");
2709
+ function withTimeout(promiseFn, timeout) {
2710
+ return Promise.race([
2711
+ promiseFn,
2712
+ wait(timeout).then(() => {
2713
+ throw new Error(`Step function timed out after ${timeout}ms`);
2714
+ })
2715
+ ]);
2716
+ }
2717
+ __name(withTimeout, "withTimeout");
2718
+
2719
+ // src/handleFlow.js
2720
+ async function handleFlow(request, config) {
2721
+ const activeContext = opentelemetry6.propagation.extract(
2722
+ opentelemetry6.context.active(),
2723
+ request.meta?.tracing
2724
+ );
2725
+ return opentelemetry6.context.with(activeContext, () => {
2726
+ return withSpan(request.method, async (span) => {
2727
+ let db = null;
2728
+ let flowConfig = null;
2729
+ const runId = request.meta?.runId;
2730
+ try {
2731
+ if (!runId) {
2732
+ throw new Error("no runId provided");
2733
+ }
2734
+ const { flows } = config;
2735
+ if (!(request.method in flows)) {
2736
+ const message = `no corresponding flow found for '${request.method}'`;
2737
+ span.setStatus({
2738
+ code: opentelemetry6.SpanStatusCode.ERROR,
2739
+ message
2740
+ });
2741
+ return (0, import_json_rpc_26.createJSONRPCErrorResponse)(
2742
+ request.id,
2743
+ import_json_rpc_26.JSONRPCErrorCode.MethodNotFound,
2744
+ message
2745
+ );
2746
+ }
2747
+ db = createDatabaseClient({
2748
+ connString: request.meta?.secrets?.KEEL_DB_CONN
2749
+ });
2750
+ const flowRun = await db.selectFrom("keel_flow_run").where("id", "=", runId).selectAll().executeTakeFirst();
2751
+ if (!flowRun) {
2752
+ throw new Error("no flow run found");
2753
+ }
2754
+ const ctx = createFlowContext(
2755
+ request.meta.runId,
2756
+ request.meta.data,
2757
+ span.spanContext().spanId
2758
+ );
2759
+ const flowFunction = flows[request.method].fn;
2760
+ flowConfig = flows[request.method].config;
2761
+ await tryExecuteFlow(db, async () => {
2762
+ const inputs = parseInputs(flowRun.input);
2763
+ return flowFunction(ctx, inputs);
2764
+ });
2765
+ return (0, import_json_rpc_26.createJSONRPCSuccessResponse)(request.id, {
2766
+ runId,
2767
+ runCompleted: true,
2768
+ config: flowConfig
2769
+ });
2770
+ } catch (e) {
2771
+ if (e instanceof StepCreatedDisrupt) {
2772
+ return (0, import_json_rpc_26.createJSONRPCSuccessResponse)(request.id, {
2773
+ runId,
2774
+ runCompleted: false,
2775
+ config: flowConfig
2776
+ });
2777
+ }
2778
+ if (e instanceof UIRenderDisrupt) {
2779
+ return (0, import_json_rpc_26.createJSONRPCSuccessResponse)(request.id, {
2780
+ runId,
2781
+ stepId: e.stepId,
2782
+ config: flowConfig,
2783
+ ui: e.contents
2784
+ });
2785
+ }
2786
+ span.recordException(e);
2787
+ span.setStatus({
2788
+ code: opentelemetry6.SpanStatusCode.ERROR,
2789
+ message: e.message
2790
+ });
2791
+ if (e instanceof ExhuastedRetriesDisrupt) {
2792
+ return (0, import_json_rpc_26.createJSONRPCSuccessResponse)(request.id, {
2793
+ runId,
2794
+ runCompleted: true,
2795
+ error: "flow failed due to exhausted step retries",
2796
+ config: flowConfig
2797
+ });
2798
+ }
2799
+ return (0, import_json_rpc_26.createJSONRPCErrorResponse)(
2800
+ request.id,
2801
+ import_json_rpc_26.JSONRPCErrorCode.InternalError,
2802
+ e.message
2803
+ );
2804
+ } finally {
2805
+ if (db) {
2806
+ await db.destroy();
2807
+ }
2808
+ }
2809
+ });
2810
+ });
2811
+ }
2812
+ __name(handleFlow, "handleFlow");
2813
+
2814
+ // src/index.ts
2815
+ var import_ksuid2 = __toESM(require("ksuid"), 1);
2816
+ function ksuid() {
2817
+ return import_ksuid2.default.randomSync().string;
2818
+ }
2819
+ __name(ksuid, "ksuid");
2820
+ // Annotate the CommonJS export names for ESM import in node:
2821
+ 0 && (module.exports = {
2822
+ Duration,
2823
+ ErrorPresets,
2824
+ File,
2825
+ InlineFile,
2826
+ KSUID,
2827
+ ModelAPI,
2828
+ PERMISSION_STATE,
2829
+ Permissions,
2830
+ RequestHeaders,
2831
+ checkBuiltInPermissions,
2832
+ createFlowContext,
2833
+ handleFlow,
2834
+ handleJob,
2835
+ handleRequest,
2836
+ handleRoute,
2837
+ handleSubscriber,
2838
+ ksuid,
2839
+ tracing,
2840
+ useDatabase
2841
+ });
2842
+ //# sourceMappingURL=index.cjs.map