From cc82a5771098665b51c7c5e33ab723ce12391ad2 Mon Sep 17 00:00:00 2001 From: Radu Gruia Date: Mon, 20 Jan 2025 11:34:12 +0000 Subject: [PATCH] fix: extend camelcasePlugin to ignore rich types --- .../functions/createDurationInHook.ts | 17 ++++--- .../duration/functions/writeCustomFunction.ts | 10 ++--- integration/testdata/duration/tests.test.ts | 2 +- .../functions-runtime/src/camelCasePlugin.js | 45 +++++++++++++++++++ packages/functions-runtime/src/database.js | 7 ++- packages/functions-runtime/src/parsing.js | 9 ++++ 6 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 packages/functions-runtime/src/camelCasePlugin.js diff --git a/integration/testdata/duration/functions/createDurationInHook.ts b/integration/testdata/duration/functions/createDurationInHook.ts index e12070500..5b6b4271f 100755 --- a/integration/testdata/duration/functions/createDurationInHook.ts +++ b/integration/testdata/duration/functions/createDurationInHook.ts @@ -1,13 +1,16 @@ -import { CreateDurationInHook, CreateDurationInHookHooks, Duration } from '@teamkeel/sdk'; +import { + CreateDurationInHook, + CreateDurationInHookHooks, + Duration, +} from "@teamkeel/sdk"; // To learn more about what you can do with hooks, visit https://docs.keel.so/functions const hooks: CreateDurationInHookHooks = { - beforeWrite: async (ctx, inputs) => { - return { - dur: Duration.fromISOString("PT1H") - } - }, + beforeWrite: async (ctx, inputs) => { + return { + dur: Duration.fromISOString("PT1H"), + }; + }, }; export default CreateDurationInHook(hooks); - \ No newline at end of file diff --git a/integration/testdata/duration/functions/writeCustomFunction.ts b/integration/testdata/duration/functions/writeCustomFunction.ts index a5362f2af..57aa6dbca 100755 --- a/integration/testdata/duration/functions/writeCustomFunction.ts +++ b/integration/testdata/duration/functions/writeCustomFunction.ts @@ -1,9 +1,9 @@ -import { WriteCustomFunction, models } from '@teamkeel/sdk'; +import { WriteCustomFunction, models } from "@teamkeel/sdk"; // To learn more about what you can do with custom functions, visit https://docs.keel.so/functions export default WriteCustomFunction(async (ctx, inputs) => { - const mod = await models.myDuration.create({ dur: inputs.dur }) - return { - model: mod - } + const mod = await models.myDuration.create({ dur: inputs.dur }); + return { + model: mod, + }; }); diff --git a/integration/testdata/duration/tests.test.ts b/integration/testdata/duration/tests.test.ts index 718b4c4e8..f75a99ab4 100644 --- a/integration/testdata/duration/tests.test.ts +++ b/integration/testdata/duration/tests.test.ts @@ -37,7 +37,7 @@ test("duration - write custom function", async () => { expect(result.model.dur).toEqual("PT1H2M3S"); - const mydurs = await useDatabase() + const mydurs = await useDatabase() .selectFrom("my_duration") .selectAll() .execute(); diff --git a/packages/functions-runtime/src/camelCasePlugin.js b/packages/functions-runtime/src/camelCasePlugin.js new file mode 100644 index 000000000..443a5f0a6 --- /dev/null +++ b/packages/functions-runtime/src/camelCasePlugin.js @@ -0,0 +1,45 @@ +const { CamelCasePlugin } = require("kysely"); +const { isPlainObject, isRichType } = require("./parsing"); +// KeelCamelCasePlugin is a wrapper around kysely's camel case plugin. +class KeelCamelCasePlugin { + constructor(opt) { + this.opt = opt; + this.CamelCasePlugin = new CamelCasePlugin(opt); + } + + transformQuery(args) { + return this.CamelCasePlugin.transformQuery(args); + } + + async transformResult(args) { + if (args.result.rows && Array.isArray(args.result.rows)) { + return { + ...args.result, + rows: args.result.rows.map((row) => this.mapRow(row)), + }; + } + return args.result; + } + mapRow(row) { + return Object.keys(row).reduce((obj, key) => { + let value = row[key]; + if (Array.isArray(value)) { + value = value.map((it) => + canMap(it, this.opt) ? this.mapRow(it) : it + ); + } else if (canMap(value, this.opt)) { + value = this.mapRow(value); + } + obj[this.CamelCasePlugin.camelCase(key)] = value; + return obj; + }, {}); + } +} + +function canMap(obj, opt) { + return ( + isPlainObject(obj) && !opt?.maintainNestedObjectKeys && !isRichType(obj) + ); +} + +module.exports.KeelCamelCasePlugin = KeelCamelCasePlugin; diff --git a/packages/functions-runtime/src/database.js b/packages/functions-runtime/src/database.js index c852335ec..85825564f 100644 --- a/packages/functions-runtime/src/database.js +++ b/packages/functions-runtime/src/database.js @@ -1,7 +1,8 @@ -const { Kysely, PostgresDialect, CamelCasePlugin } = require("kysely"); +const { Kysely, PostgresDialect } = require("kysely"); const neonserverless = require("@neondatabase/serverless"); const { AsyncLocalStorage } = require("async_hooks"); const { AuditContextPlugin } = require("./auditing"); +const { KeelCamelCasePlugin } = require("./camelCasePlugin"); const pg = require("pg"); const { withSpan } = require("./tracing"); const ws = require("ws"); @@ -78,9 +79,7 @@ function createDatabaseClient({ connString } = {}) { // https://kysely-org.github.io/kysely/classes/CamelCasePlugin.html // If they don't, then we can create a custom implementation of the plugin where we control // the casing behaviour (see url above for example) - new CamelCasePlugin({ - maintainNestedObjectKeys: true, - }), + new KeelCamelCasePlugin(), ], log(event) { if ("DEBUG" in process.env) { diff --git a/packages/functions-runtime/src/parsing.js b/packages/functions-runtime/src/parsing.js index 395dcbda6..f55b97c2f 100644 --- a/packages/functions-runtime/src/parsing.js +++ b/packages/functions-runtime/src/parsing.js @@ -85,6 +85,14 @@ function isPlainObject(obj) { return Object.prototype.toString.call(obj) === "[object Object]"; } +function isRichType(obj) { + if (!isPlainObject(obj)) { + return false; + } + + return obj instanceof Duration || obj instanceof File; +} + function isReferencingExistingRecord(value) { return Object.keys(value).length === 1 && value.id; } @@ -94,5 +102,6 @@ module.exports = { parseOutputs, transformRichDataTypes, isPlainObject, + isRichType, isReferencingExistingRecord, };