@teamkeel/functions-runtime 0.393.2 → 0.394.0-prerelease
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/package.json +1 -1
- package/src/File.js +247 -0
- package/src/ModelAPI.js +21 -40
- package/src/ModelAPI.test.js +2 -3
- package/src/QueryBuilder.js +4 -3
- package/src/database.js +16 -30
- package/src/handleJob.js +11 -5
- package/src/handleRequest.js +13 -6
- package/src/handleSubscriber.js +13 -3
- package/src/index.d.ts +66 -0
- package/src/index.js +2 -1
- package/src/parsing.js +67 -12
- package/src/parsing.test.js +61 -22
- package/src/InlineFile.js +0 -183
package/package.json
CHANGED
package/src/File.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const {
|
|
2
|
+
S3Client,
|
|
3
|
+
PutObjectCommand,
|
|
4
|
+
GetObjectCommand,
|
|
5
|
+
} = require("@aws-sdk/client-s3");
|
|
6
|
+
const { fromEnv } = require("@aws-sdk/credential-providers");
|
|
7
|
+
const { useDatabase } = require("./database");
|
|
8
|
+
const { DatabaseError } = require("./errors");
|
|
9
|
+
const KSUID = require("ksuid");
|
|
10
|
+
|
|
11
|
+
class InlineFile {
|
|
12
|
+
constructor({ filename, contentType }) {
|
|
13
|
+
this._filename = filename;
|
|
14
|
+
this._contentType = contentType;
|
|
15
|
+
this._contents = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static fromDataURL(dataURL) {
|
|
19
|
+
var info = dataURL.split(",")[0].split(":")[1];
|
|
20
|
+
var data = dataURL.split(",")[1];
|
|
21
|
+
var mime = info.split(";")[0];
|
|
22
|
+
var name = info.split(";")[1].split("=")[1];
|
|
23
|
+
var buffer = Buffer.from(data, "base64");
|
|
24
|
+
var file = new InlineFile({ filename: name, contentType: mime });
|
|
25
|
+
file.write(buffer);
|
|
26
|
+
|
|
27
|
+
return file;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get size() {
|
|
31
|
+
if (this._contents) {
|
|
32
|
+
return this._contents.size;
|
|
33
|
+
} else {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get contentType() {
|
|
39
|
+
return this._contentType;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get filename() {
|
|
43
|
+
return this._filename;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
write(buffer) {
|
|
47
|
+
this._contents = new Blob([buffer]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Read the contents of the file. If URL is set, it will be read from the remote storage, otherwise, if dataURL is set
|
|
51
|
+
// on the instance, it will return a blob with the file contents
|
|
52
|
+
async read() {
|
|
53
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
54
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
55
|
+
|
|
56
|
+
return buffer;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async store(expires = null) {
|
|
60
|
+
const content = await this.read();
|
|
61
|
+
const key = KSUID.randomSync().string;
|
|
62
|
+
|
|
63
|
+
await storeFile(
|
|
64
|
+
content,
|
|
65
|
+
key,
|
|
66
|
+
this._filename,
|
|
67
|
+
this._contentType,
|
|
68
|
+
this.size,
|
|
69
|
+
expires
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return new File({
|
|
73
|
+
key: key,
|
|
74
|
+
size: this.size,
|
|
75
|
+
filename: this.filename,
|
|
76
|
+
contentType: this.contentType,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class File extends InlineFile {
|
|
82
|
+
constructor(input) {
|
|
83
|
+
super({ filename: input.filename, contentType: input.contentType });
|
|
84
|
+
this._key = input.key;
|
|
85
|
+
this._size = input.size;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static fromDbRecord({ key, filename, size, contentType }) {
|
|
89
|
+
return new File({
|
|
90
|
+
key: key,
|
|
91
|
+
filename: filename,
|
|
92
|
+
size: size,
|
|
93
|
+
contentType: contentType,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get size() {
|
|
98
|
+
return this._size;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get key() {
|
|
102
|
+
return this._key;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async read() {
|
|
106
|
+
if (this._contents) {
|
|
107
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
108
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
109
|
+
|
|
110
|
+
return buffer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isS3Storage()) {
|
|
114
|
+
const s3Client = new S3Client({
|
|
115
|
+
credentials: fromEnv(),
|
|
116
|
+
region: process.env.KEEL_REGION,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const params = {
|
|
120
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
121
|
+
Key: "files/" + this.key,
|
|
122
|
+
};
|
|
123
|
+
const command = new GetObjectCommand(params);
|
|
124
|
+
const response = await s3Client.send(command);
|
|
125
|
+
const blob = response.Body.transformToByteArray();
|
|
126
|
+
return Buffer.from(blob);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// default to db storage
|
|
130
|
+
const db = useDatabase();
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
let query = db
|
|
134
|
+
.selectFrom("keel_storage")
|
|
135
|
+
.select("data")
|
|
136
|
+
.where("id", "=", this.key);
|
|
137
|
+
|
|
138
|
+
const row = await query.executeTakeFirstOrThrow();
|
|
139
|
+
return row.data;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
throw new DatabaseError(e);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async store(expires = null) {
|
|
146
|
+
// Only necessary to store the file if the contents have been changed
|
|
147
|
+
if (this._contents) {
|
|
148
|
+
const contents = await this.read();
|
|
149
|
+
await storeFile(
|
|
150
|
+
contents,
|
|
151
|
+
this.key,
|
|
152
|
+
this.filename,
|
|
153
|
+
this.contentType,
|
|
154
|
+
expires
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
toDbRecord() {
|
|
161
|
+
return {
|
|
162
|
+
key: this.key,
|
|
163
|
+
filename: this.filename,
|
|
164
|
+
contentType: this.contentType,
|
|
165
|
+
size: this._size,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
toJSON() {
|
|
170
|
+
return {
|
|
171
|
+
key: this.key,
|
|
172
|
+
filename: this.filename,
|
|
173
|
+
contentType: this.contentType,
|
|
174
|
+
size: this.size,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function storeFile(contents, key, filename, contentType, expires) {
|
|
180
|
+
if (isS3Storage()) {
|
|
181
|
+
const s3Client = new S3Client({
|
|
182
|
+
credentials: fromEnv(),
|
|
183
|
+
region: process.env.KEEL_REGION,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const params = {
|
|
187
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
188
|
+
Key: "files/" + key,
|
|
189
|
+
Body: contents,
|
|
190
|
+
ContentType: contentType,
|
|
191
|
+
Metadata: {
|
|
192
|
+
filename: filename,
|
|
193
|
+
},
|
|
194
|
+
ACL: "private",
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (expires) {
|
|
198
|
+
params.Expires = expires;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const command = new PutObjectCommand(params);
|
|
202
|
+
try {
|
|
203
|
+
await s3Client.send(command);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error("Error uploading file:", error);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// default to db storage
|
|
211
|
+
const db = useDatabase();
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
let query = db
|
|
215
|
+
.insertInto("keel_storage")
|
|
216
|
+
.values({
|
|
217
|
+
id: key,
|
|
218
|
+
filename: filename,
|
|
219
|
+
content_type: contentType,
|
|
220
|
+
data: contents,
|
|
221
|
+
})
|
|
222
|
+
.onConflict((oc) =>
|
|
223
|
+
oc
|
|
224
|
+
.column("id")
|
|
225
|
+
.doUpdateSet(() => ({
|
|
226
|
+
filename: filename,
|
|
227
|
+
content_type: contentType,
|
|
228
|
+
data: contents,
|
|
229
|
+
}))
|
|
230
|
+
.where("keel_storage.id", "=", key)
|
|
231
|
+
)
|
|
232
|
+
.returningAll();
|
|
233
|
+
|
|
234
|
+
await query.execute();
|
|
235
|
+
} catch (e) {
|
|
236
|
+
throw new DatabaseError(e);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isS3Storage() {
|
|
241
|
+
return "KEEL_FILES_BUCKET_NAME" in process.env;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
InlineFile,
|
|
246
|
+
File,
|
|
247
|
+
};
|
package/src/ModelAPI.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
const { sql } = require("kysely");
|
|
2
2
|
const { useDatabase } = require("./database");
|
|
3
|
+
const {
|
|
4
|
+
transformRichDataTypes,
|
|
5
|
+
isPlainObject,
|
|
6
|
+
isReferencingExistingRecord,
|
|
7
|
+
} = require("./parsing");
|
|
3
8
|
const { QueryBuilder } = require("./QueryBuilder");
|
|
4
9
|
const { QueryContext } = require("./QueryContext");
|
|
5
10
|
const { applyWhereConditions } = require("./applyWhereConditions");
|
|
6
11
|
const { applyJoins } = require("./applyJoins");
|
|
7
|
-
const { InlineFile } = require("./
|
|
12
|
+
const { InlineFile, File } = require("./File");
|
|
8
13
|
|
|
9
14
|
const {
|
|
10
15
|
applyLimit,
|
|
@@ -160,12 +165,15 @@ class ModelAPI {
|
|
|
160
165
|
const value = values[key];
|
|
161
166
|
// handle files that need uploading
|
|
162
167
|
if (value instanceof InlineFile) {
|
|
163
|
-
const
|
|
164
|
-
row[key] =
|
|
168
|
+
const storedFile = await value.store();
|
|
169
|
+
row[key] = storedFile.toDbRecord();
|
|
170
|
+
} else if (value instanceof File) {
|
|
171
|
+
row[key] = value.toDbRecord();
|
|
165
172
|
} else {
|
|
166
173
|
row[key] = value;
|
|
167
174
|
}
|
|
168
175
|
}
|
|
176
|
+
|
|
169
177
|
builder = builder.set(snakeCaseObject(row));
|
|
170
178
|
|
|
171
179
|
const context = new QueryContext([this._tableName], this._tableConfigMap);
|
|
@@ -177,7 +185,7 @@ class ModelAPI {
|
|
|
177
185
|
|
|
178
186
|
try {
|
|
179
187
|
const row = await builder.executeTakeFirstOrThrow();
|
|
180
|
-
return camelCaseObject(row);
|
|
188
|
+
return transformRichDataTypes(camelCaseObject(row));
|
|
181
189
|
} catch (e) {
|
|
182
190
|
throw new DatabaseError(e);
|
|
183
191
|
}
|
|
@@ -225,6 +233,7 @@ class ModelAPI {
|
|
|
225
233
|
|
|
226
234
|
async function create(conn, tableName, tableConfigs, values) {
|
|
227
235
|
try {
|
|
236
|
+
console.log(values);
|
|
228
237
|
let query = conn.insertInto(tableName);
|
|
229
238
|
|
|
230
239
|
const keys = values ? Object.keys(values) : [];
|
|
@@ -241,14 +250,17 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
241
250
|
const columnConfig = tableConfig[key];
|
|
242
251
|
|
|
243
252
|
if (!columnConfig) {
|
|
244
|
-
|
|
253
|
+
console.log(key);
|
|
254
|
+
console.log(typeof value);
|
|
255
|
+
console.log(value instanceof InlineFile);
|
|
245
256
|
if (value instanceof InlineFile) {
|
|
246
|
-
const
|
|
247
|
-
row[key] =
|
|
257
|
+
const storedFile = await value.store();
|
|
258
|
+
row[key] = storedFile.toDbRecord();
|
|
259
|
+
} else if (value instanceof File) {
|
|
260
|
+
row[key] = value.toDbRecord();
|
|
248
261
|
} else {
|
|
249
262
|
row[key] = value;
|
|
250
263
|
}
|
|
251
|
-
|
|
252
264
|
continue;
|
|
253
265
|
}
|
|
254
266
|
|
|
@@ -321,43 +333,12 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
321
333
|
})
|
|
322
334
|
);
|
|
323
335
|
|
|
324
|
-
return created;
|
|
336
|
+
return transformRichDataTypes(created);
|
|
325
337
|
} catch (e) {
|
|
326
338
|
throw new DatabaseError(e);
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
341
|
|
|
330
|
-
// Iterate through the given object's keys and if any of the values are a rich data type, instantiate their respective class
|
|
331
|
-
function transformRichDataTypes(data) {
|
|
332
|
-
const keys = data ? Object.keys(data) : [];
|
|
333
|
-
const row = {};
|
|
334
|
-
|
|
335
|
-
for (const key of keys) {
|
|
336
|
-
const value = data[key];
|
|
337
|
-
if (isPlainObject(value)) {
|
|
338
|
-
// if we've got an InlineFile...
|
|
339
|
-
if (value.key && value.size && value.filename && value.contentType) {
|
|
340
|
-
row[key] = InlineFile.fromObject(value);
|
|
341
|
-
} else {
|
|
342
|
-
row[key] = value;
|
|
343
|
-
}
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
row[key] = value;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return row;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function isPlainObject(obj) {
|
|
354
|
-
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function isReferencingExistingRecord(value) {
|
|
358
|
-
return Object.keys(value).length === 1 && value.id;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
342
|
module.exports = {
|
|
362
343
|
ModelAPI,
|
|
363
344
|
DatabaseError,
|
package/src/ModelAPI.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { test, expect, beforeEach, describe } from "vitest";
|
|
2
|
-
|
|
2
|
+
const { InlineFile, File } = require("./File");
|
|
3
3
|
const { ModelAPI } = require("./ModelAPI");
|
|
4
4
|
const { sql } = require("kysely");
|
|
5
5
|
const { useDatabase } = require("./database");
|
|
@@ -37,8 +37,7 @@ beforeEach(async () => {
|
|
|
37
37
|
CREATE TABLE author(
|
|
38
38
|
id text PRIMARY KEY,
|
|
39
39
|
name text NOT NULL
|
|
40
|
-
);
|
|
41
|
-
`.execute(db);
|
|
40
|
+
);`.execute(db);
|
|
42
41
|
|
|
43
42
|
const tableConfigMap = {
|
|
44
43
|
person: {
|
package/src/QueryBuilder.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
upperCamelCase,
|
|
12
12
|
} = require("./casing");
|
|
13
13
|
const { useDatabase } = require("./database");
|
|
14
|
+
const { transformRichDataTypes } = require("./parsing");
|
|
14
15
|
const { QueryContext } = require("./QueryContext");
|
|
15
16
|
const tracing = require("./tracing");
|
|
16
17
|
const { DatabaseError } = require("./errors");
|
|
@@ -85,7 +86,7 @@ class QueryBuilder {
|
|
|
85
86
|
);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
return camelCaseObject(result[0]);
|
|
89
|
+
return transformRichDataTypes(camelCaseObject(result[0]));
|
|
89
90
|
} catch (e) {
|
|
90
91
|
throw new DatabaseError(e);
|
|
91
92
|
}
|
|
@@ -139,7 +140,7 @@ class QueryBuilder {
|
|
|
139
140
|
return null;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
return camelCaseObject(row);
|
|
143
|
+
return transformRichDataTypes(camelCaseObject(row));
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -214,7 +215,7 @@ class QueryBuilder {
|
|
|
214
215
|
|
|
215
216
|
span.setAttribute("sql", query.compile().sql);
|
|
216
217
|
const rows = await builder.execute();
|
|
217
|
-
return rows.map((x) => camelCaseObject(x));
|
|
218
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
221
|
}
|
package/src/database.js
CHANGED
|
@@ -43,10 +43,11 @@ async function withDatabase(db, actionType, cb) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
let db = null;
|
|
47
|
-
|
|
48
46
|
const dbInstance = new AsyncLocalStorage();
|
|
49
47
|
|
|
48
|
+
// used to establish a singleton for our vitest environment
|
|
49
|
+
let vitestDb = null;
|
|
50
|
+
|
|
50
51
|
// useDatabase will retrieve the database client set by withDatabase from the local storage
|
|
51
52
|
function useDatabase() {
|
|
52
53
|
// retrieve the instance of the database client from the store which is aware of
|
|
@@ -61,7 +62,10 @@ function useDatabase() {
|
|
|
61
62
|
// which covers any test files ending in *.test.ts. Custom function code runs in a different node process which will not have this environment variable. Tests written using our testing
|
|
62
63
|
// framework call actions (and in turn custom function code) over http using the ActionExecutor class
|
|
63
64
|
if ("NODE_ENV" in process.env && process.env.NODE_ENV == "test") {
|
|
64
|
-
|
|
65
|
+
if (!vitestDb) {
|
|
66
|
+
vitestDb = createDatabaseClient();
|
|
67
|
+
}
|
|
68
|
+
return vitestDb;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
// If we've gotten to this point, then we know that we are in a custom function runtime server
|
|
@@ -69,17 +73,11 @@ function useDatabase() {
|
|
|
69
73
|
throw new Error("useDatabase must be called within a function");
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
//
|
|
76
|
+
// createDatabaseClient will return a brand new instance of Kysely. Every instance of Kysely
|
|
73
77
|
// represents an individual connection to the database.
|
|
74
78
|
// not to be exported externally from our sdk - consumers should use useDatabase
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
// as a module scope variable.
|
|
78
|
-
if (db) {
|
|
79
|
-
return db;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
db = new Kysely({
|
|
79
|
+
function createDatabaseClient() {
|
|
80
|
+
const db = new Kysely({
|
|
83
81
|
dialect: getDialect(),
|
|
84
82
|
plugins: [
|
|
85
83
|
// ensures that the audit context data is written to Postgres configuration parameters
|
|
@@ -109,7 +107,6 @@ class InstrumentedPool extends pg.Pool {
|
|
|
109
107
|
const _super = super.connect.bind(this);
|
|
110
108
|
return withSpan("Database Connect", function (span) {
|
|
111
109
|
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
112
|
-
span.setAttribute("timeout", connectionTimeout());
|
|
113
110
|
return _super(...args);
|
|
114
111
|
});
|
|
115
112
|
}
|
|
@@ -120,7 +117,6 @@ class InstrumentedNeonServerlessPool extends neonserverless.Pool {
|
|
|
120
117
|
const _super = super.connect.bind(this);
|
|
121
118
|
return withSpan("Database Connect", function (span) {
|
|
122
119
|
span.setAttribute("dialect", process.env["KEEL_DB_CONN_TYPE"]);
|
|
123
|
-
span.setAttribute("timeout", connectionTimeout());
|
|
124
120
|
return _super(...args);
|
|
125
121
|
});
|
|
126
122
|
}
|
|
@@ -179,7 +175,7 @@ function getDialect() {
|
|
|
179
175
|
// Although I doubt we will run into these freeze/thaw issues if idleTimeoutMillis is always shorter than the
|
|
180
176
|
// time is takes for a lambda to freeze (which is not a constant, but could be as short as several minutes,
|
|
181
177
|
// https://www.pluralsight.com/resources/blog/cloud/how-long-does-aws-lambda-keep-your-idle-functions-around-before-a-cold-start)
|
|
182
|
-
idleTimeoutMillis:
|
|
178
|
+
idleTimeoutMillis: 50000,
|
|
183
179
|
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
184
180
|
}),
|
|
185
181
|
});
|
|
@@ -187,7 +183,6 @@ function getDialect() {
|
|
|
187
183
|
neonserverless.neonConfig.webSocketConstructor = ws;
|
|
188
184
|
|
|
189
185
|
const pool = new InstrumentedNeonServerlessPool({
|
|
190
|
-
idleTimeoutMillis: connectionTimeout(),
|
|
191
186
|
connectionString: mustEnv("KEEL_DB_CONN"),
|
|
192
187
|
});
|
|
193
188
|
|
|
@@ -229,17 +224,8 @@ function mustEnv(key) {
|
|
|
229
224
|
return v;
|
|
230
225
|
}
|
|
231
226
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return v;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// initialise the database client at module scope level so the db variable is set
|
|
241
|
-
getDatabaseClient();
|
|
242
|
-
|
|
243
|
-
module.exports.getDatabaseClient = getDatabaseClient;
|
|
244
|
-
module.exports.useDatabase = useDatabase;
|
|
245
|
-
module.exports.withDatabase = withDatabase;
|
|
227
|
+
module.exports = {
|
|
228
|
+
createDatabaseClient,
|
|
229
|
+
useDatabase,
|
|
230
|
+
withDatabase,
|
|
231
|
+
};
|
package/src/handleJob.js
CHANGED
|
@@ -3,13 +3,13 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { createDatabaseClient } = require("./database");
|
|
7
7
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
8
8
|
const opentelemetry = require("@opentelemetry/api");
|
|
9
9
|
const { withSpan } = require("./tracing");
|
|
10
|
-
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
11
10
|
const { tryExecuteJob } = require("./tryExecuteJob");
|
|
12
|
-
const {
|
|
11
|
+
const { parseInputs } = require("./parsing");
|
|
12
|
+
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
13
13
|
|
|
14
14
|
// Generic handler function that is agnostic to runtime environment (local or lambda)
|
|
15
15
|
// to execute a job function based on the contents of a jsonrpc-2.0 payload object.
|
|
@@ -25,6 +25,8 @@ async function handleJob(request, config) {
|
|
|
25
25
|
return opentelemetry.context.with(activeContext, () => {
|
|
26
26
|
// Wrapping span for the whole request
|
|
27
27
|
return withSpan(request.method, async (span) => {
|
|
28
|
+
let db = null;
|
|
29
|
+
|
|
28
30
|
try {
|
|
29
31
|
const { createJobContextAPI, jobs } = config;
|
|
30
32
|
|
|
@@ -51,7 +53,7 @@ async function handleJob(request, config) {
|
|
|
51
53
|
? true
|
|
52
54
|
: null;
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
db = createDatabaseClient();
|
|
55
57
|
const jobFunction = jobs[request.method];
|
|
56
58
|
const actionType = PROTO_ACTION_TYPES.JOB;
|
|
57
59
|
|
|
@@ -59,7 +61,7 @@ async function handleJob(request, config) {
|
|
|
59
61
|
{ request, permitted, db, actionType },
|
|
60
62
|
async () => {
|
|
61
63
|
// parse request params to convert objects into rich field types (e.g. InlineFile)
|
|
62
|
-
const inputs =
|
|
64
|
+
const inputs = parseInputs(request.params);
|
|
63
65
|
|
|
64
66
|
// Return the job function to the containing tryExecuteJob block
|
|
65
67
|
return jobFunction(ctx, inputs);
|
|
@@ -89,6 +91,10 @@ async function handleJob(request, config) {
|
|
|
89
91
|
RuntimeErrors.UnknownError,
|
|
90
92
|
message
|
|
91
93
|
);
|
|
94
|
+
} finally {
|
|
95
|
+
if (db) {
|
|
96
|
+
await db.destroy();
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
});
|
|
94
100
|
});
|
package/src/handleRequest.js
CHANGED
|
@@ -3,12 +3,12 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { createDatabaseClient } = require("./database");
|
|
7
7
|
const { tryExecuteFunction } = require("./tryExecuteFunction");
|
|
8
8
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
9
9
|
const opentelemetry = require("@opentelemetry/api");
|
|
10
10
|
const { withSpan } = require("./tracing");
|
|
11
|
-
const {
|
|
11
|
+
const { parseInputs, parseOutputs } = require("./parsing");
|
|
12
12
|
|
|
13
13
|
// Generic handler function that is agnostic to runtime environment (local or lambda)
|
|
14
14
|
// to execute a custom function based on the contents of a jsonrpc-2.0 payload object.
|
|
@@ -28,6 +28,8 @@ async function handleRequest(request, config) {
|
|
|
28
28
|
return opentelemetry.context.with(activeContext, () => {
|
|
29
29
|
// Wrapping span for the whole request
|
|
30
30
|
return withSpan(request.method, async (span) => {
|
|
31
|
+
let db = null;
|
|
32
|
+
|
|
31
33
|
try {
|
|
32
34
|
const { createContextAPI, functions, permissionFns, actionTypes } =
|
|
33
35
|
config;
|
|
@@ -62,7 +64,7 @@ async function handleRequest(request, config) {
|
|
|
62
64
|
? true
|
|
63
65
|
: null;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
db = createDatabaseClient();
|
|
66
68
|
const customFunction = functions[request.method];
|
|
67
69
|
const actionType = actionTypes[request.method];
|
|
68
70
|
|
|
@@ -70,7 +72,7 @@ async function handleRequest(request, config) {
|
|
|
70
72
|
{ request, ctx, permitted, db, permissionFns, actionType },
|
|
71
73
|
async () => {
|
|
72
74
|
// parse request params to convert objects into rich field types (e.g. InlineFile)
|
|
73
|
-
const inputs =
|
|
75
|
+
const inputs = parseInputs(request.params);
|
|
74
76
|
|
|
75
77
|
// Return the custom function to the containing tryExecuteFunction block
|
|
76
78
|
// Once the custom function is called, tryExecuteFunction will check the schema's permission rules to see if it can continue committing
|
|
@@ -78,7 +80,6 @@ async function handleRequest(request, config) {
|
|
|
78
80
|
return customFunction(ctx, inputs);
|
|
79
81
|
}
|
|
80
82
|
);
|
|
81
|
-
|
|
82
83
|
if (result instanceof Error) {
|
|
83
84
|
span.recordException(result);
|
|
84
85
|
span.setStatus({
|
|
@@ -88,7 +89,9 @@ async function handleRequest(request, config) {
|
|
|
88
89
|
return errorToJSONRPCResponse(request, result);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
const
|
|
92
|
+
const parsed = await parseOutputs(result);
|
|
93
|
+
|
|
94
|
+
const response = createJSONRPCSuccessResponse(request.id, parsed);
|
|
92
95
|
|
|
93
96
|
const responseHeaders = {};
|
|
94
97
|
for (const pair of headers.entries()) {
|
|
@@ -122,6 +125,10 @@ async function handleRequest(request, config) {
|
|
|
122
125
|
RuntimeErrors.UnknownError,
|
|
123
126
|
message
|
|
124
127
|
);
|
|
128
|
+
} finally {
|
|
129
|
+
if (db) {
|
|
130
|
+
await db.destroy();
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
});
|
|
127
134
|
});
|
package/src/handleSubscriber.js
CHANGED
|
@@ -3,12 +3,13 @@ const {
|
|
|
3
3
|
createJSONRPCSuccessResponse,
|
|
4
4
|
JSONRPCErrorCode,
|
|
5
5
|
} = require("json-rpc-2.0");
|
|
6
|
-
const {
|
|
6
|
+
const { createDatabaseClient } = require("./database");
|
|
7
7
|
const { errorToJSONRPCResponse, RuntimeErrors } = require("./errors");
|
|
8
8
|
const opentelemetry = require("@opentelemetry/api");
|
|
9
9
|
const { withSpan } = require("./tracing");
|
|
10
10
|
const { PROTO_ACTION_TYPES } = require("./consts");
|
|
11
11
|
const { tryExecuteSubscriber } = require("./tryExecuteSubscriber");
|
|
12
|
+
const { parseInputs } = require("./parsing");
|
|
12
13
|
|
|
13
14
|
// Generic handler function that is agnostic to runtime environment (local or lambda)
|
|
14
15
|
// to execute a subscriber function based on the contents of a jsonrpc-2.0 payload object.
|
|
@@ -24,6 +25,8 @@ async function handleSubscriber(request, config) {
|
|
|
24
25
|
return opentelemetry.context.with(activeContext, () => {
|
|
25
26
|
// Wrapping span for the whole request
|
|
26
27
|
return withSpan(request.method, async (span) => {
|
|
28
|
+
let db = null;
|
|
29
|
+
|
|
27
30
|
try {
|
|
28
31
|
const { createSubscriberContextAPI, subscribers } = config;
|
|
29
32
|
|
|
@@ -45,13 +48,16 @@ async function handleSubscriber(request, config) {
|
|
|
45
48
|
meta: request.meta,
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
db = createDatabaseClient();
|
|
49
52
|
const subscriberFunction = subscribers[request.method];
|
|
50
53
|
const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
|
|
51
54
|
|
|
52
55
|
await tryExecuteSubscriber({ request, db, actionType }, async () => {
|
|
56
|
+
// parse request params to convert objects into rich field types (e.g. InlineFile)
|
|
57
|
+
const inputs = parseInputs(request.params);
|
|
58
|
+
|
|
53
59
|
// Return the subscriber function to the containing tryExecuteSubscriber block
|
|
54
|
-
return subscriberFunction(ctx,
|
|
60
|
+
return subscriberFunction(ctx, inputs);
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
return createJSONRPCSuccessResponse(request.id, null);
|
|
@@ -77,6 +83,10 @@ async function handleSubscriber(request, config) {
|
|
|
77
83
|
RuntimeErrors.UnknownError,
|
|
78
84
|
message
|
|
79
85
|
);
|
|
86
|
+
} finally {
|
|
87
|
+
if (db) {
|
|
88
|
+
await db.destroy();
|
|
89
|
+
}
|
|
80
90
|
}
|
|
81
91
|
});
|
|
82
92
|
});
|
package/src/index.d.ts
CHANGED
|
@@ -124,6 +124,72 @@ export type PageInfo = {
|
|
|
124
124
|
count: number;
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
type MimeType =
|
|
128
|
+
| "application/json"
|
|
129
|
+
| "application/gzip"
|
|
130
|
+
| "application/pdf"
|
|
131
|
+
| "application/rtf"
|
|
132
|
+
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
133
|
+
| "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
134
|
+
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
135
|
+
| "application/vnd.ms-excel"
|
|
136
|
+
| "application/vnd.ms-powerpoint"
|
|
137
|
+
| "application/msword"
|
|
138
|
+
| "application/zip"
|
|
139
|
+
| "application/xml"
|
|
140
|
+
| "application/x-7z-compressed"
|
|
141
|
+
| "application/x-tar"
|
|
142
|
+
| "image/gif"
|
|
143
|
+
| "image/jpeg"
|
|
144
|
+
| "image/svg+xml"
|
|
145
|
+
| "image/png"
|
|
146
|
+
| "text/html"
|
|
147
|
+
| "text/csv"
|
|
148
|
+
| "text/javascript"
|
|
149
|
+
| "text/plain"
|
|
150
|
+
| "text/calendar"
|
|
151
|
+
| (string & {});
|
|
152
|
+
|
|
153
|
+
export type InlineFileConstructor = {
|
|
154
|
+
filename: string;
|
|
155
|
+
contentType: MimeType;
|
|
156
|
+
};
|
|
157
|
+
export declare class InlineFile {
|
|
158
|
+
constructor(input: InlineFileConstructor);
|
|
159
|
+
static fromDataURL(url: string): InlineFile;
|
|
160
|
+
// Reads the contents of the file as a buffer
|
|
161
|
+
read(): Promise<Buffer>;
|
|
162
|
+
// Write the files contents from a buffer
|
|
163
|
+
write(data: Buffer): void;
|
|
164
|
+
// Persists the file
|
|
165
|
+
store(expires?: Date, isPublic?: boolean): Promise<File>;
|
|
166
|
+
// Gets the name of the file
|
|
167
|
+
get filename(): string;
|
|
168
|
+
// Gets the media type of the file contents
|
|
169
|
+
get contentType(): string;
|
|
170
|
+
// Gets size of the file's contents in bytes
|
|
171
|
+
get size(): number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export declare class File extends InlineFile {
|
|
175
|
+
// Gets the stored key
|
|
176
|
+
get key(): string;
|
|
177
|
+
// Gets size of the file's contents in bytes
|
|
178
|
+
get isPublic(): boolean;
|
|
179
|
+
static fromDbRecord(input: FileDbRecord): File;
|
|
180
|
+
// Persists the file
|
|
181
|
+
toDbRecord(): FileDbRecord;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type FileDbRecord = {
|
|
185
|
+
key: string;
|
|
186
|
+
filename: string;
|
|
187
|
+
contentType: string;
|
|
188
|
+
size: number;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type SortDirection = "asc" | "desc" | "ASC" | "DESC";
|
|
192
|
+
|
|
127
193
|
// Request headers cannot be mutated, so remove any methods that mutate
|
|
128
194
|
export type RequestHeaders = Omit<Headers, "append" | "delete" | "set">;
|
|
129
195
|
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
checkBuiltInPermissions,
|
|
12
12
|
} = require("./permissions");
|
|
13
13
|
const tracing = require("./tracing");
|
|
14
|
-
const { InlineFile } = require("./
|
|
14
|
+
const { InlineFile, File } = require("./File");
|
|
15
15
|
const { ErrorPresets } = require("./errors");
|
|
16
16
|
|
|
17
17
|
module.exports = {
|
|
@@ -22,6 +22,7 @@ module.exports = {
|
|
|
22
22
|
handleSubscriber,
|
|
23
23
|
useDatabase,
|
|
24
24
|
InlineFile,
|
|
25
|
+
File,
|
|
25
26
|
Permissions,
|
|
26
27
|
PERMISSION_STATE,
|
|
27
28
|
checkBuiltInPermissions,
|
package/src/parsing.js
CHANGED
|
@@ -1,30 +1,85 @@
|
|
|
1
|
-
const { InlineFile } = require("./
|
|
1
|
+
const { InlineFile, File } = require("./File");
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// parseInputs takes a set of inputs and creates objects for the ones that are of a complex type.
|
|
4
4
|
//
|
|
5
5
|
// inputs that are objects and contain a "__typename" field are resolved to instances of the complex type
|
|
6
6
|
// they represent. At the moment, the only supported type is `InlineFile`
|
|
7
|
-
function
|
|
7
|
+
function parseInputs(inputs) {
|
|
8
8
|
if (inputs != null && typeof inputs === "object") {
|
|
9
|
-
Object.keys(inputs)
|
|
10
|
-
if (inputs[
|
|
11
|
-
if ("__typename" in inputs[
|
|
12
|
-
switch (inputs[
|
|
9
|
+
for (const k of Object.keys(inputs)) {
|
|
10
|
+
if (inputs[k] !== null && typeof inputs[k] === "object") {
|
|
11
|
+
if ("__typename" in inputs[k]) {
|
|
12
|
+
switch (inputs[k].__typename) {
|
|
13
13
|
case "InlineFile":
|
|
14
|
-
inputs[
|
|
14
|
+
inputs[k] = InlineFile.fromDataURL(inputs[k].dataURL);
|
|
15
15
|
break;
|
|
16
|
-
|
|
17
16
|
default:
|
|
18
17
|
break;
|
|
19
18
|
}
|
|
20
19
|
} else {
|
|
21
|
-
inputs[
|
|
20
|
+
inputs[k] = parseInputs(inputs[k]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return inputs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// parseOutputs will take a response from the custom function and perform operations on any fields if necessary.
|
|
30
|
+
//
|
|
31
|
+
// For example, InlineFiles need to be stored before returning the response.
|
|
32
|
+
async function parseOutputs(inputs) {
|
|
33
|
+
if (inputs != null && typeof inputs === "object") {
|
|
34
|
+
for (const k of Object.keys(inputs)) {
|
|
35
|
+
if (inputs[k] !== null && typeof inputs[k] === "object") {
|
|
36
|
+
if (inputs[k] instanceof InlineFile) {
|
|
37
|
+
const stored = await inputs[k].store();
|
|
38
|
+
inputs[k] = stored;
|
|
39
|
+
} else {
|
|
40
|
+
inputs[k] = await parseOutputs(inputs[k]);
|
|
22
41
|
}
|
|
23
42
|
}
|
|
24
|
-
}
|
|
43
|
+
}
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
return inputs;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
|
-
|
|
49
|
+
// transformRichDataTypes iterates through the given object's keys and if any of the values are a rich data type, instantiate their respective class
|
|
50
|
+
function transformRichDataTypes(data) {
|
|
51
|
+
const keys = data ? Object.keys(data) : [];
|
|
52
|
+
const row = {};
|
|
53
|
+
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
const value = data[key];
|
|
56
|
+
if (isPlainObject(value)) {
|
|
57
|
+
if (value.key && value.size && value.filename && value.contentType) {
|
|
58
|
+
row[key] = File.fromDbRecord(value);
|
|
59
|
+
} else {
|
|
60
|
+
row[key] = value;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
row[key] = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return row;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPlainObject(obj) {
|
|
72
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isReferencingExistingRecord(value) {
|
|
76
|
+
return Object.keys(value).length === 1 && value.id;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
parseInputs,
|
|
81
|
+
parseOutputs,
|
|
82
|
+
transformRichDataTypes,
|
|
83
|
+
isPlainObject,
|
|
84
|
+
isReferencingExistingRecord,
|
|
85
|
+
};
|
package/src/parsing.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const { InlineFile } = require("./
|
|
1
|
+
const { parseInputs } = require("./parsing");
|
|
2
|
+
const { InlineFile } = require("./File");
|
|
3
3
|
import { test, expect } from "vitest";
|
|
4
4
|
|
|
5
5
|
test("simple test", async () => {
|
|
@@ -9,7 +9,7 @@ test("simple test", async () => {
|
|
|
9
9
|
nullable: null,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const parsedParams =
|
|
12
|
+
const parsedParams = parseInputs(params);
|
|
13
13
|
expect(parsedParams).toEqual({
|
|
14
14
|
number: 123,
|
|
15
15
|
string: "String value",
|
|
@@ -21,15 +21,18 @@ test("simple image test", async () => {
|
|
|
21
21
|
const params = {
|
|
22
22
|
file: {
|
|
23
23
|
__typename: "InlineFile",
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
size: 2024,
|
|
24
|
+
dataURL:
|
|
25
|
+
"data:image/png;name=cover.png;base64,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",
|
|
27
26
|
},
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
const
|
|
29
|
+
const file = InlineFile.fromDataURL(
|
|
30
|
+
"data:image/png;name=cover.png;base64,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"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const parsedParams = parseInputs(params);
|
|
31
34
|
expect(parsedParams).toEqual({
|
|
32
|
-
file:
|
|
35
|
+
file: file,
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
|
|
@@ -43,7 +46,7 @@ test("dataURL image test", async () => {
|
|
|
43
46
|
otherData: "other",
|
|
44
47
|
};
|
|
45
48
|
|
|
46
|
-
const parsedParams =
|
|
49
|
+
const parsedParams = parseInputs(params);
|
|
47
50
|
expect(parsedParams).toEqual({
|
|
48
51
|
otherData: "other",
|
|
49
52
|
file: InlineFile.fromDataURL(
|
|
@@ -57,9 +60,8 @@ test("nested image test", async () => {
|
|
|
57
60
|
post: {
|
|
58
61
|
file: {
|
|
59
62
|
__typename: "InlineFile",
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
size: 2024,
|
|
63
|
+
dataURL:
|
|
64
|
+
"data:image/png;name=cover.png;base64,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",
|
|
63
65
|
},
|
|
64
66
|
id: 123,
|
|
65
67
|
},
|
|
@@ -68,34 +70,71 @@ test("nested image test", async () => {
|
|
|
68
70
|
deeperNested: {
|
|
69
71
|
deeperNestedImage: {
|
|
70
72
|
__typename: "InlineFile",
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
size: 2024,
|
|
73
|
+
dataURL:
|
|
74
|
+
"data:image/png;name=cover.png;base64,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",
|
|
74
75
|
},
|
|
75
76
|
otherDeeperNestedValue: true,
|
|
76
77
|
},
|
|
77
78
|
deepNestedImage: {
|
|
78
79
|
__typename: "InlineFile",
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
size: 2024,
|
|
80
|
+
dataURL:
|
|
81
|
+
"data:image/png;name=cover.png;base64,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",
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
const parsedParams =
|
|
86
|
+
const parsedParams = parseInputs(params);
|
|
87
87
|
expect(parsedParams).toEqual({
|
|
88
88
|
post: {
|
|
89
89
|
id: 123,
|
|
90
|
-
file:
|
|
90
|
+
file: InlineFile.fromDataURL(
|
|
91
|
+
"data:image/png;name=cover.png;base64,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"
|
|
92
|
+
),
|
|
91
93
|
},
|
|
92
94
|
otherData: "someOtherValue",
|
|
93
95
|
deepNest: {
|
|
94
96
|
deeperNested: {
|
|
95
|
-
deeperNestedImage:
|
|
97
|
+
deeperNestedImage: InlineFile.fromDataURL(
|
|
98
|
+
"data:image/png;name=cover.png;base64,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"
|
|
99
|
+
),
|
|
96
100
|
otherDeeperNestedValue: true,
|
|
97
101
|
},
|
|
98
|
-
deepNestedImage:
|
|
102
|
+
deepNestedImage: InlineFile.fromDataURL(
|
|
103
|
+
"data:image/png;name=cover.png;base64,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"
|
|
104
|
+
),
|
|
99
105
|
},
|
|
100
106
|
});
|
|
101
107
|
});
|
|
108
|
+
|
|
109
|
+
test("nested structure", async () => {
|
|
110
|
+
const params = {
|
|
111
|
+
names: ["james", "susan"],
|
|
112
|
+
ages: [1, 2],
|
|
113
|
+
things: [
|
|
114
|
+
{
|
|
115
|
+
type: "type",
|
|
116
|
+
file: {
|
|
117
|
+
__typename: "InlineFile",
|
|
118
|
+
dataURL:
|
|
119
|
+
"data:image/png;name=cover.png;base64,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",
|
|
120
|
+
},
|
|
121
|
+
names: ["james", "susan"],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const parsedParams = parseInputs(params);
|
|
127
|
+
expect(parsedParams).toEqual({
|
|
128
|
+
names: ["james", "susan"],
|
|
129
|
+
ages: [1, 2],
|
|
130
|
+
things: [
|
|
131
|
+
{
|
|
132
|
+
type: "type",
|
|
133
|
+
file: InlineFile.fromDataURL(
|
|
134
|
+
"data:image/png;name=cover.png;base64,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"
|
|
135
|
+
),
|
|
136
|
+
names: ["james", "susan"],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
});
|
package/src/InlineFile.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
S3Client,
|
|
3
|
-
PutObjectCommand,
|
|
4
|
-
GetObjectCommand,
|
|
5
|
-
} = require("@aws-sdk/client-s3");
|
|
6
|
-
const { fromEnv } = require("@aws-sdk/credential-providers");
|
|
7
|
-
const { useDatabase } = require("./database");
|
|
8
|
-
const { DatabaseError } = require("./errors");
|
|
9
|
-
const KSUID = require("ksuid");
|
|
10
|
-
|
|
11
|
-
class InlineFile {
|
|
12
|
-
constructor(filename, contentType, size, url, key, pub) {
|
|
13
|
-
this.filename = filename;
|
|
14
|
-
this.contentType = contentType;
|
|
15
|
-
this.size = size;
|
|
16
|
-
this.url = url;
|
|
17
|
-
this.key = key;
|
|
18
|
-
this.public = pub || false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Create an InlineFile instance from a given json object.
|
|
22
|
-
static fromObject(obj) {
|
|
23
|
-
if (obj.dataURL) {
|
|
24
|
-
var file = InlineFile.fromDataURL(obj.dataURL);
|
|
25
|
-
file._dataURL = obj.dataURL;
|
|
26
|
-
return file;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return new InlineFile(
|
|
30
|
-
obj.filename,
|
|
31
|
-
obj.contentType,
|
|
32
|
-
obj.size,
|
|
33
|
-
obj.url,
|
|
34
|
-
obj.key,
|
|
35
|
-
obj.public
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Create an InlineFile instance from a given dataURL
|
|
40
|
-
static fromDataURL(dataURL) {
|
|
41
|
-
var info = dataURL.split(",")[0].split(":")[1];
|
|
42
|
-
var data = dataURL.split(",")[1];
|
|
43
|
-
|
|
44
|
-
var mime = info.split(";")[0];
|
|
45
|
-
var name = info.split(";")[1].split("=")[1];
|
|
46
|
-
var byteString = Buffer.from(data, "base64");
|
|
47
|
-
var blob = new Blob([byteString], { type: mime });
|
|
48
|
-
|
|
49
|
-
var file = new InlineFile(name, mime, blob.size);
|
|
50
|
-
file._dataURL = dataURL;
|
|
51
|
-
return file;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Read the contents of the file. If URL is set, it will be read from the remote storage, otherwise, if dataURL is set
|
|
55
|
-
// on the instance, it will return a blob with the file contents
|
|
56
|
-
async read() {
|
|
57
|
-
if (this._dataURL) {
|
|
58
|
-
var data = this._dataURL.split(",")[1];
|
|
59
|
-
return Buffer.from(data, "base64");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// if we don't have a key nor a dataURL, this inline file has no data
|
|
63
|
-
if (!this.key) {
|
|
64
|
-
throw new Error("invalid file data");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (isS3Storage()) {
|
|
68
|
-
const s3Client = new S3Client({
|
|
69
|
-
credentials: fromEnv(),
|
|
70
|
-
region: process.env.KEEL_REGION,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const params = {
|
|
74
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
75
|
-
Key: "files/" + this.key,
|
|
76
|
-
};
|
|
77
|
-
const command = new GetObjectCommand(params);
|
|
78
|
-
const response = await s3Client.send(command);
|
|
79
|
-
const blob = response.Body.transformToByteArray();
|
|
80
|
-
return Buffer.from(blob);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// default to db storage
|
|
84
|
-
const db = useDatabase();
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
let query = db
|
|
88
|
-
.selectFrom("keel_storage")
|
|
89
|
-
.select("data")
|
|
90
|
-
.where("id", "=", this.key);
|
|
91
|
-
|
|
92
|
-
const row = await query.executeTakeFirstOrThrow();
|
|
93
|
-
return row.data;
|
|
94
|
-
} catch (e) {
|
|
95
|
-
throw new DatabaseError(e);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async store(expires = null) {
|
|
100
|
-
const content = await this.read();
|
|
101
|
-
this.key = KSUID.randomSync().string;
|
|
102
|
-
|
|
103
|
-
if (isS3Storage()) {
|
|
104
|
-
const s3Client = new S3Client({
|
|
105
|
-
credentials: fromEnv(),
|
|
106
|
-
region: process.env.KEEL_REGION,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const params = {
|
|
110
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
111
|
-
Key: "files/" + this.key,
|
|
112
|
-
Body: content,
|
|
113
|
-
ContentType: this.contentType,
|
|
114
|
-
Metadata: {
|
|
115
|
-
filename: this.filename,
|
|
116
|
-
},
|
|
117
|
-
ACL: this.public ? "public-read" : "private",
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
if (expires) {
|
|
121
|
-
params.Expires = expires;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const command = new PutObjectCommand(params);
|
|
125
|
-
try {
|
|
126
|
-
await s3Client.send(command);
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
key: this.key,
|
|
130
|
-
size: this.size,
|
|
131
|
-
filename: this.filename,
|
|
132
|
-
contentType: this.contentType,
|
|
133
|
-
public: this.public,
|
|
134
|
-
};
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.error("Error uploading file:", error);
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// default to db storage
|
|
142
|
-
const db = useDatabase();
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
let query = db.insertInto("keel_storage").values({
|
|
146
|
-
id: this.key,
|
|
147
|
-
filename: this.filename,
|
|
148
|
-
content_type: this.contentType,
|
|
149
|
-
data: content,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
await query.returningAll().executeTakeFirstOrThrow();
|
|
153
|
-
return {
|
|
154
|
-
key: this.key,
|
|
155
|
-
size: this.size,
|
|
156
|
-
filename: this.filename,
|
|
157
|
-
contentType: this.contentType,
|
|
158
|
-
};
|
|
159
|
-
} catch (e) {
|
|
160
|
-
throw new DatabaseError(e);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
toJSON() {
|
|
165
|
-
return {
|
|
166
|
-
__typename: "InlineFile",
|
|
167
|
-
dataURL: this._dataURL,
|
|
168
|
-
filename: this.filename,
|
|
169
|
-
contentType: this.contentType,
|
|
170
|
-
size: this.size,
|
|
171
|
-
url: this.url,
|
|
172
|
-
public: this.public,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
module.exports = {
|
|
178
|
-
InlineFile,
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
function isS3Storage() {
|
|
182
|
-
return "KEEL_FILES_BUCKET_NAME" in process.env;
|
|
183
|
-
}
|