@teamkeel/functions-runtime 0.459.0 → 0.460.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,336 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
3
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, { get: all[name], enumerable: true });
6
10
  };
7
11
 
12
+ // src/File.ts
13
+ var File_exports = {};
14
+ __export(File_exports, {
15
+ File: () => File,
16
+ InlineFile: () => InlineFile,
17
+ buildContentDisposition: () => buildContentDisposition,
18
+ deleteStoredFile: () => deleteStoredFile,
19
+ rewriteFilesDomain: () => rewriteFilesDomain
20
+ });
21
+ import {
22
+ S3Client,
23
+ PutObjectCommand,
24
+ GetObjectCommand,
25
+ DeleteObjectCommand
26
+ } from "@aws-sdk/client-s3";
27
+ import { fromEnv } from "@aws-sdk/credential-providers";
28
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
29
+ import KSUID from "ksuid";
30
+ function rewriteFilesDomain(url) {
31
+ const domain = process.env.KEEL_FILES_DOMAIN;
32
+ if (domain) {
33
+ const override = new URL(domain);
34
+ url.protocol = override.protocol;
35
+ url.hostname = override.hostname;
36
+ url.port = override.port;
37
+ const overridePath = override.pathname.replace(/\/+$/, "");
38
+ if (overridePath && overridePath !== "/") {
39
+ url.pathname = overridePath + url.pathname;
40
+ }
41
+ }
42
+ return url;
43
+ }
44
+ function encodeRFC5987(value) {
45
+ return encodeURIComponent(value).replace(
46
+ /['()*]/g,
47
+ (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
48
+ );
49
+ }
50
+ function buildContentDisposition(disposition, filename) {
51
+ const type = disposition === "attachment" ? "attachment" : "inline";
52
+ if (!filename || filename.trim() === "") {
53
+ return type;
54
+ }
55
+ const asciiFallback = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
56
+ return `${type}; filename="${asciiFallback}"; filename*=UTF-8''${encodeRFC5987(
57
+ filename
58
+ )}`;
59
+ }
60
+ async function storeFile(contents, key, filename, contentType, size, expires) {
61
+ if (!s3Client) {
62
+ throw new Error("S3 client is required");
63
+ }
64
+ const params = {
65
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
66
+ Key: "files/" + key,
67
+ Body: contents,
68
+ ContentType: contentType,
69
+ ContentDisposition: `attachment; filename="${encodeURIComponent(
70
+ filename
71
+ )}"`,
72
+ Metadata: {
73
+ filename
74
+ },
75
+ ACL: "private"
76
+ };
77
+ if (expires) {
78
+ if (expires instanceof Date) {
79
+ params.Expires = expires;
80
+ } else {
81
+ console.warn("Invalid expires value. Skipping Expires parameter.");
82
+ }
83
+ }
84
+ const command = new PutObjectCommand(params);
85
+ try {
86
+ await s3Client.send(command);
87
+ } catch (error) {
88
+ console.error("Error uploading file:", error);
89
+ throw error;
90
+ }
91
+ }
92
+ async function deleteStoredFile(key) {
93
+ if (!s3Client) {
94
+ throw new Error("S3 client is required");
95
+ }
96
+ await s3Client.send(
97
+ new DeleteObjectCommand({
98
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
99
+ Key: "files/" + key
100
+ })
101
+ );
102
+ }
103
+ var s3Client, InlineFile, File;
104
+ var init_File = __esm({
105
+ "src/File.ts"() {
106
+ "use strict";
107
+ s3Client = (() => {
108
+ if (!process.env.KEEL_FILES_BUCKET_NAME) {
109
+ return null;
110
+ }
111
+ const endpoint = process.env.KEEL_S3_ENDPOINT;
112
+ if (endpoint) {
113
+ return new S3Client({
114
+ region: process.env.KEEL_REGION,
115
+ credentials: {
116
+ accessKeyId: "keelstorage",
117
+ secretAccessKey: "keelstorage"
118
+ },
119
+ endpoint
120
+ });
121
+ }
122
+ const testEndpoint = process.env.TEST_AWS_ENDPOINT;
123
+ if (testEndpoint) {
124
+ return new S3Client({
125
+ region: process.env.KEEL_REGION,
126
+ credentials: {
127
+ accessKeyId: "test",
128
+ secretAccessKey: "test"
129
+ },
130
+ endpointProvider: /* @__PURE__ */ __name(() => {
131
+ return {
132
+ url: new URL(testEndpoint)
133
+ };
134
+ }, "endpointProvider")
135
+ });
136
+ }
137
+ return new S3Client({
138
+ region: process.env.KEEL_REGION,
139
+ credentials: fromEnv()
140
+ });
141
+ })();
142
+ __name(rewriteFilesDomain, "rewriteFilesDomain");
143
+ __name(encodeRFC5987, "encodeRFC5987");
144
+ __name(buildContentDisposition, "buildContentDisposition");
145
+ InlineFile = class _InlineFile {
146
+ static {
147
+ __name(this, "InlineFile");
148
+ }
149
+ constructor(input) {
150
+ this._filename = input.filename;
151
+ this._contentType = input.contentType;
152
+ this._contents = null;
153
+ }
154
+ static fromDataURL(dataURL) {
155
+ const info = dataURL.split(",")[0].split(":")[1];
156
+ const data = dataURL.split(",")[1];
157
+ const mimeType = info.split(";")[0];
158
+ const name = info.split(";")[1].split("=")[1] || "file";
159
+ const buffer = Buffer.from(data, "base64");
160
+ const file2 = new _InlineFile({ filename: name, contentType: mimeType });
161
+ file2.write(buffer);
162
+ return file2;
163
+ }
164
+ // Gets size of the file's contents in bytes
165
+ get size() {
166
+ if (this._contents) {
167
+ return this._contents.size;
168
+ }
169
+ return 0;
170
+ }
171
+ // Gets the media type of the file contents
172
+ get contentType() {
173
+ return this._contentType;
174
+ }
175
+ // Gets the name of the file
176
+ get filename() {
177
+ return this._filename;
178
+ }
179
+ // Write the files contents from a buffer
180
+ write(buffer) {
181
+ this._contents = new Blob([
182
+ new Uint8Array(
183
+ buffer.buffer,
184
+ buffer.byteOffset,
185
+ buffer.byteLength
186
+ )
187
+ ]);
188
+ }
189
+ // Reads the contents of the file as a buffer
190
+ async read() {
191
+ if (!this._contents) {
192
+ throw new Error("No contents to read");
193
+ }
194
+ const arrayBuffer = await this._contents.arrayBuffer();
195
+ return Buffer.from(arrayBuffer);
196
+ }
197
+ // Persists the file
198
+ async store(expires = null) {
199
+ const content = await this.read();
200
+ const key = KSUID.randomSync().string;
201
+ await storeFile(
202
+ content,
203
+ key,
204
+ this._filename,
205
+ this._contentType,
206
+ this.size,
207
+ expires
208
+ );
209
+ return new File({
210
+ key,
211
+ size: this.size,
212
+ filename: this.filename,
213
+ contentType: this.contentType
214
+ });
215
+ }
216
+ };
217
+ File = class _File extends InlineFile {
218
+ static {
219
+ __name(this, "File");
220
+ }
221
+ constructor(input) {
222
+ super({
223
+ filename: input.filename || "",
224
+ contentType: input.contentType || ""
225
+ });
226
+ this._key = input.key || "";
227
+ this._size = input.size || 0;
228
+ }
229
+ // Creates a new instance from the database record
230
+ static fromDbRecord(input) {
231
+ return new _File({
232
+ key: input.key,
233
+ filename: input.filename,
234
+ size: input.size,
235
+ contentType: input.contentType
236
+ });
237
+ }
238
+ get size() {
239
+ return this._size;
240
+ }
241
+ // Gets the stored key
242
+ get key() {
243
+ return this._key;
244
+ }
245
+ get isPublic() {
246
+ return false;
247
+ }
248
+ async read() {
249
+ if (this._contents) {
250
+ const arrayBuffer = await this._contents.arrayBuffer();
251
+ return Buffer.from(arrayBuffer);
252
+ }
253
+ if (!s3Client) {
254
+ throw new Error("S3 client is required");
255
+ }
256
+ const params = {
257
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
258
+ Key: "files/" + this.key
259
+ };
260
+ const command = new GetObjectCommand(params);
261
+ const response = await s3Client.send(command);
262
+ const blob = await response.Body.transformToByteArray();
263
+ return Buffer.from(blob);
264
+ }
265
+ async store(expires = null) {
266
+ if (this._contents) {
267
+ const contents = await this.read();
268
+ await storeFile(
269
+ contents,
270
+ this.key,
271
+ this.filename,
272
+ this.contentType,
273
+ this.size,
274
+ expires
275
+ );
276
+ }
277
+ return this;
278
+ }
279
+ // Generates a presigned download URL.
280
+ //
281
+ // By default the browser previews the file inline and, when saved, uses the
282
+ // file's own filename. Pass `contentDisposition: "attachment"` to force a
283
+ // download, or `filename` to override the suggested name.
284
+ async getPresignedUrl(options) {
285
+ if (!s3Client) {
286
+ throw new Error("S3 client is required");
287
+ }
288
+ const disposition = options?.contentDisposition ?? "inline";
289
+ const filename = options?.filename ?? this.filename;
290
+ const command = new GetObjectCommand({
291
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
292
+ Key: "files/" + this.key,
293
+ ResponseContentDisposition: buildContentDisposition(
294
+ disposition,
295
+ filename
296
+ )
297
+ });
298
+ const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
299
+ return rewriteFilesDomain(new URL(url));
300
+ }
301
+ // Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
302
+ async getPresignedUploadUrl() {
303
+ if (!s3Client) {
304
+ throw new Error("S3 client is required");
305
+ }
306
+ if (!this.key) {
307
+ this._key = KSUID.randomSync().string;
308
+ }
309
+ const command = new PutObjectCommand({
310
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
311
+ Key: "files/" + this.key
312
+ });
313
+ const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
314
+ return rewriteFilesDomain(new URL(url));
315
+ }
316
+ // Persists the file
317
+ toDbRecord() {
318
+ return {
319
+ key: this.key,
320
+ filename: this.filename,
321
+ contentType: this.contentType,
322
+ size: this.size
323
+ };
324
+ }
325
+ toJSON() {
326
+ return this.toDbRecord();
327
+ }
328
+ };
329
+ __name(storeFile, "storeFile");
330
+ __name(deleteStoredFile, "deleteStoredFile");
331
+ }
332
+ });
333
+
8
334
  // src/ModelAPI.js
9
335
  import { sql as sql3 } from "kysely";
10
336
 
@@ -72,6 +398,9 @@ var AuditContextPlugin = class {
72
398
  const rawNode = sql`set_trace_id(${audit.traceId})`.as(this.traceIdAlias).toOperationNode();
73
399
  returning.selections.push(SelectionNode.create(rawNode));
74
400
  }
401
+ if (returning.selections.length === 0) {
402
+ return { ...args.node };
403
+ }
75
404
  return {
76
405
  ...args.node,
77
406
  returning
@@ -143,6 +472,20 @@ function isRichType(obj) {
143
472
  }
144
473
  __name(isRichType, "isRichType");
145
474
 
475
+ // src/jsonColumnValue.js
476
+ var JsonColumnValue = class {
477
+ static {
478
+ __name(this, "JsonColumnValue");
479
+ }
480
+ constructor(value) {
481
+ this.value = value;
482
+ }
483
+ };
484
+ function parseJsonColumnValue(value) {
485
+ return new JsonColumnValue(JSON.parse(value));
486
+ }
487
+ __name(parseJsonColumnValue, "parseJsonColumnValue");
488
+
146
489
  // src/camelCasePlugin.js
147
490
  var KeelCamelCasePlugin = class {
148
491
  static {
@@ -150,41 +493,77 @@ var KeelCamelCasePlugin = class {
150
493
  }
151
494
  constructor(opt) {
152
495
  this.opt = opt;
496
+ this.auditQueryIds = /* @__PURE__ */ new WeakSet();
153
497
  this.CamelCasePlugin = new CamelCasePlugin({
154
498
  ...opt,
155
499
  underscoreBeforeDigits: true
156
500
  });
157
501
  }
158
502
  transformQuery(args) {
503
+ if (args.queryId && referencesTable(args.node, "keel_audit")) {
504
+ this.auditQueryIds.add(args.queryId);
505
+ }
159
506
  return this.CamelCasePlugin.transformQuery(args);
160
507
  }
161
508
  async transformResult(args) {
162
509
  if (args.result.rows && Array.isArray(args.result.rows)) {
510
+ const mapAuditJson = args.queryId && this.auditQueryIds.has(args.queryId);
163
511
  return {
164
512
  ...args.result,
165
- rows: args.result.rows.map((row) => this.mapRow(row))
513
+ rows: args.result.rows.map((row) => this.mapRow(row, { mapAuditJson }))
166
514
  };
167
515
  }
168
516
  return args.result;
169
517
  }
170
- mapRow(row) {
518
+ mapRow(row, context7 = {}) {
171
519
  return Object.keys(row).reduce((obj, key) => {
172
520
  if (key.endsWith("__sequence")) {
173
521
  return obj;
174
522
  }
175
523
  let value = row[key];
524
+ if (value instanceof JsonColumnValue) {
525
+ value = shouldMapJsonColumn(row, key, context7) && canMap(value.value, this.opt) ? this.mapRow(value.value, context7) : value.value;
526
+ obj[this.CamelCasePlugin.camelCase(key)] = value;
527
+ return obj;
528
+ }
176
529
  if (Array.isArray(value)) {
177
530
  value = value.map(
178
- (it) => canMap(it, this.opt) ? this.mapRow(it) : it
531
+ (it) => canMap(it, this.opt) ? this.mapRow(it, context7) : it
179
532
  );
180
533
  } else if (canMap(value, this.opt)) {
181
- value = this.mapRow(value);
534
+ value = this.mapRow(value, context7);
182
535
  }
183
536
  obj[this.CamelCasePlugin.camelCase(key)] = value;
184
537
  return obj;
185
538
  }, {});
186
539
  }
187
540
  };
541
+ function shouldMapJsonColumn(row, key, context7) {
542
+ return context7.mapAuditJson && key === "data" && "id" in row && "table_name" in row && "op" in row && "identity_id" in row && "trace_id" in row && "created_at" in row && "event_processed_at" in row;
543
+ }
544
+ __name(shouldMapJsonColumn, "shouldMapJsonColumn");
545
+ function referencesTable(node, tableName) {
546
+ if (!node || typeof node !== "object") {
547
+ return false;
548
+ }
549
+ if (node.kind === "IdentifierNode" && (node.name === tableName || node.name === "keelAudit")) {
550
+ return true;
551
+ }
552
+ if (node.kind === "RawNode" && Array.isArray(node.sqlFragments) && node.sqlFragments.some(
553
+ (fragment) => new RegExp(`(^|[^A-Za-z0-9_])${tableName}($|[^A-Za-z0-9_])`, "i").test(
554
+ fragment
555
+ )
556
+ )) {
557
+ return true;
558
+ }
559
+ return Object.values(node).some((value) => {
560
+ if (Array.isArray(value)) {
561
+ return value.some((item) => referencesTable(item, tableName));
562
+ }
563
+ return referencesTable(value, tableName);
564
+ });
565
+ }
566
+ __name(referencesTable, "referencesTable");
188
567
  function canMap(obj, opt) {
189
568
  return isPlainObject(obj) && !opt?.maintainNestedObjectKeys && !isRichType(obj);
190
569
  }
@@ -441,20 +820,44 @@ var KEEL_INTERNAL_CHILDREN = "includeChildrenSpans";
441
820
  import WebSocket from "ws";
442
821
  import { readFileSync } from "fs";
443
822
  var dbInstance = new AsyncLocalStorage2();
823
+ var fileCleanupStore = new AsyncLocalStorage2();
824
+ function deferFileDeletion(key) {
825
+ fileCleanupStore.getStore()?.add(key);
826
+ }
827
+ __name(deferFileDeletion, "deferFileDeletion");
828
+ async function flushFileDeletions(keys) {
829
+ if (keys.size === 0) {
830
+ return;
831
+ }
832
+ const { deleteStoredFile: deleteStoredFile2 } = await Promise.resolve().then(() => (init_File(), File_exports));
833
+ for (const key of keys) {
834
+ try {
835
+ await deleteStoredFile2(key);
836
+ } catch (e) {
837
+ console.error("failed to delete orphaned file from storage", e);
838
+ }
839
+ }
840
+ }
841
+ __name(flushFileDeletions, "flushFileDeletions");
444
842
  var vitestDb = null;
445
843
  async function withDatabase(db, requiresTransaction, cb) {
844
+ const pending = /* @__PURE__ */ new Set();
446
845
  if (requiresTransaction) {
447
- return db.transaction().execute(async (transaction) => {
448
- return dbInstance.run(transaction, async () => {
449
- return cb({ transaction });
846
+ const result2 = await db.transaction().execute(async (transaction) => {
847
+ return dbInstance.run(transaction, () => {
848
+ return fileCleanupStore.run(pending, () => cb({ transaction }));
450
849
  });
451
850
  });
851
+ await flushFileDeletions(pending);
852
+ return result2;
452
853
  }
453
- return db.connection().execute(async (sDb) => {
454
- return dbInstance.run(sDb, async () => {
455
- return cb({ sDb });
854
+ const result = await db.connection().execute(async (sDb) => {
855
+ return dbInstance.run(sDb, () => {
856
+ return fileCleanupStore.run(pending, () => cb({ sDb }));
456
857
  });
457
858
  });
859
+ await flushFileDeletions(pending);
860
+ return result;
458
861
  }
459
862
  __name(withDatabase, "withDatabase");
460
863
  function useDatabase() {
@@ -561,6 +964,8 @@ function getDialect(connString) {
561
964
  pgTypes.builtins.INTERVAL,
562
965
  (val) => new Duration(val)
563
966
  );
967
+ pgTypes.setTypeParser(pgTypes.builtins.JSON, parseJsonColumnValue);
968
+ pgTypes.setTypeParser(pgTypes.builtins.JSONB, parseJsonColumnValue);
564
969
  const poolConfig = {
565
970
  Client: InstrumentedClient,
566
971
  // Increased idle time before closing a connection in the local pool (from 10s default).
@@ -595,6 +1000,8 @@ function getDialect(connString) {
595
1000
  pgTypes.builtins.INTERVAL,
596
1001
  (val) => new Duration(val)
597
1002
  );
1003
+ neon.types.setTypeParser(pgTypes.builtins.JSON, parseJsonColumnValue);
1004
+ neon.types.setTypeParser(pgTypes.builtins.JSONB, parseJsonColumnValue);
598
1005
  neon.neonConfig.webSocketConstructor = WebSocket;
599
1006
  const pool = new InstrumentedNeonServerlessPool({
600
1007
  // If connString is not passed fall back to reading from env var
@@ -629,275 +1036,8 @@ function getDialect(connString) {
629
1036
  }
630
1037
  __name(getDialect, "getDialect");
631
1038
 
632
- // src/File.ts
633
- import {
634
- S3Client,
635
- PutObjectCommand,
636
- GetObjectCommand
637
- } from "@aws-sdk/client-s3";
638
- import { fromEnv } from "@aws-sdk/credential-providers";
639
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
640
- import KSUID from "ksuid";
641
- var s3Client = (() => {
642
- if (!process.env.KEEL_FILES_BUCKET_NAME) {
643
- return null;
644
- }
645
- const endpoint = process.env.KEEL_S3_ENDPOINT;
646
- if (endpoint) {
647
- return new S3Client({
648
- region: process.env.KEEL_REGION,
649
- credentials: {
650
- accessKeyId: "keelstorage",
651
- secretAccessKey: "keelstorage"
652
- },
653
- endpoint
654
- });
655
- }
656
- const testEndpoint = process.env.TEST_AWS_ENDPOINT;
657
- if (testEndpoint) {
658
- return new S3Client({
659
- region: process.env.KEEL_REGION,
660
- credentials: {
661
- accessKeyId: "test",
662
- secretAccessKey: "test"
663
- },
664
- endpointProvider: /* @__PURE__ */ __name(() => {
665
- return {
666
- url: new URL(testEndpoint)
667
- };
668
- }, "endpointProvider")
669
- });
670
- }
671
- return new S3Client({
672
- region: process.env.KEEL_REGION,
673
- credentials: fromEnv()
674
- });
675
- })();
676
- function rewriteFilesDomain(url) {
677
- const domain = process.env.KEEL_FILES_DOMAIN;
678
- if (domain) {
679
- const override = new URL(domain);
680
- url.protocol = override.protocol;
681
- url.hostname = override.hostname;
682
- url.port = override.port;
683
- const overridePath = override.pathname.replace(/\/+$/, "");
684
- if (overridePath && overridePath !== "/") {
685
- url.pathname = overridePath + url.pathname;
686
- }
687
- }
688
- return url;
689
- }
690
- __name(rewriteFilesDomain, "rewriteFilesDomain");
691
- var InlineFile = class _InlineFile {
692
- static {
693
- __name(this, "InlineFile");
694
- }
695
- constructor(input) {
696
- this._filename = input.filename;
697
- this._contentType = input.contentType;
698
- this._contents = null;
699
- }
700
- static fromDataURL(dataURL) {
701
- const info = dataURL.split(",")[0].split(":")[1];
702
- const data = dataURL.split(",")[1];
703
- const mimeType = info.split(";")[0];
704
- const name = info.split(";")[1].split("=")[1] || "file";
705
- const buffer = Buffer.from(data, "base64");
706
- const file2 = new _InlineFile({ filename: name, contentType: mimeType });
707
- file2.write(buffer);
708
- return file2;
709
- }
710
- // Gets size of the file's contents in bytes
711
- get size() {
712
- if (this._contents) {
713
- return this._contents.size;
714
- }
715
- return 0;
716
- }
717
- // Gets the media type of the file contents
718
- get contentType() {
719
- return this._contentType;
720
- }
721
- // Gets the name of the file
722
- get filename() {
723
- return this._filename;
724
- }
725
- // Write the files contents from a buffer
726
- write(buffer) {
727
- this._contents = new Blob([
728
- new Uint8Array(
729
- buffer.buffer,
730
- buffer.byteOffset,
731
- buffer.byteLength
732
- )
733
- ]);
734
- }
735
- // Reads the contents of the file as a buffer
736
- async read() {
737
- if (!this._contents) {
738
- throw new Error("No contents to read");
739
- }
740
- const arrayBuffer = await this._contents.arrayBuffer();
741
- return Buffer.from(arrayBuffer);
742
- }
743
- // Persists the file
744
- async store(expires = null) {
745
- const content = await this.read();
746
- const key = KSUID.randomSync().string;
747
- await storeFile(
748
- content,
749
- key,
750
- this._filename,
751
- this._contentType,
752
- this.size,
753
- expires
754
- );
755
- return new File({
756
- key,
757
- size: this.size,
758
- filename: this.filename,
759
- contentType: this.contentType
760
- });
761
- }
762
- };
763
- var File = class _File extends InlineFile {
764
- static {
765
- __name(this, "File");
766
- }
767
- constructor(input) {
768
- super({
769
- filename: input.filename || "",
770
- contentType: input.contentType || ""
771
- });
772
- this._key = input.key || "";
773
- this._size = input.size || 0;
774
- }
775
- // Creates a new instance from the database record
776
- static fromDbRecord(input) {
777
- return new _File({
778
- key: input.key,
779
- filename: input.filename,
780
- size: input.size,
781
- contentType: input.contentType
782
- });
783
- }
784
- get size() {
785
- return this._size;
786
- }
787
- // Gets the stored key
788
- get key() {
789
- return this._key;
790
- }
791
- get isPublic() {
792
- return false;
793
- }
794
- async read() {
795
- if (this._contents) {
796
- const arrayBuffer = await this._contents.arrayBuffer();
797
- return Buffer.from(arrayBuffer);
798
- }
799
- if (!s3Client) {
800
- throw new Error("S3 client is required");
801
- }
802
- const params = {
803
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
804
- Key: "files/" + this.key
805
- };
806
- const command = new GetObjectCommand(params);
807
- const response = await s3Client.send(command);
808
- const blob = await response.Body.transformToByteArray();
809
- return Buffer.from(blob);
810
- }
811
- async store(expires = null) {
812
- if (this._contents) {
813
- const contents = await this.read();
814
- await storeFile(
815
- contents,
816
- this.key,
817
- this.filename,
818
- this.contentType,
819
- this.size,
820
- expires
821
- );
822
- }
823
- return this;
824
- }
825
- // Generates a presigned download URL
826
- async getPresignedUrl() {
827
- if (!s3Client) {
828
- throw new Error("S3 client is required");
829
- }
830
- const command = new GetObjectCommand({
831
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
832
- Key: "files/" + this.key,
833
- ResponseContentDisposition: "inline"
834
- });
835
- const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
836
- return rewriteFilesDomain(new URL(url));
837
- }
838
- // Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
839
- async getPresignedUploadUrl() {
840
- if (!s3Client) {
841
- throw new Error("S3 client is required");
842
- }
843
- if (!this.key) {
844
- this._key = KSUID.randomSync().string;
845
- }
846
- const command = new PutObjectCommand({
847
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
848
- Key: "files/" + this.key
849
- });
850
- const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
851
- return rewriteFilesDomain(new URL(url));
852
- }
853
- // Persists the file
854
- toDbRecord() {
855
- return {
856
- key: this.key,
857
- filename: this.filename,
858
- contentType: this.contentType,
859
- size: this.size
860
- };
861
- }
862
- toJSON() {
863
- return this.toDbRecord();
864
- }
865
- };
866
- async function storeFile(contents, key, filename, contentType, size, expires) {
867
- if (!s3Client) {
868
- throw new Error("S3 client is required");
869
- }
870
- const params = {
871
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
872
- Key: "files/" + key,
873
- Body: contents,
874
- ContentType: contentType,
875
- ContentDisposition: `attachment; filename="${encodeURIComponent(
876
- filename
877
- )}"`,
878
- Metadata: {
879
- filename
880
- },
881
- ACL: "private"
882
- };
883
- if (expires) {
884
- if (expires instanceof Date) {
885
- params.Expires = expires;
886
- } else {
887
- console.warn("Invalid expires value. Skipping Expires parameter.");
888
- }
889
- }
890
- const command = new PutObjectCommand(params);
891
- try {
892
- await s3Client.send(command);
893
- } catch (error) {
894
- console.error("Error uploading file:", error);
895
- throw error;
896
- }
897
- }
898
- __name(storeFile, "storeFile");
899
-
900
1039
  // src/parsing.js
1040
+ init_File();
901
1041
  var dateFormat = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
902
1042
  function parseInputs(inputs) {
903
1043
  if (inputs != null && typeof inputs === "object") {
@@ -1833,6 +1973,7 @@ var QueryBuilder = class _QueryBuilder {
1833
1973
  };
1834
1974
 
1835
1975
  // src/ModelAPI.js
1976
+ init_File();
1836
1977
  var ModelAPI = class {
1837
1978
  static {
1838
1979
  __name(this, "ModelAPI");
@@ -1842,9 +1983,10 @@ var ModelAPI = class {
1842
1983
  * @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
1843
1984
  * @param {TableConfigMap} tableConfigMap
1844
1985
  */
1845
- constructor(tableName, _, tableConfigMap = {}) {
1986
+ constructor(tableName, _, tableConfigMap = {}, fileFieldsMap = {}) {
1846
1987
  this._tableName = tableName;
1847
1988
  this._tableConfigMap = tableConfigMap;
1989
+ this._fileFields = fileFieldsMap[tableName] || {};
1848
1990
  this._modelName = upperCamelCase(this._tableName);
1849
1991
  }
1850
1992
  async create(values) {
@@ -1914,6 +2056,10 @@ var ModelAPI = class {
1914
2056
  const name = spanNameForModelAPI(this._modelName, "update");
1915
2057
  const db = useDatabase();
1916
2058
  return withSpan(name, async (span) => {
2059
+ const fileColumns = Object.keys(values || {}).filter(
2060
+ (k) => k in this._fileFields
2061
+ );
2062
+ const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
1917
2063
  let builder = db.updateTable(this._tableName).returningAll();
1918
2064
  const keys = values ? Object.keys(values) : [];
1919
2065
  const row = {};
@@ -1952,7 +2098,9 @@ var ModelAPI = class {
1952
2098
  span.setAttribute("sql", builder.compile().sql);
1953
2099
  try {
1954
2100
  const row2 = await builder.executeTakeFirstOrThrow();
1955
- return transformRichDataTypes(camelCaseObject(row2));
2101
+ const result = transformRichDataTypes(camelCaseObject(row2));
2102
+ this._deferReplacedFiles(existingFileRows, fileColumns, result);
2103
+ return result;
1956
2104
  } catch (e) {
1957
2105
  throw new DatabaseError(e);
1958
2106
  }
@@ -1962,12 +2110,15 @@ var ModelAPI = class {
1962
2110
  const name = spanNameForModelAPI(this._modelName, "delete");
1963
2111
  const db = useDatabase();
1964
2112
  return withSpan(name, async (span) => {
2113
+ const fileColumns = Object.keys(this._fileFields);
2114
+ const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
1965
2115
  let builder = db.deleteFrom(this._tableName).returning(["id"]);
1966
2116
  const context7 = new QueryContext([this._tableName], this._tableConfigMap);
1967
2117
  builder = applyWhereConditions(context7, builder, where);
1968
2118
  span.setAttribute("sql", builder.compile().sql);
1969
2119
  try {
1970
2120
  const row = await builder.executeTakeFirstOrThrow();
2121
+ this._deferReplacedFiles(existingFileRows, fileColumns, null);
1971
2122
  return row.id;
1972
2123
  } catch (e) {
1973
2124
  throw new DatabaseError(e);
@@ -1982,6 +2133,30 @@ var ModelAPI = class {
1982
2133
  builder = applyWhereConditions(context7, builder, where);
1983
2134
  return new QueryBuilder(this._tableName, context7, builder);
1984
2135
  }
2136
+ // Reads the current file-column values for rows matched by `where`.
2137
+ async _selectExistingFileValues(where, fileColumns) {
2138
+ if (fileColumns.length === 0) {
2139
+ return [];
2140
+ }
2141
+ const db = useDatabase();
2142
+ let builder = db.selectFrom(this._tableName).selectAll(this._tableName);
2143
+ const context7 = new QueryContext([this._tableName], this._tableConfigMap);
2144
+ builder = applyWhereConditions(context7, builder, where);
2145
+ const rows = await builder.execute();
2146
+ return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
2147
+ }
2148
+ // Defers deletion of every old file key in existingRows that is not still
2149
+ // referenced by newRow.
2150
+ _deferReplacedFiles(existingRows, fileColumns, newRow) {
2151
+ const retained = new Set(
2152
+ collectFileKeys(newRow ? [newRow] : [], fileColumns)
2153
+ );
2154
+ for (const key of collectFileKeys(existingRows, fileColumns)) {
2155
+ if (!retained.has(key)) {
2156
+ deferFileDeletion(key);
2157
+ }
2158
+ }
2159
+ }
1985
2160
  };
1986
2161
  async function create(conn, tableName, tableConfigs, values) {
1987
2162
  try {
@@ -2091,6 +2266,23 @@ async function create(conn, tableName, tableConfigs, values) {
2091
2266
  }
2092
2267
  }
2093
2268
  __name(create, "create");
2269
+ function collectFileKeys(rows, fileColumns) {
2270
+ const keys = [];
2271
+ for (const row of rows || []) {
2272
+ if (!row) continue;
2273
+ for (const col of fileColumns) {
2274
+ const v = row[col];
2275
+ if (!v) continue;
2276
+ if (Array.isArray(v)) {
2277
+ for (const f of v) if (f?.key) keys.push(f.key);
2278
+ } else if (v.key) {
2279
+ keys.push(v.key);
2280
+ }
2281
+ }
2282
+ }
2283
+ return keys;
2284
+ }
2285
+ __name(collectFileKeys, "collectFileKeys");
2094
2286
 
2095
2287
  // src/TaskAPI.js
2096
2288
  import jwt from "jsonwebtoken";
@@ -2762,6 +2954,61 @@ var FlowsAPI = class _FlowsAPI {
2762
2954
  }
2763
2955
  };
2764
2956
 
2957
+ // src/integrationServer.js
2958
+ import jwt3 from "jsonwebtoken";
2959
+ function buildHeaders3(identity) {
2960
+ const headers = { "Content-Type": "application/json" };
2961
+ const base64pk = process.env.KEEL_PRIVATE_KEY;
2962
+ if (!base64pk) {
2963
+ throw new Error(
2964
+ "KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
2965
+ );
2966
+ }
2967
+ const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
2968
+ const subject = identity && identity.id ? identity.id : "integration-proxy";
2969
+ headers["Authorization"] = "Bearer " + jwt3.sign({}, privateKey, {
2970
+ algorithm: "RS256",
2971
+ expiresIn: 60 * 60 * 24,
2972
+ subject,
2973
+ // Scope the token to the integration proxy so it can't be mistaken for an ordinary user
2974
+ // access token; the runtime proxy verifies this audience before injecting credentials.
2975
+ audience: "integration-proxy",
2976
+ issuer: "https://keel.so"
2977
+ });
2978
+ return headers;
2979
+ }
2980
+ __name(buildHeaders3, "buildHeaders");
2981
+ function getApiUrl3() {
2982
+ const apiUrl = process.env.KEEL_API_URL;
2983
+ if (!apiUrl) {
2984
+ throw new Error("KEEL_API_URL environment variable is not set");
2985
+ }
2986
+ return apiUrl;
2987
+ }
2988
+ __name(getApiUrl3, "getApiUrl");
2989
+ function createIntegrationServer(name, identity) {
2990
+ return {
2991
+ do: /* @__PURE__ */ __name(async (request) => {
2992
+ const url = `${getApiUrl3()}/integrations/${encodeURIComponent(
2993
+ name
2994
+ )}/proxy`;
2995
+ const response = await fetch(url, {
2996
+ method: "POST",
2997
+ headers: buildHeaders3(identity ?? null),
2998
+ body: JSON.stringify(request ?? {})
2999
+ });
3000
+ if (!response.ok) {
3001
+ const text = await response.text();
3002
+ throw new Error(
3003
+ `integration "${name}" proxy request failed (${response.status}): ${text}`
3004
+ );
3005
+ }
3006
+ return await response.json();
3007
+ }, "do")
3008
+ };
3009
+ }
3010
+ __name(createIntegrationServer, "createIntegrationServer");
3011
+
2765
3012
  // src/RequestHeaders.ts
2766
3013
  var RequestHeaders = class {
2767
3014
  /**
@@ -3530,12 +3777,29 @@ async function applyElementGetData(content, data) {
3530
3777
  return data;
3531
3778
  }
3532
3779
  __name(applyElementGetData, "applyElementGetData");
3533
- async function callbackFn(elements, elementName, callbackName, data) {
3534
- const element = elements.find(
3535
- (el) => el.uiConfig && el.uiConfig.name === elementName
3780
+ var ITERATOR_SCOPED_ELEMENT = /^([^[]+)\[(\d+)\]:(.+)$/;
3781
+ async function callbackFn(elements, elementPath, callbackName, data) {
3782
+ const scoped = ITERATOR_SCOPED_ELEMENT.exec(elementPath);
3783
+ let searchScope = elements;
3784
+ let elementName = elementPath;
3785
+ let iteratorName = null;
3786
+ if (scoped) {
3787
+ iteratorName = scoped[1];
3788
+ elementName = scoped[3];
3789
+ const iter = elements.find(
3790
+ (el) => el?.uiConfig?.__type === "ui.iterator" && el.uiConfig.name === iteratorName
3791
+ );
3792
+ if (!iter) {
3793
+ throw new Error(`Iterator with name ${iteratorName} not found`);
3794
+ }
3795
+ searchScope = iter.uiConfig.content;
3796
+ }
3797
+ const element = searchScope.find(
3798
+ (el) => el?.uiConfig && el.uiConfig.name === elementName
3536
3799
  );
3537
3800
  if (!element) {
3538
- throw new Error(`Element with name ${elementName} not found`);
3801
+ const where = iteratorName ? ` in iterator ${iteratorName}` : "";
3802
+ throw new Error(`Element with name ${elementName} not found${where}`);
3539
3803
  }
3540
3804
  const cb = element[callbackName];
3541
3805
  if (typeof cb !== "function") {
@@ -3962,6 +4226,7 @@ var datePickerInput = /* @__PURE__ */ __name((name, options) => {
3962
4226
  }, "datePickerInput");
3963
4227
 
3964
4228
  // src/flows/ui/elements/input/file.ts
4229
+ init_File();
3965
4230
  var fileInput = /* @__PURE__ */ __name((name, options) => {
3966
4231
  return {
3967
4232
  __type: "input",
@@ -3987,6 +4252,7 @@ var fileInput = /* @__PURE__ */ __name((name, options) => {
3987
4252
  }, "fileInput");
3988
4253
 
3989
4254
  // src/flows/ui/elements/input/imageCapture.ts
4255
+ init_File();
3990
4256
  var isMultiOptions = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiOptions");
3991
4257
  function validateEntry(entry, requireCaption, context7) {
3992
4258
  if (!entry?.file?.key) {
@@ -4482,7 +4748,7 @@ var defaultOpts = {
4482
4748
  async function insertNewStep(db, runId, name, stage) {
4483
4749
  await db.transaction().execute(async (trx) => {
4484
4750
  await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
4485
- const existing = await trx.selectFrom("keel.flow_step").select("id").where("run_id", "=", runId).where("name", "=", name).where("status", "=", "NEW" /* NEW */).executeTakeFirst();
4751
+ const existing = await trx.selectFrom("keel.flow_step").select("id").where("run_id", "=", runId).where("name", "=", name).where("status", "in", ["NEW" /* NEW */, "RUNNING" /* RUNNING */]).executeTakeFirst();
4486
4752
  if (existing) {
4487
4753
  return;
4488
4754
  }
@@ -4505,6 +4771,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4505
4771
  now: ctx.now,
4506
4772
  secrets: ctx.secrets,
4507
4773
  trace: ctx.trace,
4774
+ module: ctx.module,
4508
4775
  complete: /* @__PURE__ */ __name((options) => {
4509
4776
  return {
4510
4777
  __type: "ui.complete",
@@ -4567,6 +4834,11 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4567
4834
  const raw = completedSteps[0].valueRaw;
4568
4835
  return raw == null ? void 0 : JSON.parse(raw);
4569
4836
  }
4837
+ if (runningSteps.length >= 1) {
4838
+ span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4839
+ span.setAttribute("step.status", "RUNNING" /* RUNNING */);
4840
+ throw new StepCreatedDisrupt();
4841
+ }
4570
4842
  if (newSteps.length === 1) {
4571
4843
  let result = null;
4572
4844
  const claimed = await db.updateTable("keel.flow_step").set({
@@ -4622,11 +4894,6 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
4622
4894
  span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
4623
4895
  return result;
4624
4896
  }
4625
- if (runningSteps.length >= 1) {
4626
- span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4627
- span.setAttribute("step.status", "RUNNING" /* RUNNING */);
4628
- throw new StepCreatedDisrupt();
4629
- }
4630
4897
  await insertNewStep(db, runId, name, options.stage);
4631
4898
  span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
4632
4899
  span.setAttribute("step.status", "NEW" /* NEW */);
@@ -5006,7 +5273,183 @@ async function handleFlow(request, config) {
5006
5273
  __name(handleFlow, "handleFlow");
5007
5274
 
5008
5275
  // src/index.ts
5276
+ import KSUID4 from "ksuid";
5277
+ init_File();
5278
+
5279
+ // src/notifications/email-template.tsx
5280
+ import {
5281
+ Html,
5282
+ Head,
5283
+ Body,
5284
+ Container,
5285
+ Heading,
5286
+ Text,
5287
+ Button,
5288
+ Section
5289
+ } from "@react-email/components";
5290
+ import { render } from "@react-email/render";
5291
+ import { jsx, jsxs } from "react/jsx-runtime";
5292
+ function renderBody(text) {
5293
+ return text.split("\n").flatMap((line, i) => i === 0 ? [line] : [/* @__PURE__ */ jsx("br", {}, `br-${i}`), line]);
5294
+ }
5295
+ __name(renderBody, "renderBody");
5296
+ var StockEmailTemplate = /* @__PURE__ */ __name(({ content }) => /* @__PURE__ */ jsxs(Html, { children: [
5297
+ /* @__PURE__ */ jsx(Head, {}),
5298
+ /* @__PURE__ */ jsx(
5299
+ Body,
5300
+ {
5301
+ style: {
5302
+ backgroundColor: "#f6f6f6",
5303
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
5304
+ },
5305
+ children: /* @__PURE__ */ jsxs(
5306
+ Container,
5307
+ {
5308
+ style: {
5309
+ backgroundColor: "#ffffff",
5310
+ margin: "40px auto",
5311
+ padding: "40px",
5312
+ maxWidth: "560px",
5313
+ borderRadius: "8px"
5314
+ },
5315
+ children: [
5316
+ content.title && /* @__PURE__ */ jsx(
5317
+ Heading,
5318
+ {
5319
+ style: {
5320
+ fontSize: "22px",
5321
+ fontWeight: 600,
5322
+ color: "#111827",
5323
+ margin: "0 0 16px"
5324
+ },
5325
+ children: content.title
5326
+ }
5327
+ ),
5328
+ /* @__PURE__ */ jsx(
5329
+ Text,
5330
+ {
5331
+ style: {
5332
+ fontSize: "15px",
5333
+ lineHeight: "1.6",
5334
+ color: "#374151",
5335
+ margin: "0 0 24px"
5336
+ },
5337
+ children: renderBody(content.body)
5338
+ }
5339
+ ),
5340
+ (content.actions ?? []).length > 0 && /* @__PURE__ */ jsx(Section, { style: { margin: "0 0 12px" }, children: content.actions.map((action, i) => /* @__PURE__ */ jsx(
5341
+ Button,
5342
+ {
5343
+ href: action.url,
5344
+ style: {
5345
+ backgroundColor: "#111827",
5346
+ color: "#ffffff",
5347
+ padding: "12px 24px",
5348
+ borderRadius: "6px",
5349
+ fontSize: "14px",
5350
+ fontWeight: 600,
5351
+ textDecoration: "none",
5352
+ display: "inline-block",
5353
+ // Lay buttons out side by side; space between adjacent buttons.
5354
+ marginRight: i < content.actions.length - 1 ? "12px" : "0"
5355
+ },
5356
+ children: action.label
5357
+ },
5358
+ i
5359
+ )) })
5360
+ ]
5361
+ }
5362
+ )
5363
+ }
5364
+ )
5365
+ ] }), "StockEmailTemplate");
5366
+ async function renderEmailHtml(content) {
5367
+ return render(/* @__PURE__ */ jsx(StockEmailTemplate, { content }), { pretty: false });
5368
+ }
5369
+ __name(renderEmailHtml, "renderEmailHtml");
5370
+
5371
+ // src/notifications/notify.ts
5009
5372
  import KSUID3 from "ksuid";
5373
+ function getApiUrl4() {
5374
+ const apiUrl = process.env.KEEL_API_URL;
5375
+ if (!apiUrl) {
5376
+ throw new Error("KEEL_API_URL environment variable is not set");
5377
+ }
5378
+ return apiUrl;
5379
+ }
5380
+ __name(getApiUrl4, "getApiUrl");
5381
+ function toArray(v) {
5382
+ if (v === void 0) return [];
5383
+ return Array.isArray(v) ? v : [v];
5384
+ }
5385
+ __name(toArray, "toArray");
5386
+ function normaliseGroup(group) {
5387
+ if (!group) return [];
5388
+ const refs = [];
5389
+ for (const email of toArray(group.emails)) {
5390
+ refs.push({ kind: "email", value: email });
5391
+ }
5392
+ for (const user of toArray(group.users)) {
5393
+ refs.push({
5394
+ kind: "user",
5395
+ value: typeof user === "string" ? user : user.id
5396
+ });
5397
+ }
5398
+ for (const identity of toArray(group.identities)) {
5399
+ refs.push({
5400
+ kind: "identity",
5401
+ value: typeof identity === "string" ? identity : identity.id
5402
+ });
5403
+ }
5404
+ for (const team of toArray(group.teams)) {
5405
+ refs.push({ kind: "team", value: team });
5406
+ }
5407
+ return refs;
5408
+ }
5409
+ __name(normaliseGroup, "normaliseGroup");
5410
+ async function notifyEmail(input) {
5411
+ return withSpan("notify.email", async () => {
5412
+ const id = KSUID3.randomSync().string;
5413
+ let rendered;
5414
+ let content;
5415
+ if (typeof input.content === "string") {
5416
+ rendered = input.content;
5417
+ content = void 0;
5418
+ } else {
5419
+ rendered = await renderEmailHtml(input.content);
5420
+ content = input.content;
5421
+ }
5422
+ const body = {
5423
+ id,
5424
+ subject: input.subject,
5425
+ rendered,
5426
+ content,
5427
+ recipients: {
5428
+ to: normaliseGroup(input.recipients.to),
5429
+ cc: normaliseGroup(input.recipients.cc),
5430
+ bcc: normaliseGroup(input.recipients.bcc)
5431
+ }
5432
+ };
5433
+ const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
5434
+ method: "POST",
5435
+ headers: { "Content-Type": "application/json" },
5436
+ body: JSON.stringify(body)
5437
+ });
5438
+ if (!response.ok) {
5439
+ const errorBody = await response.json().catch(() => ({}));
5440
+ throw new Error(
5441
+ `Failed to send notification: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
5442
+ );
5443
+ }
5444
+ await response.body?.cancel();
5445
+ return id;
5446
+ });
5447
+ }
5448
+ __name(notifyEmail, "notifyEmail");
5449
+ function createNotifier() {
5450
+ return { email: notifyEmail };
5451
+ }
5452
+ __name(createNotifier, "createNotifier");
5010
5453
 
5011
5454
  // src/experimental.ts
5012
5455
  var experimental_exports = {};
@@ -5371,16 +5814,17 @@ __name(LlmFlowStep, "LlmFlowStep");
5371
5814
  import { z } from "zod";
5372
5815
  var createTraceAPI2 = createTraceAPI;
5373
5816
  function ksuid() {
5374
- return KSUID3.randomSync().string;
5817
+ return KSUID4.randomSync().string;
5375
5818
  }
5376
5819
  __name(ksuid, "ksuid");
5820
+ var notify = createNotifier();
5377
5821
  export {
5378
5822
  Duration,
5379
5823
  ErrorPresets,
5380
5824
  File,
5381
5825
  FlowsAPI,
5382
5826
  InlineFile,
5383
- KSUID3 as KSUID,
5827
+ KSUID4 as KSUID,
5384
5828
  ModelAPI,
5385
5829
  NonRetriableError,
5386
5830
  PERMISSION_STATE,
@@ -5395,6 +5839,8 @@ export {
5395
5839
  TaskAPI,
5396
5840
  checkBuiltInPermissions,
5397
5841
  createFlowContext,
5842
+ createIntegrationServer,
5843
+ createNotifier,
5398
5844
  createTraceAPI2 as createTraceAPI,
5399
5845
  experimental_exports as experimental,
5400
5846
  handleFlow,
@@ -5402,7 +5848,9 @@ export {
5402
5848
  handleRequest,
5403
5849
  handleRoute,
5404
5850
  handleSubscriber,
5851
+ insertNewStep,
5405
5852
  ksuid,
5853
+ notify,
5406
5854
  tracing_exports as tracing,
5407
5855
  useDatabase,
5408
5856
  z