@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.cjs
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __export = (target, all) => {
|
|
10
13
|
for (var name in all)
|
|
11
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -28,6 +31,323 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
31
|
));
|
|
29
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
33
|
|
|
34
|
+
// src/File.ts
|
|
35
|
+
var File_exports = {};
|
|
36
|
+
__export(File_exports, {
|
|
37
|
+
File: () => File,
|
|
38
|
+
InlineFile: () => InlineFile,
|
|
39
|
+
buildContentDisposition: () => buildContentDisposition,
|
|
40
|
+
deleteStoredFile: () => deleteStoredFile,
|
|
41
|
+
rewriteFilesDomain: () => rewriteFilesDomain
|
|
42
|
+
});
|
|
43
|
+
function rewriteFilesDomain(url) {
|
|
44
|
+
const domain = process.env.KEEL_FILES_DOMAIN;
|
|
45
|
+
if (domain) {
|
|
46
|
+
const override = new URL(domain);
|
|
47
|
+
url.protocol = override.protocol;
|
|
48
|
+
url.hostname = override.hostname;
|
|
49
|
+
url.port = override.port;
|
|
50
|
+
const overridePath = override.pathname.replace(/\/+$/, "");
|
|
51
|
+
if (overridePath && overridePath !== "/") {
|
|
52
|
+
url.pathname = overridePath + url.pathname;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return url;
|
|
56
|
+
}
|
|
57
|
+
function encodeRFC5987(value) {
|
|
58
|
+
return encodeURIComponent(value).replace(
|
|
59
|
+
/['()*]/g,
|
|
60
|
+
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function buildContentDisposition(disposition, filename) {
|
|
64
|
+
const type = disposition === "attachment" ? "attachment" : "inline";
|
|
65
|
+
if (!filename || filename.trim() === "") {
|
|
66
|
+
return type;
|
|
67
|
+
}
|
|
68
|
+
const asciiFallback = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
|
|
69
|
+
return `${type}; filename="${asciiFallback}"; filename*=UTF-8''${encodeRFC5987(
|
|
70
|
+
filename
|
|
71
|
+
)}`;
|
|
72
|
+
}
|
|
73
|
+
async function storeFile(contents, key, filename, contentType, size, expires) {
|
|
74
|
+
if (!s3Client) {
|
|
75
|
+
throw new Error("S3 client is required");
|
|
76
|
+
}
|
|
77
|
+
const params = {
|
|
78
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
79
|
+
Key: "files/" + key,
|
|
80
|
+
Body: contents,
|
|
81
|
+
ContentType: contentType,
|
|
82
|
+
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
83
|
+
filename
|
|
84
|
+
)}"`,
|
|
85
|
+
Metadata: {
|
|
86
|
+
filename
|
|
87
|
+
},
|
|
88
|
+
ACL: "private"
|
|
89
|
+
};
|
|
90
|
+
if (expires) {
|
|
91
|
+
if (expires instanceof Date) {
|
|
92
|
+
params.Expires = expires;
|
|
93
|
+
} else {
|
|
94
|
+
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const command = new import_client_s3.PutObjectCommand(params);
|
|
98
|
+
try {
|
|
99
|
+
await s3Client.send(command);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error("Error uploading file:", error);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function deleteStoredFile(key) {
|
|
106
|
+
if (!s3Client) {
|
|
107
|
+
throw new Error("S3 client is required");
|
|
108
|
+
}
|
|
109
|
+
await s3Client.send(
|
|
110
|
+
new import_client_s3.DeleteObjectCommand({
|
|
111
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
112
|
+
Key: "files/" + key
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
var import_client_s3, import_credential_providers, import_s3_request_presigner, import_ksuid, s3Client, InlineFile, File;
|
|
117
|
+
var init_File = __esm({
|
|
118
|
+
"src/File.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
import_client_s3 = require("@aws-sdk/client-s3");
|
|
121
|
+
import_credential_providers = require("@aws-sdk/credential-providers");
|
|
122
|
+
import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
123
|
+
import_ksuid = __toESM(require("ksuid"), 1);
|
|
124
|
+
s3Client = (() => {
|
|
125
|
+
if (!process.env.KEEL_FILES_BUCKET_NAME) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const endpoint = process.env.KEEL_S3_ENDPOINT;
|
|
129
|
+
if (endpoint) {
|
|
130
|
+
return new import_client_s3.S3Client({
|
|
131
|
+
region: process.env.KEEL_REGION,
|
|
132
|
+
credentials: {
|
|
133
|
+
accessKeyId: "keelstorage",
|
|
134
|
+
secretAccessKey: "keelstorage"
|
|
135
|
+
},
|
|
136
|
+
endpoint
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const testEndpoint = process.env.TEST_AWS_ENDPOINT;
|
|
140
|
+
if (testEndpoint) {
|
|
141
|
+
return new import_client_s3.S3Client({
|
|
142
|
+
region: process.env.KEEL_REGION,
|
|
143
|
+
credentials: {
|
|
144
|
+
accessKeyId: "test",
|
|
145
|
+
secretAccessKey: "test"
|
|
146
|
+
},
|
|
147
|
+
endpointProvider: /* @__PURE__ */ __name(() => {
|
|
148
|
+
return {
|
|
149
|
+
url: new URL(testEndpoint)
|
|
150
|
+
};
|
|
151
|
+
}, "endpointProvider")
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return new import_client_s3.S3Client({
|
|
155
|
+
region: process.env.KEEL_REGION,
|
|
156
|
+
credentials: (0, import_credential_providers.fromEnv)()
|
|
157
|
+
});
|
|
158
|
+
})();
|
|
159
|
+
__name(rewriteFilesDomain, "rewriteFilesDomain");
|
|
160
|
+
__name(encodeRFC5987, "encodeRFC5987");
|
|
161
|
+
__name(buildContentDisposition, "buildContentDisposition");
|
|
162
|
+
InlineFile = class _InlineFile {
|
|
163
|
+
static {
|
|
164
|
+
__name(this, "InlineFile");
|
|
165
|
+
}
|
|
166
|
+
constructor(input) {
|
|
167
|
+
this._filename = input.filename;
|
|
168
|
+
this._contentType = input.contentType;
|
|
169
|
+
this._contents = null;
|
|
170
|
+
}
|
|
171
|
+
static fromDataURL(dataURL) {
|
|
172
|
+
const info = dataURL.split(",")[0].split(":")[1];
|
|
173
|
+
const data = dataURL.split(",")[1];
|
|
174
|
+
const mimeType = info.split(";")[0];
|
|
175
|
+
const name = info.split(";")[1].split("=")[1] || "file";
|
|
176
|
+
const buffer = Buffer.from(data, "base64");
|
|
177
|
+
const file2 = new _InlineFile({ filename: name, contentType: mimeType });
|
|
178
|
+
file2.write(buffer);
|
|
179
|
+
return file2;
|
|
180
|
+
}
|
|
181
|
+
// Gets size of the file's contents in bytes
|
|
182
|
+
get size() {
|
|
183
|
+
if (this._contents) {
|
|
184
|
+
return this._contents.size;
|
|
185
|
+
}
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
// Gets the media type of the file contents
|
|
189
|
+
get contentType() {
|
|
190
|
+
return this._contentType;
|
|
191
|
+
}
|
|
192
|
+
// Gets the name of the file
|
|
193
|
+
get filename() {
|
|
194
|
+
return this._filename;
|
|
195
|
+
}
|
|
196
|
+
// Write the files contents from a buffer
|
|
197
|
+
write(buffer) {
|
|
198
|
+
this._contents = new Blob([
|
|
199
|
+
new Uint8Array(
|
|
200
|
+
buffer.buffer,
|
|
201
|
+
buffer.byteOffset,
|
|
202
|
+
buffer.byteLength
|
|
203
|
+
)
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
// Reads the contents of the file as a buffer
|
|
207
|
+
async read() {
|
|
208
|
+
if (!this._contents) {
|
|
209
|
+
throw new Error("No contents to read");
|
|
210
|
+
}
|
|
211
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
212
|
+
return Buffer.from(arrayBuffer);
|
|
213
|
+
}
|
|
214
|
+
// Persists the file
|
|
215
|
+
async store(expires = null) {
|
|
216
|
+
const content = await this.read();
|
|
217
|
+
const key = import_ksuid.default.randomSync().string;
|
|
218
|
+
await storeFile(
|
|
219
|
+
content,
|
|
220
|
+
key,
|
|
221
|
+
this._filename,
|
|
222
|
+
this._contentType,
|
|
223
|
+
this.size,
|
|
224
|
+
expires
|
|
225
|
+
);
|
|
226
|
+
return new File({
|
|
227
|
+
key,
|
|
228
|
+
size: this.size,
|
|
229
|
+
filename: this.filename,
|
|
230
|
+
contentType: this.contentType
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
File = class _File extends InlineFile {
|
|
235
|
+
static {
|
|
236
|
+
__name(this, "File");
|
|
237
|
+
}
|
|
238
|
+
constructor(input) {
|
|
239
|
+
super({
|
|
240
|
+
filename: input.filename || "",
|
|
241
|
+
contentType: input.contentType || ""
|
|
242
|
+
});
|
|
243
|
+
this._key = input.key || "";
|
|
244
|
+
this._size = input.size || 0;
|
|
245
|
+
}
|
|
246
|
+
// Creates a new instance from the database record
|
|
247
|
+
static fromDbRecord(input) {
|
|
248
|
+
return new _File({
|
|
249
|
+
key: input.key,
|
|
250
|
+
filename: input.filename,
|
|
251
|
+
size: input.size,
|
|
252
|
+
contentType: input.contentType
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
get size() {
|
|
256
|
+
return this._size;
|
|
257
|
+
}
|
|
258
|
+
// Gets the stored key
|
|
259
|
+
get key() {
|
|
260
|
+
return this._key;
|
|
261
|
+
}
|
|
262
|
+
get isPublic() {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
async read() {
|
|
266
|
+
if (this._contents) {
|
|
267
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
268
|
+
return Buffer.from(arrayBuffer);
|
|
269
|
+
}
|
|
270
|
+
if (!s3Client) {
|
|
271
|
+
throw new Error("S3 client is required");
|
|
272
|
+
}
|
|
273
|
+
const params = {
|
|
274
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
275
|
+
Key: "files/" + this.key
|
|
276
|
+
};
|
|
277
|
+
const command = new import_client_s3.GetObjectCommand(params);
|
|
278
|
+
const response = await s3Client.send(command);
|
|
279
|
+
const blob = await response.Body.transformToByteArray();
|
|
280
|
+
return Buffer.from(blob);
|
|
281
|
+
}
|
|
282
|
+
async store(expires = null) {
|
|
283
|
+
if (this._contents) {
|
|
284
|
+
const contents = await this.read();
|
|
285
|
+
await storeFile(
|
|
286
|
+
contents,
|
|
287
|
+
this.key,
|
|
288
|
+
this.filename,
|
|
289
|
+
this.contentType,
|
|
290
|
+
this.size,
|
|
291
|
+
expires
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
// Generates a presigned download URL.
|
|
297
|
+
//
|
|
298
|
+
// By default the browser previews the file inline and, when saved, uses the
|
|
299
|
+
// file's own filename. Pass `contentDisposition: "attachment"` to force a
|
|
300
|
+
// download, or `filename` to override the suggested name.
|
|
301
|
+
async getPresignedUrl(options) {
|
|
302
|
+
if (!s3Client) {
|
|
303
|
+
throw new Error("S3 client is required");
|
|
304
|
+
}
|
|
305
|
+
const disposition = options?.contentDisposition ?? "inline";
|
|
306
|
+
const filename = options?.filename ?? this.filename;
|
|
307
|
+
const command = new import_client_s3.GetObjectCommand({
|
|
308
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
309
|
+
Key: "files/" + this.key,
|
|
310
|
+
ResponseContentDisposition: buildContentDisposition(
|
|
311
|
+
disposition,
|
|
312
|
+
filename
|
|
313
|
+
)
|
|
314
|
+
});
|
|
315
|
+
const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
|
|
316
|
+
return rewriteFilesDomain(new URL(url));
|
|
317
|
+
}
|
|
318
|
+
// Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
|
|
319
|
+
async getPresignedUploadUrl() {
|
|
320
|
+
if (!s3Client) {
|
|
321
|
+
throw new Error("S3 client is required");
|
|
322
|
+
}
|
|
323
|
+
if (!this.key) {
|
|
324
|
+
this._key = import_ksuid.default.randomSync().string;
|
|
325
|
+
}
|
|
326
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
327
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
328
|
+
Key: "files/" + this.key
|
|
329
|
+
});
|
|
330
|
+
const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
|
|
331
|
+
return rewriteFilesDomain(new URL(url));
|
|
332
|
+
}
|
|
333
|
+
// Persists the file
|
|
334
|
+
toDbRecord() {
|
|
335
|
+
return {
|
|
336
|
+
key: this.key,
|
|
337
|
+
filename: this.filename,
|
|
338
|
+
contentType: this.contentType,
|
|
339
|
+
size: this.size
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
toJSON() {
|
|
343
|
+
return this.toDbRecord();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
__name(storeFile, "storeFile");
|
|
347
|
+
__name(deleteStoredFile, "deleteStoredFile");
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
31
351
|
// src/index.ts
|
|
32
352
|
var index_exports = {};
|
|
33
353
|
__export(index_exports, {
|
|
@@ -36,7 +356,7 @@ __export(index_exports, {
|
|
|
36
356
|
File: () => File,
|
|
37
357
|
FlowsAPI: () => FlowsAPI,
|
|
38
358
|
InlineFile: () => InlineFile,
|
|
39
|
-
KSUID: () =>
|
|
359
|
+
KSUID: () => import_ksuid4.default,
|
|
40
360
|
ModelAPI: () => ModelAPI,
|
|
41
361
|
NonRetriableError: () => NonRetriableError,
|
|
42
362
|
PERMISSION_STATE: () => PERMISSION_STATE,
|
|
@@ -51,6 +371,8 @@ __export(index_exports, {
|
|
|
51
371
|
TaskAPI: () => TaskAPI,
|
|
52
372
|
checkBuiltInPermissions: () => checkBuiltInPermissions,
|
|
53
373
|
createFlowContext: () => createFlowContext,
|
|
374
|
+
createIntegrationServer: () => createIntegrationServer,
|
|
375
|
+
createNotifier: () => createNotifier,
|
|
54
376
|
createTraceAPI: () => createTraceAPI2,
|
|
55
377
|
experimental: () => experimental_exports,
|
|
56
378
|
handleFlow: () => handleFlow,
|
|
@@ -58,7 +380,9 @@ __export(index_exports, {
|
|
|
58
380
|
handleRequest: () => handleRequest,
|
|
59
381
|
handleRoute: () => handleRoute,
|
|
60
382
|
handleSubscriber: () => handleSubscriber,
|
|
383
|
+
insertNewStep: () => insertNewStep,
|
|
61
384
|
ksuid: () => ksuid,
|
|
385
|
+
notify: () => notify,
|
|
62
386
|
tracing: () => tracing_exports,
|
|
63
387
|
useDatabase: () => useDatabase,
|
|
64
388
|
z: () => import_zod.z
|
|
@@ -132,6 +456,9 @@ var AuditContextPlugin = class {
|
|
|
132
456
|
const rawNode = import_kysely.sql`set_trace_id(${audit.traceId})`.as(this.traceIdAlias).toOperationNode();
|
|
133
457
|
returning.selections.push(import_kysely.SelectionNode.create(rawNode));
|
|
134
458
|
}
|
|
459
|
+
if (returning.selections.length === 0) {
|
|
460
|
+
return { ...args.node };
|
|
461
|
+
}
|
|
135
462
|
return {
|
|
136
463
|
...args.node,
|
|
137
464
|
returning
|
|
@@ -501,20 +828,44 @@ var KEEL_INTERNAL_CHILDREN = "includeChildrenSpans";
|
|
|
501
828
|
var import_ws = __toESM(require("ws"), 1);
|
|
502
829
|
var import_node_fs = require("fs");
|
|
503
830
|
var dbInstance = new import_node_async_hooks2.AsyncLocalStorage();
|
|
831
|
+
var fileCleanupStore = new import_node_async_hooks2.AsyncLocalStorage();
|
|
832
|
+
function deferFileDeletion(key) {
|
|
833
|
+
fileCleanupStore.getStore()?.add(key);
|
|
834
|
+
}
|
|
835
|
+
__name(deferFileDeletion, "deferFileDeletion");
|
|
836
|
+
async function flushFileDeletions(keys) {
|
|
837
|
+
if (keys.size === 0) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const { deleteStoredFile: deleteStoredFile2 } = await Promise.resolve().then(() => (init_File(), File_exports));
|
|
841
|
+
for (const key of keys) {
|
|
842
|
+
try {
|
|
843
|
+
await deleteStoredFile2(key);
|
|
844
|
+
} catch (e) {
|
|
845
|
+
console.error("failed to delete orphaned file from storage", e);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
__name(flushFileDeletions, "flushFileDeletions");
|
|
504
850
|
var vitestDb = null;
|
|
505
851
|
async function withDatabase(db, requiresTransaction, cb) {
|
|
852
|
+
const pending = /* @__PURE__ */ new Set();
|
|
506
853
|
if (requiresTransaction) {
|
|
507
|
-
|
|
508
|
-
return dbInstance.run(transaction,
|
|
509
|
-
return cb({ transaction });
|
|
854
|
+
const result2 = await db.transaction().execute(async (transaction) => {
|
|
855
|
+
return dbInstance.run(transaction, () => {
|
|
856
|
+
return fileCleanupStore.run(pending, () => cb({ transaction }));
|
|
510
857
|
});
|
|
511
858
|
});
|
|
859
|
+
await flushFileDeletions(pending);
|
|
860
|
+
return result2;
|
|
512
861
|
}
|
|
513
|
-
|
|
514
|
-
return dbInstance.run(sDb,
|
|
515
|
-
return cb({ sDb });
|
|
862
|
+
const result = await db.connection().execute(async (sDb) => {
|
|
863
|
+
return dbInstance.run(sDb, () => {
|
|
864
|
+
return fileCleanupStore.run(pending, () => cb({ sDb }));
|
|
516
865
|
});
|
|
517
866
|
});
|
|
867
|
+
await flushFileDeletions(pending);
|
|
868
|
+
return result;
|
|
518
869
|
}
|
|
519
870
|
__name(withDatabase, "withDatabase");
|
|
520
871
|
function useDatabase() {
|
|
@@ -689,271 +1040,8 @@ function getDialect(connString) {
|
|
|
689
1040
|
}
|
|
690
1041
|
__name(getDialect, "getDialect");
|
|
691
1042
|
|
|
692
|
-
// src/File.ts
|
|
693
|
-
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
694
|
-
var import_credential_providers = require("@aws-sdk/credential-providers");
|
|
695
|
-
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
696
|
-
var import_ksuid = __toESM(require("ksuid"), 1);
|
|
697
|
-
var s3Client = (() => {
|
|
698
|
-
if (!process.env.KEEL_FILES_BUCKET_NAME) {
|
|
699
|
-
return null;
|
|
700
|
-
}
|
|
701
|
-
const endpoint = process.env.KEEL_S3_ENDPOINT;
|
|
702
|
-
if (endpoint) {
|
|
703
|
-
return new import_client_s3.S3Client({
|
|
704
|
-
region: process.env.KEEL_REGION,
|
|
705
|
-
credentials: {
|
|
706
|
-
accessKeyId: "keelstorage",
|
|
707
|
-
secretAccessKey: "keelstorage"
|
|
708
|
-
},
|
|
709
|
-
endpoint
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
const testEndpoint = process.env.TEST_AWS_ENDPOINT;
|
|
713
|
-
if (testEndpoint) {
|
|
714
|
-
return new import_client_s3.S3Client({
|
|
715
|
-
region: process.env.KEEL_REGION,
|
|
716
|
-
credentials: {
|
|
717
|
-
accessKeyId: "test",
|
|
718
|
-
secretAccessKey: "test"
|
|
719
|
-
},
|
|
720
|
-
endpointProvider: /* @__PURE__ */ __name(() => {
|
|
721
|
-
return {
|
|
722
|
-
url: new URL(testEndpoint)
|
|
723
|
-
};
|
|
724
|
-
}, "endpointProvider")
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
return new import_client_s3.S3Client({
|
|
728
|
-
region: process.env.KEEL_REGION,
|
|
729
|
-
credentials: (0, import_credential_providers.fromEnv)()
|
|
730
|
-
});
|
|
731
|
-
})();
|
|
732
|
-
function rewriteFilesDomain(url) {
|
|
733
|
-
const domain = process.env.KEEL_FILES_DOMAIN;
|
|
734
|
-
if (domain) {
|
|
735
|
-
const override = new URL(domain);
|
|
736
|
-
url.protocol = override.protocol;
|
|
737
|
-
url.hostname = override.hostname;
|
|
738
|
-
url.port = override.port;
|
|
739
|
-
const overridePath = override.pathname.replace(/\/+$/, "");
|
|
740
|
-
if (overridePath && overridePath !== "/") {
|
|
741
|
-
url.pathname = overridePath + url.pathname;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
return url;
|
|
745
|
-
}
|
|
746
|
-
__name(rewriteFilesDomain, "rewriteFilesDomain");
|
|
747
|
-
var InlineFile = class _InlineFile {
|
|
748
|
-
static {
|
|
749
|
-
__name(this, "InlineFile");
|
|
750
|
-
}
|
|
751
|
-
constructor(input) {
|
|
752
|
-
this._filename = input.filename;
|
|
753
|
-
this._contentType = input.contentType;
|
|
754
|
-
this._contents = null;
|
|
755
|
-
}
|
|
756
|
-
static fromDataURL(dataURL) {
|
|
757
|
-
const info = dataURL.split(",")[0].split(":")[1];
|
|
758
|
-
const data = dataURL.split(",")[1];
|
|
759
|
-
const mimeType = info.split(";")[0];
|
|
760
|
-
const name = info.split(";")[1].split("=")[1] || "file";
|
|
761
|
-
const buffer = Buffer.from(data, "base64");
|
|
762
|
-
const file2 = new _InlineFile({ filename: name, contentType: mimeType });
|
|
763
|
-
file2.write(buffer);
|
|
764
|
-
return file2;
|
|
765
|
-
}
|
|
766
|
-
// Gets size of the file's contents in bytes
|
|
767
|
-
get size() {
|
|
768
|
-
if (this._contents) {
|
|
769
|
-
return this._contents.size;
|
|
770
|
-
}
|
|
771
|
-
return 0;
|
|
772
|
-
}
|
|
773
|
-
// Gets the media type of the file contents
|
|
774
|
-
get contentType() {
|
|
775
|
-
return this._contentType;
|
|
776
|
-
}
|
|
777
|
-
// Gets the name of the file
|
|
778
|
-
get filename() {
|
|
779
|
-
return this._filename;
|
|
780
|
-
}
|
|
781
|
-
// Write the files contents from a buffer
|
|
782
|
-
write(buffer) {
|
|
783
|
-
this._contents = new Blob([
|
|
784
|
-
new Uint8Array(
|
|
785
|
-
buffer.buffer,
|
|
786
|
-
buffer.byteOffset,
|
|
787
|
-
buffer.byteLength
|
|
788
|
-
)
|
|
789
|
-
]);
|
|
790
|
-
}
|
|
791
|
-
// Reads the contents of the file as a buffer
|
|
792
|
-
async read() {
|
|
793
|
-
if (!this._contents) {
|
|
794
|
-
throw new Error("No contents to read");
|
|
795
|
-
}
|
|
796
|
-
const arrayBuffer = await this._contents.arrayBuffer();
|
|
797
|
-
return Buffer.from(arrayBuffer);
|
|
798
|
-
}
|
|
799
|
-
// Persists the file
|
|
800
|
-
async store(expires = null) {
|
|
801
|
-
const content = await this.read();
|
|
802
|
-
const key = import_ksuid.default.randomSync().string;
|
|
803
|
-
await storeFile(
|
|
804
|
-
content,
|
|
805
|
-
key,
|
|
806
|
-
this._filename,
|
|
807
|
-
this._contentType,
|
|
808
|
-
this.size,
|
|
809
|
-
expires
|
|
810
|
-
);
|
|
811
|
-
return new File({
|
|
812
|
-
key,
|
|
813
|
-
size: this.size,
|
|
814
|
-
filename: this.filename,
|
|
815
|
-
contentType: this.contentType
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
};
|
|
819
|
-
var File = class _File extends InlineFile {
|
|
820
|
-
static {
|
|
821
|
-
__name(this, "File");
|
|
822
|
-
}
|
|
823
|
-
constructor(input) {
|
|
824
|
-
super({
|
|
825
|
-
filename: input.filename || "",
|
|
826
|
-
contentType: input.contentType || ""
|
|
827
|
-
});
|
|
828
|
-
this._key = input.key || "";
|
|
829
|
-
this._size = input.size || 0;
|
|
830
|
-
}
|
|
831
|
-
// Creates a new instance from the database record
|
|
832
|
-
static fromDbRecord(input) {
|
|
833
|
-
return new _File({
|
|
834
|
-
key: input.key,
|
|
835
|
-
filename: input.filename,
|
|
836
|
-
size: input.size,
|
|
837
|
-
contentType: input.contentType
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
get size() {
|
|
841
|
-
return this._size;
|
|
842
|
-
}
|
|
843
|
-
// Gets the stored key
|
|
844
|
-
get key() {
|
|
845
|
-
return this._key;
|
|
846
|
-
}
|
|
847
|
-
get isPublic() {
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
async read() {
|
|
851
|
-
if (this._contents) {
|
|
852
|
-
const arrayBuffer = await this._contents.arrayBuffer();
|
|
853
|
-
return Buffer.from(arrayBuffer);
|
|
854
|
-
}
|
|
855
|
-
if (!s3Client) {
|
|
856
|
-
throw new Error("S3 client is required");
|
|
857
|
-
}
|
|
858
|
-
const params = {
|
|
859
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
860
|
-
Key: "files/" + this.key
|
|
861
|
-
};
|
|
862
|
-
const command = new import_client_s3.GetObjectCommand(params);
|
|
863
|
-
const response = await s3Client.send(command);
|
|
864
|
-
const blob = await response.Body.transformToByteArray();
|
|
865
|
-
return Buffer.from(blob);
|
|
866
|
-
}
|
|
867
|
-
async store(expires = null) {
|
|
868
|
-
if (this._contents) {
|
|
869
|
-
const contents = await this.read();
|
|
870
|
-
await storeFile(
|
|
871
|
-
contents,
|
|
872
|
-
this.key,
|
|
873
|
-
this.filename,
|
|
874
|
-
this.contentType,
|
|
875
|
-
this.size,
|
|
876
|
-
expires
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
return this;
|
|
880
|
-
}
|
|
881
|
-
// Generates a presigned download URL
|
|
882
|
-
async getPresignedUrl() {
|
|
883
|
-
if (!s3Client) {
|
|
884
|
-
throw new Error("S3 client is required");
|
|
885
|
-
}
|
|
886
|
-
const command = new import_client_s3.GetObjectCommand({
|
|
887
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
888
|
-
Key: "files/" + this.key,
|
|
889
|
-
ResponseContentDisposition: "inline"
|
|
890
|
-
});
|
|
891
|
-
const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
|
|
892
|
-
return rewriteFilesDomain(new URL(url));
|
|
893
|
-
}
|
|
894
|
-
// Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
|
|
895
|
-
async getPresignedUploadUrl() {
|
|
896
|
-
if (!s3Client) {
|
|
897
|
-
throw new Error("S3 client is required");
|
|
898
|
-
}
|
|
899
|
-
if (!this.key) {
|
|
900
|
-
this._key = import_ksuid.default.randomSync().string;
|
|
901
|
-
}
|
|
902
|
-
const command = new import_client_s3.PutObjectCommand({
|
|
903
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
904
|
-
Key: "files/" + this.key
|
|
905
|
-
});
|
|
906
|
-
const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
|
|
907
|
-
return rewriteFilesDomain(new URL(url));
|
|
908
|
-
}
|
|
909
|
-
// Persists the file
|
|
910
|
-
toDbRecord() {
|
|
911
|
-
return {
|
|
912
|
-
key: this.key,
|
|
913
|
-
filename: this.filename,
|
|
914
|
-
contentType: this.contentType,
|
|
915
|
-
size: this.size
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
toJSON() {
|
|
919
|
-
return this.toDbRecord();
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
async function storeFile(contents, key, filename, contentType, size, expires) {
|
|
923
|
-
if (!s3Client) {
|
|
924
|
-
throw new Error("S3 client is required");
|
|
925
|
-
}
|
|
926
|
-
const params = {
|
|
927
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
928
|
-
Key: "files/" + key,
|
|
929
|
-
Body: contents,
|
|
930
|
-
ContentType: contentType,
|
|
931
|
-
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
932
|
-
filename
|
|
933
|
-
)}"`,
|
|
934
|
-
Metadata: {
|
|
935
|
-
filename
|
|
936
|
-
},
|
|
937
|
-
ACL: "private"
|
|
938
|
-
};
|
|
939
|
-
if (expires) {
|
|
940
|
-
if (expires instanceof Date) {
|
|
941
|
-
params.Expires = expires;
|
|
942
|
-
} else {
|
|
943
|
-
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const command = new import_client_s3.PutObjectCommand(params);
|
|
947
|
-
try {
|
|
948
|
-
await s3Client.send(command);
|
|
949
|
-
} catch (error) {
|
|
950
|
-
console.error("Error uploading file:", error);
|
|
951
|
-
throw error;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
__name(storeFile, "storeFile");
|
|
955
|
-
|
|
956
1043
|
// src/parsing.js
|
|
1044
|
+
init_File();
|
|
957
1045
|
var dateFormat = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
958
1046
|
function parseInputs(inputs) {
|
|
959
1047
|
if (inputs != null && typeof inputs === "object") {
|
|
@@ -1886,6 +1974,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1886
1974
|
};
|
|
1887
1975
|
|
|
1888
1976
|
// src/ModelAPI.js
|
|
1977
|
+
init_File();
|
|
1889
1978
|
var ModelAPI = class {
|
|
1890
1979
|
static {
|
|
1891
1980
|
__name(this, "ModelAPI");
|
|
@@ -1895,9 +1984,10 @@ var ModelAPI = class {
|
|
|
1895
1984
|
* @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
|
|
1896
1985
|
* @param {TableConfigMap} tableConfigMap
|
|
1897
1986
|
*/
|
|
1898
|
-
constructor(tableName, _, tableConfigMap = {}) {
|
|
1987
|
+
constructor(tableName, _, tableConfigMap = {}, fileFieldsMap = {}) {
|
|
1899
1988
|
this._tableName = tableName;
|
|
1900
1989
|
this._tableConfigMap = tableConfigMap;
|
|
1990
|
+
this._fileFields = fileFieldsMap[tableName] || {};
|
|
1901
1991
|
this._modelName = upperCamelCase(this._tableName);
|
|
1902
1992
|
}
|
|
1903
1993
|
async create(values) {
|
|
@@ -1967,6 +2057,10 @@ var ModelAPI = class {
|
|
|
1967
2057
|
const name = spanNameForModelAPI(this._modelName, "update");
|
|
1968
2058
|
const db = useDatabase();
|
|
1969
2059
|
return withSpan(name, async (span) => {
|
|
2060
|
+
const fileColumns = Object.keys(values || {}).filter(
|
|
2061
|
+
(k) => k in this._fileFields
|
|
2062
|
+
);
|
|
2063
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1970
2064
|
let builder = db.updateTable(this._tableName).returningAll();
|
|
1971
2065
|
const keys = values ? Object.keys(values) : [];
|
|
1972
2066
|
const row = {};
|
|
@@ -2005,7 +2099,9 @@ var ModelAPI = class {
|
|
|
2005
2099
|
span.setAttribute("sql", builder.compile().sql);
|
|
2006
2100
|
try {
|
|
2007
2101
|
const row2 = await builder.executeTakeFirstOrThrow();
|
|
2008
|
-
|
|
2102
|
+
const result = transformRichDataTypes(camelCaseObject(row2));
|
|
2103
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, result);
|
|
2104
|
+
return result;
|
|
2009
2105
|
} catch (e) {
|
|
2010
2106
|
throw new DatabaseError(e);
|
|
2011
2107
|
}
|
|
@@ -2015,12 +2111,15 @@ var ModelAPI = class {
|
|
|
2015
2111
|
const name = spanNameForModelAPI(this._modelName, "delete");
|
|
2016
2112
|
const db = useDatabase();
|
|
2017
2113
|
return withSpan(name, async (span) => {
|
|
2114
|
+
const fileColumns = Object.keys(this._fileFields);
|
|
2115
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
2018
2116
|
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
2019
2117
|
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2020
2118
|
builder = applyWhereConditions(context7, builder, where);
|
|
2021
2119
|
span.setAttribute("sql", builder.compile().sql);
|
|
2022
2120
|
try {
|
|
2023
2121
|
const row = await builder.executeTakeFirstOrThrow();
|
|
2122
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, null);
|
|
2024
2123
|
return row.id;
|
|
2025
2124
|
} catch (e) {
|
|
2026
2125
|
throw new DatabaseError(e);
|
|
@@ -2035,6 +2134,30 @@ var ModelAPI = class {
|
|
|
2035
2134
|
builder = applyWhereConditions(context7, builder, where);
|
|
2036
2135
|
return new QueryBuilder(this._tableName, context7, builder);
|
|
2037
2136
|
}
|
|
2137
|
+
// Reads the current file-column values for rows matched by `where`.
|
|
2138
|
+
async _selectExistingFileValues(where, fileColumns) {
|
|
2139
|
+
if (fileColumns.length === 0) {
|
|
2140
|
+
return [];
|
|
2141
|
+
}
|
|
2142
|
+
const db = useDatabase();
|
|
2143
|
+
let builder = db.selectFrom(this._tableName).selectAll(this._tableName);
|
|
2144
|
+
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2145
|
+
builder = applyWhereConditions(context7, builder, where);
|
|
2146
|
+
const rows = await builder.execute();
|
|
2147
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
2148
|
+
}
|
|
2149
|
+
// Defers deletion of every old file key in existingRows that is not still
|
|
2150
|
+
// referenced by newRow.
|
|
2151
|
+
_deferReplacedFiles(existingRows, fileColumns, newRow) {
|
|
2152
|
+
const retained = new Set(
|
|
2153
|
+
collectFileKeys(newRow ? [newRow] : [], fileColumns)
|
|
2154
|
+
);
|
|
2155
|
+
for (const key of collectFileKeys(existingRows, fileColumns)) {
|
|
2156
|
+
if (!retained.has(key)) {
|
|
2157
|
+
deferFileDeletion(key);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2038
2161
|
};
|
|
2039
2162
|
async function create(conn, tableName, tableConfigs, values) {
|
|
2040
2163
|
try {
|
|
@@ -2144,6 +2267,23 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
2144
2267
|
}
|
|
2145
2268
|
}
|
|
2146
2269
|
__name(create, "create");
|
|
2270
|
+
function collectFileKeys(rows, fileColumns) {
|
|
2271
|
+
const keys = [];
|
|
2272
|
+
for (const row of rows || []) {
|
|
2273
|
+
if (!row) continue;
|
|
2274
|
+
for (const col of fileColumns) {
|
|
2275
|
+
const v = row[col];
|
|
2276
|
+
if (!v) continue;
|
|
2277
|
+
if (Array.isArray(v)) {
|
|
2278
|
+
for (const f of v) if (f?.key) keys.push(f.key);
|
|
2279
|
+
} else if (v.key) {
|
|
2280
|
+
keys.push(v.key);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return keys;
|
|
2285
|
+
}
|
|
2286
|
+
__name(collectFileKeys, "collectFileKeys");
|
|
2147
2287
|
|
|
2148
2288
|
// src/TaskAPI.js
|
|
2149
2289
|
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
@@ -2815,6 +2955,61 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2815
2955
|
}
|
|
2816
2956
|
};
|
|
2817
2957
|
|
|
2958
|
+
// src/integrationServer.js
|
|
2959
|
+
var import_jsonwebtoken3 = __toESM(require("jsonwebtoken"), 1);
|
|
2960
|
+
function buildHeaders3(identity) {
|
|
2961
|
+
const headers = { "Content-Type": "application/json" };
|
|
2962
|
+
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
2963
|
+
if (!base64pk) {
|
|
2964
|
+
throw new Error(
|
|
2965
|
+
"KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
2969
|
+
const subject = identity && identity.id ? identity.id : "integration-proxy";
|
|
2970
|
+
headers["Authorization"] = "Bearer " + import_jsonwebtoken3.default.sign({}, privateKey, {
|
|
2971
|
+
algorithm: "RS256",
|
|
2972
|
+
expiresIn: 60 * 60 * 24,
|
|
2973
|
+
subject,
|
|
2974
|
+
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
2975
|
+
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
2976
|
+
audience: "integration-proxy",
|
|
2977
|
+
issuer: "https://keel.so"
|
|
2978
|
+
});
|
|
2979
|
+
return headers;
|
|
2980
|
+
}
|
|
2981
|
+
__name(buildHeaders3, "buildHeaders");
|
|
2982
|
+
function getApiUrl3() {
|
|
2983
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
2984
|
+
if (!apiUrl) {
|
|
2985
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
2986
|
+
}
|
|
2987
|
+
return apiUrl;
|
|
2988
|
+
}
|
|
2989
|
+
__name(getApiUrl3, "getApiUrl");
|
|
2990
|
+
function createIntegrationServer(name, identity) {
|
|
2991
|
+
return {
|
|
2992
|
+
do: /* @__PURE__ */ __name(async (request) => {
|
|
2993
|
+
const url = `${getApiUrl3()}/integrations/${encodeURIComponent(
|
|
2994
|
+
name
|
|
2995
|
+
)}/proxy`;
|
|
2996
|
+
const response = await fetch(url, {
|
|
2997
|
+
method: "POST",
|
|
2998
|
+
headers: buildHeaders3(identity ?? null),
|
|
2999
|
+
body: JSON.stringify(request ?? {})
|
|
3000
|
+
});
|
|
3001
|
+
if (!response.ok) {
|
|
3002
|
+
const text = await response.text();
|
|
3003
|
+
throw new Error(
|
|
3004
|
+
`integration "${name}" proxy request failed (${response.status}): ${text}`
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
return await response.json();
|
|
3008
|
+
}, "do")
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
__name(createIntegrationServer, "createIntegrationServer");
|
|
3012
|
+
|
|
2818
3013
|
// src/RequestHeaders.ts
|
|
2819
3014
|
var RequestHeaders = class {
|
|
2820
3015
|
/**
|
|
@@ -3995,6 +4190,7 @@ var datePickerInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3995
4190
|
}, "datePickerInput");
|
|
3996
4191
|
|
|
3997
4192
|
// src/flows/ui/elements/input/file.ts
|
|
4193
|
+
init_File();
|
|
3998
4194
|
var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
3999
4195
|
return {
|
|
4000
4196
|
__type: "input",
|
|
@@ -4020,6 +4216,7 @@ var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
4020
4216
|
}, "fileInput");
|
|
4021
4217
|
|
|
4022
4218
|
// src/flows/ui/elements/input/imageCapture.ts
|
|
4219
|
+
init_File();
|
|
4023
4220
|
var isMultiOptions = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiOptions");
|
|
4024
4221
|
function validateEntry(entry, requireCaption, context7) {
|
|
4025
4222
|
if (!entry?.file?.key) {
|
|
@@ -4510,7 +4707,7 @@ var defaultOpts = {
|
|
|
4510
4707
|
async function insertNewStep(db, runId, name, stage) {
|
|
4511
4708
|
await db.transaction().execute(async (trx) => {
|
|
4512
4709
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
4513
|
-
const existing = await trx.selectFrom("keel.flow_step").select("id").where("run_id", "=", runId).where("name", "=", name).where("status", "
|
|
4710
|
+
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();
|
|
4514
4711
|
if (existing) {
|
|
4515
4712
|
return;
|
|
4516
4713
|
}
|
|
@@ -4533,6 +4730,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4533
4730
|
now: ctx.now,
|
|
4534
4731
|
secrets: ctx.secrets,
|
|
4535
4732
|
trace: ctx.trace,
|
|
4733
|
+
module: ctx.module,
|
|
4536
4734
|
complete: /* @__PURE__ */ __name((options) => {
|
|
4537
4735
|
return {
|
|
4538
4736
|
__type: "ui.complete",
|
|
@@ -4595,6 +4793,11 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4595
4793
|
const raw = completedSteps[0].valueRaw;
|
|
4596
4794
|
return raw == null ? void 0 : JSON.parse(raw);
|
|
4597
4795
|
}
|
|
4796
|
+
if (runningSteps.length >= 1) {
|
|
4797
|
+
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4798
|
+
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4799
|
+
throw new StepCreatedDisrupt();
|
|
4800
|
+
}
|
|
4598
4801
|
if (newSteps.length === 1) {
|
|
4599
4802
|
let result = null;
|
|
4600
4803
|
const claimed = await db.updateTable("keel.flow_step").set({
|
|
@@ -4650,11 +4853,6 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4650
4853
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4651
4854
|
return result;
|
|
4652
4855
|
}
|
|
4653
|
-
if (runningSteps.length >= 1) {
|
|
4654
|
-
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4655
|
-
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4656
|
-
throw new StepCreatedDisrupt();
|
|
4657
|
-
}
|
|
4658
4856
|
await insertNewStep(db, runId, name, options.stage);
|
|
4659
4857
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4660
4858
|
span.setAttribute("step.status", "NEW" /* NEW */);
|
|
@@ -5034,7 +5232,174 @@ async function handleFlow(request, config) {
|
|
|
5034
5232
|
__name(handleFlow, "handleFlow");
|
|
5035
5233
|
|
|
5036
5234
|
// src/index.ts
|
|
5235
|
+
var import_ksuid4 = __toESM(require("ksuid"), 1);
|
|
5236
|
+
init_File();
|
|
5237
|
+
|
|
5238
|
+
// src/notifications/email-template.tsx
|
|
5239
|
+
var import_components = require("@react-email/components");
|
|
5240
|
+
var import_render = require("@react-email/render");
|
|
5241
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
5242
|
+
function renderBody(text) {
|
|
5243
|
+
return text.split("\n").flatMap((line, i) => i === 0 ? [line] : [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}, `br-${i}`), line]);
|
|
5244
|
+
}
|
|
5245
|
+
__name(renderBody, "renderBody");
|
|
5246
|
+
var StockEmailTemplate = /* @__PURE__ */ __name(({ content }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Html, { children: [
|
|
5247
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Head, {}),
|
|
5248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5249
|
+
import_components.Body,
|
|
5250
|
+
{
|
|
5251
|
+
style: {
|
|
5252
|
+
backgroundColor: "#f6f6f6",
|
|
5253
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
5254
|
+
},
|
|
5255
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
5256
|
+
import_components.Container,
|
|
5257
|
+
{
|
|
5258
|
+
style: {
|
|
5259
|
+
backgroundColor: "#ffffff",
|
|
5260
|
+
margin: "40px auto",
|
|
5261
|
+
padding: "40px",
|
|
5262
|
+
maxWidth: "560px",
|
|
5263
|
+
borderRadius: "8px"
|
|
5264
|
+
},
|
|
5265
|
+
children: [
|
|
5266
|
+
content.title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5267
|
+
import_components.Heading,
|
|
5268
|
+
{
|
|
5269
|
+
style: {
|
|
5270
|
+
fontSize: "22px",
|
|
5271
|
+
fontWeight: 600,
|
|
5272
|
+
color: "#111827",
|
|
5273
|
+
margin: "0 0 16px"
|
|
5274
|
+
},
|
|
5275
|
+
children: content.title
|
|
5276
|
+
}
|
|
5277
|
+
),
|
|
5278
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5279
|
+
import_components.Text,
|
|
5280
|
+
{
|
|
5281
|
+
style: {
|
|
5282
|
+
fontSize: "15px",
|
|
5283
|
+
lineHeight: "1.6",
|
|
5284
|
+
color: "#374151",
|
|
5285
|
+
margin: "0 0 24px"
|
|
5286
|
+
},
|
|
5287
|
+
children: renderBody(content.body)
|
|
5288
|
+
}
|
|
5289
|
+
),
|
|
5290
|
+
(content.actions ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Section, { style: { margin: "0 0 12px" }, children: content.actions.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5291
|
+
import_components.Button,
|
|
5292
|
+
{
|
|
5293
|
+
href: action.url,
|
|
5294
|
+
style: {
|
|
5295
|
+
backgroundColor: "#111827",
|
|
5296
|
+
color: "#ffffff",
|
|
5297
|
+
padding: "12px 24px",
|
|
5298
|
+
borderRadius: "6px",
|
|
5299
|
+
fontSize: "14px",
|
|
5300
|
+
fontWeight: 600,
|
|
5301
|
+
textDecoration: "none",
|
|
5302
|
+
display: "inline-block",
|
|
5303
|
+
// Lay buttons out side by side; space between adjacent buttons.
|
|
5304
|
+
marginRight: i < content.actions.length - 1 ? "12px" : "0"
|
|
5305
|
+
},
|
|
5306
|
+
children: action.label
|
|
5307
|
+
},
|
|
5308
|
+
i
|
|
5309
|
+
)) })
|
|
5310
|
+
]
|
|
5311
|
+
}
|
|
5312
|
+
)
|
|
5313
|
+
}
|
|
5314
|
+
)
|
|
5315
|
+
] }), "StockEmailTemplate");
|
|
5316
|
+
async function renderEmailHtml(content) {
|
|
5317
|
+
return (0, import_render.render)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StockEmailTemplate, { content }), { pretty: false });
|
|
5318
|
+
}
|
|
5319
|
+
__name(renderEmailHtml, "renderEmailHtml");
|
|
5320
|
+
|
|
5321
|
+
// src/notifications/notify.ts
|
|
5037
5322
|
var import_ksuid3 = __toESM(require("ksuid"), 1);
|
|
5323
|
+
function getApiUrl4() {
|
|
5324
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
5325
|
+
if (!apiUrl) {
|
|
5326
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
5327
|
+
}
|
|
5328
|
+
return apiUrl;
|
|
5329
|
+
}
|
|
5330
|
+
__name(getApiUrl4, "getApiUrl");
|
|
5331
|
+
function toArray(v) {
|
|
5332
|
+
if (v === void 0) return [];
|
|
5333
|
+
return Array.isArray(v) ? v : [v];
|
|
5334
|
+
}
|
|
5335
|
+
__name(toArray, "toArray");
|
|
5336
|
+
function normaliseGroup(group) {
|
|
5337
|
+
if (!group) return [];
|
|
5338
|
+
const refs = [];
|
|
5339
|
+
for (const email of toArray(group.emails)) {
|
|
5340
|
+
refs.push({ kind: "email", value: email });
|
|
5341
|
+
}
|
|
5342
|
+
for (const user of toArray(group.users)) {
|
|
5343
|
+
refs.push({
|
|
5344
|
+
kind: "user",
|
|
5345
|
+
value: typeof user === "string" ? user : user.id
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5348
|
+
for (const identity of toArray(group.identities)) {
|
|
5349
|
+
refs.push({
|
|
5350
|
+
kind: "identity",
|
|
5351
|
+
value: typeof identity === "string" ? identity : identity.id
|
|
5352
|
+
});
|
|
5353
|
+
}
|
|
5354
|
+
for (const team of toArray(group.teams)) {
|
|
5355
|
+
refs.push({ kind: "team", value: team });
|
|
5356
|
+
}
|
|
5357
|
+
return refs;
|
|
5358
|
+
}
|
|
5359
|
+
__name(normaliseGroup, "normaliseGroup");
|
|
5360
|
+
async function notifyEmail(input) {
|
|
5361
|
+
return withSpan("notify.email", async () => {
|
|
5362
|
+
const id = import_ksuid3.default.randomSync().string;
|
|
5363
|
+
let rendered;
|
|
5364
|
+
let content;
|
|
5365
|
+
if (typeof input.content === "string") {
|
|
5366
|
+
rendered = input.content;
|
|
5367
|
+
content = void 0;
|
|
5368
|
+
} else {
|
|
5369
|
+
rendered = await renderEmailHtml(input.content);
|
|
5370
|
+
content = input.content;
|
|
5371
|
+
}
|
|
5372
|
+
const body = {
|
|
5373
|
+
id,
|
|
5374
|
+
subject: input.subject,
|
|
5375
|
+
rendered,
|
|
5376
|
+
content,
|
|
5377
|
+
recipients: {
|
|
5378
|
+
to: normaliseGroup(input.recipients.to),
|
|
5379
|
+
cc: normaliseGroup(input.recipients.cc),
|
|
5380
|
+
bcc: normaliseGroup(input.recipients.bcc)
|
|
5381
|
+
}
|
|
5382
|
+
};
|
|
5383
|
+
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5384
|
+
method: "POST",
|
|
5385
|
+
headers: { "Content-Type": "application/json" },
|
|
5386
|
+
body: JSON.stringify(body)
|
|
5387
|
+
});
|
|
5388
|
+
if (!response.ok) {
|
|
5389
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
5390
|
+
throw new Error(
|
|
5391
|
+
`Failed to send notification: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
|
|
5392
|
+
);
|
|
5393
|
+
}
|
|
5394
|
+
await response.body?.cancel();
|
|
5395
|
+
return id;
|
|
5396
|
+
});
|
|
5397
|
+
}
|
|
5398
|
+
__name(notifyEmail, "notifyEmail");
|
|
5399
|
+
function createNotifier() {
|
|
5400
|
+
return { email: notifyEmail };
|
|
5401
|
+
}
|
|
5402
|
+
__name(createNotifier, "createNotifier");
|
|
5038
5403
|
|
|
5039
5404
|
// src/experimental.ts
|
|
5040
5405
|
var experimental_exports = {};
|
|
@@ -5399,9 +5764,10 @@ __name(LlmFlowStep, "LlmFlowStep");
|
|
|
5399
5764
|
var import_zod = require("zod");
|
|
5400
5765
|
var createTraceAPI2 = createTraceAPI;
|
|
5401
5766
|
function ksuid() {
|
|
5402
|
-
return
|
|
5767
|
+
return import_ksuid4.default.randomSync().string;
|
|
5403
5768
|
}
|
|
5404
5769
|
__name(ksuid, "ksuid");
|
|
5770
|
+
var notify = createNotifier();
|
|
5405
5771
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5406
5772
|
0 && (module.exports = {
|
|
5407
5773
|
Duration,
|
|
@@ -5424,6 +5790,8 @@ __name(ksuid, "ksuid");
|
|
|
5424
5790
|
TaskAPI,
|
|
5425
5791
|
checkBuiltInPermissions,
|
|
5426
5792
|
createFlowContext,
|
|
5793
|
+
createIntegrationServer,
|
|
5794
|
+
createNotifier,
|
|
5427
5795
|
createTraceAPI,
|
|
5428
5796
|
experimental,
|
|
5429
5797
|
handleFlow,
|
|
@@ -5431,7 +5799,9 @@ __name(ksuid, "ksuid");
|
|
|
5431
5799
|
handleRequest,
|
|
5432
5800
|
handleRoute,
|
|
5433
5801
|
handleSubscriber,
|
|
5802
|
+
insertNewStep,
|
|
5434
5803
|
ksuid,
|
|
5804
|
+
notify,
|
|
5435
5805
|
tracing,
|
|
5436
5806
|
useDatabase,
|
|
5437
5807
|
z
|