@teamkeel/functions-runtime 0.393.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamkeel/functions-runtime",
3
- "version": "0.393.3",
3
+ "version": "0.394.0-prerelease",
4
4
  "description": "Internal package used by @teamkeel/sdk",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
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("./InlineFile");
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 dbValue = await value.store();
164
- row[key] = dbValue;
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
- // handle files that need uploading
253
+ console.log(key);
254
+ console.log(typeof value);
255
+ console.log(value instanceof InlineFile);
245
256
  if (value instanceof InlineFile) {
246
- const dbValue = await value.store();
247
- row[key] = dbValue;
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,
@@ -1,5 +1,5 @@
1
1
  import { test, expect, beforeEach, describe } from "vitest";
2
- import { InlineFile } from "./InlineFile";
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: {
@@ -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
@@ -224,6 +224,8 @@ function mustEnv(key) {
224
224
  return v;
225
225
  }
226
226
 
227
- module.exports.createDatabaseClient = createDatabaseClient;
228
- module.exports.useDatabase = useDatabase;
229
- module.exports.withDatabase = withDatabase;
227
+ module.exports = {
228
+ createDatabaseClient,
229
+ useDatabase,
230
+ withDatabase,
231
+ };
package/src/handleJob.js CHANGED
@@ -7,9 +7,9 @@ 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 { parseParams } = require("./parsing");
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.
@@ -61,7 +61,7 @@ async function handleJob(request, config) {
61
61
  { request, permitted, db, actionType },
62
62
  async () => {
63
63
  // parse request params to convert objects into rich field types (e.g. InlineFile)
64
- const inputs = parseParams(request.params);
64
+ const inputs = parseInputs(request.params);
65
65
 
66
66
  // Return the job function to the containing tryExecuteJob block
67
67
  return jobFunction(ctx, inputs);
@@ -8,7 +8,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 { parseParams } = require("./parsing");
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.
@@ -72,7 +72,7 @@ async function handleRequest(request, config) {
72
72
  { request, ctx, permitted, db, permissionFns, actionType },
73
73
  async () => {
74
74
  // parse request params to convert objects into rich field types (e.g. InlineFile)
75
- const inputs = parseParams(request.params);
75
+ const inputs = parseInputs(request.params);
76
76
 
77
77
  // Return the custom function to the containing tryExecuteFunction block
78
78
  // Once the custom function is called, tryExecuteFunction will check the schema's permission rules to see if it can continue committing
@@ -80,7 +80,6 @@ async function handleRequest(request, config) {
80
80
  return customFunction(ctx, inputs);
81
81
  }
82
82
  );
83
-
84
83
  if (result instanceof Error) {
85
84
  span.recordException(result);
86
85
  span.setStatus({
@@ -90,7 +89,9 @@ async function handleRequest(request, config) {
90
89
  return errorToJSONRPCResponse(request, result);
91
90
  }
92
91
 
93
- const response = createJSONRPCSuccessResponse(request.id, result);
92
+ const parsed = await parseOutputs(result);
93
+
94
+ const response = createJSONRPCSuccessResponse(request.id, parsed);
94
95
 
95
96
  const responseHeaders = {};
96
97
  for (const pair of headers.entries()) {
@@ -9,6 +9,7 @@ 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.
@@ -52,8 +53,11 @@ async function handleSubscriber(request, config) {
52
53
  const actionType = PROTO_ACTION_TYPES.SUBSCRIBER;
53
54
 
54
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
+
55
59
  // Return the subscriber function to the containing tryExecuteSubscriber block
56
- return subscriberFunction(ctx, request.params);
60
+ return subscriberFunction(ctx, inputs);
57
61
  });
58
62
 
59
63
  return createJSONRPCSuccessResponse(request.id, null);
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("./InlineFile");
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("./InlineFile");
1
+ const { InlineFile, File } = require("./File");
2
2
 
3
- // parseParams takes a set of inputs and creates objects for the ones that are of a complex type.
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 parseParams(inputs) {
7
+ function parseInputs(inputs) {
8
8
  if (inputs != null && typeof inputs === "object") {
9
- Object.keys(inputs).forEach((i) => {
10
- if (inputs[i] !== null && typeof inputs[i] === "object") {
11
- if ("__typename" in inputs[i]) {
12
- switch (inputs[i].__typename) {
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[i] = InlineFile.fromObject(inputs[i]);
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[i] = parseParams(inputs[i]);
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
- module.exports.parseParams = parseParams;
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
+ };
@@ -1,5 +1,5 @@
1
- const { parseParams } = require("./parsing");
2
- const { InlineFile } = require("./InlineFile");
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 = parseParams(params);
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
- contentType: "image/png",
25
- filename: "myFile.png",
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 parsedParams = parseParams(params);
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: new InlineFile("myFile.png", "image/png", 2024),
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 = parseParams(params);
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
- contentType: "image/png",
61
- filename: "myFile.png",
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
- contentType: "image/png",
72
- filename: "myFile.png",
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
- contentType: "image/png",
80
- filename: "myFile.png",
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 = parseParams(params);
86
+ const parsedParams = parseInputs(params);
87
87
  expect(parsedParams).toEqual({
88
88
  post: {
89
89
  id: 123,
90
- file: new InlineFile("myFile.png", "image/png", 2024),
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: new InlineFile("myFile.png", "image/png", 2024),
97
+ deeperNestedImage: InlineFile.fromDataURL(
98
+ "data:image/png;name=cover.png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAACnCAYAAAABm/BPAAABRmlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8bABYQcDIYMoonJxQWOAQE+QCUMMBoVfLvGwAiiL+uCzHJ8xnLWPCCkLE+1q1pt05x/mOpRAFdKanEykP4DxGnJBUUlDAyMKUC2cnlJAYjdAWSLFAEdBWTPAbHTIewNIHYShH0ErCYkyBnIvgFkCyRnJALNYHwBZOskIYmnI7Gh9oIAj4urj49CqJG5oakHAeeSDkpSK0pAtHN+QWVRZnpGiYIjMJRSFTzzkvV0FIwMjIwYGEBhDlH9ORAcloxiZxBi+YsYGCy+MjAwT0CIJc1kYNjeysAgcQshprKAgYG/hYFh2/mCxKJEuAMYv7EUpxkbQdg8TgwMrPf+//+sxsDAPpmB4e+E//9/L/r//+9ioPl3GBgO5AEAzGpgJI9yWQgAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAOSgAwAEAAAAAQAAAKcAAAAAQVNDSUkAAABTY3JlZW5zaG905/7QcgAAAdZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTY3PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjIyODwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlVzZXJDb21tZW50PlNjcmVlbnNob3Q8L2V4aWY6VXNlckNvbW1lbnQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpCGUzcAAAEGUlEQVR4Ae3TsQ0AIRADwefrICGi/wpBoooN5iqw5uyx5j6fI0AgIfAnUghBgMATMEhFIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASOACCAICsR8kFlUAAAAASUVORK5CYII="
99
+ ),
96
100
  otherDeeperNestedValue: true,
97
101
  },
98
- deepNestedImage: new InlineFile("myFile.png", "image/png", 2024),
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,iVBORw0KGgoAAAANSUhEUgAAAOQAAACnCAYAAAABm/BPAAABRmlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8bABYQcDIYMoonJxQWOAQE+QCUMMBoVfLvGwAiiL+uCzHJ8xnLWPCCkLE+1q1pt05x/mOpRAFdKanEykP4DxGnJBUUlDAyMKUC2cnlJAYjdAWSLFAEdBWTPAbHTIewNIHYShH0ErCYkyBnIvgFkCyRnJALNYHwBZOskIYmnI7Gh9oIAj4urj49CqJG5oakHAeeSDkpSK0pAtHN+QWVRZnpGiYIjMJRSFTzzkvV0FIwMjIwYGEBhDlH9ORAcloxiZxBi+YsYGCy+MjAwT0CIJc1kYNjeysAgcQshprKAgYG/hYFh2/mCxKJEuAMYv7EUpxkbQdg8TgwMrPf+//+sxsDAPpmB4e+E//9/L/r//+9ioPl3GBgO5AEAzGpgJI9yWQgAAABWZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAOShgAHAAAAEgAAAESgAgAEAAAAAQAAAOSgAwAEAAAAAQAAAKcAAAAAQVNDSUkAAABTY3JlZW5zaG905/7QcgAAAdZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTY3PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjIyODwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlVzZXJDb21tZW50PlNjcmVlbnNob3Q8L2V4aWY6VXNlckNvbW1lbnQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpCGUzcAAAEGUlEQVR4Ae3TsQ0AIRADwefrICGi/wpBoooN5iqw5uyx5j6fI0AgIfAnUghBgMATMEhFIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASMAgQ88QhYBB6gCBkIBBhp4hCgGD1AECIQGDDD1DFAIGqQMEQgIGGXqGKAQMUgcIhAQMMvQMUQgYpA4QCAkYZOgZohAwSB0gEBIwyNAzRCFgkDpAICRgkKFniELAIHWAQEjAIEPPEIWAQeoAgZCAQYaeIQoBg9QBAiEBgww9QxQCBqkDBEICBhl6higEDFIHCIQEDDL0DFEIGKQOEAgJGGToGaIQMEgdIBASMMjQM0QhYJA6QCAkYJChZ4hCwCB1gEBIwCBDzxCFgEHqAIGQgEGGniEKAYPUAQIhAYMMPUMUAgapAwRCAgYZeoYoBAxSBwiEBAwy9AxRCBikDhAICRhk6BmiEDBIHSAQEjDI0DNEIWCQOkAgJGCQoWeIQsAgdYBASOACCAICsR8kFlUAAAAASUVORK5CYII="
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
- }