@teamkeel/functions-runtime 0.459.0 → 0.460.0
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 +650 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -9
- package/dist/index.d.ts +83 -9
- package/dist/index.js +661 -284
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
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
|
|
@@ -441,20 +770,44 @@ var KEEL_INTERNAL_CHILDREN = "includeChildrenSpans";
|
|
|
441
770
|
import WebSocket from "ws";
|
|
442
771
|
import { readFileSync } from "fs";
|
|
443
772
|
var dbInstance = new AsyncLocalStorage2();
|
|
773
|
+
var fileCleanupStore = new AsyncLocalStorage2();
|
|
774
|
+
function deferFileDeletion(key) {
|
|
775
|
+
fileCleanupStore.getStore()?.add(key);
|
|
776
|
+
}
|
|
777
|
+
__name(deferFileDeletion, "deferFileDeletion");
|
|
778
|
+
async function flushFileDeletions(keys) {
|
|
779
|
+
if (keys.size === 0) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const { deleteStoredFile: deleteStoredFile2 } = await Promise.resolve().then(() => (init_File(), File_exports));
|
|
783
|
+
for (const key of keys) {
|
|
784
|
+
try {
|
|
785
|
+
await deleteStoredFile2(key);
|
|
786
|
+
} catch (e) {
|
|
787
|
+
console.error("failed to delete orphaned file from storage", e);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
__name(flushFileDeletions, "flushFileDeletions");
|
|
444
792
|
var vitestDb = null;
|
|
445
793
|
async function withDatabase(db, requiresTransaction, cb) {
|
|
794
|
+
const pending = /* @__PURE__ */ new Set();
|
|
446
795
|
if (requiresTransaction) {
|
|
447
|
-
|
|
448
|
-
return dbInstance.run(transaction,
|
|
449
|
-
return cb({ transaction });
|
|
796
|
+
const result2 = await db.transaction().execute(async (transaction) => {
|
|
797
|
+
return dbInstance.run(transaction, () => {
|
|
798
|
+
return fileCleanupStore.run(pending, () => cb({ transaction }));
|
|
450
799
|
});
|
|
451
800
|
});
|
|
801
|
+
await flushFileDeletions(pending);
|
|
802
|
+
return result2;
|
|
452
803
|
}
|
|
453
|
-
|
|
454
|
-
return dbInstance.run(sDb,
|
|
455
|
-
return cb({ sDb });
|
|
804
|
+
const result = await db.connection().execute(async (sDb) => {
|
|
805
|
+
return dbInstance.run(sDb, () => {
|
|
806
|
+
return fileCleanupStore.run(pending, () => cb({ sDb }));
|
|
456
807
|
});
|
|
457
808
|
});
|
|
809
|
+
await flushFileDeletions(pending);
|
|
810
|
+
return result;
|
|
458
811
|
}
|
|
459
812
|
__name(withDatabase, "withDatabase");
|
|
460
813
|
function useDatabase() {
|
|
@@ -629,275 +982,8 @@ function getDialect(connString) {
|
|
|
629
982
|
}
|
|
630
983
|
__name(getDialect, "getDialect");
|
|
631
984
|
|
|
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
985
|
// src/parsing.js
|
|
986
|
+
init_File();
|
|
901
987
|
var dateFormat = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
902
988
|
function parseInputs(inputs) {
|
|
903
989
|
if (inputs != null && typeof inputs === "object") {
|
|
@@ -1833,6 +1919,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1833
1919
|
};
|
|
1834
1920
|
|
|
1835
1921
|
// src/ModelAPI.js
|
|
1922
|
+
init_File();
|
|
1836
1923
|
var ModelAPI = class {
|
|
1837
1924
|
static {
|
|
1838
1925
|
__name(this, "ModelAPI");
|
|
@@ -1842,9 +1929,10 @@ var ModelAPI = class {
|
|
|
1842
1929
|
* @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
|
|
1843
1930
|
* @param {TableConfigMap} tableConfigMap
|
|
1844
1931
|
*/
|
|
1845
|
-
constructor(tableName, _, tableConfigMap = {}) {
|
|
1932
|
+
constructor(tableName, _, tableConfigMap = {}, fileFieldsMap = {}) {
|
|
1846
1933
|
this._tableName = tableName;
|
|
1847
1934
|
this._tableConfigMap = tableConfigMap;
|
|
1935
|
+
this._fileFields = fileFieldsMap[tableName] || {};
|
|
1848
1936
|
this._modelName = upperCamelCase(this._tableName);
|
|
1849
1937
|
}
|
|
1850
1938
|
async create(values) {
|
|
@@ -1914,6 +2002,10 @@ var ModelAPI = class {
|
|
|
1914
2002
|
const name = spanNameForModelAPI(this._modelName, "update");
|
|
1915
2003
|
const db = useDatabase();
|
|
1916
2004
|
return withSpan(name, async (span) => {
|
|
2005
|
+
const fileColumns = Object.keys(values || {}).filter(
|
|
2006
|
+
(k) => k in this._fileFields
|
|
2007
|
+
);
|
|
2008
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1917
2009
|
let builder = db.updateTable(this._tableName).returningAll();
|
|
1918
2010
|
const keys = values ? Object.keys(values) : [];
|
|
1919
2011
|
const row = {};
|
|
@@ -1952,7 +2044,9 @@ var ModelAPI = class {
|
|
|
1952
2044
|
span.setAttribute("sql", builder.compile().sql);
|
|
1953
2045
|
try {
|
|
1954
2046
|
const row2 = await builder.executeTakeFirstOrThrow();
|
|
1955
|
-
|
|
2047
|
+
const result = transformRichDataTypes(camelCaseObject(row2));
|
|
2048
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, result);
|
|
2049
|
+
return result;
|
|
1956
2050
|
} catch (e) {
|
|
1957
2051
|
throw new DatabaseError(e);
|
|
1958
2052
|
}
|
|
@@ -1962,12 +2056,15 @@ var ModelAPI = class {
|
|
|
1962
2056
|
const name = spanNameForModelAPI(this._modelName, "delete");
|
|
1963
2057
|
const db = useDatabase();
|
|
1964
2058
|
return withSpan(name, async (span) => {
|
|
2059
|
+
const fileColumns = Object.keys(this._fileFields);
|
|
2060
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1965
2061
|
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
1966
2062
|
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1967
2063
|
builder = applyWhereConditions(context7, builder, where);
|
|
1968
2064
|
span.setAttribute("sql", builder.compile().sql);
|
|
1969
2065
|
try {
|
|
1970
2066
|
const row = await builder.executeTakeFirstOrThrow();
|
|
2067
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, null);
|
|
1971
2068
|
return row.id;
|
|
1972
2069
|
} catch (e) {
|
|
1973
2070
|
throw new DatabaseError(e);
|
|
@@ -1982,6 +2079,30 @@ var ModelAPI = class {
|
|
|
1982
2079
|
builder = applyWhereConditions(context7, builder, where);
|
|
1983
2080
|
return new QueryBuilder(this._tableName, context7, builder);
|
|
1984
2081
|
}
|
|
2082
|
+
// Reads the current file-column values for rows matched by `where`.
|
|
2083
|
+
async _selectExistingFileValues(where, fileColumns) {
|
|
2084
|
+
if (fileColumns.length === 0) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
2087
|
+
const db = useDatabase();
|
|
2088
|
+
let builder = db.selectFrom(this._tableName).selectAll(this._tableName);
|
|
2089
|
+
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2090
|
+
builder = applyWhereConditions(context7, builder, where);
|
|
2091
|
+
const rows = await builder.execute();
|
|
2092
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
2093
|
+
}
|
|
2094
|
+
// Defers deletion of every old file key in existingRows that is not still
|
|
2095
|
+
// referenced by newRow.
|
|
2096
|
+
_deferReplacedFiles(existingRows, fileColumns, newRow) {
|
|
2097
|
+
const retained = new Set(
|
|
2098
|
+
collectFileKeys(newRow ? [newRow] : [], fileColumns)
|
|
2099
|
+
);
|
|
2100
|
+
for (const key of collectFileKeys(existingRows, fileColumns)) {
|
|
2101
|
+
if (!retained.has(key)) {
|
|
2102
|
+
deferFileDeletion(key);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1985
2106
|
};
|
|
1986
2107
|
async function create(conn, tableName, tableConfigs, values) {
|
|
1987
2108
|
try {
|
|
@@ -2091,6 +2212,23 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
2091
2212
|
}
|
|
2092
2213
|
}
|
|
2093
2214
|
__name(create, "create");
|
|
2215
|
+
function collectFileKeys(rows, fileColumns) {
|
|
2216
|
+
const keys = [];
|
|
2217
|
+
for (const row of rows || []) {
|
|
2218
|
+
if (!row) continue;
|
|
2219
|
+
for (const col of fileColumns) {
|
|
2220
|
+
const v = row[col];
|
|
2221
|
+
if (!v) continue;
|
|
2222
|
+
if (Array.isArray(v)) {
|
|
2223
|
+
for (const f of v) if (f?.key) keys.push(f.key);
|
|
2224
|
+
} else if (v.key) {
|
|
2225
|
+
keys.push(v.key);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return keys;
|
|
2230
|
+
}
|
|
2231
|
+
__name(collectFileKeys, "collectFileKeys");
|
|
2094
2232
|
|
|
2095
2233
|
// src/TaskAPI.js
|
|
2096
2234
|
import jwt from "jsonwebtoken";
|
|
@@ -2762,6 +2900,61 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2762
2900
|
}
|
|
2763
2901
|
};
|
|
2764
2902
|
|
|
2903
|
+
// src/integrationServer.js
|
|
2904
|
+
import jwt3 from "jsonwebtoken";
|
|
2905
|
+
function buildHeaders3(identity) {
|
|
2906
|
+
const headers = { "Content-Type": "application/json" };
|
|
2907
|
+
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
2908
|
+
if (!base64pk) {
|
|
2909
|
+
throw new Error(
|
|
2910
|
+
"KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
2914
|
+
const subject = identity && identity.id ? identity.id : "integration-proxy";
|
|
2915
|
+
headers["Authorization"] = "Bearer " + jwt3.sign({}, privateKey, {
|
|
2916
|
+
algorithm: "RS256",
|
|
2917
|
+
expiresIn: 60 * 60 * 24,
|
|
2918
|
+
subject,
|
|
2919
|
+
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
2920
|
+
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
2921
|
+
audience: "integration-proxy",
|
|
2922
|
+
issuer: "https://keel.so"
|
|
2923
|
+
});
|
|
2924
|
+
return headers;
|
|
2925
|
+
}
|
|
2926
|
+
__name(buildHeaders3, "buildHeaders");
|
|
2927
|
+
function getApiUrl3() {
|
|
2928
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
2929
|
+
if (!apiUrl) {
|
|
2930
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
2931
|
+
}
|
|
2932
|
+
return apiUrl;
|
|
2933
|
+
}
|
|
2934
|
+
__name(getApiUrl3, "getApiUrl");
|
|
2935
|
+
function createIntegrationServer(name, identity) {
|
|
2936
|
+
return {
|
|
2937
|
+
do: /* @__PURE__ */ __name(async (request) => {
|
|
2938
|
+
const url = `${getApiUrl3()}/integrations/${encodeURIComponent(
|
|
2939
|
+
name
|
|
2940
|
+
)}/proxy`;
|
|
2941
|
+
const response = await fetch(url, {
|
|
2942
|
+
method: "POST",
|
|
2943
|
+
headers: buildHeaders3(identity ?? null),
|
|
2944
|
+
body: JSON.stringify(request ?? {})
|
|
2945
|
+
});
|
|
2946
|
+
if (!response.ok) {
|
|
2947
|
+
const text = await response.text();
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`integration "${name}" proxy request failed (${response.status}): ${text}`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
return await response.json();
|
|
2953
|
+
}, "do")
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
__name(createIntegrationServer, "createIntegrationServer");
|
|
2957
|
+
|
|
2765
2958
|
// src/RequestHeaders.ts
|
|
2766
2959
|
var RequestHeaders = class {
|
|
2767
2960
|
/**
|
|
@@ -3962,6 +4155,7 @@ var datePickerInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3962
4155
|
}, "datePickerInput");
|
|
3963
4156
|
|
|
3964
4157
|
// src/flows/ui/elements/input/file.ts
|
|
4158
|
+
init_File();
|
|
3965
4159
|
var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
3966
4160
|
return {
|
|
3967
4161
|
__type: "input",
|
|
@@ -3987,6 +4181,7 @@ var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3987
4181
|
}, "fileInput");
|
|
3988
4182
|
|
|
3989
4183
|
// src/flows/ui/elements/input/imageCapture.ts
|
|
4184
|
+
init_File();
|
|
3990
4185
|
var isMultiOptions = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiOptions");
|
|
3991
4186
|
function validateEntry(entry, requireCaption, context7) {
|
|
3992
4187
|
if (!entry?.file?.key) {
|
|
@@ -4482,7 +4677,7 @@ var defaultOpts = {
|
|
|
4482
4677
|
async function insertNewStep(db, runId, name, stage) {
|
|
4483
4678
|
await db.transaction().execute(async (trx) => {
|
|
4484
4679
|
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", "
|
|
4680
|
+
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
4681
|
if (existing) {
|
|
4487
4682
|
return;
|
|
4488
4683
|
}
|
|
@@ -4505,6 +4700,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4505
4700
|
now: ctx.now,
|
|
4506
4701
|
secrets: ctx.secrets,
|
|
4507
4702
|
trace: ctx.trace,
|
|
4703
|
+
module: ctx.module,
|
|
4508
4704
|
complete: /* @__PURE__ */ __name((options) => {
|
|
4509
4705
|
return {
|
|
4510
4706
|
__type: "ui.complete",
|
|
@@ -4567,6 +4763,11 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4567
4763
|
const raw = completedSteps[0].valueRaw;
|
|
4568
4764
|
return raw == null ? void 0 : JSON.parse(raw);
|
|
4569
4765
|
}
|
|
4766
|
+
if (runningSteps.length >= 1) {
|
|
4767
|
+
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4768
|
+
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4769
|
+
throw new StepCreatedDisrupt();
|
|
4770
|
+
}
|
|
4570
4771
|
if (newSteps.length === 1) {
|
|
4571
4772
|
let result = null;
|
|
4572
4773
|
const claimed = await db.updateTable("keel.flow_step").set({
|
|
@@ -4622,11 +4823,6 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4622
4823
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4623
4824
|
return result;
|
|
4624
4825
|
}
|
|
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
4826
|
await insertNewStep(db, runId, name, options.stage);
|
|
4631
4827
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4632
4828
|
span.setAttribute("step.status", "NEW" /* NEW */);
|
|
@@ -5006,7 +5202,183 @@ async function handleFlow(request, config) {
|
|
|
5006
5202
|
__name(handleFlow, "handleFlow");
|
|
5007
5203
|
|
|
5008
5204
|
// src/index.ts
|
|
5205
|
+
import KSUID4 from "ksuid";
|
|
5206
|
+
init_File();
|
|
5207
|
+
|
|
5208
|
+
// src/notifications/email-template.tsx
|
|
5209
|
+
import {
|
|
5210
|
+
Html,
|
|
5211
|
+
Head,
|
|
5212
|
+
Body,
|
|
5213
|
+
Container,
|
|
5214
|
+
Heading,
|
|
5215
|
+
Text,
|
|
5216
|
+
Button,
|
|
5217
|
+
Section
|
|
5218
|
+
} from "@react-email/components";
|
|
5219
|
+
import { render } from "@react-email/render";
|
|
5220
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5221
|
+
function renderBody(text) {
|
|
5222
|
+
return text.split("\n").flatMap((line, i) => i === 0 ? [line] : [/* @__PURE__ */ jsx("br", {}, `br-${i}`), line]);
|
|
5223
|
+
}
|
|
5224
|
+
__name(renderBody, "renderBody");
|
|
5225
|
+
var StockEmailTemplate = /* @__PURE__ */ __name(({ content }) => /* @__PURE__ */ jsxs(Html, { children: [
|
|
5226
|
+
/* @__PURE__ */ jsx(Head, {}),
|
|
5227
|
+
/* @__PURE__ */ jsx(
|
|
5228
|
+
Body,
|
|
5229
|
+
{
|
|
5230
|
+
style: {
|
|
5231
|
+
backgroundColor: "#f6f6f6",
|
|
5232
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
5233
|
+
},
|
|
5234
|
+
children: /* @__PURE__ */ jsxs(
|
|
5235
|
+
Container,
|
|
5236
|
+
{
|
|
5237
|
+
style: {
|
|
5238
|
+
backgroundColor: "#ffffff",
|
|
5239
|
+
margin: "40px auto",
|
|
5240
|
+
padding: "40px",
|
|
5241
|
+
maxWidth: "560px",
|
|
5242
|
+
borderRadius: "8px"
|
|
5243
|
+
},
|
|
5244
|
+
children: [
|
|
5245
|
+
content.title && /* @__PURE__ */ jsx(
|
|
5246
|
+
Heading,
|
|
5247
|
+
{
|
|
5248
|
+
style: {
|
|
5249
|
+
fontSize: "22px",
|
|
5250
|
+
fontWeight: 600,
|
|
5251
|
+
color: "#111827",
|
|
5252
|
+
margin: "0 0 16px"
|
|
5253
|
+
},
|
|
5254
|
+
children: content.title
|
|
5255
|
+
}
|
|
5256
|
+
),
|
|
5257
|
+
/* @__PURE__ */ jsx(
|
|
5258
|
+
Text,
|
|
5259
|
+
{
|
|
5260
|
+
style: {
|
|
5261
|
+
fontSize: "15px",
|
|
5262
|
+
lineHeight: "1.6",
|
|
5263
|
+
color: "#374151",
|
|
5264
|
+
margin: "0 0 24px"
|
|
5265
|
+
},
|
|
5266
|
+
children: renderBody(content.body)
|
|
5267
|
+
}
|
|
5268
|
+
),
|
|
5269
|
+
(content.actions ?? []).length > 0 && /* @__PURE__ */ jsx(Section, { style: { margin: "0 0 12px" }, children: content.actions.map((action, i) => /* @__PURE__ */ jsx(
|
|
5270
|
+
Button,
|
|
5271
|
+
{
|
|
5272
|
+
href: action.url,
|
|
5273
|
+
style: {
|
|
5274
|
+
backgroundColor: "#111827",
|
|
5275
|
+
color: "#ffffff",
|
|
5276
|
+
padding: "12px 24px",
|
|
5277
|
+
borderRadius: "6px",
|
|
5278
|
+
fontSize: "14px",
|
|
5279
|
+
fontWeight: 600,
|
|
5280
|
+
textDecoration: "none",
|
|
5281
|
+
display: "inline-block",
|
|
5282
|
+
// Lay buttons out side by side; space between adjacent buttons.
|
|
5283
|
+
marginRight: i < content.actions.length - 1 ? "12px" : "0"
|
|
5284
|
+
},
|
|
5285
|
+
children: action.label
|
|
5286
|
+
},
|
|
5287
|
+
i
|
|
5288
|
+
)) })
|
|
5289
|
+
]
|
|
5290
|
+
}
|
|
5291
|
+
)
|
|
5292
|
+
}
|
|
5293
|
+
)
|
|
5294
|
+
] }), "StockEmailTemplate");
|
|
5295
|
+
async function renderEmailHtml(content) {
|
|
5296
|
+
return render(/* @__PURE__ */ jsx(StockEmailTemplate, { content }), { pretty: false });
|
|
5297
|
+
}
|
|
5298
|
+
__name(renderEmailHtml, "renderEmailHtml");
|
|
5299
|
+
|
|
5300
|
+
// src/notifications/notify.ts
|
|
5009
5301
|
import KSUID3 from "ksuid";
|
|
5302
|
+
function getApiUrl4() {
|
|
5303
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
5304
|
+
if (!apiUrl) {
|
|
5305
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
5306
|
+
}
|
|
5307
|
+
return apiUrl;
|
|
5308
|
+
}
|
|
5309
|
+
__name(getApiUrl4, "getApiUrl");
|
|
5310
|
+
function toArray(v) {
|
|
5311
|
+
if (v === void 0) return [];
|
|
5312
|
+
return Array.isArray(v) ? v : [v];
|
|
5313
|
+
}
|
|
5314
|
+
__name(toArray, "toArray");
|
|
5315
|
+
function normaliseGroup(group) {
|
|
5316
|
+
if (!group) return [];
|
|
5317
|
+
const refs = [];
|
|
5318
|
+
for (const email of toArray(group.emails)) {
|
|
5319
|
+
refs.push({ kind: "email", value: email });
|
|
5320
|
+
}
|
|
5321
|
+
for (const user of toArray(group.users)) {
|
|
5322
|
+
refs.push({
|
|
5323
|
+
kind: "user",
|
|
5324
|
+
value: typeof user === "string" ? user : user.id
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
for (const identity of toArray(group.identities)) {
|
|
5328
|
+
refs.push({
|
|
5329
|
+
kind: "identity",
|
|
5330
|
+
value: typeof identity === "string" ? identity : identity.id
|
|
5331
|
+
});
|
|
5332
|
+
}
|
|
5333
|
+
for (const team of toArray(group.teams)) {
|
|
5334
|
+
refs.push({ kind: "team", value: team });
|
|
5335
|
+
}
|
|
5336
|
+
return refs;
|
|
5337
|
+
}
|
|
5338
|
+
__name(normaliseGroup, "normaliseGroup");
|
|
5339
|
+
async function notifyEmail(input) {
|
|
5340
|
+
return withSpan("notify.email", async () => {
|
|
5341
|
+
const id = KSUID3.randomSync().string;
|
|
5342
|
+
let rendered;
|
|
5343
|
+
let content;
|
|
5344
|
+
if (typeof input.content === "string") {
|
|
5345
|
+
rendered = input.content;
|
|
5346
|
+
content = void 0;
|
|
5347
|
+
} else {
|
|
5348
|
+
rendered = await renderEmailHtml(input.content);
|
|
5349
|
+
content = input.content;
|
|
5350
|
+
}
|
|
5351
|
+
const body = {
|
|
5352
|
+
id,
|
|
5353
|
+
subject: input.subject,
|
|
5354
|
+
rendered,
|
|
5355
|
+
content,
|
|
5356
|
+
recipients: {
|
|
5357
|
+
to: normaliseGroup(input.recipients.to),
|
|
5358
|
+
cc: normaliseGroup(input.recipients.cc),
|
|
5359
|
+
bcc: normaliseGroup(input.recipients.bcc)
|
|
5360
|
+
}
|
|
5361
|
+
};
|
|
5362
|
+
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5363
|
+
method: "POST",
|
|
5364
|
+
headers: { "Content-Type": "application/json" },
|
|
5365
|
+
body: JSON.stringify(body)
|
|
5366
|
+
});
|
|
5367
|
+
if (!response.ok) {
|
|
5368
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
5369
|
+
throw new Error(
|
|
5370
|
+
`Failed to send notification: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
await response.body?.cancel();
|
|
5374
|
+
return id;
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
__name(notifyEmail, "notifyEmail");
|
|
5378
|
+
function createNotifier() {
|
|
5379
|
+
return { email: notifyEmail };
|
|
5380
|
+
}
|
|
5381
|
+
__name(createNotifier, "createNotifier");
|
|
5010
5382
|
|
|
5011
5383
|
// src/experimental.ts
|
|
5012
5384
|
var experimental_exports = {};
|
|
@@ -5371,16 +5743,17 @@ __name(LlmFlowStep, "LlmFlowStep");
|
|
|
5371
5743
|
import { z } from "zod";
|
|
5372
5744
|
var createTraceAPI2 = createTraceAPI;
|
|
5373
5745
|
function ksuid() {
|
|
5374
|
-
return
|
|
5746
|
+
return KSUID4.randomSync().string;
|
|
5375
5747
|
}
|
|
5376
5748
|
__name(ksuid, "ksuid");
|
|
5749
|
+
var notify = createNotifier();
|
|
5377
5750
|
export {
|
|
5378
5751
|
Duration,
|
|
5379
5752
|
ErrorPresets,
|
|
5380
5753
|
File,
|
|
5381
5754
|
FlowsAPI,
|
|
5382
5755
|
InlineFile,
|
|
5383
|
-
|
|
5756
|
+
KSUID4 as KSUID,
|
|
5384
5757
|
ModelAPI,
|
|
5385
5758
|
NonRetriableError,
|
|
5386
5759
|
PERMISSION_STATE,
|
|
@@ -5395,6 +5768,8 @@ export {
|
|
|
5395
5768
|
TaskAPI,
|
|
5396
5769
|
checkBuiltInPermissions,
|
|
5397
5770
|
createFlowContext,
|
|
5771
|
+
createIntegrationServer,
|
|
5772
|
+
createNotifier,
|
|
5398
5773
|
createTraceAPI2 as createTraceAPI,
|
|
5399
5774
|
experimental_exports as experimental,
|
|
5400
5775
|
handleFlow,
|
|
@@ -5402,7 +5777,9 @@ export {
|
|
|
5402
5777
|
handleRequest,
|
|
5403
5778
|
handleRoute,
|
|
5404
5779
|
handleSubscriber,
|
|
5780
|
+
insertNewStep,
|
|
5405
5781
|
ksuid,
|
|
5782
|
+
notify,
|
|
5406
5783
|
tracing_exports as tracing,
|
|
5407
5784
|
useDatabase,
|
|
5408
5785
|
z
|