diff --git a/integration/testdata/duration/functions/createDurationInHook.ts b/integration/testdata/duration/functions/createDurationInHook.ts new file mode 100755 index 000000000..e12070500 --- /dev/null +++ b/integration/testdata/duration/functions/createDurationInHook.ts @@ -0,0 +1,13 @@ +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") + } + }, +}; + +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 new file mode 100755 index 000000000..a5362f2af --- /dev/null +++ b/integration/testdata/duration/functions/writeCustomFunction.ts @@ -0,0 +1,9 @@ +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 + } +}); diff --git a/integration/testdata/duration/schema.keel b/integration/testdata/duration/schema.keel new file mode 100644 index 000000000..d03667249 --- /dev/null +++ b/integration/testdata/duration/schema.keel @@ -0,0 +1,31 @@ +model MyDuration { + fields { + dur Duration? + } + actions { + create createDuration() with (dur) + update updateDuration(id) with (dur) + get getDuration(id) + list listDurations() { + @orderBy(createdAt: asc) + } + create createDurationInHook() @function + write writeCustomFunction(DurationMessage) returns (ModelMessage) { + @permission(expression: true) + } + } + + @permission(expression: true, actions: [get, list, create, update]) +} + +message NestedDurationMessage { + msg DurationMessage +} + +message DurationMessage { + dur Duration +} + +message ModelMessage { + model MyDuration +} diff --git a/integration/testdata/duration/tests.test.ts b/integration/testdata/duration/tests.test.ts new file mode 100644 index 000000000..718b4c4e8 --- /dev/null +++ b/integration/testdata/duration/tests.test.ts @@ -0,0 +1,60 @@ +import { actions, resetDatabase, models } from "@teamkeel/testing"; +import { beforeEach, expect, test } from "vitest"; +import { useDatabase, Duration } from "@teamkeel/sdk"; +import { sql } from "kysely"; + +beforeEach(resetDatabase); + +test("duration - create action with duration input", async () => { + const result = await actions.createDuration({ + dur: Duration.fromISOString("PT2H3M4S"), + }); + + expect(result.dur).toEqual("PT2H3M4S"); +}); + +test("duration - update action with duration input", async () => { + const result = await actions.createDuration({ + dur: Duration.fromISOString("PT2H3M4S"), + }); + + const updated = await actions.updateDuration({ + where: { + id: result.id, + }, + values: { + dur: Duration.fromISOString("PT1S"), + }, + }); + + expect(updated.dur).toEqual("PT1S"); +}); + +test("duration - write custom function", async () => { + const result = await actions.writeCustomFunction({ + dur: Duration.fromISOString("PT1H2M3S"), + }); + + expect(result.model.dur).toEqual("PT1H2M3S"); + + const mydurs = await useDatabase() + .selectFrom("my_duration") + .selectAll() + .execute(); + + expect(mydurs.length).toEqual(1); + expect(mydurs[0].id).toEqual(result.model.id); + expect(mydurs[0].dur?.toISOString()).toEqual("PT1H2M3S"); +}); + +test("durs - create and store duration in hook", async () => { + await actions.createDurationInHook({}); + + const mydurs = await useDatabase() + .selectFrom("my_duration") + .selectAll() + .execute(); + + expect(mydurs.length).toEqual(1); + expect(mydurs[0].dur?.toISOString()).toEqual("PT1H"); +}); diff --git a/packages/functions-runtime/src/Duration.js b/packages/functions-runtime/src/Duration.js index b4e6975b4..6940a5a93 100644 --- a/packages/functions-runtime/src/Duration.js +++ b/packages/functions-runtime/src/Duration.js @@ -5,6 +5,8 @@ const isoRegex = class Duration { constructor(postgresString) { + this._typename = "Duration"; + this.pgInterval = postgresString; this._interval = parseInterval(postgresString); } diff --git a/packages/functions-runtime/src/database.js b/packages/functions-runtime/src/database.js index fd36ceff7..c852335ec 100644 --- a/packages/functions-runtime/src/database.js +++ b/packages/functions-runtime/src/database.js @@ -78,7 +78,9 @@ 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(), + new CamelCasePlugin({ + maintainNestedObjectKeys: true, + }), ], log(event) { if ("DEBUG" in process.env) { @@ -153,10 +155,7 @@ function getDialect(connString) { // Adding a custom type parser for interval fields: see https://kysely.dev/docs/recipes/data-types#configuring-runtime-javascript-types // 1186 = type for INTERVAL pg.types.setTypeParser(1186, function (val) { - return { - _Typename: "Duration", - pgInterval: val, - }; + return new Duration(val); }); return new PostgresDialect({ @@ -194,10 +193,7 @@ function getDialect(connString) { // Adding a custom type parser for interval fields: see https://kysely.dev/docs/recipes/data-types#configuring-runtime-javascript-types // 1186 = type for INTERVAL neonserverless.types.setTypeParser(1186, function (val) { - return { - _Typename: "Duration", - pgInterval: val, - }; + return new Duration(val); }); neonserverless.neonConfig.webSocketConstructor = ws; diff --git a/packages/functions-runtime/src/parsing.js b/packages/functions-runtime/src/parsing.js index 5c269484b..395dcbda6 100644 --- a/packages/functions-runtime/src/parsing.js +++ b/packages/functions-runtime/src/parsing.js @@ -60,7 +60,7 @@ function transformRichDataTypes(data) { for (const key of keys) { const value = data[key]; if (isPlainObject(value)) { - if (value._Typename == "Duration" && value.pgInterval) { + if (value._typename == "Duration" && value.pgInterval) { row[key] = new Duration(value.pgInterval); } else if ( value.key &&