Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: TheEdoRan/next-safe-action
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.8.2
Choose a base ref
...
head repository: TheEdoRan/next-safe-action
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.9.0
Choose a head ref
  • 3 commits
  • 17 files changed
  • 1 contributor

Commits on Sep 3, 2024

  1. chore(github): update issue template

    TheEdoRan committed Sep 3, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    TheEdoRan Edoardo Ranghieri
    Copy the full SHA
    6292213 View commit details
  2. chore(website): better explain optimisticState behavior in `useOpti…

    …misticAction` hook page
    TheEdoRan committed Sep 3, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    TheEdoRan Edoardo Ranghieri
    Copy the full SHA
    4a74df9 View commit details

Commits on Sep 4, 2024

  1. feat: merge server error handling functions into handleServerError (#…

    …257)
    
    Code in this PR merges the functionality of `handleServerErrorLog` and
    `handleReturnedServerError` functions into a single optional
    initialization function called `handleServerError`. This change has been
    made because having two functions for server error handling is
    unnecessary, you can easily manage both logging and returned error
    within a single function.
    TheEdoRan authored Sep 4, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c900720 View commit details
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/1_bug_report.yml
Original file line number Diff line number Diff line change
@@ -45,8 +45,8 @@ body:
required: true
- type: input
attributes:
label: Minimal reproduction example
description: Link to a minimal example that reproduces the bug. Please provide a GitHub/CodeSandbox link with as little code as possible to reproduce the bug.
label: Link to a minimal reproduction of the issue
description: Link to a minimal example that reproduces the bug. Please provide a GitHub/CodeSandbox link with as little code as possible to reproduce the issue. Without a link, the issue will be closed as "not planned" until a valid URL is provided.
placeholder: https://github.com/...
validations:
required: true
15 changes: 5 additions & 10 deletions apps/playground/src/lib/safe-action.ts
Original file line number Diff line number Diff line change
@@ -9,16 +9,11 @@ export class ActionError extends Error {}

