@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.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
|
|
@@ -203,6 +530,20 @@ function isRichType(obj) {
|
|
|
203
530
|
}
|
|
204
531
|
__name(isRichType, "isRichType");
|
|
205
532
|
|
|
533
|
+
// src/jsonColumnValue.js
|
|
534
|
+
var JsonColumnValue = class {
|
|
535
|
+
static {
|
|
536
|
+
__name(this, "JsonColumnValue");
|
|
537
|
+
}
|
|
538
|
+
constructor(value) {
|
|
539
|
+
this.value = value;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
function parseJsonColumnValue(value) {
|
|
543
|
+
return new JsonColumnValue(JSON.parse(value));
|
|
544
|
+
}
|
|
545
|
+
__name(parseJsonColumnValue, "parseJsonColumnValue");
|
|
546
|
+
|
|
206
547
|
// src/camelCasePlugin.js
|
|
207
548
|
var KeelCamelCasePlugin = class {
|
|
208
549
|
static {
|
|
@@ -210,41 +551,77 @@ var KeelCamelCasePlugin = class {
|
|
|
210
551
|
}
|
|
211
552
|
constructor(opt) {
|
|
212
553
|
this.opt = opt;
|
|
554
|
+
this.auditQueryIds = /* @__PURE__ */ new WeakSet();
|
|
213
555
|
this.CamelCasePlugin = new import_kysely2.CamelCasePlugin({
|
|
214
556
|
...opt,
|
|
215
557
|
underscoreBeforeDigits: true
|
|
216
558
|
});
|
|
217
559
|
}
|
|
218
560
|
transformQuery(args) {
|
|
561
|
+
if (args.queryId && referencesTable(args.node, "keel_audit")) {
|
|
562
|
+
this.auditQueryIds.add(args.queryId);
|
|
563
|
+
}
|
|
219
564
|
return this.CamelCasePlugin.transformQuery(args);
|
|
220
565
|
}
|
|
221
566
|
async transformResult(args) {
|
|
222
567
|
if (args.result.rows && Array.isArray(args.result.rows)) {
|
|
568
|
+
const mapAuditJson = args.queryId && this.auditQueryIds.has(args.queryId);
|
|
223
569
|
return {
|
|
224
570
|
...args.result,
|
|
225
|
-
rows: args.result.rows.map((row) => this.mapRow(row))
|
|
571
|
+
rows: args.result.rows.map((row) => this.mapRow(row, { mapAuditJson }))
|
|
226
572
|
};
|
|
227
573
|
}
|
|
228
574
|
return args.result;
|
|
229
575
|
}
|
|
230
|
-
mapRow(row) {
|
|
576
|
+
mapRow(row, context7 = {}) {
|
|
231
577
|
return Object.keys(row).reduce((obj, key) => {
|
|
232
578
|
if (key.endsWith("__sequence")) {
|
|
233
579
|
return obj;
|
|
234
580
|
}
|
|
235
581
|
let value = row[key];
|
|
582
|
+
if (value instanceof JsonColumnValue) {
|
|
583
|
+
value = shouldMapJsonColumn(row, key, context7) && canMap(value.value, this.opt) ? this.mapRow(value.value, context7) : value.value;
|
|
584
|
+
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
585
|
+
return obj;
|
|
586
|
+
}
|
|
236
587
|
if (Array.isArray(value)) {
|
|
237
588
|
value = value.map(
|
|
238
|
-
(it) => canMap(it, this.opt) ? this.mapRow(it) : it
|
|
589
|
+
(it) => canMap(it, this.opt) ? this.mapRow(it, context7) : it
|
|
239
590
|
);
|
|
240
591
|
} else if (canMap(value, this.opt)) {
|
|
241
|
-
value = this.mapRow(value);
|
|
592
|
+
value = this.mapRow(value, context7);
|
|
242
593
|
}
|
|
243
594
|
obj[this.CamelCasePlugin.camelCase(key)] = value;
|
|
244
595
|
return obj;
|
|
245
596
|
}, {});
|
|
246
597
|
}
|
|
247
598
|
};
|
|
599
|
+
function shouldMapJsonColumn(row, key, context7) {
|
|
600
|
+
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;
|
|
601
|
+
}
|
|
602
|
+
__name(shouldMapJsonColumn, "shouldMapJsonColumn");
|
|
603
|
+
function referencesTable(node, tableName) {
|
|
604
|
+
if (!node || typeof node !== "object") {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (node.kind === "IdentifierNode" && (node.name === tableName || node.name === "keelAudit")) {
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
if (node.kind === "RawNode" && Array.isArray(node.sqlFragments) && node.sqlFragments.some(
|
|
611
|
+
(fragment) => new RegExp(`(^|[^A-Za-z0-9_])${tableName}($|[^A-Za-z0-9_])`, "i").test(
|
|
612
|
+
fragment
|
|
613
|
+
)
|
|
614
|
+
)) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
return Object.values(node).some((value) => {
|
|
618
|
+
if (Array.isArray(value)) {
|
|
619
|
+
return value.some((item) => referencesTable(item, tableName));
|
|
620
|
+
}
|
|
621
|
+
return referencesTable(value, tableName);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
__name(referencesTable, "referencesTable");
|
|
248
625
|
function canMap(obj, opt) {
|
|
249
626
|
return isPlainObject(obj) && !opt?.maintainNestedObjectKeys && !isRichType(obj);
|
|
250
627
|
}
|
|
@@ -501,20 +878,44 @@ var KEEL_INTERNAL_CHILDREN = "includeChildrenSpans";
|
|
|
501
878
|
var import_ws = __toESM(require("ws"), 1);
|
|
502
879
|
var import_node_fs = require("fs");
|
|
503
880
|
var dbInstance = new import_node_async_hooks2.AsyncLocalStorage();
|
|
881
|
+
var fileCleanupStore = new import_node_async_hooks2.AsyncLocalStorage();
|
|
882
|
+
function deferFileDeletion(key) {
|
|
883
|
+
fileCleanupStore.getStore()?.add(key);
|
|
884
|
+
}
|
|
885
|
+
__name(deferFileDeletion, "deferFileDeletion");
|
|
886
|
+
async function flushFileDeletions(keys) {
|
|
887
|
+
if (keys.size === 0) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const { deleteStoredFile: deleteStoredFile2 } = await Promise.resolve().then(() => (init_File(), File_exports));
|
|
891
|
+
for (const key of keys) {
|
|
892
|
+
try {
|
|
893
|
+
await deleteStoredFile2(key);
|
|
894
|
+
} catch (e) {
|
|
895
|
+
console.error("failed to delete orphaned file from storage", e);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
__name(flushFileDeletions, "flushFileDeletions");
|
|
504
900
|
var vitestDb = null;
|
|
505
901
|
async function withDatabase(db, requiresTransaction, cb) {
|
|
902
|
+
const pending = /* @__PURE__ */ new Set();
|
|
506
903
|
if (requiresTransaction) {
|
|
507
|
-
|
|
508
|
-
return dbInstance.run(transaction,
|
|
509
|
-
return cb({ transaction });
|
|
904
|
+
const result2 = await db.transaction().execute(async (transaction) => {
|
|
905
|
+
return dbInstance.run(transaction, () => {
|
|
906
|
+
return fileCleanupStore.run(pending, () => cb({ transaction }));
|
|
510
907
|
});
|
|
511
908
|
});
|
|
909
|
+
await flushFileDeletions(pending);
|
|
910
|
+
return result2;
|
|
512
911
|
}
|
|
513
|
-
|
|
514
|
-
return dbInstance.run(sDb,
|
|
515
|
-
return cb({ sDb });
|
|
912
|
+
const result = await db.connection().execute(async (sDb) => {
|
|
913
|
+
return dbInstance.run(sDb, () => {
|
|
914
|
+
return fileCleanupStore.run(pending, () => cb({ sDb }));
|
|
516
915
|
});
|
|
517
916
|
});
|
|
917
|
+
await flushFileDeletions(pending);
|
|
918
|
+
return result;
|
|
518
919
|
}
|
|
519
920
|
__name(withDatabase, "withDatabase");
|
|
520
921
|
function useDatabase() {
|
|
@@ -621,6 +1022,8 @@ function getDialect(connString) {
|
|
|
621
1022
|
import_pg.types.builtins.INTERVAL,
|
|
622
1023
|
(val) => new Duration(val)
|
|
623
1024
|
);
|
|
1025
|
+
import_pg.types.setTypeParser(import_pg.types.builtins.JSON, parseJsonColumnValue);
|
|
1026
|
+
import_pg.types.setTypeParser(import_pg.types.builtins.JSONB, parseJsonColumnValue);
|
|
624
1027
|
const poolConfig = {
|
|
625
1028
|
Client: InstrumentedClient,
|
|
626
1029
|
// Increased idle time before closing a connection in the local pool (from 10s default).
|
|
@@ -655,6 +1058,8 @@ function getDialect(connString) {
|
|
|
655
1058
|
import_pg.types.builtins.INTERVAL,
|
|
656
1059
|
(val) => new Duration(val)
|
|
657
1060
|
);
|
|
1061
|
+
neon.types.setTypeParser(import_pg.types.builtins.JSON, parseJsonColumnValue);
|
|
1062
|
+
neon.types.setTypeParser(import_pg.types.builtins.JSONB, parseJsonColumnValue);
|
|
658
1063
|
neon.neonConfig.webSocketConstructor = import_ws.default;
|
|
659
1064
|
const pool = new InstrumentedNeonServerlessPool({
|
|
660
1065
|
// If connString is not passed fall back to reading from env var
|
|
@@ -689,271 +1094,8 @@ function getDialect(connString) {
|
|
|
689
1094
|
}
|
|
690
1095
|
__name(getDialect, "getDialect");
|
|
691
1096
|
|
|
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
1097
|
// src/parsing.js
|
|
1098
|
+
init_File();
|
|
957
1099
|
var dateFormat = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
958
1100
|
function parseInputs(inputs) {
|
|
959
1101
|
if (inputs != null && typeof inputs === "object") {
|
|
@@ -1886,6 +2028,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1886
2028
|
};
|
|
1887
2029
|
|
|
1888
2030
|
// src/ModelAPI.js
|
|
2031
|
+
init_File();
|
|
1889
2032
|
var ModelAPI = class {
|
|
1890
2033
|
static {
|
|
1891
2034
|
__name(this, "ModelAPI");
|
|
@@ -1895,9 +2038,10 @@ var ModelAPI = class {
|
|
|
1895
2038
|
* @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
|
|
1896
2039
|
* @param {TableConfigMap} tableConfigMap
|
|
1897
2040
|
*/
|
|
1898
|
-
constructor(tableName, _, tableConfigMap = {}) {
|
|
2041
|
+
constructor(tableName, _, tableConfigMap = {}, fileFieldsMap = {}) {
|
|
1899
2042
|
this._tableName = tableName;
|
|
1900
2043
|
this._tableConfigMap = tableConfigMap;
|
|
2044
|
+
this._fileFields = fileFieldsMap[tableName] || {};
|
|
1901
2045
|
this._modelName = upperCamelCase(this._tableName);
|
|
1902
2046
|
}
|
|
1903
2047
|
async create(values) {
|
|
@@ -1967,6 +2111,10 @@ var ModelAPI = class {
|
|
|
1967
2111
|
const name = spanNameForModelAPI(this._modelName, "update");
|
|
1968
2112
|
const db = useDatabase();
|
|
1969
2113
|
return withSpan(name, async (span) => {
|
|
2114
|
+
const fileColumns = Object.keys(values || {}).filter(
|
|
2115
|
+
(k) => k in this._fileFields
|
|
2116
|
+
);
|
|
2117
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1970
2118
|
let builder = db.updateTable(this._tableName).returningAll();
|
|
1971
2119
|
const keys = values ? Object.keys(values) : [];
|
|
1972
2120
|
const row = {};
|
|
@@ -2005,7 +2153,9 @@ var ModelAPI = class {
|
|
|
2005
2153
|
span.setAttribute("sql", builder.compile().sql);
|
|
2006
2154
|
try {
|
|
2007
2155
|
const row2 = await builder.executeTakeFirstOrThrow();
|
|
2008
|
-
|
|
2156
|
+
const result = transformRichDataTypes(camelCaseObject(row2));
|
|
2157
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, result);
|
|
2158
|
+
return result;
|
|
2009
2159
|
} catch (e) {
|
|
2010
2160
|
throw new DatabaseError(e);
|
|
2011
2161
|
}
|
|
@@ -2015,12 +2165,15 @@ var ModelAPI = class {
|
|
|
2015
2165
|
const name = spanNameForModelAPI(this._modelName, "delete");
|
|
2016
2166
|
const db = useDatabase();
|
|
2017
2167
|
return withSpan(name, async (span) => {
|
|
2168
|
+
const fileColumns = Object.keys(this._fileFields);
|
|
2169
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
2018
2170
|
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
2019
2171
|
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2020
2172
|
builder = applyWhereConditions(context7, builder, where);
|
|
2021
2173
|
span.setAttribute("sql", builder.compile().sql);
|
|
2022
2174
|
try {
|
|
2023
2175
|
const row = await builder.executeTakeFirstOrThrow();
|
|
2176
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, null);
|
|
2024
2177
|
return row.id;
|
|
2025
2178
|
} catch (e) {
|
|
2026
2179
|
throw new DatabaseError(e);
|
|
@@ -2035,6 +2188,30 @@ var ModelAPI = class {
|
|
|
2035
2188
|
builder = applyWhereConditions(context7, builder, where);
|
|
2036
2189
|
return new QueryBuilder(this._tableName, context7, builder);
|
|
2037
2190
|
}
|
|
2191
|
+
// Reads the current file-column values for rows matched by `where`.
|
|
2192
|
+
async _selectExistingFileValues(where, fileColumns) {
|
|
2193
|
+
if (fileColumns.length === 0) {
|
|
2194
|
+
return [];
|
|
2195
|
+
}
|
|
2196
|
+
const db = useDatabase();
|
|
2197
|
+
let builder = db.selectFrom(this._tableName).selectAll(this._tableName);
|
|
2198
|
+
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2199
|
+
builder = applyWhereConditions(context7, builder, where);
|
|
2200
|
+
const rows = await builder.execute();
|
|
2201
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
2202
|
+
}
|
|
2203
|
+
// Defers deletion of every old file key in existingRows that is not still
|
|
2204
|
+
// referenced by newRow.
|
|
2205
|
+
_deferReplacedFiles(existingRows, fileColumns, newRow) {
|
|
2206
|
+
const retained = new Set(
|
|
2207
|
+
collectFileKeys(newRow ? [newRow] : [], fileColumns)
|
|
2208
|
+
);
|
|
2209
|
+
for (const key of collectFileKeys(existingRows, fileColumns)) {
|
|
2210
|
+
if (!retained.has(key)) {
|
|
2211
|
+
deferFileDeletion(key);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2038
2215
|
};
|
|
2039
2216
|
async function create(conn, tableName, tableConfigs, values) {
|
|
2040
2217
|
try {
|
|
@@ -2144,6 +2321,23 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
2144
2321
|
}
|
|
2145
2322
|
}
|
|
2146
2323
|
__name(create, "create");
|
|
2324
|
+
function collectFileKeys(rows, fileColumns) {
|
|
2325
|
+
const keys = [];
|
|
2326
|
+
for (const row of rows || []) {
|
|
2327
|
+
if (!row) continue;
|
|
2328
|
+
for (const col of fileColumns) {
|
|
2329
|
+
const v = row[col];
|
|
2330
|
+
if (!v) continue;
|
|
2331
|
+
if (Array.isArray(v)) {
|
|
2332
|
+
for (const f of v) if (f?.key) keys.push(f.key);
|
|
2333
|
+
} else if (v.key) {
|
|
2334
|
+
keys.push(v.key);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return keys;
|
|
2339
|
+
}
|
|
2340
|
+
__name(collectFileKeys, "collectFileKeys");
|
|
2147
2341
|
|
|
2148
2342
|
// src/TaskAPI.js
|
|
2149
2343
|
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
@@ -2815,6 +3009,61 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2815
3009
|
}
|
|
2816
3010
|
};
|
|
2817
3011
|
|
|
3012
|
+
// src/integrationServer.js
|
|
3013
|
+
var import_jsonwebtoken3 = __toESM(require("jsonwebtoken"), 1);
|
|
3014
|
+
function buildHeaders3(identity) {
|
|
3015
|
+
const headers = { "Content-Type": "application/json" };
|
|
3016
|
+
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
3017
|
+
if (!base64pk) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
"KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
3023
|
+
const subject = identity && identity.id ? identity.id : "integration-proxy";
|
|
3024
|
+
headers["Authorization"] = "Bearer " + import_jsonwebtoken3.default.sign({}, privateKey, {
|
|
3025
|
+
algorithm: "RS256",
|
|
3026
|
+
expiresIn: 60 * 60 * 24,
|
|
3027
|
+
subject,
|
|
3028
|
+
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
3029
|
+
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
3030
|
+
audience: "integration-proxy",
|
|
3031
|
+
issuer: "https://keel.so"
|
|
3032
|
+
});
|
|
3033
|
+
return headers;
|
|
3034
|
+
}
|
|
3035
|
+
__name(buildHeaders3, "buildHeaders");
|
|
3036
|
+
function getApiUrl3() {
|
|
3037
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
3038
|
+
if (!apiUrl) {
|
|
3039
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
3040
|
+
}
|
|
3041
|
+
return apiUrl;
|
|
3042
|
+
}
|
|
3043
|
+
__name(getApiUrl3, "getApiUrl");
|
|
3044
|
+
function createIntegrationServer(name, identity) {
|
|
3045
|
+
return {
|
|
3046
|
+
do: /* @__PURE__ */ __name(async (request) => {
|
|
3047
|
+
const url = `${getApiUrl3()}/integrations/${encodeURIComponent(
|
|
3048
|
+
name
|
|
3049
|
+
)}/proxy`;
|
|
3050
|
+
const response = await fetch(url, {
|
|
3051
|
+
method: "POST",
|
|
3052
|
+
headers: buildHeaders3(identity ?? null),
|
|
3053
|
+
body: JSON.stringify(request ?? {})
|
|
3054
|
+
});
|
|
3055
|
+
if (!response.ok) {
|
|
3056
|
+
const text = await response.text();
|
|
3057
|
+
throw new Error(
|
|
3058
|
+
`integration "${name}" proxy request failed (${response.status}): ${text}`
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
return await response.json();
|
|
3062
|
+
}, "do")
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
__name(createIntegrationServer, "createIntegrationServer");
|
|
3066
|
+
|
|
2818
3067
|
// src/RequestHeaders.ts
|
|
2819
3068
|
var RequestHeaders = class {
|
|
2820
3069
|
/**
|
|
@@ -3563,12 +3812,29 @@ async function applyElementGetData(content, data) {
|
|
|
3563
3812
|
return data;
|
|
3564
3813
|
}
|
|
3565
3814
|
__name(applyElementGetData, "applyElementGetData");
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3815
|
+
var ITERATOR_SCOPED_ELEMENT = /^([^[]+)\[(\d+)\]:(.+)$/;
|
|
3816
|
+
async function callbackFn(elements, elementPath, callbackName, data) {
|
|
3817
|
+
const scoped = ITERATOR_SCOPED_ELEMENT.exec(elementPath);
|
|
3818
|
+
let searchScope = elements;
|
|
3819
|
+
let elementName = elementPath;
|
|
3820
|
+
let iteratorName = null;
|
|
3821
|
+
if (scoped) {
|
|
3822
|
+
iteratorName = scoped[1];
|
|
3823
|
+
elementName = scoped[3];
|
|
3824
|
+
const iter = elements.find(
|
|
3825
|
+
(el) => el?.uiConfig?.__type === "ui.iterator" && el.uiConfig.name === iteratorName
|
|
3826
|
+
);
|
|
3827
|
+
if (!iter) {
|
|
3828
|
+
throw new Error(`Iterator with name ${iteratorName} not found`);
|
|
3829
|
+
}
|
|
3830
|
+
searchScope = iter.uiConfig.content;
|
|
3831
|
+
}
|
|
3832
|
+
const element = searchScope.find(
|
|
3833
|
+
(el) => el?.uiConfig && el.uiConfig.name === elementName
|
|
3569
3834
|
);
|
|
3570
3835
|
if (!element) {
|
|
3571
|
-
|
|
3836
|
+
const where = iteratorName ? ` in iterator ${iteratorName}` : "";
|
|
3837
|
+
throw new Error(`Element with name ${elementName} not found${where}`);
|
|
3572
3838
|
}
|
|
3573
3839
|
const cb = element[callbackName];
|
|
3574
3840
|
if (typeof cb !== "function") {
|
|
@@ -3995,6 +4261,7 @@ var datePickerInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3995
4261
|
}, "datePickerInput");
|
|
3996
4262
|
|
|
3997
4263
|
// src/flows/ui/elements/input/file.ts
|
|
4264
|
+
init_File();
|
|
3998
4265
|
var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
3999
4266
|
return {
|
|
4000
4267
|
__type: "input",
|
|
@@ -4020,6 +4287,7 @@ var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
4020
4287
|
}, "fileInput");
|
|
4021
4288
|
|
|
4022
4289
|
// src/flows/ui/elements/input/imageCapture.ts
|
|
4290
|
+
init_File();
|
|
4023
4291
|
var isMultiOptions = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiOptions");
|
|
4024
4292
|
function validateEntry(entry, requireCaption, context7) {
|
|
4025
4293
|
if (!entry?.file?.key) {
|
|
@@ -4510,7 +4778,7 @@ var defaultOpts = {
|
|
|
4510
4778
|
async function insertNewStep(db, runId, name, stage) {
|
|
4511
4779
|
await db.transaction().execute(async (trx) => {
|
|
4512
4780
|
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", "
|
|
4781
|
+
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
4782
|
if (existing) {
|
|
4515
4783
|
return;
|
|
4516
4784
|
}
|
|
@@ -4533,6 +4801,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4533
4801
|
now: ctx.now,
|
|
4534
4802
|
secrets: ctx.secrets,
|
|
4535
4803
|
trace: ctx.trace,
|
|
4804
|
+
module: ctx.module,
|
|
4536
4805
|
complete: /* @__PURE__ */ __name((options) => {
|
|
4537
4806
|
return {
|
|
4538
4807
|
__type: "ui.complete",
|
|
@@ -4595,6 +4864,11 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4595
4864
|
const raw = completedSteps[0].valueRaw;
|
|
4596
4865
|
return raw == null ? void 0 : JSON.parse(raw);
|
|
4597
4866
|
}
|
|
4867
|
+
if (runningSteps.length >= 1) {
|
|
4868
|
+
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4869
|
+
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4870
|
+
throw new StepCreatedDisrupt();
|
|
4871
|
+
}
|
|
4598
4872
|
if (newSteps.length === 1) {
|
|
4599
4873
|
let result = null;
|
|
4600
4874
|
const claimed = await db.updateTable("keel.flow_step").set({
|
|
@@ -4650,11 +4924,6 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4650
4924
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4651
4925
|
return result;
|
|
4652
4926
|
}
|
|
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
4927
|
await insertNewStep(db, runId, name, options.stage);
|
|
4659
4928
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4660
4929
|
span.setAttribute("step.status", "NEW" /* NEW */);
|
|
@@ -5034,7 +5303,174 @@ async function handleFlow(request, config) {
|
|
|
5034
5303
|
__name(handleFlow, "handleFlow");
|
|
5035
5304
|
|
|
5036
5305
|
// src/index.ts
|
|
5306
|
+
var import_ksuid4 = __toESM(require("ksuid"), 1);
|
|
5307
|
+
init_File();
|
|
5308
|
+
|
|
5309
|
+
// src/notifications/email-template.tsx
|
|
5310
|
+
var import_components = require("@react-email/components");
|
|
5311
|
+
var import_render = require("@react-email/render");
|
|
5312
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
5313
|
+
function renderBody(text) {
|
|
5314
|
+
return text.split("\n").flatMap((line, i) => i === 0 ? [line] : [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}, `br-${i}`), line]);
|
|
5315
|
+
}
|
|
5316
|
+
__name(renderBody, "renderBody");
|
|
5317
|
+
var StockEmailTemplate = /* @__PURE__ */ __name(({ content }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Html, { children: [
|
|
5318
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Head, {}),
|
|
5319
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5320
|
+
import_components.Body,
|
|
5321
|
+
{
|
|
5322
|
+
style: {
|
|
5323
|
+
backgroundColor: "#f6f6f6",
|
|
5324
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
5325
|
+
},
|
|
5326
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
5327
|
+
import_components.Container,
|
|
5328
|
+
{
|
|
5329
|
+
style: {
|
|
5330
|
+
backgroundColor: "#ffffff",
|
|
5331
|
+
margin: "40px auto",
|
|
5332
|
+
padding: "40px",
|
|
5333
|
+
maxWidth: "560px",
|
|
5334
|
+
borderRadius: "8px"
|
|
5335
|
+
},
|
|
5336
|
+
children: [
|
|
5337
|
+
content.title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5338
|
+
import_components.Heading,
|
|
5339
|
+
{
|
|
5340
|
+
style: {
|
|
5341
|
+
fontSize: "22px",
|
|
5342
|
+
fontWeight: 600,
|
|
5343
|
+
color: "#111827",
|
|
5344
|
+
margin: "0 0 16px"
|
|
5345
|
+
},
|
|
5346
|
+
children: content.title
|
|
5347
|
+
}
|
|
5348
|
+
),
|
|
5349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5350
|
+
import_components.Text,
|
|
5351
|
+
{
|
|
5352
|
+
style: {
|
|
5353
|
+
fontSize: "15px",
|
|
5354
|
+
lineHeight: "1.6",
|
|
5355
|
+
color: "#374151",
|
|
5356
|
+
margin: "0 0 24px"
|
|
5357
|
+
},
|
|
5358
|
+
children: renderBody(content.body)
|
|
5359
|
+
}
|
|
5360
|
+
),
|
|
5361
|
+
(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)(
|
|
5362
|
+
import_components.Button,
|
|
5363
|
+
{
|
|
5364
|
+
href: action.url,
|
|
5365
|
+
style: {
|
|
5366
|
+
backgroundColor: "#111827",
|
|
5367
|
+
color: "#ffffff",
|
|
5368
|
+
padding: "12px 24px",
|
|
5369
|
+
borderRadius: "6px",
|
|
5370
|
+
fontSize: "14px",
|
|
5371
|
+
fontWeight: 600,
|
|
5372
|
+
textDecoration: "none",
|
|
5373
|
+
display: "inline-block",
|
|
5374
|
+
// Lay buttons out side by side; space between adjacent buttons.
|
|
5375
|
+
marginRight: i < content.actions.length - 1 ? "12px" : "0"
|
|
5376
|
+
},
|
|
5377
|
+
children: action.label
|
|
5378
|
+
},
|
|
5379
|
+
i
|
|
5380
|
+
)) })
|
|
5381
|
+
]
|
|
5382
|
+
}
|
|
5383
|
+
)
|
|
5384
|
+
}
|
|
5385
|
+
)
|
|
5386
|
+
] }), "StockEmailTemplate");
|
|
5387
|
+
async function renderEmailHtml(content) {
|
|
5388
|
+
return (0, import_render.render)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StockEmailTemplate, { content }), { pretty: false });
|
|
5389
|
+
}
|
|
5390
|
+
__name(renderEmailHtml, "renderEmailHtml");
|
|
5391
|
+
|
|
5392
|
+
// src/notifications/notify.ts
|
|
5037
5393
|
var import_ksuid3 = __toESM(require("ksuid"), 1);
|
|
5394
|
+
function getApiUrl4() {
|
|
5395
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
5396
|
+
if (!apiUrl) {
|
|
5397
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
5398
|
+
}
|
|
5399
|
+
return apiUrl;
|
|
5400
|
+
}
|
|
5401
|
+
__name(getApiUrl4, "getApiUrl");
|
|
5402
|
+
function toArray(v) {
|
|
5403
|
+
if (v === void 0) return [];
|
|
5404
|
+
return Array.isArray(v) ? v : [v];
|
|
5405
|
+
}
|
|
5406
|
+
__name(toArray, "toArray");
|
|
5407
|
+
function normaliseGroup(group) {
|
|
5408
|
+
if (!group) return [];
|
|
5409
|
+
const refs = [];
|
|
5410
|
+
for (const email of toArray(group.emails)) {
|
|
5411
|
+
refs.push({ kind: "email", value: email });
|
|
5412
|
+
}
|
|
5413
|
+
for (const user of toArray(group.users)) {
|
|
5414
|
+
refs.push({
|
|
5415
|
+
kind: "user",
|
|
5416
|
+
value: typeof user === "string" ? user : user.id
|
|
5417
|
+
});
|
|
5418
|
+
}
|
|
5419
|
+
for (const identity of toArray(group.identities)) {
|
|
5420
|
+
refs.push({
|
|
5421
|
+
kind: "identity",
|
|
5422
|
+
value: typeof identity === "string" ? identity : identity.id
|
|
5423
|
+
});
|
|
5424
|
+
}
|
|
5425
|
+
for (const team of toArray(group.teams)) {
|
|
5426
|
+
refs.push({ kind: "team", value: team });
|
|
5427
|
+
}
|
|
5428
|
+
return refs;
|
|
5429
|
+
}
|
|
5430
|
+
__name(normaliseGroup, "normaliseGroup");
|
|
5431
|
+
async function notifyEmail(input) {
|
|
5432
|
+
return withSpan("notify.email", async () => {
|
|
5433
|
+
const id = import_ksuid3.default.randomSync().string;
|
|
5434
|
+
let rendered;
|
|
5435
|
+
let content;
|
|
5436
|
+
if (typeof input.content === "string") {
|
|
5437
|
+
rendered = input.content;
|
|
5438
|
+
content = void 0;
|
|
5439
|
+
} else {
|
|
5440
|
+
rendered = await renderEmailHtml(input.content);
|
|
5441
|
+
content = input.content;
|
|
5442
|
+
}
|
|
5443
|
+
const body = {
|
|
5444
|
+
id,
|
|
5445
|
+
subject: input.subject,
|
|
5446
|
+
rendered,
|
|
5447
|
+
content,
|
|
5448
|
+
recipients: {
|
|
5449
|
+
to: normaliseGroup(input.recipients.to),
|
|
5450
|
+
cc: normaliseGroup(input.recipients.cc),
|
|
5451
|
+
bcc: normaliseGroup(input.recipients.bcc)
|
|
5452
|
+
}
|
|
5453
|
+
};
|
|
5454
|
+
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5455
|
+
method: "POST",
|
|
5456
|
+
headers: { "Content-Type": "application/json" },
|
|
5457
|
+
body: JSON.stringify(body)
|
|
5458
|
+
});
|
|
5459
|
+
if (!response.ok) {
|
|
5460
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
5461
|
+
throw new Error(
|
|
5462
|
+
`Failed to send notification: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
|
|
5463
|
+
);
|
|
5464
|
+
}
|
|
5465
|
+
await response.body?.cancel();
|
|
5466
|
+
return id;
|
|
5467
|
+
});
|
|
5468
|
+
}
|
|
5469
|
+
__name(notifyEmail, "notifyEmail");
|
|
5470
|
+
function createNotifier() {
|
|
5471
|
+
return { email: notifyEmail };
|
|
5472
|
+
}
|
|
5473
|
+
__name(createNotifier, "createNotifier");
|
|
5038
5474
|
|
|
5039
5475
|
// src/experimental.ts
|
|
5040
5476
|
var experimental_exports = {};
|
|
@@ -5399,9 +5835,10 @@ __name(LlmFlowStep, "LlmFlowStep");
|
|
|
5399
5835
|
var import_zod = require("zod");
|
|
5400
5836
|
var createTraceAPI2 = createTraceAPI;
|
|
5401
5837
|
function ksuid() {
|
|
5402
|
-
return
|
|
5838
|
+
return import_ksuid4.default.randomSync().string;
|
|
5403
5839
|
}
|
|
5404
5840
|
__name(ksuid, "ksuid");
|
|
5841
|
+
var notify = createNotifier();
|
|
5405
5842
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5406
5843
|
0 && (module.exports = {
|
|
5407
5844
|
Duration,
|
|
@@ -5424,6 +5861,8 @@ __name(ksuid, "ksuid");
|
|
|
5424
5861
|
TaskAPI,
|
|
5425
5862
|
checkBuiltInPermissions,
|
|
5426
5863
|
createFlowContext,
|
|
5864
|
+
createIntegrationServer,
|
|
5865
|
+
createNotifier,
|
|
5427
5866
|
createTraceAPI,
|
|
5428
5867
|
experimental,
|
|
5429
5868
|
handleFlow,
|
|
@@ -5431,7 +5870,9 @@ __name(ksuid, "ksuid");
|
|
|
5431
5870
|
handleRequest,
|
|
5432
5871
|
handleRoute,
|
|
5433
5872
|
handleSubscriber,
|
|
5873
|
+
insertNewStep,
|
|
5434
5874
|
ksuid,
|
|
5875
|
+
notify,
|
|
5435
5876
|
tracing,
|
|
5436
5877
|
useDatabase,
|
|
5437
5878
|
z
|