next-action-plus 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +285 -0
- package/dist/index.d.ts +214 -0
- package/dist/index.js +289 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# next-action-plus
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**next-action-plus** is a tiny TypeScript library for **type-safe Next.js Server Actions**.
|
|
6
|
+
|
|
7
|
+
You write a server action like you always do. You optionally add a schema. You get:
|
|
8
|
+
|
|
9
|
+
- Input validation (Zod compatible, but schema-agnostic)
|
|
10
|
+
- Correct `parsedInput` type inference
|
|
11
|
+
- A native-feeling API: `.schema(...).action(async (...) => ...)`
|
|
12
|
+
|
|
13
|
+
No framework lock-in. No new mental model.
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Why next-action-plus](#why-next-action-plus)
|
|
18
|
+
- [Install](#install)
|
|
19
|
+
- [Quick start](#quick-start)
|
|
20
|
+
- [Error handling](#error-handling)
|
|
21
|
+
- [Examples](#examples)
|
|
22
|
+
- [Next.js Server Action](#nextjs-server-action)
|
|
23
|
+
- [Client Component usage](#client-component-usage)
|
|
24
|
+
- [FormData + File uploads](#formdata--file-uploads)
|
|
25
|
+
- [Middleware (ctx)](#middleware-ctx)
|
|
26
|
+
- [Schema-agnostic validation](#schema-agnostic-validation)
|
|
27
|
+
- [FAQ](#faq)
|
|
28
|
+
- [Release](#release)
|
|
29
|
+
|
|
30
|
+
## Why next-action-plus
|
|
31
|
+
|
|
32
|
+
Next.js Server Actions are great because they are simple: a function that runs on the server.
|
|
33
|
+
But when you accept user input, you usually need:
|
|
34
|
+
|
|
35
|
+
- validation
|
|
36
|
+
- safe parsing
|
|
37
|
+
- accurate types in the handler
|
|
38
|
+
|
|
39
|
+
next-action-plus keeps the **server action function shape** and adds **type-safe input parsing**.
|
|
40
|
+
You can treat your action like a normal function, and you still get strict TypeScript inference.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm i next-action-plus
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you want Zod schemas, also install:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm i zod
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
58
|
+
import { z } from 'zod';
|
|
59
|
+
|
|
60
|
+
export const sayHello = createSafeActionClient()
|
|
61
|
+
.schema(z.object({ name: z.string().min(1) }))
|
|
62
|
+
.action(async ({ parsedInput }) => {
|
|
63
|
+
return { message: `Hello ${parsedInput.name}` };
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### What you get
|
|
68
|
+
|
|
69
|
+
- `sayHello` is still “just a function” you can call.
|
|
70
|
+
- `parsedInput` is inferred from your schema.
|
|
71
|
+
- The return type is inferred from your handler.
|
|
72
|
+
|
|
73
|
+
## Error handling
|
|
74
|
+
|
|
75
|
+
If schema validation fails, the action **throws** an `Error`.
|
|
76
|
+
|
|
77
|
+
The error message is intentionally short and looks like:
|
|
78
|
+
|
|
79
|
+
```txt
|
|
80
|
+
Input (name) is error: String must contain at least 1 character(s)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Notes:
|
|
84
|
+
|
|
85
|
+
- Only the **first** validation issue is used to build the message.
|
|
86
|
+
- The thrown error is a `SafeActionValidationError` (extends `Error`) with a developer-friendly payload.
|
|
87
|
+
- The original validator error is preserved on `error.cause`.
|
|
88
|
+
- Normalized issues are available on `error.issues`.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
try {
|
|
92
|
+
await sayHello({ name: '' });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
// error.message => "Input (name) is error: ..."
|
|
95
|
+
// (error as SafeActionValidationError).code => "VALIDATION_ERROR"
|
|
96
|
+
// (error as SafeActionValidationError).issues => [{ path, message, raw }]
|
|
97
|
+
// (error as any).cause => original validator error (e.g. ZodError)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Customizing errors (options)
|
|
102
|
+
|
|
103
|
+
`createSafeActionClient` accepts options to control logging and customize thrown errors.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
107
|
+
|
|
108
|
+
export const client = createSafeActionClient({
|
|
109
|
+
logger: false,
|
|
110
|
+
formatValidationError: ({ message, issues, error }) => {
|
|
111
|
+
const e = new Error(message);
|
|
112
|
+
(e as any).issues = issues;
|
|
113
|
+
(e as any).cause = error;
|
|
114
|
+
return e;
|
|
115
|
+
},
|
|
116
|
+
onError: ({ phase, error }) => {
|
|
117
|
+
// report errors (Sentry, etc)
|
|
118
|
+
// phase: "validation" | "middleware" | "handler"
|
|
119
|
+
void error;
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
### Next.js Server Action
|
|
127
|
+
|
|
128
|
+
This keeps the native Server Action feel.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import 'server-only';
|
|
132
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
133
|
+
import { z } from 'zod';
|
|
134
|
+
|
|
135
|
+
export const updateProfile = createSafeActionClient()
|
|
136
|
+
.schema(z.object({ displayName: z.string().min(2) }))
|
|
137
|
+
.action(async ({ parsedInput }) => {
|
|
138
|
+
// parsedInput.displayName is string
|
|
139
|
+
return { ok: true };
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
More: [examples/nextjs-server-action.ts](https://github.com/muhgholy/next-action-plus/blob/main/examples/nextjs-server-action.ts)
|
|
144
|
+
|
|
145
|
+
### Client Component usage
|
|
146
|
+
|
|
147
|
+
You can import a Server Action into a Client Component and call it like a normal async function.
|
|
148
|
+
Next.js runs it on the server.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
'use client';
|
|
152
|
+
|
|
153
|
+
import { useState, useTransition } from 'react';
|
|
154
|
+
import { sayHello } from '@/app/actions';
|
|
155
|
+
|
|
156
|
+
export function SayHelloClient() {
|
|
157
|
+
const [name, setName] = useState('');
|
|
158
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
159
|
+
const [pending, startTransition] = useTransition();
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<input value={name} onChange={e => setName(e.target.value)} placeholder='Ada' />
|
|
164
|
+
<button
|
|
165
|
+
disabled={pending}
|
|
166
|
+
onClick={() =>
|
|
167
|
+
startTransition(async () => {
|
|
168
|
+
const result = await sayHello({ name });
|
|
169
|
+
setMessage(result.message);
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
>
|
|
173
|
+
{pending ? 'Sending…' : 'Say hello'}
|
|
174
|
+
</button>
|
|
175
|
+
{message ? <p>{message}</p> : null}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
More:
|
|
182
|
+
|
|
183
|
+
- [examples/nextjs-server-action-for-client.ts](https://github.com/muhgholy/next-action-plus/blob/main/examples/nextjs-server-action-for-client.ts)
|
|
184
|
+
- [examples/nextjs-client-component.tsx](https://github.com/muhgholy/next-action-plus/blob/main/examples/nextjs-client-component.tsx)
|
|
185
|
+
|
|
186
|
+
### FormData + File uploads
|
|
187
|
+
|
|
188
|
+
Works with `FormData` and `File` using `zod-form-data`.
|
|
189
|
+
|
|
190
|
+
If you chain multiple schemas and the input is a `FormData`, next-action-plus will first try to find a schema that can parse the `FormData` into a plain object, then validate the remaining schemas against that object.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
194
|
+
import { zfd } from 'zod-form-data';
|
|
195
|
+
|
|
196
|
+
export const uploadAvatar = createSafeActionClient()
|
|
197
|
+
.schema(
|
|
198
|
+
zfd.formData({
|
|
199
|
+
avatar: zfd.file(),
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
.action(async ({ parsedInput }) => {
|
|
203
|
+
// parsedInput.avatar is File
|
|
204
|
+
return { filename: parsedInput.avatar.name };
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Middleware (ctx)
|
|
209
|
+
|
|
210
|
+
Add data to context in a type-safe way.
|
|
211
|
+
|
|
212
|
+
Validation runs first, then middleware runs, then your handler runs.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
216
|
+
import { z } from 'zod';
|
|
217
|
+
|
|
218
|
+
const client = createSafeActionClient().use(async ({ next }) => next({ ctx: { userId: 'u_123' } }));
|
|
219
|
+
|
|
220
|
+
export const deletePost = client.schema(z.object({ postId: z.string() })).action(async ({ parsedInput, ctx }) => {
|
|
221
|
+
// ctx.userId is string
|
|
222
|
+
// parsedInput.postId is string
|
|
223
|
+
return { ok: true };
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Schema-agnostic validation
|
|
228
|
+
|
|
229
|
+
You are not forced into one validator.
|
|
230
|
+
|
|
231
|
+
Supported schema shapes:
|
|
232
|
+
|
|
233
|
+
- Zod (`parse` / `parseAsync`)
|
|
234
|
+
- Generic `{ parse(...) }` and `{ parseAsync(...) }`
|
|
235
|
+
- Standard Schema v1 (`~standard.validate`)
|
|
236
|
+
|
|
237
|
+
If you already have a schema system, you can plug it in.
|
|
238
|
+
|
|
239
|
+
#### Schema chaining
|
|
240
|
+
|
|
241
|
+
You can chain multiple schemas. If they return objects, outputs are merged.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { createSafeActionClient } from 'next-action-plus';
|
|
245
|
+
|
|
246
|
+
const s1 = { parse: (_: unknown) => ({ a: 'a' }) };
|
|
247
|
+
const s2 = { parse: (_: unknown) => ({ b: 2 }) };
|
|
248
|
+
|
|
249
|
+
export const demo = createSafeActionClient()
|
|
250
|
+
.schema(s1)
|
|
251
|
+
.schema(s2)
|
|
252
|
+
.action(async ({ parsedInput }) => {
|
|
253
|
+
// parsedInput is { a: string; b: number }
|
|
254
|
+
return parsedInput;
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## FAQ
|
|
259
|
+
|
|
260
|
+
### Is next-action-plus only for Next.js?
|
|
261
|
+
|
|
262
|
+
No. It is built for the Server Actions style, but it runs in any Node 20+ runtime.
|
|
263
|
+
|
|
264
|
+
### Do I need to learn a new pattern?
|
|
265
|
+
|
|
266
|
+
No. The API is intentionally small: `createSafeActionClient() → .schema() → .use() → .action()`.
|
|
267
|
+
|
|
268
|
+
### Can I validate `FormData`?
|
|
269
|
+
|
|
270
|
+
Yes. Use `zod-form-data` (see [FormData + File uploads](#formdata--file-uploads)).
|
|
271
|
+
|
|
272
|
+
### Does it change my return types?
|
|
273
|
+
|
|
274
|
+
No. The returned action function keeps the exact return type of your handler.
|
|
275
|
+
|
|
276
|
+
### Can I chain multiple schemas?
|
|
277
|
+
|
|
278
|
+
Yes. Multiple schemas can validate the same base input. If they produce objects, outputs are merged.
|
|
279
|
+
|
|
280
|
+
## Release
|
|
281
|
+
|
|
282
|
+
This repository ships with `semantic-release`.
|
|
283
|
+
|
|
284
|
+
- Push Conventional Commits to `main`
|
|
285
|
+
- GitHub Actions runs `npm test`, `npm run build`, then publishes
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type TMiddlewareFn<Ctx extends Record<string, unknown> = Record<string, never>, NextCtx extends Record<string, unknown> = Record<string, never>> = (opts: {
|
|
4
|
+
input: unknown;
|
|
5
|
+
ctx: Readonly<Ctx>;
|
|
6
|
+
next: {
|
|
7
|
+
<NC extends Record<string, unknown> = Record<string, never>>(opts?: {
|
|
8
|
+
ctx?: NC;
|
|
9
|
+
}): Promise<Ctx & NC>;
|
|
10
|
+
};
|
|
11
|
+
}) => Promise<Ctx & NextCtx>;
|
|
12
|
+
|
|
13
|
+
type TIsAny<T> = 0 extends 1 & T ? true : false;
|
|
14
|
+
/**
|
|
15
|
+
* Makes a type disappear if it relies on a missing optional dependency.
|
|
16
|
+
*
|
|
17
|
+
* In practice this is used with `zod` as a peer dependency.
|
|
18
|
+
*/
|
|
19
|
+
type TIfInstalled<T> = TIsAny<T> extends true ? never : T;
|
|
20
|
+
type TPrettify<T> = {
|
|
21
|
+
[K in keyof T]: T[K];
|
|
22
|
+
} & {};
|
|
23
|
+
type TUnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
24
|
+
|
|
25
|
+
type TSafeActionIssuePathSegment = string | number;
|
|
26
|
+
type TSafeActionIssue = {
|
|
27
|
+
path: TSafeActionIssuePathSegment[];
|
|
28
|
+
message: string;
|
|
29
|
+
raw?: unknown;
|
|
30
|
+
};
|
|
31
|
+
type TSafeActionErrorCode = 'VALIDATION_ERROR' | 'MIDDLEWARE_ERROR' | 'HANDLER_ERROR' | 'ACTION_ERROR';
|
|
32
|
+
type TSafeActionErrorPhase = 'validation' | 'middleware' | 'handler';
|
|
33
|
+
type TSafeActionUnknownErrorContext = {
|
|
34
|
+
phase: TSafeActionErrorPhase;
|
|
35
|
+
error: unknown;
|
|
36
|
+
input?: unknown;
|
|
37
|
+
parsedInput?: unknown;
|
|
38
|
+
ctx?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
type TSafeActionValidationErrorContext = TSafeActionUnknownErrorContext & {
|
|
41
|
+
message: string;
|
|
42
|
+
issues: TSafeActionIssue[];
|
|
43
|
+
};
|
|
44
|
+
type TSafeActionErrorContext = TSafeActionUnknownErrorContext | TSafeActionValidationErrorContext;
|
|
45
|
+
type TSafeActionClientOptions = {
|
|
46
|
+
/**
|
|
47
|
+
* Control logging when an action throws.
|
|
48
|
+
* - `false` disables logging.
|
|
49
|
+
* - Defaults to `console`.
|
|
50
|
+
*/
|
|
51
|
+
logger?: false | {
|
|
52
|
+
error: (...args: unknown[]) => void;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Hook called whenever an action throws (validation, middleware, or handler).
|
|
56
|
+
*/
|
|
57
|
+
onError?: (ctx: TSafeActionErrorContext) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Customize the thrown error when validation fails.
|
|
60
|
+
* The returned Error will be thrown.
|
|
61
|
+
*/
|
|
62
|
+
formatValidationError?: (ctx: TSafeActionValidationErrorContext) => Error;
|
|
63
|
+
/**
|
|
64
|
+
* Customize the thrown error for non-validation failures.
|
|
65
|
+
* If omitted, the original error is re-thrown.
|
|
66
|
+
*/
|
|
67
|
+
formatError?: (ctx: TSafeActionUnknownErrorContext) => Error;
|
|
68
|
+
/**
|
|
69
|
+
* When enabled, include `input`, `parsedInput`, and `ctx` in error details.
|
|
70
|
+
* Off by default because it can accidentally expose sensitive data.
|
|
71
|
+
*/
|
|
72
|
+
includeInputInErrorDetails?: boolean;
|
|
73
|
+
};
|
|
74
|
+
declare class SafeActionError extends Error {
|
|
75
|
+
code: TSafeActionErrorCode;
|
|
76
|
+
phase?: TSafeActionErrorPhase;
|
|
77
|
+
data?: unknown;
|
|
78
|
+
cause?: unknown;
|
|
79
|
+
constructor(message: string, opts?: {
|
|
80
|
+
code: TSafeActionErrorCode;
|
|
81
|
+
phase?: TSafeActionErrorPhase;
|
|
82
|
+
cause?: unknown;
|
|
83
|
+
data?: unknown;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
declare class SafeActionValidationError extends SafeActionError {
|
|
87
|
+
issues: TSafeActionIssue[];
|
|
88
|
+
constructor(message: string, opts: {
|
|
89
|
+
issues: TSafeActionIssue[];
|
|
90
|
+
phase?: TSafeActionErrorPhase;
|
|
91
|
+
cause?: unknown;
|
|
92
|
+
data?: unknown;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
declare const isSafeActionError: (error: unknown) => error is SafeActionError;
|
|
96
|
+
declare const isSafeActionValidationError: (error: unknown) => error is SafeActionValidationError;
|
|
97
|
+
|
|
98
|
+
type TGenericSchema = {
|
|
99
|
+
parse: (input: unknown) => unknown;
|
|
100
|
+
safeParse?: (input: unknown) => {
|
|
101
|
+
success: boolean;
|
|
102
|
+
data?: unknown;
|
|
103
|
+
error?: unknown;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
type TGenericSchemaAsync = {
|
|
107
|
+
parseAsync: (input: unknown) => Promise<unknown>;
|
|
108
|
+
safeParseAsync?: (input: unknown) => Promise<{
|
|
109
|
+
success: boolean;
|
|
110
|
+
data?: unknown;
|
|
111
|
+
error?: unknown;
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type TStandardSchemaV1<Input = unknown, Output = Input> = {
|
|
116
|
+
readonly '~standard': {
|
|
117
|
+
readonly version: 1;
|
|
118
|
+
readonly vendor: string;
|
|
119
|
+
readonly validate: (value: unknown) => TStandardSchemaV1Result<Output> | Promise<TStandardSchemaV1Result<Output>>;
|
|
120
|
+
readonly types?: {
|
|
121
|
+
readonly input: Input;
|
|
122
|
+
readonly output: Output;
|
|
123
|
+
} | undefined;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
type TStandardSchemaV1PathSegment = {
|
|
127
|
+
readonly key: PropertyKey;
|
|
128
|
+
};
|
|
129
|
+
type TStandardSchemaV1Issue = {
|
|
130
|
+
readonly message: string;
|
|
131
|
+
readonly path?: ReadonlyArray<PropertyKey | TStandardSchemaV1PathSegment> | undefined;
|
|
132
|
+
};
|
|
133
|
+
type TStandardSchemaV1Result<Output> = {
|
|
134
|
+
readonly value: Output;
|
|
135
|
+
readonly issues?: undefined;
|
|
136
|
+
} | {
|
|
137
|
+
readonly issues: ReadonlyArray<TStandardSchemaV1Issue>;
|
|
138
|
+
readonly value?: undefined;
|
|
139
|
+
};
|
|
140
|
+
type TStandardSchemaV1InferInput<S extends TStandardSchemaV1<any, any>> = S extends TStandardSchemaV1<infer Input, any> ? Input : unknown;
|
|
141
|
+
type TStandardSchemaV1InferOutput<S extends TStandardSchemaV1<any, any>> = S extends TStandardSchemaV1<any, infer Output> ? Output : unknown;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Helper type for `zod-form-data` (and similar) schemas.
|
|
145
|
+
*
|
|
146
|
+
* `zod-form-data`'s `zfd.formData(...)` wraps an object schema in a `z.preprocess`,
|
|
147
|
+
* so the resulting schema is a `ZodEffects` whose input includes `FormData`.
|
|
148
|
+
*/
|
|
149
|
+
type TZodFormDataSchemaFactory = {
|
|
150
|
+
<T extends z.ZodRawShape>(shape: T): z.ZodEffects<z.ZodObject<T>, z.output<z.ZodObject<T>>, FormData | z.input<z.ZodObject<T>>>;
|
|
151
|
+
<T extends z.ZodTypeAny>(schema: T): z.ZodEffects<T, z.output<T>, FormData | z.input<T>>;
|
|
152
|
+
};
|
|
153
|
+
type TZodAcceptsFormData<S extends z.ZodTypeAny> = S extends z.ZodEffects<z.ZodTypeAny, unknown, infer I> ? (FormData extends I ? true : false) : false;
|
|
154
|
+
type TZodFormDataInput<S extends z.ZodTypeAny> = TZodAcceptsFormData<S> extends true ? FormData | z.input<S> : z.input<S>;
|
|
155
|
+
|
|
156
|
+
declare const schemaAvailable: readonly ["zod", "generic", "genericAsync", "standard"];
|
|
157
|
+
type TSchemaAvailable = (typeof schemaAvailable)[number];
|
|
158
|
+
type TSchema = TIfInstalled<z.ZodTypeAny> | TIfInstalled<TGenericSchema> | TIfInstalled<TGenericSchemaAsync> | TStandardSchemaV1<any, any>;
|
|
159
|
+
type TInfer<S extends TSchema> = S extends TIfInstalled<z.ZodTypeAny> ? z.infer<S> : S extends TIfInstalled<TGenericSchema> ? ReturnType<S['parse']> : S extends TIfInstalled<TGenericSchemaAsync> ? Awaited<ReturnType<S['parseAsync']>> : S extends TStandardSchemaV1<any, any> ? TStandardSchemaV1InferOutput<S> : never;
|
|
160
|
+
type TInferIn<S extends TSchema> = S extends TIfInstalled<z.ZodTypeAny> ? z.input<S> : S extends TIfInstalled<TGenericSchema> ? Parameters<S['parse']>[0] : S extends TIfInstalled<TGenericSchemaAsync> ? Parameters<S['parseAsync']>[0] : S extends TStandardSchemaV1<any, any> ? TStandardSchemaV1InferInput<S> : never;
|
|
161
|
+
type TInferArray<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? [TInfer<First>, ...TInferArray<Rest>] : [];
|
|
162
|
+
type TInferInArray<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? [TInferIn<First>, ...TInferInArray<Rest>] : [];
|
|
163
|
+
type TIsFormData<S extends TSchema> = S extends z.ZodTypeAny ? TZodAcceptsFormData<S> : S extends TStandardSchemaV1<infer Input, any> ? (FormData extends Input ? true : false) : false;
|
|
164
|
+
type TFormDataInput<S extends TSchema> = S extends z.ZodTypeAny ? TZodFormDataInput<S> : S extends TStandardSchemaV1<infer Input, any> ? (FormData extends Input ? FormData | TInferIn<S> : TInferIn<S>) : TInferIn<S>;
|
|
165
|
+
type TFormDataCompatibleInput<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? (TIsFormData<First> extends true ? FormData : never) | TPrettify<TUnionToIntersection<TInferIn<First>>> | (Rest['length'] extends 0 ? never : TFormDataCompatibleInput<Rest>) : Record<string, never>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Class that handles safe server actions with validation
|
|
169
|
+
*/
|
|
170
|
+
declare class SafeActionClient<Schemas extends TSchema[] = [], Ctx extends Record<string, unknown> = Record<string, never>, Middlewares extends readonly TMiddlewareFn<any, any>[] = []> {
|
|
171
|
+
private schemas;
|
|
172
|
+
private middlewares;
|
|
173
|
+
private ctx;
|
|
174
|
+
private options;
|
|
175
|
+
constructor(options?: {
|
|
176
|
+
schemas?: Schemas;
|
|
177
|
+
middlewares?: Middlewares;
|
|
178
|
+
ctx?: Ctx;
|
|
179
|
+
options?: TSafeActionClientOptions;
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* Adds a validation schema
|
|
183
|
+
* @param schema Validation schema
|
|
184
|
+
*/
|
|
185
|
+
schema<S extends TSchema>(schema: S): SafeActionClient<[...Schemas, S], Ctx, Middlewares>;
|
|
186
|
+
/**
|
|
187
|
+
* Adds a middleware function
|
|
188
|
+
* @param middleware Middleware function
|
|
189
|
+
*/
|
|
190
|
+
use<NextCtx extends Record<string, unknown>>(middleware: TMiddlewareFn<Ctx, NextCtx>): SafeActionClient<Schemas, Ctx & NextCtx, [...Middlewares, TMiddlewareFn<Ctx, NextCtx>]>;
|
|
191
|
+
/**
|
|
192
|
+
* Creates an action with validation and middleware
|
|
193
|
+
* @param handler Action handler function
|
|
194
|
+
*/
|
|
195
|
+
action<Output>(handler: (props: {
|
|
196
|
+
parsedInput: Schemas extends (infer S extends TSchema)[] ? TPrettify<TUnionToIntersection<TInfer<S>>> : Record<string, never>;
|
|
197
|
+
ctx: TPrettify<Ctx>;
|
|
198
|
+
}) => Promise<Output>): (input?: Schemas extends [infer S extends TSchema] ? TFormDataInput<S> : Schemas extends (infer _S extends TSchema)[] ? TFormDataCompatibleInput<Schemas> : unknown) => Promise<Output>;
|
|
199
|
+
private normalizeIssuePath;
|
|
200
|
+
private normalizeIssues;
|
|
201
|
+
private formatValidationErrorParts;
|
|
202
|
+
private validateInput;
|
|
203
|
+
private isPlainObject;
|
|
204
|
+
private parseSchema;
|
|
205
|
+
private tryParseSchema;
|
|
206
|
+
private runMiddlewareChain;
|
|
207
|
+
private isValidationError;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Creates a safe action client that handles validation and execution of server actions
|
|
211
|
+
*/
|
|
212
|
+
declare const createSafeActionClient: (options?: TSafeActionClientOptions) => SafeActionClient<[], Record<string, never>, []>;
|
|
213
|
+
|
|
214
|
+
export { SafeActionClient, SafeActionError, SafeActionValidationError, type TFormDataCompatibleInput, type TFormDataInput, type TGenericSchema, type TGenericSchemaAsync, type TIfInstalled, type TInfer, type TInferArray, type TInferIn, type TInferInArray, type TIsAny, type TIsFormData, type TMiddlewareFn, type TPrettify, type TSafeActionClientOptions, type TSafeActionErrorCode, type TSafeActionErrorContext, type TSafeActionErrorPhase, type TSafeActionIssue, type TSafeActionIssuePathSegment, type TSafeActionUnknownErrorContext, type TSafeActionValidationErrorContext, type TSchema, type TSchemaAvailable, type TStandardSchemaV1, type TStandardSchemaV1InferInput, type TStandardSchemaV1InferOutput, type TStandardSchemaV1Issue, type TStandardSchemaV1PathSegment, type TStandardSchemaV1Result, type TUnionToIntersection, type TZodAcceptsFormData, type TZodFormDataInput, type TZodFormDataSchemaFactory, createSafeActionClient, isSafeActionError, isSafeActionValidationError, schemaAvailable };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// src/types/errors.ts
|
|
2
|
+
var SafeActionError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
phase;
|
|
5
|
+
data;
|
|
6
|
+
constructor(message, opts = { code: "ACTION_ERROR" }) {
|
|
7
|
+
super(message, { cause: opts.cause });
|
|
8
|
+
this.name = "SafeActionError";
|
|
9
|
+
this.code = opts.code;
|
|
10
|
+
this.phase = opts.phase;
|
|
11
|
+
this.data = opts.data;
|
|
12
|
+
this.cause = opts.cause;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var SafeActionValidationError = class extends SafeActionError {
|
|
16
|
+
issues;
|
|
17
|
+
constructor(message, opts) {
|
|
18
|
+
super(message, { code: "VALIDATION_ERROR", phase: opts.phase, cause: opts.cause, data: opts.data });
|
|
19
|
+
this.name = "SafeActionValidationError";
|
|
20
|
+
this.issues = opts.issues;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var isSafeActionError = (error) => {
|
|
24
|
+
if (typeof error !== "object" || error === null) return false;
|
|
25
|
+
if (!("code" in error)) return false;
|
|
26
|
+
const name = error.name;
|
|
27
|
+
return name === "SafeActionError" || name === "SafeActionValidationError";
|
|
28
|
+
};
|
|
29
|
+
var isSafeActionValidationError = (error) => {
|
|
30
|
+
return typeof error === "object" && error !== null && "issues" in error && error.name === "SafeActionValidationError";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/types/schema/index.ts
|
|
34
|
+
var schemaAvailable = ["zod", "generic", "genericAsync", "standard"];
|
|
35
|
+
|
|
36
|
+
// src/index.ts
|
|
37
|
+
var SafeActionClient = class _SafeActionClient {
|
|
38
|
+
schemas;
|
|
39
|
+
middlewares;
|
|
40
|
+
ctx;
|
|
41
|
+
options;
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.schemas = options.schemas || [];
|
|
44
|
+
this.middlewares = options.middlewares || [];
|
|
45
|
+
this.ctx = options.ctx || {};
|
|
46
|
+
this.options = options.options ?? {};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Adds a validation schema
|
|
50
|
+
* @param schema Validation schema
|
|
51
|
+
*/
|
|
52
|
+
schema(schema) {
|
|
53
|
+
return new _SafeActionClient({
|
|
54
|
+
schemas: [...this.schemas, schema],
|
|
55
|
+
middlewares: this.middlewares,
|
|
56
|
+
ctx: this.ctx,
|
|
57
|
+
options: this.options
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Adds a middleware function
|
|
62
|
+
* @param middleware Middleware function
|
|
63
|
+
*/
|
|
64
|
+
use(middleware) {
|
|
65
|
+
return new _SafeActionClient({
|
|
66
|
+
schemas: this.schemas,
|
|
67
|
+
middlewares: [...this.middlewares, middleware],
|
|
68
|
+
ctx: this.ctx,
|
|
69
|
+
options: this.options
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates an action with validation and middleware
|
|
74
|
+
* @param handler Action handler function
|
|
75
|
+
*/
|
|
76
|
+
action(handler) {
|
|
77
|
+
return async (input) => {
|
|
78
|
+
let phase = "validation";
|
|
79
|
+
let parsedInputForErrors = void 0;
|
|
80
|
+
let ctxForErrors = void 0;
|
|
81
|
+
try {
|
|
82
|
+
phase = "validation";
|
|
83
|
+
const parsedInput = await this.validateInput(input);
|
|
84
|
+
parsedInputForErrors = parsedInput;
|
|
85
|
+
phase = "middleware";
|
|
86
|
+
const ctx = await this.runMiddlewareChain(parsedInput);
|
|
87
|
+
ctxForErrors = ctx;
|
|
88
|
+
phase = "handler";
|
|
89
|
+
return await handler({ parsedInput, ctx });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const logger = this.options.logger === void 0 ? console : this.options.logger;
|
|
92
|
+
if (logger !== false) {
|
|
93
|
+
logger.error("Action error:", error);
|
|
94
|
+
}
|
|
95
|
+
const includeInput = this.options.includeInputInErrorDetails === true;
|
|
96
|
+
const baseContext = {
|
|
97
|
+
phase,
|
|
98
|
+
error,
|
|
99
|
+
input: includeInput ? input : void 0,
|
|
100
|
+
parsedInput: includeInput ? parsedInputForErrors : void 0,
|
|
101
|
+
ctx: includeInput ? ctxForErrors : void 0
|
|
102
|
+
};
|
|
103
|
+
if (this.isValidationError(error)) {
|
|
104
|
+
const { message, issues } = this.formatValidationErrorParts(error);
|
|
105
|
+
const validationContext = { ...baseContext, message, issues };
|
|
106
|
+
this.options.onError?.(validationContext);
|
|
107
|
+
const formatted = this.options.formatValidationError?.(validationContext) ?? new SafeActionValidationError(message, {
|
|
108
|
+
issues,
|
|
109
|
+
phase,
|
|
110
|
+
cause: error,
|
|
111
|
+
data: includeInput ? { input, parsedInput: parsedInputForErrors, ctx: ctxForErrors } : void 0
|
|
112
|
+
});
|
|
113
|
+
throw formatted;
|
|
114
|
+
}
|
|
115
|
+
this.options.onError?.(baseContext);
|
|
116
|
+
const maybeFormatted = this.options.formatError?.(baseContext);
|
|
117
|
+
if (maybeFormatted) throw maybeFormatted;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
normalizeIssuePath(path) {
|
|
123
|
+
if (!Array.isArray(path)) return [];
|
|
124
|
+
return path.map((seg) => {
|
|
125
|
+
if (typeof seg === "string" || typeof seg === "number") return seg;
|
|
126
|
+
if (typeof seg === "object" && seg !== null && "key" in seg) {
|
|
127
|
+
const key = seg.key;
|
|
128
|
+
if (typeof key === "string" || typeof key === "number") return key;
|
|
129
|
+
return String(key);
|
|
130
|
+
}
|
|
131
|
+
return String(seg);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
normalizeIssues(error) {
|
|
135
|
+
if (typeof error !== "object" || error === null) return [];
|
|
136
|
+
const maybe = error;
|
|
137
|
+
const rawIssues = maybe.errors ?? maybe.issues ?? [];
|
|
138
|
+
return rawIssues.map((issue) => ({
|
|
139
|
+
path: this.normalizeIssuePath(issue.path),
|
|
140
|
+
message: typeof issue.message === "string" ? issue.message : String(issue.message ?? ""),
|
|
141
|
+
raw: issue
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
formatValidationErrorParts(error) {
|
|
145
|
+
const issues = this.normalizeIssues(error);
|
|
146
|
+
const first = issues[0];
|
|
147
|
+
if (!first) {
|
|
148
|
+
return { message: `Validation error: ${String(error)}`, issues: [] };
|
|
149
|
+
}
|
|
150
|
+
const fieldPath = first.path.length ? first.path.join(".") : "unknown";
|
|
151
|
+
const message = first.message || String(error);
|
|
152
|
+
return { message: `Input (${fieldPath}) is error: ${message}`, issues };
|
|
153
|
+
}
|
|
154
|
+
async validateInput(input) {
|
|
155
|
+
if (!this.schemas.length) return input;
|
|
156
|
+
const schemasToRun = [...this.schemas];
|
|
157
|
+
let baseInput = input;
|
|
158
|
+
if (input instanceof FormData) {
|
|
159
|
+
for (let i = 0; i < schemasToRun.length; i++) {
|
|
160
|
+
const candidate = schemasToRun[i];
|
|
161
|
+
const parsed = await this.tryParseSchema(candidate, input);
|
|
162
|
+
if (!parsed.ok) continue;
|
|
163
|
+
schemasToRun.splice(i, 1);
|
|
164
|
+
baseInput = parsed.value;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (schemasToRun.length === 0) {
|
|
169
|
+
return baseInput;
|
|
170
|
+
}
|
|
171
|
+
if (schemasToRun.length === 1) {
|
|
172
|
+
return await this.parseSchema(schemasToRun[0], baseInput);
|
|
173
|
+
}
|
|
174
|
+
let mergedObject;
|
|
175
|
+
let lastNonObject = void 0;
|
|
176
|
+
for (const schema of schemasToRun) {
|
|
177
|
+
const parsed = await this.parseSchema(schema, baseInput);
|
|
178
|
+
if (this.isPlainObject(parsed)) {
|
|
179
|
+
mergedObject = { ...mergedObject ?? {}, ...parsed };
|
|
180
|
+
} else {
|
|
181
|
+
lastNonObject = parsed;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return mergedObject ?? lastNonObject;
|
|
185
|
+
}
|
|
186
|
+
isPlainObject(value) {
|
|
187
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
188
|
+
}
|
|
189
|
+
async parseSchema(schema, input) {
|
|
190
|
+
if (schema && typeof schema === "object") {
|
|
191
|
+
const maybeAsync = schema.parseAsync;
|
|
192
|
+
if (typeof maybeAsync === "function") {
|
|
193
|
+
return await schema.parseAsync(input);
|
|
194
|
+
}
|
|
195
|
+
const maybeSync = schema.parse;
|
|
196
|
+
if (typeof maybeSync === "function") {
|
|
197
|
+
return schema.parse(input);
|
|
198
|
+
}
|
|
199
|
+
const maybeStandard = schema["~standard"];
|
|
200
|
+
if (maybeStandard && typeof maybeStandard === "object") {
|
|
201
|
+
const validate = maybeStandard.validate;
|
|
202
|
+
if (typeof validate === "function") {
|
|
203
|
+
const result = await validate(input);
|
|
204
|
+
if (result && typeof result === "object" && "issues" in result) {
|
|
205
|
+
const err = new Error("Validation error");
|
|
206
|
+
err.issues = result.issues;
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
if (result && typeof result === "object" && "value" in result) {
|
|
210
|
+
return result.value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
throw new Error("Unsupported schema type");
|
|
216
|
+
}
|
|
217
|
+
async tryParseSchema(schema, input) {
|
|
218
|
+
try {
|
|
219
|
+
if (schema && typeof schema === "object") {
|
|
220
|
+
const safeAsync = schema.safeParseAsync;
|
|
221
|
+
if (typeof safeAsync === "function") {
|
|
222
|
+
const res = await schema.safeParseAsync(input);
|
|
223
|
+
return res.success ? { ok: true, value: res.data } : { ok: false };
|
|
224
|
+
}
|
|
225
|
+
const safeSync = schema.safeParse;
|
|
226
|
+
if (typeof safeSync === "function") {
|
|
227
|
+
const res = schema.safeParse(input);
|
|
228
|
+
return res.success ? { ok: true, value: res.data } : { ok: false };
|
|
229
|
+
}
|
|
230
|
+
const maybeStandard = schema["~standard"];
|
|
231
|
+
if (maybeStandard && typeof maybeStandard === "object") {
|
|
232
|
+
const validate = maybeStandard.validate;
|
|
233
|
+
if (typeof validate === "function") {
|
|
234
|
+
const result = await validate(input);
|
|
235
|
+
if (result && typeof result === "object" && "issues" in result) return { ok: false };
|
|
236
|
+
if (result && typeof result === "object" && "value" in result) return { ok: true, value: result.value };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return { ok: true, value: await this.parseSchema(schema, input) };
|
|
241
|
+
} catch {
|
|
242
|
+
return { ok: false };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async runMiddlewareChain(input) {
|
|
246
|
+
let ctx = { ...this.ctx };
|
|
247
|
+
let currentIndex = 0;
|
|
248
|
+
const middlewares = this.middlewares;
|
|
249
|
+
const runNext = async (options) => {
|
|
250
|
+
if (options?.ctx) {
|
|
251
|
+
ctx = { ...ctx, ...options.ctx };
|
|
252
|
+
}
|
|
253
|
+
if (currentIndex < middlewares.length) {
|
|
254
|
+
const middleware = middlewares[currentIndex];
|
|
255
|
+
currentIndex++;
|
|
256
|
+
return await middleware({
|
|
257
|
+
input,
|
|
258
|
+
ctx: Object.freeze({ ...ctx }),
|
|
259
|
+
next: runNext
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return ctx;
|
|
263
|
+
};
|
|
264
|
+
return await runNext();
|
|
265
|
+
}
|
|
266
|
+
isValidationError(error) {
|
|
267
|
+
if (typeof error !== "object" || error === null) return false;
|
|
268
|
+
if ("name" in error) {
|
|
269
|
+
const errName = error.name;
|
|
270
|
+
if (errName === "ZodError" || errName.includes("ValidationError")) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return "errors" in error || "issues" in error;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var createSafeActionClient = (options) => {
|
|
278
|
+
return new SafeActionClient({ options });
|
|
279
|
+
};
|
|
280
|
+
export {
|
|
281
|
+
SafeActionClient,
|
|
282
|
+
SafeActionError,
|
|
283
|
+
SafeActionValidationError,
|
|
284
|
+
createSafeActionClient,
|
|
285
|
+
isSafeActionError,
|
|
286
|
+
isSafeActionValidationError,
|
|
287
|
+
schemaAvailable
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/errors.ts","../src/types/schema/index.ts","../src/index.ts"],"sourcesContent":["export type TSafeActionIssuePathSegment = string | number;\r\n\r\nexport type TSafeActionIssue = {\r\n\tpath: TSafeActionIssuePathSegment[];\r\n\tmessage: string;\r\n\traw?: unknown;\r\n};\r\n\r\nexport type TSafeActionErrorCode = 'VALIDATION_ERROR' | 'MIDDLEWARE_ERROR' | 'HANDLER_ERROR' | 'ACTION_ERROR';\r\n\r\nexport type TSafeActionErrorPhase = 'validation' | 'middleware' | 'handler';\r\n\r\nexport type TSafeActionUnknownErrorContext = {\r\n\tphase: TSafeActionErrorPhase;\r\n\terror: unknown;\r\n\tinput?: unknown;\r\n\tparsedInput?: unknown;\r\n\tctx?: Record<string, unknown>;\r\n};\r\n\r\nexport type TSafeActionValidationErrorContext = TSafeActionUnknownErrorContext & {\r\n\tmessage: string;\r\n\tissues: TSafeActionIssue[];\r\n};\r\n\r\nexport type TSafeActionErrorContext = TSafeActionUnknownErrorContext | TSafeActionValidationErrorContext;\r\n\r\nexport type TSafeActionClientOptions = {\r\n\t/**\r\n\t * Control logging when an action throws.\r\n\t * - `false` disables logging.\r\n\t * - Defaults to `console`.\r\n\t */\r\n\tlogger?: false | { error: (...args: unknown[]) => void };\r\n\r\n\t/**\r\n\t * Hook called whenever an action throws (validation, middleware, or handler).\r\n\t */\r\n\tonError?: (ctx: TSafeActionErrorContext) => void;\r\n\r\n\t/**\r\n\t * Customize the thrown error when validation fails.\r\n\t * The returned Error will be thrown.\r\n\t */\r\n\tformatValidationError?: (ctx: TSafeActionValidationErrorContext) => Error;\r\n\r\n\t/**\r\n\t * Customize the thrown error for non-validation failures.\r\n\t * If omitted, the original error is re-thrown.\r\n\t */\r\n\tformatError?: (ctx: TSafeActionUnknownErrorContext) => Error;\r\n\r\n\t/**\r\n\t * When enabled, include `input`, `parsedInput`, and `ctx` in error details.\r\n\t * Off by default because it can accidentally expose sensitive data.\r\n\t */\r\n\tincludeInputInErrorDetails?: boolean;\r\n};\r\n\r\nexport class SafeActionError extends Error {\r\n\tcode: TSafeActionErrorCode;\r\n\tphase?: TSafeActionErrorPhase;\r\n\tdata?: unknown;\r\n\tdeclare cause?: unknown;\r\n\r\n\tconstructor(\r\n\t\tmessage: string,\r\n\t\topts: {\r\n\t\t\tcode: TSafeActionErrorCode;\r\n\t\t\tphase?: TSafeActionErrorPhase;\r\n\t\t\tcause?: unknown;\r\n\t\t\tdata?: unknown;\r\n\t\t} = { code: 'ACTION_ERROR' },\r\n\t) {\r\n\t\tsuper(message, { cause: opts.cause });\r\n\t\tthis.name = 'SafeActionError';\r\n\t\tthis.code = opts.code;\r\n\t\tthis.phase = opts.phase;\r\n\t\tthis.data = opts.data;\r\n\t\tthis.cause = opts.cause;\r\n\t}\r\n}\r\n\r\nexport class SafeActionValidationError extends SafeActionError {\r\n\tissues: TSafeActionIssue[];\r\n\r\n\tconstructor(\r\n\t\tmessage: string,\r\n\t\topts: {\r\n\t\t\tissues: TSafeActionIssue[];\r\n\t\t\tphase?: TSafeActionErrorPhase;\r\n\t\t\tcause?: unknown;\r\n\t\t\tdata?: unknown;\r\n\t\t},\r\n\t) {\r\n\t\tsuper(message, { code: 'VALIDATION_ERROR', phase: opts.phase, cause: opts.cause, data: opts.data });\r\n\t\tthis.name = 'SafeActionValidationError';\r\n\t\tthis.issues = opts.issues;\r\n\t}\r\n}\r\n\r\nexport const isSafeActionError = (error: unknown): error is SafeActionError => {\r\n\tif (typeof error !== 'object' || error === null) return false;\r\n\tif (!('code' in error)) return false;\r\n\tconst name = (error as { name?: unknown }).name;\r\n\treturn name === 'SafeActionError' || name === 'SafeActionValidationError';\r\n};\r\n\r\nexport const isSafeActionValidationError = (error: unknown): error is SafeActionValidationError => {\r\n\treturn typeof error === 'object' && error !== null && 'issues' in error && (error as { name?: unknown }).name === 'SafeActionValidationError';\r\n};\r\n","import type { z } from 'zod';\r\n\r\nimport type { TIfInstalled, TPrettify, TUnionToIntersection } from '../utils';\r\nimport type { TGenericSchema, TGenericSchemaAsync } from './generic';\r\nimport type { TStandardSchemaV1, TStandardSchemaV1InferInput, TStandardSchemaV1InferOutput } from './standard';\r\nimport type { TZodAcceptsFormData, TZodFormDataInput } from './zod';\r\n\r\nexport const schemaAvailable = ['zod', 'generic', 'genericAsync', 'standard'] as const;\r\nexport type TSchemaAvailable = (typeof schemaAvailable)[number];\r\n\r\nexport type TSchema = TIfInstalled<z.ZodTypeAny> | TIfInstalled<TGenericSchema> | TIfInstalled<TGenericSchemaAsync> | TStandardSchemaV1<any, any>;\r\n\r\nexport type TInfer<S extends TSchema> = S extends TIfInstalled<z.ZodTypeAny> ? z.infer<S> : S extends TIfInstalled<TGenericSchema> ? ReturnType<S['parse']> : S extends TIfInstalled<TGenericSchemaAsync> ? Awaited<ReturnType<S['parseAsync']>> : S extends TStandardSchemaV1<any, any> ? TStandardSchemaV1InferOutput<S> : never;\r\n\r\nexport type TInferIn<S extends TSchema> = S extends TIfInstalled<z.ZodTypeAny> ? z.input<S> : S extends TIfInstalled<TGenericSchema> ? Parameters<S['parse']>[0] : S extends TIfInstalled<TGenericSchemaAsync> ? Parameters<S['parseAsync']>[0] : S extends TStandardSchemaV1<any, any> ? TStandardSchemaV1InferInput<S> : never;\r\n\r\nexport type TInferArray<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? [TInfer<First>, ...TInferArray<Rest>] : [];\r\n\r\nexport type TInferInArray<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? [TInferIn<First>, ...TInferInArray<Rest>] : [];\r\n\r\nexport type TIsFormData<S extends TSchema> = S extends z.ZodTypeAny ? TZodAcceptsFormData<S> : S extends TStandardSchemaV1<infer Input, any> ? (FormData extends Input ? true : false) : false;\r\n\r\nexport type TFormDataInput<S extends TSchema> = S extends z.ZodTypeAny ? TZodFormDataInput<S> : S extends TStandardSchemaV1<infer Input, any> ? (FormData extends Input ? FormData | TInferIn<S> : TInferIn<S>) : TInferIn<S>;\r\n\r\nexport type TFormDataCompatibleInput<S extends readonly TSchema[]> = S extends readonly [infer First extends TSchema, ...infer Rest extends TSchema[]] ? (TIsFormData<First> extends true ? FormData : never) | TPrettify<TUnionToIntersection<TInferIn<First>>> | (Rest['length'] extends 0 ? never : TFormDataCompatibleInput<Rest>) : Record<string, never>;\r\n","import type { TSafeActionClientOptions, TSafeActionErrorPhase, TSafeActionIssue, TFormDataCompatibleInput, TFormDataInput, TInfer, TPrettify, TSchema, TMiddlewareFn, TUnionToIntersection } from './types';\r\nimport { SafeActionValidationError } from './types';\r\n\r\nexport { schemaAvailable } from './types';\r\nexport { SafeActionError, SafeActionValidationError, isSafeActionError, isSafeActionValidationError } from './types';\r\nexport type * from './types';\r\n\r\n/**\r\n * Class that handles safe server actions with validation\r\n */\r\nexport class SafeActionClient<Schemas extends TSchema[] = [], Ctx extends Record<string, unknown> = Record<string, never>, Middlewares extends readonly TMiddlewareFn<any, any>[] = []> {\r\n\tprivate schemas: Schemas;\r\n\tprivate middlewares: Middlewares;\r\n\tprivate ctx: Ctx;\r\n\tprivate options: TSafeActionClientOptions;\r\n\r\n\tconstructor(\r\n\t\toptions: {\r\n\t\t\tschemas?: Schemas;\r\n\t\t\tmiddlewares?: Middlewares;\r\n\t\t\tctx?: Ctx;\r\n\t\t\toptions?: TSafeActionClientOptions;\r\n\t\t} = {},\r\n\t) {\r\n\t\tthis.schemas = (options.schemas || []) as unknown as Schemas;\r\n\t\tthis.middlewares = (options.middlewares || []) as unknown as Middlewares;\r\n\t\tthis.ctx = (options.ctx || {}) as Ctx;\r\n\t\tthis.options = options.options ?? {};\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a validation schema\r\n\t * @param schema Validation schema\r\n\t */\r\n\tschema<S extends TSchema>(schema: S): SafeActionClient<[...Schemas, S], Ctx, Middlewares> {\r\n\t\treturn new SafeActionClient({\r\n\t\t\tschemas: [...this.schemas, schema] as unknown as [...Schemas, S],\r\n\t\t\tmiddlewares: this.middlewares,\r\n\t\t\tctx: this.ctx,\r\n\t\t\toptions: this.options,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a middleware function\r\n\t * @param middleware Middleware function\r\n\t */\r\n\tuse<NextCtx extends Record<string, unknown>>(middleware: TMiddlewareFn<Ctx, NextCtx>): SafeActionClient<Schemas, Ctx & NextCtx, [...Middlewares, TMiddlewareFn<Ctx, NextCtx>]> {\r\n\t\treturn new SafeActionClient({\r\n\t\t\tschemas: this.schemas,\r\n\t\t\tmiddlewares: [...this.middlewares, middleware] as unknown as [...Middlewares, TMiddlewareFn<Ctx, NextCtx>],\r\n\t\t\tctx: this.ctx as unknown as Ctx & NextCtx,\r\n\t\t\toptions: this.options,\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Creates an action with validation and middleware\r\n\t * @param handler Action handler function\r\n\t */\r\n\taction<Output>(handler: (props: { parsedInput: Schemas extends (infer S extends TSchema)[] ? TPrettify<TUnionToIntersection<TInfer<S>>> : Record<string, never>; ctx: TPrettify<Ctx> }) => Promise<Output>): (input?: Schemas extends [infer S extends TSchema] ? TFormDataInput<S> : Schemas extends (infer _S extends TSchema)[] ? TFormDataCompatibleInput<Schemas> : unknown) => Promise<Output> {\r\n\t\treturn async (input?: unknown): Promise<Output> => {\r\n\t\t\tlet phase: TSafeActionErrorPhase = 'validation';\r\n\t\t\tlet parsedInputForErrors: unknown = undefined;\r\n\t\t\tlet ctxForErrors: unknown = undefined;\r\n\r\n\t\t\ttry {\r\n\t\t\t\t// Validate input against all schemas\r\n\t\t\t\ttype ParsedInput = Schemas extends (infer S extends TSchema)[] ? TPrettify<TUnionToIntersection<TInfer<S>>> : Record<string, never>;\r\n\t\t\t\tphase = 'validation';\r\n\t\t\t\tconst parsedInput = (await this.validateInput(input)) as ParsedInput;\r\n\t\t\t\tparsedInputForErrors = parsedInput;\r\n\r\n\t\t\t\t// Run middleware chain\r\n\t\t\t\tphase = 'middleware';\r\n\t\t\t\tconst ctx = await this.runMiddlewareChain(parsedInput);\r\n\t\t\t\tctxForErrors = ctx;\r\n\r\n\t\t\t\t// Execute handler\r\n\t\t\t\tphase = 'handler';\r\n\t\t\t\treturn await handler({ parsedInput, ctx });\r\n\t\t\t} catch (error: unknown) {\r\n\t\t\t\tconst logger = this.options.logger === undefined ? console : this.options.logger;\r\n\t\t\t\tif (logger !== false) {\r\n\t\t\t\t\tlogger.error('Action error:', error);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst includeInput = this.options.includeInputInErrorDetails === true;\r\n\t\t\t\tconst baseContext = {\r\n\t\t\t\t\tphase,\r\n\t\t\t\t\terror,\r\n\t\t\t\t\tinput: includeInput ? input : undefined,\r\n\t\t\t\t\tparsedInput: includeInput ? parsedInputForErrors : undefined,\r\n\t\t\t\t\tctx: includeInput ? (ctxForErrors as Record<string, unknown> | undefined) : undefined,\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// Handle validation errors\r\n\t\t\t\tif (this.isValidationError(error)) {\r\n\t\t\t\t\tconst { message, issues } = this.formatValidationErrorParts(error);\r\n\t\t\t\t\tconst validationContext = { ...baseContext, message, issues };\r\n\t\t\t\t\tthis.options.onError?.(validationContext);\r\n\r\n\t\t\t\t\tconst formatted =\r\n\t\t\t\t\t\tthis.options.formatValidationError?.(validationContext) ??\r\n\t\t\t\t\t\tnew SafeActionValidationError(message, {\r\n\t\t\t\t\t\t\tissues,\r\n\t\t\t\t\t\t\tphase,\r\n\t\t\t\t\t\t\tcause: error,\r\n\t\t\t\t\t\t\tdata: includeInput ? { input, parsedInput: parsedInputForErrors, ctx: ctxForErrors } : undefined,\r\n\t\t\t\t\t\t});\r\n\r\n\t\t\t\t\tthrow formatted;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.options.onError?.(baseContext);\r\n\t\t\t\tconst maybeFormatted = this.options.formatError?.(baseContext);\r\n\t\t\t\tif (maybeFormatted) throw maybeFormatted;\r\n\r\n\t\t\t\tthrow error;\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n\r\n\tprivate normalizeIssuePath(path: unknown): Array<string | number> {\r\n\t\tif (!Array.isArray(path)) return [];\r\n\t\treturn path.map(seg => {\r\n\t\t\tif (typeof seg === 'string' || typeof seg === 'number') return seg;\r\n\t\t\tif (typeof seg === 'object' && seg !== null && 'key' in (seg as Record<string, unknown>)) {\r\n\t\t\t\tconst key = (seg as { key: unknown }).key;\r\n\t\t\t\tif (typeof key === 'string' || typeof key === 'number') return key;\r\n\t\t\t\treturn String(key);\r\n\t\t\t}\r\n\t\t\treturn String(seg);\r\n\t\t});\r\n\t}\r\n\r\n\tprivate normalizeIssues(error: unknown): TSafeActionIssue[] {\r\n\t\tif (typeof error !== 'object' || error === null) return [];\r\n\r\n\t\tconst maybe = error as {\r\n\t\t\terrors?: Array<{ path?: unknown; message?: unknown }>;\r\n\t\t\tissues?: Array<{ path?: unknown; message?: unknown }>;\r\n\t\t};\r\n\r\n\t\tconst rawIssues = (maybe.errors ?? maybe.issues ?? []) as Array<{ path?: unknown; message?: unknown }>;\r\n\t\treturn rawIssues.map(issue => ({\r\n\t\t\tpath: this.normalizeIssuePath(issue.path),\r\n\t\t\tmessage: typeof issue.message === 'string' ? issue.message : String(issue.message ?? ''),\r\n\t\t\traw: issue,\r\n\t\t}));\r\n\t}\r\n\r\n\tprivate formatValidationErrorParts(error: unknown): { message: string; issues: TSafeActionIssue[] } {\r\n\t\tconst issues = this.normalizeIssues(error);\r\n\t\tconst first = issues[0];\r\n\t\tif (!first) {\r\n\t\t\treturn { message: `Validation error: ${String(error)}`, issues: [] };\r\n\t\t}\r\n\r\n\t\tconst fieldPath = first.path.length ? first.path.join('.') : 'unknown';\r\n\t\tconst message = first.message || String(error);\r\n\t\treturn { message: `Input (${fieldPath}) is error: ${message}`, issues };\r\n\t}\r\n\r\n\tprivate async validateInput(input: unknown): Promise<unknown> {\r\n\t\tif (!this.schemas.length) return input;\r\n\r\n\t\t// zod-form-data uses `z.preprocess` and accepts FormData/URLSearchParams at runtime.\r\n\t\t// If the incoming input is FormData, try to find a single schema that can parse it into a\r\n\t\t// regular object first (regardless of ordering). Then validate *all* remaining schemas\r\n\t\t// against the same base input so schemas can validate different slices reliably.\r\n\t\tconst schemasToRun = [...this.schemas];\r\n\t\tlet baseInput: unknown = input;\r\n\r\n\t\tif (input instanceof FormData) {\r\n\t\t\tfor (let i = 0; i < schemasToRun.length; i++) {\r\n\t\t\t\tconst candidate = schemasToRun[i];\r\n\t\t\t\tconst parsed = await this.tryParseSchema(candidate, input);\r\n\t\t\t\tif (!parsed.ok) continue;\r\n\r\n\t\t\t\tschemasToRun.splice(i, 1);\r\n\t\t\t\tbaseInput = parsed.value;\r\n\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// If a FormData schema consumed the input and there are no schemas left,\r\n\t\t// the parsed base input is the validated result.\r\n\t\tif (schemasToRun.length === 0) {\r\n\t\t\treturn baseInput;\r\n\t\t}\r\n\r\n\t\t// Single schema: return parsed value directly.\r\n\t\tif (schemasToRun.length === 1) {\r\n\t\t\treturn await this.parseSchema(schemasToRun[0] as TSchema, baseInput);\r\n\t\t}\r\n\r\n\t\tlet mergedObject: Record<string, unknown> | undefined;\r\n\t\tlet lastNonObject: unknown = undefined;\r\n\r\n\t\tfor (const schema of schemasToRun) {\r\n\t\t\tconst parsed = await this.parseSchema(schema, baseInput);\r\n\t\t\tif (this.isPlainObject(parsed)) {\r\n\t\t\t\tmergedObject = { ...(mergedObject ?? {}), ...(parsed as Record<string, unknown>) };\r\n\t\t\t} else {\r\n\t\t\t\tlastNonObject = parsed;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn mergedObject ?? lastNonObject;\r\n\t}\r\n\r\n\tprivate isPlainObject(value: unknown): value is Record<string, unknown> {\r\n\t\treturn typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);\r\n\t}\r\n\r\n\tprivate async parseSchema(schema: TSchema, input: unknown): Promise<unknown> {\r\n\t\tif (schema && typeof schema === 'object') {\r\n\t\t\tconst maybeAsync = (schema as { parseAsync?: unknown }).parseAsync;\r\n\t\t\tif (typeof maybeAsync === 'function') {\r\n\t\t\t\treturn await (schema as { parseAsync: (i: unknown) => Promise<unknown> }).parseAsync(input);\r\n\t\t\t}\r\n\t\t\tconst maybeSync = (schema as { parse?: unknown }).parse;\r\n\t\t\tif (typeof maybeSync === 'function') {\r\n\t\t\t\treturn (schema as { parse: (i: unknown) => unknown }).parse(input);\r\n\t\t\t}\r\n\r\n\t\t\t// Standard Schema v1 support.\r\n\t\t\t// Note: Zod v3+ exposes a `~standard` adapter too, but we prefer Zod's native parse/parseAsync\r\n\t\t\t// when available so validation failures produce a real `ZodError`.\r\n\t\t\tconst maybeStandard = (schema as { '~standard'?: unknown })['~standard'];\r\n\t\t\tif (maybeStandard && typeof maybeStandard === 'object') {\r\n\t\t\t\tconst validate = (maybeStandard as { validate?: unknown }).validate;\r\n\t\t\t\tif (typeof validate === 'function') {\r\n\t\t\t\t\tconst result = await (validate as (v: unknown) => unknown)(input);\r\n\t\t\t\t\tif (result && typeof result === 'object' && 'issues' in result) {\r\n\t\t\t\t\t\tconst err = new Error('Validation error');\r\n\t\t\t\t\t\t(err as unknown as { issues: unknown }).issues = (result as { issues: unknown }).issues;\r\n\t\t\t\t\t\tthrow err;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (result && typeof result === 'object' && 'value' in result) {\r\n\t\t\t\t\t\treturn (result as { value: unknown }).value;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthrow new Error('Unsupported schema type');\r\n\t}\r\n\r\n\tprivate async tryParseSchema(schema: TSchema, input: unknown): Promise<{ ok: true; value: unknown } | { ok: false }> {\r\n\t\ttry {\r\n\t\t\tif (schema && typeof schema === 'object') {\r\n\t\t\t\tconst safeAsync = (schema as { safeParseAsync?: unknown }).safeParseAsync;\r\n\t\t\t\tif (typeof safeAsync === 'function') {\r\n\t\t\t\t\tconst res = await (\r\n\t\t\t\t\t\tschema as {\r\n\t\t\t\t\t\t\tsafeParseAsync: (i: unknown) => Promise<{ success: boolean; data?: unknown }>;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t).safeParseAsync(input);\r\n\t\t\t\t\treturn res.success ? { ok: true, value: res.data } : { ok: false };\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst safeSync = (schema as { safeParse?: unknown }).safeParse;\r\n\t\t\t\tif (typeof safeSync === 'function') {\r\n\t\t\t\t\tconst res = (schema as { safeParse: (i: unknown) => { success: boolean; data?: unknown } }).safeParse(input);\r\n\t\t\t\t\treturn res.success ? { ok: true, value: res.data } : { ok: false };\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Standard Schema v1 support.\r\n\t\t\t\tconst maybeStandard = (schema as { '~standard'?: unknown })['~standard'];\r\n\t\t\t\tif (maybeStandard && typeof maybeStandard === 'object') {\r\n\t\t\t\t\tconst validate = (maybeStandard as { validate?: unknown }).validate;\r\n\t\t\t\t\tif (typeof validate === 'function') {\r\n\t\t\t\t\t\tconst result = await (validate as (v: unknown) => unknown)(input);\r\n\t\t\t\t\t\tif (result && typeof result === 'object' && 'issues' in result) return { ok: false };\r\n\t\t\t\t\t\tif (result && typeof result === 'object' && 'value' in result) return { ok: true, value: (result as { value: unknown }).value };\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Fallback to throwing parse.\r\n\t\t\treturn { ok: true, value: await this.parseSchema(schema, input) };\r\n\t\t} catch {\r\n\t\t\treturn { ok: false };\r\n\t\t}\r\n\t}\r\n\r\n\tprivate async runMiddlewareChain(input: unknown): Promise<Ctx> {\r\n\t\tlet ctx = { ...(this.ctx as Record<string, unknown>) } as Ctx;\r\n\t\tlet currentIndex = 0;\r\n\t\tconst middlewares = this.middlewares as unknown as readonly TMiddlewareFn<any, any>[];\r\n\r\n\t\tconst runNext = async <NC extends Record<string, unknown> = Record<string, never>>(options?: { ctx?: NC }): Promise<Ctx & NC> => {\r\n\t\t\tif (options?.ctx) {\r\n\t\t\t\tctx = { ...ctx, ...options.ctx } as Ctx;\r\n\t\t\t}\r\n\r\n\t\t\tif (currentIndex < middlewares.length) {\r\n\t\t\t\tconst middleware = middlewares[currentIndex] as TMiddlewareFn<any, any>;\r\n\t\t\t\tcurrentIndex++;\r\n\r\n\t\t\t\treturn (await middleware({\r\n\t\t\t\t\tinput,\r\n\t\t\t\t\tctx: Object.freeze({ ...ctx }),\r\n\t\t\t\t\tnext: runNext,\r\n\t\t\t\t})) as Ctx & NC;\r\n\t\t\t}\r\n\r\n\t\t\treturn ctx as Ctx & NC;\r\n\t\t};\r\n\r\n\t\treturn await runNext();\r\n\t}\r\n\r\n\tprivate isValidationError(error: unknown): boolean {\r\n\t\tif (typeof error !== 'object' || error === null) return false;\r\n\r\n\t\tif ('name' in error) {\r\n\t\t\tconst errName = (error as { name: string }).name;\r\n\t\t\tif (errName === 'ZodError' || errName.includes('ValidationError')) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn 'errors' in error || 'issues' in error;\r\n\t}\r\n}\r\n\r\n/**\r\n * Creates a safe action client that handles validation and execution of server actions\r\n */\r\nexport const createSafeActionClient = (options?: TSafeActionClientOptions) => {\r\n\treturn new SafeActionClient({ options });\r\n};\r\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-action-plus",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Plug-and-play, type-safe server actions with schema validation (schema-agnostic, Zod compatible).",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/muhgholy/next-action-plus.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/muhgholy/next-action-plus#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/muhgholy/next-action-plus/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"next.js",
|
|
16
|
+
"nextjs",
|
|
17
|
+
"server actions",
|
|
18
|
+
"server action",
|
|
19
|
+
"type-safe",
|
|
20
|
+
"typescript",
|
|
21
|
+
"validation",
|
|
22
|
+
"zod",
|
|
23
|
+
"formdata",
|
|
24
|
+
"standard schema"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"clean": "rm -rf dist",
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
49
|
+
"test": "vitest run --typecheck",
|
|
50
|
+
"test:watch": "vitest --typecheck",
|
|
51
|
+
"release": "semantic-release"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"zod": "^3.22.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
58
|
+
"@semantic-release/commit-analyzer": "^12.0.0",
|
|
59
|
+
"@semantic-release/git": "^10.0.1",
|
|
60
|
+
"@semantic-release/github": "^11.0.0",
|
|
61
|
+
"@semantic-release/npm": "^12.0.0",
|
|
62
|
+
"@semantic-release/release-notes-generator": "^13.0.0",
|
|
63
|
+
"@types/node": "^22.10.2",
|
|
64
|
+
"semantic-release": "^24.0.0",
|
|
65
|
+
"tsup": "^8.5.0",
|
|
66
|
+
"typescript": "^5.6.3",
|
|
67
|
+
"vitest": "^4.0.7",
|
|
68
|
+
"zod-form-data": "^3.0.1",
|
|
69
|
+
"zod": "^3.22.0"
|
|
70
|
+
}
|
|
71
|
+
}
|