@teamkeel/functions-runtime 0.412.0-next.2 → 0.412.0-next.4

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