mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Actions: expand isInputError
to accept unknown
(#11439)
* feat: allow type `unknown` on `isInputError` * chore: move ErrorInferenceObject to internal utils * chore: changeset * deps: expect-type * feat: first types test * chore: add types test to general test command * refactor: use describe and it for organization
This commit is contained in:
parent
ea8582f4fc
commit
08baf56f32
9 changed files with 90 additions and 12 deletions
18
.changeset/nasty-poems-juggle.md
Normal file
18
.changeset/nasty-poems-juggle.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Expands the `isInputError()` utility from `astro:actions` to accept errors of any type. This should now allow type narrowing from a try / catch block.
|
||||
|
||||
```ts
|
||||
// example.ts
|
||||
import { actions, isInputError } from 'astro:actions';
|
||||
|
||||
try {
|
||||
await actions.like(new FormData());
|
||||
} catch (error) {
|
||||
if (isInputError(error)) {
|
||||
console.log(error.fields);
|
||||
}
|
||||
}
|
||||
```
|
|
@ -24,6 +24,7 @@
|
|||
"test:citgm": "pnpm -r --filter=astro test",
|
||||
"test:match": "cd packages/astro && pnpm run test:match",
|
||||
"test:unit": "cd packages/astro && pnpm run test:unit",
|
||||
"test:types": "cd packages/astro && pnpm run test:types",
|
||||
"test:unit:match": "cd packages/astro && pnpm run test:unit:match",
|
||||
"test:smoke": "pnpm test:smoke:example && pnpm test:smoke:docs",
|
||||
"test:smoke:example": "turbo run build --concurrency=100% --filter=\"@example/*\"",
|
||||
|
|
|
@ -114,12 +114,13 @@
|
|||
"build:ci": "pnpm run prebuild && astro-scripts build \"src/**/*.{ts,js}\" && pnpm run postbuild",
|
||||
"dev": "astro-scripts dev --copy-wasm --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.{ts,js}\"",
|
||||
"postbuild": "astro-scripts copy \"src/**/*.astro\" && astro-scripts copy \"src/**/*.wasm\"",
|
||||
"test": "pnpm run test:node",
|
||||
"test": "pnpm run test:node && pnpm run test:types",
|
||||
"test:match": "pnpm run test:node --match",
|
||||
"test:e2e": "pnpm test:e2e:chrome && pnpm test:e2e:firefox",
|
||||
"test:e2e:match": "playwright test -g",
|
||||
"test:e2e:chrome": "playwright test",
|
||||
"test:e2e:firefox": "playwright test --config playwright.firefox.config.js",
|
||||
"test:types": "tsc --project tsconfig.tests.json",
|
||||
"test:node": "astro-scripts test \"test/**/*.test.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -215,6 +216,7 @@
|
|||
"astro-scripts": "workspace:*",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"eol": "^0.9.1",
|
||||
"expect-type": "^0.19.0",
|
||||
"mdast-util-mdx": "^3.0.0",
|
||||
"mdast-util-mdx-jsx": "^3.1.2",
|
||||
"memfs": "^4.9.3",
|
||||
|
|
|
@ -31,3 +31,14 @@ export async function getAction(
|
|||
}
|
||||
return actionLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to preserve the input schema type in the error object.
|
||||
* This allows for type inference on the `fields` property
|
||||
* when type narrowed to an `ActionInputError`.
|
||||
*
|
||||
* Example: Action has an input schema of `{ name: z.string() }`.
|
||||
* When calling the action and checking `isInputError(result.error)`,
|
||||
* `result.error.fields` will be typed with the `name` field.
|
||||
*/
|
||||
export type ErrorInferenceObject = Record<string, any>;
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import { type ActionAPIContext, getApiContext as _getApiContext } from '../store.js';
|
||||
import { type MaybePromise } from '../utils.js';
|
||||
import {
|
||||
ActionError,
|
||||
ActionInputError,
|
||||
type ErrorInferenceObject,
|
||||
type SafeResult,
|
||||
callSafely,
|
||||
} from './shared.js';
|
||||
import type { ErrorInferenceObject, MaybePromise } from '../utils.js';
|
||||
import { ActionError, ActionInputError, type SafeResult, callSafely } from './shared.js';
|
||||
|
||||
export * from './shared.js';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { z } from 'zod';
|
||||
import type { MaybePromise } from '../utils.js';
|
||||
import type { ErrorInferenceObject, MaybePromise } from '../utils.js';
|
||||
|
||||
type ActionErrorCode =
|
||||
| 'BAD_REQUEST'
|
||||
|
@ -40,8 +40,6 @@ const statusToCodeMap: Record<number, ActionErrorCode> = Object.entries(codeToSt
|
|||
{}
|
||||
);
|
||||
|
||||
export type ErrorInferenceObject = Record<string, any>;
|
||||
|
||||
export class ActionError<T extends ErrorInferenceObject = ErrorInferenceObject> extends Error {
|
||||
type = 'AstroActionError';
|
||||
code: ActionErrorCode = 'INTERNAL_SERVER_ERROR';
|
||||
|
@ -85,6 +83,10 @@ export class ActionError<T extends ErrorInferenceObject = ErrorInferenceObject>
|
|||
|
||||
export function isInputError<T extends ErrorInferenceObject>(
|
||||
error?: ActionError<T>
|
||||
): error is ActionInputError<T>;
|
||||
export function isInputError(error?: unknown): error is ActionInputError<ErrorInferenceObject>;
|
||||
export function isInputError<T extends ErrorInferenceObject>(
|
||||
error?: unknown | ActionError<T>
|
||||
): error is ActionInputError<T> {
|
||||
return error instanceof ActionInputError;
|
||||
}
|
||||
|
|
31
packages/astro/test/types/is-input-error.ts
Normal file
31
packages/astro/test/types/is-input-error.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { expectTypeOf } from 'expect-type';
|
||||
import { isInputError, defineAction } from '../../dist/actions/runtime/virtual/server.js';
|
||||
import { z } from '../../zod.mjs';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
const exampleAction = defineAction({
|
||||
input: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
handler: () => {},
|
||||
});
|
||||
|
||||
const result = await exampleAction.safe({ name: 'Alice' });
|
||||
|
||||
describe('isInputError', () => {
|
||||
it('isInputError narrows unknown error types', async () => {
|
||||
try {
|
||||
await exampleAction({ name: 'Alice' });
|
||||
} catch (e) {
|
||||
if (isInputError(e)) {
|
||||
expectTypeOf(e.fields).toEqualTypeOf<Record<string, string[] | undefined>>();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('`isInputError` preserves `fields` object type for ActionError objects', async () => {
|
||||
if (isInputError(result.error)) {
|
||||
expectTypeOf(result.error.fields).toEqualTypeOf<{ name?: string[] }>();
|
||||
}
|
||||
});
|
||||
});
|
9
packages/astro/tsconfig.tests.json
Normal file
9
packages/astro/tsconfig.tests.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["test/types"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
}
|
||||
}
|
|
@ -794,6 +794,9 @@ importers:
|
|||
eol:
|
||||
specifier: ^0.9.1
|
||||
version: 0.9.1
|
||||
expect-type:
|
||||
specifier: ^0.19.0
|
||||
version: 0.19.0
|
||||
mdast-util-mdx:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
|
@ -8654,6 +8657,10 @@ packages:
|
|||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
expect-type@0.19.0:
|
||||
resolution: {integrity: sha512-piv9wz3IrAG4Wnk2A+n2VRCHieAyOSxrRLU872Xo6nyn39kYXKDALk4OcqnvLRnFvkz659CnWC8MWZLuuQnoqg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
express@4.19.2:
|
||||
resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
@ -9308,6 +9315,7 @@ packages:
|
|||
|
||||
libsql@0.3.12:
|
||||
resolution: {integrity: sha512-to30hj8O3DjS97wpbKN6ERZ8k66MN1IaOfFLR6oHqd25GMiPJ/ZX0VaZ7w+TsPmxcFS3p71qArj/hiedCyvXCg==}
|
||||
cpu: [x64, arm64, wasm32]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
lilconfig@2.1.0:
|
||||
|
@ -14704,6 +14712,8 @@ snapshots:
|
|||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
expect-type@0.19.0: {}
|
||||
|
||||
express@4.19.2:
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
|
|
Loading…
Reference in a new issue