Skip to content

Commit 86f00f4

Browse files
authored
feat: require Next.js 15.1 and React 19 (#305)
7.10.0 release: - updates and fixes the internal framework error handling detection, for Next.js errors that have to be rethrown - requires action callbacks and validation error shaper functions to be async (see [this](vercel/next.js#72336 (comment))). The minimum required Next.js version will be stable 15.1 and minimum React version will be 19. Older framework versions will not work with next-safe-action >= 7.10.0, and vice versa. ## Related issue(s) or discussion(s) - #288 - #299
1 parent 0e160c1 commit 86f00f4

22 files changed

+698
-2425
lines changed

apps/playground/next.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
experimental: {
4+
authInterrupts: true,
5+
},
6+
};
37

48
module.exports = nextConfig;

apps/playground/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@
1212
"dependencies": {
1313
"@hookform/resolvers": "^3.9.1",
1414
"lucide-react": "^0.460.0",
15-
"next": "15.0.3",
15+
"next": "15.1.0",
1616
"next-safe-action": "workspace:*",
17-
"react": "19.0.0-rc-69d4b800-20241021",
18-
"react-dom": "19.0.0-rc-69d4b800-20241021",
17+
"react": "^19",
18+
"react-dom": "^19",
1919
"react-hook-form": "^7.53.2",
2020
"zod": "^3.23.8",
2121
"zod-form-data": "^2.0.2"
2222
},
2323
"devDependencies": {
2424
"@types/node": "^22",
25-
"@types/react": "^18.3.12",
26-
"@types/react-dom": "18.3.1",
25+
"@types/react": "^19",
26+
"@types/react-dom": "^19",
2727
"autoprefixer": "10.4.20",
2828
"eslint": "^8.57.0",
29-
"eslint-config-next": "15.0.3",
29+
"eslint-config-next": "15.0.4-canary.45",
3030
"postcss": "8.4.49",
3131
"tailwindcss": "3.4.15",
3232
"typescript": "^5.6.3"

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"cz-conventional-changelog": "^3.3.0",
3737
"husky": "^9.1.7",
3838
"is-ci": "^3.0.1",
39-
"turbo": "^2.3.0"
39+
"turbo": "^2.3.3"
4040
},
41-
"packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
41+
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
4242
}

packages/next-safe-action/.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ module.exports = defineConfig({
1818
"@typescript-eslint/ban-ts-comment": "off",
1919
"@typescript-eslint/no-redundant-type-constituents": "off",
2020
"@typescript-eslint/no-explicit-any": "off",
21+
"@typescript-eslint/no-unsafe-function-type": "off",
22+
"@typescript-eslint/no-empty-object-type": "off",
23+
"@typescript-eslint/prefer-promise-reject-errors": "off",
24+
"@typescript-eslint/only-throw-error": "off",
2125
"@typescript-eslint/ban-types": "off",
2226
"react-hooks/exhaustive-deps": "warn",
2327
"@typescript-eslint/require-await": "off",

packages/next-safe-action/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@
6969
"@eslint/js": "^9.15.0",
7070
"@sinclair/typebox": "^0.34.4",
7171
"@types/node": "^22",
72-
"@types/react": "^18.3.12",
73-
"@types/react-dom": "18.3.1",
72+
"@types/react": "^19",
73+
"@types/react-dom": "^19",
7474
"deepmerge-ts": "^7.1.3",
7575
"eslint": "^8.57.0",
7676
"eslint-config-prettier": "^9.1.0",
7777
"eslint-define-config": "^2.1.0",
7878
"eslint-plugin-react-hooks": "^5.0.0",
79-
"next": "15.0.3",
79+
"next": "15.1.0",
8080
"prettier": "^3.3.3",
81-
"react": "18.3.1",
82-
"react-dom": "18.3.1",
81+
"react": "^19",
82+
"react-dom": "^19",
8383
"semantic-release": "^23",
8484
"tsup": "^8.3.5",
8585
"tsx": "^4.19.2",
@@ -91,9 +91,9 @@
9191
},
9292
"peerDependencies": {
9393
"@sinclair/typebox": ">= 0.33.3",
94-
"next": ">= 14.0.0",
95-
"react": ">= 18.2.0",
96-
"react-dom": ">= 18.2.0",
94+
"next": ">= 15.1.0",
95+
"react": ">= 19.0.0",
96+
"react-dom": ">= 19.0.0",
9797
"valibot": ">= 0.36.0",
9898
"yup": ">= 1.0.0",
9999
"zod": ">= 3.0.0"

packages/next-safe-action/src/__tests__/action-callbacks.test.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ test("action with no input schema and no server errors calls `onSuccess` and `on
2727
return;
2828
},
2929
{
30-
onSuccess: () => {
30+
onSuccess: async () => {
3131
executed++;
3232
},
33-
onError: () => {
33+
onError: async () => {
3434
executed++; // should not be called
3535
},
36-
onSettled: () => {
36+
onSettled: async () => {
3737
executed++;
3838
},
3939
}
@@ -57,7 +57,15 @@ test("action with input schemas and no errors calls `onSuccess` and `onSettled`
5757
};
5858
},
5959
{
60-
onSuccess: ({ clientInput, bindArgsClientInputs, parsedInput, bindArgsParsedInputs, data, metadata, ctx }) => {
60+
onSuccess: async ({
61+
clientInput,
62+
bindArgsClientInputs,
63+
parsedInput,
64+
bindArgsParsedInputs,
65+
data,
66+
metadata,
67+
ctx,
68+
}) => {
6169
executed++;
6270

6371
assert.deepStrictEqual(
@@ -75,10 +83,10 @@ test("action with input schemas and no errors calls `onSuccess` and `onSettled`
7583
}
7684
);
7785
},
78-
onError: () => {
86+
onError: async () => {
7987
executed++; // should not be called
8088
},
81-
onSettled: ({ clientInput, bindArgsClientInputs, result, metadata, ctx }) => {
89+
onSettled: async ({ clientInput, bindArgsClientInputs, result, metadata, ctx }) => {
8290
executed++;
8391

8492
assert.deepStrictEqual(
@@ -115,10 +123,10 @@ test("action with input schemas and server error calls `onError` and `onSettled`
115123
throw new Error("Server error");
116124
},
117125
{
118-
onSuccess: () => {
126+
onSuccess: async () => {
119127
executed++; // should not be called
120128
},
121-
onError({ error, clientInput, bindArgsClientInputs, metadata, ctx }) {
129+
onError: async ({ error, clientInput, bindArgsClientInputs, metadata, ctx }) => {
122130
executed++;
123131

124132
assert.deepStrictEqual(
@@ -134,7 +142,7 @@ test("action with input schemas and server error calls `onError` and `onSettled`
134142
}
135143
);
136144
},
137-
onSettled({ clientInput, bindArgsClientInputs, result, metadata, ctx }) {
145+
onSettled: async ({ clientInput, bindArgsClientInputs, result, metadata, ctx }) => {
138146
executed++;
139147

140148
assert.deepStrictEqual(
@@ -171,10 +179,10 @@ test("action with validation errors calls `onError` and `onSettled` callbacks wi
171179
};
172180
},
173181
{
174-
onSuccess: () => {
182+
onSuccess: async () => {
175183
executed++; // should not be called
176184
},
177-
onError({ error, clientInput, bindArgsClientInputs, metadata, ctx }) {
185+
onError: async ({ error, clientInput, bindArgsClientInputs, metadata, ctx }) => {
178186
executed++;
179187

180188
assert.deepStrictEqual(
@@ -202,7 +210,7 @@ test("action with validation errors calls `onError` and `onSettled` callbacks wi
202210
}
203211
);
204212
},
205-
onSettled({ clientInput, bindArgsClientInputs, result, metadata, ctx }) {
213+
onSettled: async ({ clientInput, bindArgsClientInputs, result, metadata, ctx }) => {
206214
executed++;
207215

208216
assert.deepStrictEqual(
@@ -252,10 +260,10 @@ test("action with server validation error calls `onError` and `onSettled` callba
252260
});
253261
},
254262
{
255-
onSuccess: () => {
263+
onSuccess: async () => {
256264
executed++; // should not be called
257265
},
258-
onError({ error, clientInput, bindArgsClientInputs, metadata, ctx }) {
266+
onError: async ({ error, clientInput, bindArgsClientInputs, metadata, ctx }) => {
259267
executed++;
260268

261269
assert.deepStrictEqual(
@@ -275,7 +283,7 @@ test("action with server validation error calls `onError` and `onSettled` callba
275283
}
276284
);
277285
},
278-
onSettled({ clientInput, bindArgsClientInputs, result, metadata, ctx }) {
286+
onSettled: async ({ clientInput, bindArgsClientInputs, result, metadata, ctx }) => {
279287
executed++;
280288

281289
assert.deepStrictEqual(

packages/next-safe-action/src/__tests__/bind-args-validation-errors.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ test("action with invalid bind args input gives back an object with correct `bin
5656
];
5757

5858
const action = dac
59-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: flattenBindArgsValidationErrors })
59+
.bindArgsSchemas(bindArgsSchemas, {
60+
handleBindArgsValidationErrorsShape: async (ve) => flattenBindArgsValidationErrors(ve),
61+
})
6062
.action(async () => {
6163
return {
6264
ok: true,
@@ -138,7 +140,9 @@ test("action with invalid bind args input gives back an object with correct `bin
138140
];
139141

140142
const action = foac
141-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: flattenBindArgsValidationErrors })
143+
.bindArgsSchemas(bindArgsSchemas, {
144+
handleBindArgsValidationErrorsShape: async (ve) => flattenBindArgsValidationErrors(ve),
145+
})
142146
.action(async () => {
143147
return {
144148
ok: true,
@@ -225,7 +229,9 @@ test("action with invalid bind args input gives back an object with correct `bin
225229
];
226230

227231
const action = flac
228-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: formatBindArgsValidationErrors })
232+
.bindArgsSchemas(bindArgsSchemas, {
233+
handleBindArgsValidationErrorsShape: async (ve) => formatBindArgsValidationErrors(ve),
234+
})
229235
.action(async () => {
230236
return {
231237
ok: true,

packages/next-safe-action/src/__tests__/combined-validation-errors.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ test("action with invalid bind args input and invalid main input gives back an o
7474

7575
const action = dac
7676
.schema(schema)
77-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: flattenBindArgsValidationErrors })
77+
.bindArgsSchemas(bindArgsSchemas, {
78+
handleBindArgsValidationErrorsShape: async (ve) => flattenBindArgsValidationErrors(ve),
79+
})
7880
.action(async () => {
7981
return {
8082
ok: true,
@@ -131,7 +133,7 @@ test("action with invalid bind args input and invalid main input gives back an o
131133
];
132134

133135
const action = foac
134-
.schema(schema, { handleValidationErrorsShape: flattenValidationErrors })
136+
.schema(schema, { handleValidationErrorsShape: async (ve) => flattenValidationErrors(ve) })
135137
.bindArgsSchemas(bindArgsSchemas)
136138
.action(async () => {
137139
return {
@@ -179,7 +181,9 @@ test("action with invalid bind args input and valid main input gives back an obj
179181

180182
const action = foac
181183
.schema(schema)
182-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: flattenBindArgsValidationErrors })
184+
.bindArgsSchemas(bindArgsSchemas, {
185+
handleBindArgsValidationErrorsShape: async (ve) => flattenBindArgsValidationErrors(ve),
186+
})
183187
.action(async () => {
184188
return {
185189
ok: true,
@@ -287,8 +291,10 @@ test("action with invalid bind args input, invalid main input and root level sch
287291
];
288292

289293
const action = flac
290-
.schema(schema, { handleValidationErrorsShape: formatValidationErrors })
291-
.bindArgsSchemas(bindArgsSchemas, { handleBindArgsValidationErrorsShape: formatBindArgsValidationErrors })
294+
.schema(schema, { handleValidationErrorsShape: async (ve) => formatValidationErrors(ve) })
295+
.bindArgsSchemas(bindArgsSchemas, {
296+
handleBindArgsValidationErrorsShape: async (ve) => formatBindArgsValidationErrors(ve),
297+
})
292298
.action(async () => {
293299
return {
294300
ok: true,

packages/next-safe-action/src/__tests__/middleware.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,10 @@ test("overridden formatted validation errors in execution result from middleware
357357
z.object({
358358
username: z.string().max(3),
359359
}),
360-
{ handleValidationErrorsShape: formatValidationErrors }
360+
{ handleValidationErrorsShape: async (ve) => formatValidationErrors(ve) }
361361
)
362362
.bindArgsSchemas([z.object({ age: z.number().positive() })], {
363-
handleBindArgsValidationErrorsShape: formatBindArgsValidationErrors,
363+
handleBindArgsValidationErrorsShape: async (ve) => formatBindArgsValidationErrors(ve),
364364
})
365365
.use(async ({ next }) => {
366366
// Await action execution.

packages/next-safe-action/src/__tests__/validation-errors.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ test("action with invalid input gives back an object with correct `validationErr
126126

127127
const action = dac
128128
.schema(schema, {
129-
handleValidationErrorsShape: flattenValidationErrors,
129+
handleValidationErrorsShape: async (ve) => flattenValidationErrors(ve),
130130
})
131131
.action(async () => {
132132
return {
@@ -318,7 +318,7 @@ test("action with invalid input gives back an object with correct `validationErr
318318

319319
const action = foac
320320
.schema(schema, {
321-
handleValidationErrorsShape: flattenValidationErrors,
321+
handleValidationErrorsShape: async (ve) => flattenValidationErrors(ve),
322322
})
323323
.action(async () => {
324324
return {
@@ -452,7 +452,7 @@ test("action with invalid input gives back an object with correct `validationErr
452452

453453
const action = flac
454454
.schema(schema, {
455-
handleValidationErrorsShape: formatValidationErrors,
455+
handleValidationErrorsShape: async (ve) => formatValidationErrors(ve),
456456
})
457457
.action(async () => {
458458
return {

0 commit comments

Comments
 (0)