export const action = createSafeActionClient({
validationAdapter: zodAdapter(),
// You can provide a custom logging function, otherwise the lib will use `console.error`
// as the default logging system. If you want to disable server errors logging,
// just pass an empty Promise.
handleServerErrorLog: (e) => {
console.error(
"CUSTOM ERROR LOG FUNCTION, server error message:",
e.message
);
},
handleReturnedServerError: (e) => {
// You can provide a custom handler for server errors, otherwise the lib will use `console.error`
// as the default logging mechanism and will return the DEFAULT_SERVER_ERROR_MESSAGE for all server errors.
handleServerError: (e) => {
console.error("Action server error occurred:", e.message);

// If the error is an instance of `ActionError`, unmask the message.
if (e instanceof ActionError) {
return e.message;
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/__tests__/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import { zodAdapter } from "../adapters/zod";

const ac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleServerError: () => DEFAULT_SERVER_ERROR_MESSAGE, // disable server errors logging for these tests
defineMetadataSchema() {
return z.object({
actionName: z.string(),
7 changes: 4 additions & 3 deletions packages/next-safe-action/src/__tests__/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { z } from "zod";
import {
createMiddleware,
createSafeActionClient,
DEFAULT_SERVER_ERROR_MESSAGE,
formatBindArgsValidationErrors,
formatValidationErrors,
returnValidationErrors,
@@ -14,8 +15,8 @@ import { zodAdapter } from "../adapters/zod";

const ac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
return {
message: e.message,
};
@@ -296,7 +297,7 @@ test("server validation errors in execution result from middleware are correct",

const flac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog() {}, // disable server errors logging for these tests
handleServerError: () => DEFAULT_SERVER_ERROR_MESSAGE, // disable server errors logging for these tests
defaultValidationErrorsShape: "flattened",
});

20 changes: 10 additions & 10 deletions packages/next-safe-action/src/__tests__/server-error.test.ts
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@ class ActionError extends Error {

const ac1 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
if (e instanceof ActionError) {
return e.message;
}
@@ -107,15 +107,15 @@ test("error occurred with `throwServerError` set to true at the action level thr
// Server error is an object with a 'message' property.
const ac2 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server errors logging for these tests
return {
message: e.message,
};
},
});

test("error occurred in server code function has the correct shape defined by `handleReturnedServerError`", async () => {
test("error occurred in server code function has the correct shape defined by `handleServerError`", async () => {
const action = ac2.action(async () => {
throw new Error("Something bad happened");
});
@@ -129,7 +129,7 @@ test("error occurred in server code function has the correct shape defined by `h
assert.deepStrictEqual(actualResult, expectedResult);
});

test("error occurred in middleware function has the correct shape defined by `handleReturnedServerError`", async () => {
test("error occurred in middleware function has the correct shape defined by `handleServerError`", async () => {
const action = ac2
.use(async ({ next }) => next())
.use(async () => {
@@ -153,21 +153,21 @@ test("error occurred in middleware function has the correct shape defined by `ha
// Rethrow all server errors.
const ac3 = createSafeActionClient({
validationAdapter: zodAdapter(),
handleServerErrorLog: () => {}, // disable server errors logging for these tests
handleReturnedServerError(e) {
handleServerError(e) {
// disable server error logging for these tests
throw e;
},
});

test("action throws if an error occurred in server code function and `handleReturnedServerError` rethrows it", async () => {
test("action throws if an error occurred in server code function and `handleServerError` rethrows it", async () => {
const action = ac3.action(async () => {
throw new Error("Something bad happened");
});

assert.rejects(() => action());
});

test("action throws if an error occurred in middleware function and `handleReturnedServerError` rethrows it", async () => {
test("action throws if an error occurred in middleware function and `handleServerError` rethrows it", async () => {
const action = ac3
.use(async ({ next }) => next())
.use(async () => {
Original file line number Diff line number Diff line change
@@ -171,7 +171,8 @@ test("action with invalid output data returns the default `serverError`", async
test("action with invalid output data throws an error of the correct type", async () => {
const tac = createSafeActionClient({
validationAdapter: zodAdapter(),
handleReturnedServerError: (e) => {
handleServerError: (e) => {
// disable server error logging for this test
throw e;
},
});
17 changes: 2 additions & 15 deletions packages/next-safe-action/src/action-builder.ts
Original file line number Diff line number Diff line change
@@ -50,10 +50,7 @@ export function actionBuilder<
handleBindArgsValidationErrorsShape: HandleBindArgsValidationErrorsShapeFn<BAS, CBAVE>;
metadataSchema: MetadataSchema;
metadata: MD;
handleServerErrorLog: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, any>["handleServerErrorLog"]>;
handleReturnedServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, any>["handleReturnedServerError"]
>;
handleServerError: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, any>["handleServerError"]>;
middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
ctxType: Ctx;
throwValidationErrors: boolean;
@@ -250,7 +247,7 @@ export function actionBuilder<
// the default message.
const error = isError(e) ? e : new Error(DEFAULT_SERVER_ERROR_MESSAGE);
const returnedError = await Promise.resolve(
args.handleReturnedServerError(error, {
args.handleServerError(error, {
clientInput: clientInputs.at(-1), // pass raw client input
bindArgsClientInputs: bindArgsSchemas.length ? clientInputs.slice(0, -1) : [],
ctx: currentCtx,
@@ -259,16 +256,6 @@ export function actionBuilder<
);

middlewareResult.serverError = returnedError;

await Promise.resolve(
args.handleServerErrorLog(error, {
returnedError,
clientInput: clientInputs.at(-1), // pass raw client input
bindArgsClientInputs: bindArgsSchemas.length ? clientInputs.slice(0, -1) : [],
ctx: currentCtx,
metadata: args.metadata as MetadataSchema extends Schema ? Infer<MetadataSchema> : undefined,
})
);
}
}
};
26 changes: 8 additions & 18 deletions packages/next-safe-action/src/index.ts
Original file line number Diff line number Diff line change
@@ -40,27 +40,17 @@ export const createSafeActionClient = <
>(
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema, ODVES>
) => {
// If server log function is not provided, default to `console.error` for logging
// server error messages.
const handleServerErrorLog =
createOpts?.handleServerErrorLog ||
(((originalError: Error) => {
console.error("Action error:", originalError.message);
}) as unknown as NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerErrorLog"]>);

// If `handleReturnedServerError` is provided, use it to handle server error
// messages returned on the client.
// Otherwise mask the error and use a generic message.
const handleReturnedServerError =
createOpts?.handleReturnedServerError ||
((() => DEFAULT_SERVER_ERROR_MESSAGE) as unknown as NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleReturnedServerError"]
>);
// If `handleServerError` is provided, use it, otherwise default to log to console and generic error message.
const handleServerError: NonNullable<SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerError"]> =
createOpts?.handleServerError ||
((e) => {
console.error("Action error:", e.message);
return DEFAULT_SERVER_ERROR_MESSAGE as ServerError;
});

return new SafeActionClient({
middlewareFns: [async ({ next }) => next({ ctx: {} })],
handleServerErrorLog,
handleReturnedServerError,
handleServerError,
inputSchemaFn: undefined,
bindArgsSchemas: [],
outputSchema: undefined,
11 changes: 1 addition & 10 deletions packages/next-safe-action/src/index.types.ts
Original file line number Diff line number Diff line change
@@ -29,16 +29,7 @@ export type SafeActionClientOpts<
> = {
validationAdapter?: ValidationAdapter;
defineMetadataSchema?: () => MetadataSchema;
handleReturnedServerError?: (
error: Error,
utils: ServerErrorFunctionUtils<MetadataSchema>
) => MaybePromise<ServerError>;
handleServerErrorLog?: (
originalError: Error,
utils: ServerErrorFunctionUtils<MetadataSchema> & {
returnedError: ServerError;
}
) => MaybePromise<void>;
handleServerError?: (error: Error, utils: ServerErrorFunctionUtils<MetadataSchema>) => MaybePromise<ServerError>;
throwValidationErrors?: boolean;
defaultValidationErrorsShape?: ODVES;
};
33 changes: 11 additions & 22 deletions packages/next-safe-action/src/safe-action-client.ts
Original file line number Diff line number Diff line change
@@ -31,11 +31,8 @@ export class SafeActionClient<
CVE = undefined,
const CBAVE = undefined,
> {
readonly #handleServerErrorLog: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerErrorLog"]
>;
readonly #handleReturnedServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleReturnedServerError"]
readonly #handleServerError: NonNullable<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerError"]
>;
readonly #middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
readonly #metadataSchema: MetadataSchema;
@@ -65,13 +62,12 @@ export class SafeActionClient<
} & Required<
Pick<
SafeActionClientOpts<ServerError, MetadataSchema, ODVES>,
"handleReturnedServerError" | "handleServerErrorLog" | "defaultValidationErrorsShape" | "throwValidationErrors"
"handleServerError" | "defaultValidationErrorsShape" | "throwValidationErrors"
>
>
) {
this.#middlewareFns = opts.middlewareFns;
this.#handleServerErrorLog = opts.handleServerErrorLog;
this.#handleReturnedServerError = opts.handleReturnedServerError;
this.#handleServerError = opts.handleServerError;
this.#metadataSchema = opts.metadataSchema;
this.#metadata = opts.metadata;
this.#inputSchemaFn = (opts.inputSchemaFn ?? undefined) as ISF;
@@ -94,8 +90,7 @@ export class SafeActionClient<
use<NextCtx extends object>(middlewareFn: MiddlewareFn<ServerError, MD, Ctx, Ctx & NextCtx>) {
return new SafeActionClient({
middlewareFns: [...this.#middlewareFns, middlewareFn],
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
@@ -119,8 +114,7 @@ export class SafeActionClient<
metadata(data: MD) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: data,
inputSchemaFn: this.#inputSchemaFn,
@@ -154,8 +148,7 @@ export class SafeActionClient<
) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
// @ts-expect-error
@@ -196,8 +189,7 @@ export class SafeActionClient<
) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
@@ -222,8 +214,7 @@ export class SafeActionClient<
outputSchema<OOS extends Schema>(dataSchema: OOS) {
return new SafeActionClient({
middlewareFns: this.#middlewareFns,
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
metadataSchema: this.#metadataSchema,
metadata: this.#metadata,
inputSchemaFn: this.#inputSchemaFn,
@@ -250,8 +241,7 @@ export class SafeActionClient<
utils?: SafeActionUtils<ServerError, MD, Ctx, IS, BAS, CVE, CBAVE, Data>
) {
return actionBuilder({
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
middlewareFns: this.#middlewareFns,
ctxType: this.#ctxType,
metadataSchema: this.#metadataSchema,
@@ -279,8 +269,7 @@ export class SafeActionClient<
utils?: SafeActionUtils<ServerError, MD, Ctx, IS, BAS, CVE, CBAVE, Data>
) {
return actionBuilder({
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
handleServerError: this.#handleServerError,
middlewareFns: this.#middlewareFns,
ctxType: this.#ctxType,
metadataSchema: this.#metadataSchema,
2 changes: 1 addition & 1 deletion website/docs/define-actions/action-result-object.md
Original file line number Diff line number Diff line change
@@ -10,4 +10,4 @@ Here's how action result object is structured (all keys are optional):
- `data`: when execution is successful, what you returned in action's server code.
- `validationErrors`: when input data doesn't pass validation, an object that contains the validation errors. Can be customized using [`defaultValidationErrorsShape`](/docs/define-actions/create-the-client#defaultvalidationerrorsshape) initialization option and/or via [`handleValidationErrorsShape`function passed to `schema` method](/docs/define-actions/validation-errors#customize-validation-errors-format).
- `bindArgsValidationErrors`: when bound arguments don't pass validation, an object that contains the validation errors. Can be customized using [`defaultValidationErrorsShape`](/docs/define-actions/create-the-client#defaultvalidationerrorsshape) initialization option and/or via [`handleBindArgsValidationErrorsShape` function passed to `bindArgsSchemas` method](/docs/define-actions/validation-errors#customize-validation-errors-format).
- `serverError`: when execution fails, an error object that contains the error message, customizable by using the [`handleReturnedServerError`](/docs/define-actions/create-the-client#handlereturnedservererror) initialization function.
- `serverError`: when execution fails, an error object that contains the error message, customizable by using the [`handleServerError`](/docs/define-actions/create-the-client#handleservererror) initialization function.
Loading