Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: inline files input type support in functions runtime #1511

Merged
merged 21 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { CreatePerson, permissions, models } from "@teamkeel/sdk";
export default CreatePerson(async (_, inputs) => {
permissions.allow();

console.log(inputs);

const response = await models.person.create({
name: inputs.name,
height: inputs.height,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FileInputHandling, permissions } from "@teamkeel/sdk";

// To learn more about what you can do with custom functions, visit https://docs.keel.so/functions
export default FileInputHandling(async (ctx, inputs) => {
permissions.allow();

return {
filename: inputs.file.filename,
size: inputs.file.size,
contentType: inputs.file.contentType,
};
});
27 changes: 20 additions & 7 deletions integration/testdata/functions_custom/schema.keel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ message PersonResponse {
height Decimal?
}

message FileInput {
file InlineFile
}

message FileResponse {
filename Text
size Number
contentType Text
}

model Person {
fields {
name Text
Expand All @@ -46,15 +56,18 @@ model Person {

actions {
write createPerson(name, height) returns (PersonResponse)
read countName(name) returns (CountResponse)
read countName(name) returns (CountResponse)
read countNameAdvanced(AdvancedSearchInput) returns (CountResponse)
write createAndCount(name) returns (CountResponse)
write createManyAndCount(CreateManyInput) returns (CountResponse)
read people(PeopleInput) returns (PeopleResponse)
read customPersonSearch(CustomPersonSearchInput) returns (CustomPersonSearchResponse)
read customSearch(Any) returns (Any)
write bulkPersonUpload(BulkPersonUpload) returns (BulkPersonUpload)
write createAndCount(name) returns (CountResponse)
write createManyAndCount(CreateManyInput) returns (CountResponse)
read people(PeopleInput) returns (PeopleResponse)
read customPersonSearch(CustomPersonSearchInput) returns (
CustomPersonSearchResponse
)
read customSearch(Any) returns (Any)
write bulkPersonUpload(BulkPersonUpload) returns (BulkPersonUpload)
read noInputs() returns (Any)
read fileInputHandling(FileInput) returns (FileResponse)
}
}

Expand Down
14 changes: 13 additions & 1 deletion integration/testdata/functions_custom/tests.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { actions, models, resetDatabase } from "@teamkeel/testing";
import { Person } from "@teamkeel/sdk";
import { Person, InlineFile } from "@teamkeel/sdk";
import { test, expect, beforeEach } from "vitest";

beforeEach(resetDatabase);
Expand Down Expand Up @@ -168,3 +168,15 @@ test("Create with decimal", async () => {
expect(result.height).toEqual(175.3);
expect(result.name).toEqual("Keelio");
});

test("File input handling", async () => {
const dataUrl = `data:image/png;name=my-file.png;base64,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`;

const result = await actions.fileInputHandling({
file: dataUrl,
});

expect(result.filename).toEqual("my-file.png");
expect(result.size).toEqual(2024);
expect(result.contentType).toEqual("image/png");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
CreateBookBeforeWriteWithCover,
CreateBookBeforeWriteWithCoverHooks,
} from "@teamkeel/sdk";

export default CreateBookBeforeWriteWithCover({
async beforeWrite(ctx, inputs, values) {
return {
...values,
title: values.title.toUpperCase(),
};
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default ListBooksBeforeQueryReturnValues({
title: "Dreamcatcher",
authorId: null,
published: true,
cover: null,
},
];
},
Expand Down
18 changes: 18 additions & 0 deletions integration/testdata/functions_hooks/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ test("create beforeWrite - mutate values", async () => {
expect(dbBook!.title).toEqual("GREAT GATSBY");
});

test("create beforeWrite - with files", async () => {
const dataUrl = `data:image/png;name=cover.png;base64,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`;
const book = await actions.createBookBeforeWriteWithCover({
title: "Great Gatsby",
cover: dataUrl,
});

expect(book.title).toEqual("GREAT GATSBY");

const dbBook = await models.book.findOne({
id: book.id,
});
expect(dbBook).not.toBeNull();
expect(dbBook!.title).toEqual("GREAT GATSBY");
expect(dbBook!.cover!.filename).toEqual("cover.png");
});

test("create beforeWrite - mutate values sync", async () => {
const book = await actions.createBookBeforeWriteSync({
title: "Great Gatsby",
Expand Down Expand Up @@ -258,6 +275,7 @@ test("list beforeQuery - return values", async () => {
authorId: null,
title: "Dreamcatcher",
published: true,
cover: null,
});
});

Expand Down
2 changes: 2 additions & 0 deletions integration/testdata/functions_hooks/schema.keel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ model Book {
author Author?
title Text
published Boolean @default(false)
cover InlineFile?
}

actions {
Expand All @@ -12,6 +13,7 @@ model Book {
create createBookAfterWriteErrorRollback() with (title) @function
create createBookWithAuthor() with (author.id, title) @function
create createBookAndAuthor() with (author.name, title) @function
create createBookBeforeWriteWithCover() with (title, cover) @function
get getBookBeforeQueryFirstOrNull(title: Text) @function
get getBookBeforeQueryQueryBuilder(id, allowUnpublished: Boolean?) @function
get getBookAfterQuery(id) @function
Expand Down
12 changes: 11 additions & 1 deletion integration/testdata/jobs_basic/jobs/allInputTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AllInputTypes, Status } from "@teamkeel/sdk";
import { AllInputTypes, InlineFile, Status } from "@teamkeel/sdk";
import { IdentityUniqueConditions } from "../.build/sdk/index";

export default AllInputTypes(async (ctx, inputs) => {
if (inputs.text != "text") {
Expand All @@ -22,4 +23,13 @@ export default AllInputTypes(async (ctx, inputs) => {
if (inputs.enum != Status.GoldPost) {
throw new Error("enum not set correctly");
}
if (inputs.image.filename != "my-avatar.png") {
throw new Error("image filename not set correctly");
}
if (inputs.image.size != 2024) {
throw new Error("image not set correctly");
}
if (inputs.image.read().size != 2024) {
throw new Error("image not set correctly");
}
});
9 changes: 7 additions & 2 deletions integration/testdata/jobs_basic/schema.keel
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ model Post {
status Status @default(Status.NormalPost)
}

@permission(expression: true, actions:[get])
@permission(
expression: true,
actions: [get]
)
}

enum Status {
Expand Down Expand Up @@ -46,6 +49,8 @@ job AllInputTypes {
timestamp Timestamp
enum Status
id ID
image InlineFile
}

@permission(expression: true)
}
}
5 changes: 4 additions & 1 deletion integration/testdata/jobs_basic/tests.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { models, jobs, resetDatabase } from "@teamkeel/testing";
import { test, expect, beforeEach } from "vitest";
import { Status } from "@teamkeel/sdk";
import { Status, InlineFile } from "@teamkeel/sdk";

beforeEach(resetDatabase);

Expand Down Expand Up @@ -84,6 +84,8 @@ test("jobs - updating all values using env var - values updated", async () => {
});

test("jobs - all types as input values", async () => {
const dataUrl = `data:image/png;name=my-avatar.png;base64,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`;

await jobs.allInputTypes({
text: "text",
num: 10,
Expand All @@ -92,5 +94,6 @@ test("jobs - all types as input values", async () => {
timestamp: new Date(2022, 12, 25, 1, 3, 4),
id: "123",
enum: Status.GoldPost,
image: InlineFile.fromDataURL(dataUrl),
});
});
13 changes: 13 additions & 0 deletions node/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,19 @@ func writeClientTypes(w *codegen.Writer, schema *proto.Schema, api *proto.Api) {
w.Dedent()
w.Writeln("};")

w.Writeln(`export declare class InlineFile {`)
w.Indent()
w.Writeln(`constructor(key: any, filename: any, contentType: any, size: any, url: any);`)
w.Writeln(`static fromObject(obj: any): InlineFile;`)
w.Writeln(`static fromDataURL(url: string): InlineFile;`)
w.Writeln(`read(): Blob;`)
w.Writeln(`filename: string;`)
w.Writeln(`contentType: string;`)
w.Writeln(`size: number;`)
w.Writeln(`url: string | null;`)
w.Dedent()
w.Writeln(`}`)

}

func toClientActionReturnType(model *proto.Model, op *proto.Action) string {
Expand Down
23 changes: 21 additions & 2 deletions node/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,9 @@ func generateSdkPackage(schema *proto.Schema) codegen.GeneratedFiles {
sdkTypes.Writeln(`import * as runtime from "@teamkeel/functions-runtime"`)
sdkTypes.Writeln(`import { Headers } from 'node-fetch'`)
sdkTypes.Writeln("")
sdkTypes.Writeln(`export type SortDirection = "asc" | "desc" | "ASC" | "DESC"`)
writeBuiltInTypes(sdkTypes)

writePermissions(sdk, schema)

writeMessages(sdkTypes, schema, false)

for _, enum := range schema.Enums {
Expand All @@ -82,6 +81,7 @@ func generateSdkPackage(schema *proto.Schema) codegen.GeneratedFiles {
writeTableConfig(sdk, schema.Models)
writeAPIFactory(sdk, schema)
sdk.Writeln("module.exports.useDatabase = runtime.useDatabase;")
sdk.Writeln("module.exports.InlineFile = runtime.InlineFile;")

for _, model := range schema.Models {
writeTableInterface(sdkTypes, model)
Expand Down Expand Up @@ -152,6 +152,23 @@ func generateSdkPackage(schema *proto.Schema) codegen.GeneratedFiles {
}
}

// writeBuiltInTypes will write the types for Built In types such as InlineFile, SortDirection, etc..
func writeBuiltInTypes(w *codegen.Writer) {
w.Writeln(`export type SortDirection = "asc" | "desc" | "ASC" | "DESC"`)
w.Writeln(`export declare class InlineFile {`)
w.Indent()
w.Writeln(`constructor(key: any, filename: any, contentType: any, size: any, url: any);`)
w.Writeln(`static fromObject(obj: any): InlineFile;`)
w.Writeln(`static fromDataURL(url: string): InlineFile;`)
w.Writeln(`read(): Blob;`)
w.Writeln(`filename: string;`)
w.Writeln(`contentType: string;`)
w.Writeln(`size: number;`)
w.Writeln(`url: string | null;`)
w.Dedent()
w.Writeln(`}`)
}

func writeTableInterface(w *codegen.Writer, model *proto.Model) {
w.Writef("export interface %sTable {\n", model.Name)
w.Indent()
Expand Down Expand Up @@ -1646,6 +1663,8 @@ func toTypeScriptType(t *proto.TypeInfo, isTestingPackage bool) (ret string) {
case proto.Type_TYPE_STRING_LITERAL:
// Use string literal type for discriminating.
ret = fmt.Sprintf(`"%s"`, t.StringLiteralValue.Value)
case proto.Type_TYPE_INLINE_FILE:
ret = "InlineFile"
default:
ret = "any"
}
Expand Down
68 changes: 68 additions & 0 deletions packages/functions-runtime/src/InlineFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class InlineFile {
constructor(filename, contentType, size, url) {
this.filename = filename;
this.contentType = contentType;
this.size = size;
this.url = url;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should url maybe also be private?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, would it be url or would it actually be an S3 path?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we can figure this out when doing ModelAPI side of things

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this as being a S3 path that would be used in read() if the dataUrl is not set

}

// Create an InlineFile instance from a given json object.
static fromObject(obj) {
if (obj.dataURL) {
var file = InlineFile.fromDataURL(obj.dataURL);
file._dataURL = obj.dataURL;
return file;
}

return new InlineFile(obj.filename, obj.contentType, obj.size, obj.url);
}

// Create an InlineFile instance from a given dataURL
static fromDataURL(dataURL) {
var info = dataURL.split(",")[0].split(":")[1];
var data = dataURL.split(",")[1];

var mime = info.split(";")[0];
var name = info.split(";")[1].split("=")[1];
var byteString = Buffer.from(data, "base64");
var blob = new Blob([byteString], { type: mime });

var file = new InlineFile(name, mime, blob.size);
file._dataURL = dataURL;
return file;
}

// Read the contents of the file. If URL is set, it will be read from the remote storage, otherwise, if dataURL is set
// on the instance, it will return a blob with the file contents
read() {
if (this.url) {
// TODO: read from store
}

if (this._dataURL) {
var data = this._dataURL.split(",")[1];
var byteString = Buffer.from(data, "base64");
return new Blob([byteString], { type: this.contentType });
}
}

store() {
//TODO: actually store and generate a key
this.key = uuidv4();
}

toJSON() {
return {
__typename: "InlineFile",
dataURL: this._dataURL,
filename: this.filename,
contentType: this.contentType,
size: this.size,
url: this.url,
};
}
}

module.exports = {
InlineFile,
};
Loading
Loading