@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.cjs +729 -288
- 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 +740 -292
- 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
|
|
@@ -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
|
-
|
|
448
|
-
return dbInstance.run(transaction,
|
|
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
|
-
|
|
454
|
-
return dbInstance.run(sDb,
|
|
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
|
-
|
|
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
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
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
|
-
|
|
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", "
|
|
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
|
|
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
|
-
|
|
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
|