diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d4bc6..ec36db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Changes to be included in the next upcoming release - Support for NDC Spec v0.1.2 via the NDC TypeScript SDK v4.4.0 ([#29](https://github.com/hasura/ndc-nodejs-lambda/pull/29)). - Built-in scalar types that support equality now define it in the NDC schema. - Built-in scalar types now have an explicit type representation defined in the NDC schema. +- Fixed functions that return null causing crashes ([#31](https://github.com/hasura/ndc-nodejs-lambda/pull/31)) ## [1.2.0] - 2024-03-18 - Improved error messages when unsupported enum types or unions of literal types are found, and allow these types to be used in relaxed types mode ([#17](https://github.com/hasura/ndc-nodejs-lambda/pull/17)) diff --git a/ndc-lambda-sdk/src/execution.ts b/ndc-lambda-sdk/src/execution.ts index b0827a1..c3e2964 100644 --- a/ndc-lambda-sdk/src/execution.ts +++ b/ndc-lambda-sdk/src/execution.ts @@ -170,10 +170,10 @@ function coerceArgumentValue(value: unknown, type: schema.TypeReference, valuePa async function invokeFunction(func: Function, preparedArgs: unknown[], functionName: string): Promise { try { return await withActiveSpan(tracer, `Function: ${functionName}`, async () => { - const result = func.apply(undefined, preparedArgs); + const result: unknown = func.apply(undefined, preparedArgs); // Await the result if it is a promise - if (typeof result === "object" && 'then' in result && typeof result.then === "function") { - return await result; + if (result !== null && typeof result === "object" && "then" in result && typeof result.then === "function") { + return await (result as PromiseLike); } return result; }, { [FUNCTION_NAME_SPAN_ATTR_NAME]: functionName }); diff --git a/ndc-lambda-sdk/test/execution/execute-query.test.ts b/ndc-lambda-sdk/test/execution/execute-query.test.ts index 077fb6a..569a2b6 100644 --- a/ndc-lambda-sdk/test/execution/execute-query.test.ts +++ b/ndc-lambda-sdk/test/execution/execute-query.test.ts @@ -2,7 +2,7 @@ import { describe, it } from "mocha"; import { assert, expect } from "chai"; import * as sdk from "@hasura/ndc-sdk-typescript" import { executeQuery } from "../../src/execution"; -import { FunctionNdcKind, FunctionsSchema } from "../../src/schema"; +import { FunctionNdcKind, FunctionsSchema, NullOrUndefinability } from "../../src/schema"; import { sleep } from "../../src/util"; describe("execute query", function() { @@ -389,9 +389,13 @@ describe("execute query", function() { parallelDegree: null, arguments: [], resultType: { - type: "named", - kind: "scalar", - name: "String" + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } } } }, @@ -493,5 +497,77 @@ describe("execute query", function() { ]); assert.equal(functionCallCount, 1); }); + + it("function can return null", async function() { + let functionCallCount = 0; + const runtimeFunctions = { + "theFunction": () => { + functionCallCount++; + return null; + } + }; + const queryRequest: sdk.QueryRequest = { + collection: "theFunction", + query: { + fields: { + "__value": { + type: "column", + column: "__value" + } + } + }, + arguments: {}, + collection_relationships: {} + }; + + const result = await executeQuery(queryRequest, functionSchema, runtimeFunctions); + assert.deepStrictEqual(result, [ + { + aggregates: null, + rows: [ + { + "__value": null, + } + ] + } + ]); + assert.equal(functionCallCount, 1); + }); + + it("async function can return null", async function() { + let functionCallCount = 0; + const runtimeFunctions = { + "theFunction": async () => { + functionCallCount++; + return null; + } + }; + const queryRequest: sdk.QueryRequest = { + collection: "theFunction", + query: { + fields: { + "__value": { + type: "column", + column: "__value" + } + } + }, + arguments: {}, + collection_relationships: {} + }; + + const result = await executeQuery(queryRequest, functionSchema, runtimeFunctions); + assert.deepStrictEqual(result, [ + { + aggregates: null, + rows: [ + { + "__value": null, + } + ] + } + ]); + assert.equal(functionCallCount, 1); + }); }); });