-
Notifications
You must be signed in to change notification settings - Fork 28
Add finally callback to customFunction #516
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
base: main
Are you sure you want to change the base?
Conversation
Co-Authored-By: Ian Macartney <[email protected]>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: Ian Macartney <[email protected]>
Co-Authored-By: Ian Macartney <[email protected]>
Co-Authored-By: Ian Macartney <[email protected]>
finally?: (params: { | ||
ctx: Ctx & ModCtx; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finally?: (params: { | |
ctx: Ctx & ModCtx; | |
finally?: (ctx: Ctx & ModCtx, params: { |
result?: any; | ||
error?: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
result?: any; | |
error?: any; | |
result?: unknown; | |
error?: unknown; |
@@ -88,6 +93,7 @@ export const NoOp = { | |||
input() { | |||
return { args: {}, ctx: {} }; | |||
}, | |||
finally() {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finally() {}, |
throw e; | ||
} finally { | ||
if (mod.finally) { | ||
await mod.finally({ ctx: finalCtx, result, error }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await mod.finally({ ctx: finalCtx, result, error }); | |
await mod.finally(finalCtx, { result, error }); |
describe("finally callback", () => { | ||
test("finally callback is called with result and context", async () => { | ||
let finallyCalled = false; | ||
let finallyParams = null; | ||
|
||
const ctx = { foo: "bar" }; | ||
const args = { test: "value" }; | ||
const result = { success: true }; | ||
|
||
const handler = async (_ctx, _args) => result; | ||
|
||
const mod = { | ||
args: {}, | ||
input: async () => ({ ctx: {}, args: {} }), | ||
finally: (params) => { | ||
finallyCalled = true; | ||
finallyParams = params; | ||
}, | ||
}; | ||
|
||
let actualResult; | ||
let actualError; | ||
try { | ||
actualResult = await handler(ctx, args); | ||
} catch (e) { | ||
actualError = e; | ||
throw e; | ||
} finally { | ||
if (mod.finally) { | ||
await mod.finally({ ctx, result: actualResult, error: actualError }); | ||
} | ||
} | ||
|
||
expect(finallyCalled).toBe(true); | ||
expect(finallyParams).toEqual({ | ||
ctx, | ||
result, | ||
error: undefined, | ||
}); | ||
|
||
finallyCalled = false; | ||
finallyParams = null; | ||
|
||
const testError = new Error("Test error"); | ||
const errorHandler = async () => { | ||
throw testError; | ||
}; | ||
|
||
try { | ||
await errorHandler(); | ||
} catch (e) { | ||
} finally { | ||
if (mod.finally) { | ||
await mod.finally({ ctx, result: undefined, error: testError }); | ||
} | ||
} | ||
|
||
expect(finallyCalled).toBe(true); | ||
expect(finallyParams).toEqual({ | ||
ctx, | ||
result: undefined, | ||
error: testError, | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is useless. It doesn't test the library at all. Replace it with one that defines a function and does testApi
etc. to try calling it - and finding a clever way to spy on the handler. Maybe calling the resulting handler directly via (myfn as any)._handler(ctx, args)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the tests out of their own file into here and delete this useless test
Co-Authored-By: Ian Macartney <[email protected]>
result = await handler(finalCtx, { ...args, ...added.args }); | ||
return result; | ||
} catch (e) { | ||
error = e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe just call mod.finally(finalCtx, { error })
here instead of collecting, so it's more obvious, and so error
isn't a key unless there was an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you didn't do it, please do it
}, | ||
}); | ||
|
||
const result = await t.query(successFn, {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const result = await t.query(successFn, {}); | |
await t.run(async (ctx) => { | |
const result = await (successFn as any)._handler(ctx, {}); |
describe("finally callback", () => { | ||
test("finally callback is called with result and context", async () => { | ||
let finallyCalled = false; | ||
let finallyParams = null; | ||
|
||
const ctx = { foo: "bar" }; | ||
const args = { test: "value" }; | ||
const result = { success: true }; | ||
|
||
const handler = async (_ctx, _args) => result; | ||
|
||
const mod = { | ||
args: {}, | ||
input: async () => ({ ctx: {}, args: {} }), | ||
finally: (params) => { | ||
finallyCalled = true; | ||
finallyParams = params; | ||
}, | ||
}; | ||
|
||
let actualResult; | ||
let actualError; | ||
try { | ||
actualResult = await handler(ctx, args); | ||
} catch (e) { | ||
actualError = e; | ||
throw e; | ||
} finally { | ||
if (mod.finally) { | ||
await mod.finally({ ctx, result: actualResult, error: actualError }); | ||
} | ||
} | ||
|
||
expect(finallyCalled).toBe(true); | ||
expect(finallyParams).toEqual({ | ||
ctx, | ||
result, | ||
error: undefined, | ||
}); | ||
|
||
finallyCalled = false; | ||
finallyParams = null; | ||
|
||
const testError = new Error("Test error"); | ||
const errorHandler = async () => { | ||
throw testError; | ||
}; | ||
|
||
try { | ||
await errorHandler(); | ||
} catch (e) { | ||
} finally { | ||
if (mod.finally) { | ||
await mod.finally({ ctx, result: undefined, error: testError }); | ||
} | ||
} | ||
|
||
expect(finallyCalled).toBe(true); | ||
expect(finallyParams).toEqual({ | ||
ctx, | ||
result: undefined, | ||
error: testError, | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the tests out of their own file into here and delete this useless test
result = await handler(finalCtx, { ...args, ...added.args }); | ||
return result; | ||
} catch (e) { | ||
error = e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you didn't do it, please do it
Closing due to inactivity for more than 7 days. |
Co-Authored-By: Ian Macartney <[email protected]>
Thanks for the feedback, Ian! I've updated the implementation as requested:
All changes have been implemented and pushed. Let me know if you'd like any further adjustments! |
Thanks for the feedback, Ian! I've updated the implementation as requested:
All changes have been implemented and pushed. Let me know if you'd like any further adjustments! |
Co-Authored-By: Ian Macartney <[email protected]>
This would be super useful - any idea on when this could be resolved? |
I was thinking about it, and I think this is the wrong API, but it feels close! const withFinally = customQuery(query, {
args: {},
input: async (ctx, args) => {
const { db, flush } = wrapDbWithBatchedTriggers(ctx);
return {
ctx: { db },
args: {} ,
finally: async () => {
await flush();
}
};
}
}); |
@ghandic what's your use-case you're excited about? |
Co-Authored-By: Ian Macartney <[email protected]>
I've redesigned the finally callback API as requested. The finally callback is now returned from the input function alongside ctx and args, rather than being a top-level property. This allows the finally callback to access resources created during input processing, as shown in your example with wrapDbWithBatchedTriggers. The implementation:
All tests are now passing with the new API design. Let me know if you'd like any further adjustments. |
@ghandic do you want to try it out? I cut |
Co-Authored-By: Ian Macartney <[email protected]>
My use case at the time was trying to get a custom function for tracing with otel so I could do a finally to close the span. Then in the use of the custom function I would add attributes to the span. However, I got stuck into it without the nice API and realised it would only work for actions since it would need to be in a node runtime. |
Add the ability for customFunction to have a 'finally' function that will be called with the result/error of calling the handler. Implemented for customQuery, customMutation, and customAction with a try-finally clause.
This PR adds:
finally
property to theMod
type that takes a callback function with{ ctx, result, error }
parametersBy submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Link to Devin run: https://app.devin.ai/sessions/d03020d376ab485c8d8f124e2455b6cf
Requested by: Ian Macartney ([email protected])