diff --git a/.changeset/rare-swans-build.md b/.changeset/rare-swans-build.md new file mode 100644 index 000000000..5d6dc95e6 --- /dev/null +++ b/.changeset/rare-swans-build.md @@ -0,0 +1,5 @@ +--- +'@hono/arri-validator': major +--- + +Initial release diff --git a/packages/arri-validator/README.md b/packages/arri-validator/README.md new file mode 100644 index 000000000..c56dffea4 --- /dev/null +++ b/packages/arri-validator/README.md @@ -0,0 +1,98 @@ +# Arri validator middleware for Hono + +Validator middleware for [Hono](https://honojs.dev) applications which uses [Arri Schema](https://github.com/modiimedia/arri). You can write a schema with Arri and validate the incoming values. + +## Usage + +```ts +import { a } from '@arrirpc/schema' +import { aValidator } from '@hono/arri-validator' + +const schema = a.object({ + name: a.string(), + age: a.number(), +}) + +app.post('/author', aValidator('json', schema), (c) => { + const data = c.req.valid('json') + return c.json({ + success: true, + message: `${data.name} is ${data.age}`, + }) +}) +``` + +Hook: + +```ts +app.post( + '/post', + aValidator('json', schema, (result, c) => { + if (!result.success) { + return c.text('Invalid!', 400) + } + }) + //... +) +``` + +Throw Error: + +To throw an error instead of directly returning an error response, you can create a custom wrapper for the validator. You could also create a custom validation function which uses `a.parseUnsafe`. + +```ts +// file: validator-wrapper.ts +import type { ASchema } from '@arrirpc/schema' +import type { ValidationTargets } from 'hono' +import { aValidator as av } from '@hono/arri-validator' + +export const aValidator = ( + target: Target, + schema: T +) => + av(target, schema, (result, c) => { + if (!result.success) { + throw new HTTPException(400, { cause: result.errors }) + } + }) + +// usage +import { aValidator } from './validator-wrapper' +app.post( + '/post', + aValidator('json', schema) + //... +) +``` + +### Custom validation function + +By default, this validation is done using `a.parse`. + +```ts +await a.parse(schema, value) +``` + +If you want to use the [`a.coerce`](https://github.com/modiimedia/arri/blob/master/languages/ts/ts-schema/README.md#coerce), you can specify your own function in `validationFunction`. + +```ts +app.post( + '/', + aValidator('json', schema, undefined, { + validationFunction: (schema, value) => { + return a.coerce(schema, value) + }, + }), + (c) => { + // ... + } +) +``` + +## Author + +kalucky0 + +## License + +MIT diff --git a/packages/arri-validator/package.json b/packages/arri-validator/package.json new file mode 100644 index 000000000..03e009250 --- /dev/null +++ b/packages/arri-validator/package.json @@ -0,0 +1,54 @@ +{ + "name": "@hono/arri-validator", + "version": "0.1.0", + "description": "Validator middleware using Arri Schema", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup ./src/index.ts", + "prepack": "yarn build", + "publint": "attw --pack && publint", + "typecheck": "tsc -b tsconfig.json", + "test": "vitest" + }, + "license": "MIT", + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/honojs/middleware.git", + "directory": "packages/arri-validator" + }, + "homepage": "https://github.com/honojs/middleware", + "peerDependencies": { + "@arrirpc/schema": "^0.79.0", + "hono": ">=4.0.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.1", + "@arrirpc/schema": "^0.79.0", + "publint": "^0.3.12", + "tsup": "^8.5.0", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } +} diff --git a/packages/arri-validator/src/index.test.ts b/packages/arri-validator/src/index.test.ts new file mode 100644 index 000000000..e6465a878 --- /dev/null +++ b/packages/arri-validator/src/index.test.ts @@ -0,0 +1,384 @@ +import { a } from '@arrirpc/schema' +import { Hono } from 'hono' +import type { ContentfulStatusCode } from 'hono/utils/http-status' +import type { Equal, Expect } from 'hono/utils/types' +import { vi } from 'vitest' +import { aValidator } from '.' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type ExtractSchema = T extends Hono ? S : never + +describe('Basic', () => { + const app = new Hono() + + const jsonSchema = a.object({ + name: a.string(), + age: a.number(), + }) + + const querySchema = a.object({ + name: a.optional(a.string()), + }) + + const route = app.post( + '/author', + aValidator('json', jsonSchema), + aValidator('query', querySchema), + (c) => { + const data = c.req.valid('json') + const query = c.req.valid('query') + + return c.json({ + success: true, + message: `${data.name} is ${data.age}`, + queryName: query?.name, + }) + } + ) + + type Actual = ExtractSchema + type Expected = { + '/author': { + $post: { + input: { + json: { + name: string + age: number + } + } & { + query: { + name?: string | undefined + } + } + output: { + success: boolean + message: string + queryName: string | undefined + } + outputFormat: 'json' + status: ContentfulStatusCode + } + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type verify = Expect> + + it('Should return 200 response', async () => { + const req = new Request('http://localhost/author?name=Metallo', { + body: JSON.stringify({ + name: 'Superman', + age: 20, + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + success: true, + message: 'Superman is 20', + queryName: 'Metallo', + }) + }) + + it('Should return 400 response', async () => { + const req = new Request('http://localhost/author', { + body: JSON.stringify({ + name: 'Superman', + age: '20', + }), + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + const data = (await res.json()) as { success: boolean } + expect(data['success']).toBe(false) + }) +}) + +describe('coerce', () => { + const app = new Hono() + + const querySchema = a.object({ + page: a.number(), + }) + + const route = app.get( + '/page', + aValidator('query', querySchema, undefined, { + validationFunction: async (schema, value) => { + return a.coerce(schema, value) + }, + }), + (c) => { + const { page } = c.req.valid('query') + return c.json({ page }) + } + ) + + type Actual = ExtractSchema + type Expected = { + '/page': { + $get: { + input: { + query: { + page: string | string[] + } + } + output: { + page: number + } + outputFormat: 'json' + status: ContentfulStatusCode + } + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type verify = Expect> + + it('Should return 200 response', async () => { + const res = await app.request('/page?page=123') + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + page: 123, + }) + }) +}) + +describe('With Hook', () => { + const app = new Hono() + + const schema = a.object({ + id: a.number(), + title: a.string(), + }) + + app.post( + '/post', + aValidator('json', schema, (result, c) => { + if (!result.success) { + return c.text(`${result.data.id} is invalid!`, 400) + } + }), + (c) => { + const data = c.req.valid('json') + return c.text(`${data.id} is valid!`) + } + ) + + it('Should return 200 response', async () => { + const req = new Request('http://localhost/post', { + body: JSON.stringify({ + id: 123, + title: 'Hello', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe('123 is valid!') + }) + + it('Should return 400 response', async () => { + const req = new Request('http://localhost/post', { + body: JSON.stringify({ + id: '123', + title: 'Hello', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(await res.text()).toBe('123 is invalid!') + }) +}) + +describe('With Async Hook', () => { + const app = new Hono() + + const schema = a.object({ + id: a.number(), + title: a.string(), + }) + + app.post( + '/post', + aValidator('json', schema, async (result, c) => { + if (!result.success) { + return c.text(`${result.data.id} is invalid!`, 400) + } + }), + (c) => { + const data = c.req.valid('json') + return c.text(`${data.id} is valid!`) + } + ) + + it('Should return 200 response', async () => { + const req = new Request('http://localhost/post', { + body: JSON.stringify({ + id: 123, + title: 'Hello', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe('123 is valid!') + }) + + it('Should return 400 response', async () => { + const req = new Request('http://localhost/post', { + body: JSON.stringify({ + id: '123', + title: 'Hello', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(await res.text()).toBe('123 is invalid!') + }) +}) + +describe('With target', () => { + it('should call hook for correctly validated target', async () => { + const app = new Hono() + + const schema = a.object({ + id: a.string(), + }) + + const jsonHook = vi.fn() + const paramHook = vi.fn() + const queryHook = vi.fn() + app.post( + '/:id/post', + aValidator('json', schema, jsonHook), + aValidator('param', schema, paramHook), + aValidator('query', schema, queryHook), + (c) => { + return c.text('ok') + } + ) + + const req = new Request('http://localhost/1/post?id=2', { + body: JSON.stringify({ + id: '3', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe('ok') + expect(paramHook).toHaveBeenCalledWith( + { data: { id: '1' }, success: true, target: 'param' }, + expect.anything() + ) + expect(queryHook).toHaveBeenCalledWith( + { data: { id: '2' }, success: true, target: 'query' }, + expect.anything() + ) + expect(jsonHook).toHaveBeenCalledWith( + { data: { id: '3' }, success: true, target: 'json' }, + expect.anything() + ) + }) +}) + +describe('Only Types', () => { + it('Should return correct enum types for query', () => { + const app = new Hono() + + const querySchema = a.object({ + order: a.enumerator(['asc', 'desc']), + }) + + const route = app.get('/', aValidator('query', querySchema), (c) => { + const data = c.req.valid('query') + return c.json(data) + }) + + type Actual = ExtractSchema + type Expected = { + '/': { + $get: { + input: { + query: { + order: 'asc' | 'desc' + } + } + output: { + order: 'asc' | 'desc' + } + outputFormat: 'json' + status: ContentfulStatusCode + } + } + } + type verify = Expect> + }) +}) + +describe('Case-Insensitive Headers', () => { + it('Should ignore the case for headers in the Arri schema and return 200', () => { + const app = new Hono() + const headerSchema = a.object({ + 'Content-Type': a.string(), + ApiKey: a.string(), + onlylowercase: a.string(), + ONLYUPPERCASE: a.string(), + }) + + const route = app.get('/', aValidator('header', headerSchema), (c) => { + const headers = c.req.valid('header') + return c.json(headers) + }) + + type Actual = ExtractSchema + type Expected = { + '/': { + $get: { + input: { + header: a.infer + } + output: a.infer + outputFormat: 'json' + status: ContentfulStatusCode + } + } + } + type verify = Expect> + }) +}) diff --git a/packages/arri-validator/src/index.ts b/packages/arri-validator/src/index.ts new file mode 100644 index 000000000..dab995516 --- /dev/null +++ b/packages/arri-validator/src/index.ts @@ -0,0 +1,96 @@ +import type { ASchemaWithAdapters, InferType, Result, ValueError } from '@arrirpc/schema' +import { a } from '@arrirpc/schema' +import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono' +import { validator } from 'hono/validator' + +export type Hook< + T, + E extends Env, + P extends string, + Target extends keyof ValidationTargets = keyof ValidationTargets, + O = Record, +> = ( + result: ({ success: true; data: T } | { success: false; error: ValueError[]; data: T }) & { + target: Target + }, + c: Context +) => Response | TypedResponse | undefined | Promise | undefined> + +type HasUndefined = undefined extends T ? true : false + +export const aValidator = < + T extends ASchemaWithAdapters, + Target extends keyof ValidationTargets, + E extends Env, + P extends string, + In = InferType, + Out = InferType, + I extends Input = { + in: HasUndefined extends true + ? { + [K in Target]?: In extends ValidationTargets[K] + ? In + : { [K2 in keyof In]?: ValidationTargets[K][K2] } + } + : { + [K in Target]: In extends ValidationTargets[K] + ? In + : { [K2 in keyof In]: ValidationTargets[K][K2] } + } + out: { [K in Target]: Out } + }, + V extends I = I, +>( + target: Target, + schema: T, + hook?: Hook, E, P, Target>, + options?: { + validationFunction: ( + schema: T, + value: ValidationTargets[Target] + ) => Result> | Promise>> + } +): MiddlewareHandler => + // @ts-expect-error not typed well in hono + validator(target, async (value, c) => { + let validatorValue = value + + // Handle headers case - Hono parses all headers into lowercase + if (target === 'header' && a.validate(a.object({}, { strict: false }), schema)) { + try { + // Create an object that maps lowercase schema keys to original keys + const schemaKeys = Object.keys(schema as Record) + const caseInsensitiveKeymap = Object.fromEntries( + schemaKeys.map((key) => [key.toLowerCase(), key]) + ) + + validatorValue = Object.fromEntries( + Object.entries(value).map(([key, value]) => [caseInsensitiveKeymap[key] || key, value]) + ) + } catch (error) { + // If we can't process the schema keys, just use the original value + console.error('Error processing header schema keys:', error) + } + } + + const result = options?.validationFunction + ? await options.validationFunction(schema, validatorValue) + : a.parse(schema, validatorValue) + + if (hook) { + const hookArg = result.success + ? { success: true as const, data: validatorValue, target } + : { success: false as const, error: result.errors, data: validatorValue, target } + + const hookResult = await hook(hookArg, c) + if (hookResult) { + return hookResult + } + } + + if (!result.success) { + return c.json(result, 400) + } + + return result.value as InferType + }) diff --git a/packages/arri-validator/tsconfig.build.json b/packages/arri-validator/tsconfig.build.json new file mode 100644 index 000000000..ccc2f65ad --- /dev/null +++ b/packages/arri-validator/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo", + "emitDeclarationOnly": false + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.test.ts"], + "references": [] +} diff --git a/packages/arri-validator/tsconfig.json b/packages/arri-validator/tsconfig.json new file mode 100644 index 000000000..d4d0929e1 --- /dev/null +++ b/packages/arri-validator/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.build.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/arri-validator/tsconfig.spec.json b/packages/arri-validator/tsconfig.spec.json new file mode 100644 index 000000000..326923ab6 --- /dev/null +++ b/packages/arri-validator/tsconfig.spec.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/packages/arri-validator", + "types": ["vitest/globals"] + }, + "include": ["**/*.test.ts", "vitest.config.ts"], + "references": [ + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/packages/arri-validator/vitest.config.ts b/packages/arri-validator/vitest.config.ts new file mode 100644 index 000000000..74923f8cb --- /dev/null +++ b/packages/arri-validator/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config' + +export default defineProject({ + test: { + globals: true, + }, +}) diff --git a/yarn.lock b/yarn.lock index 30bd88f5d..97a29c8ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -51,6 +51,23 @@ __metadata: languageName: node linkType: hard +"@arethetypeswrong/cli@npm:^0.18.1": + version: 0.18.1 + resolution: "@arethetypeswrong/cli@npm:0.18.1" + dependencies: + "@arethetypeswrong/core": "npm:0.18.1" + chalk: "npm:^4.1.2" + cli-table3: "npm:^0.6.3" + commander: "npm:^10.0.1" + marked: "npm:^9.1.2" + marked-terminal: "npm:^7.1.0" + semver: "npm:^7.5.4" + bin: + attw: dist/index.js + checksum: 65cb47d9e0564dd5b1312236e620c327215a9717a4e770a36dc6a08b98ad1a699fc29ef8df4878e23cddec14bb00d29719631cb01ea6f73f14b7e09b57eb3dc8 + languageName: node + linkType: hard + "@arethetypeswrong/core@npm:0.17.4": version: 0.17.4 resolution: "@arethetypeswrong/core@npm:0.17.4" @@ -67,6 +84,22 @@ __metadata: languageName: node linkType: hard +"@arethetypeswrong/core@npm:0.18.1": + version: 0.18.1 + resolution: "@arethetypeswrong/core@npm:0.18.1" + dependencies: + "@andrewbranch/untar.js": "npm:^1.0.3" + "@loaderkit/resolve": "npm:^1.0.2" + cjs-module-lexer: "npm:^1.2.3" + fflate: "npm:^0.8.2" + lru-cache: "npm:^11.0.1" + semver: "npm:^7.5.4" + typescript: "npm:5.6.1-rc" + validate-npm-package-name: "npm:^5.0.0" + checksum: ba2a5c259f36980a487d728d28ebba761c28a1f9674eefe059ae6e26c820dac0816e39a769b8f40fd37c2da9c554178bbacc08902d3c4ac4636ec170eaaa3b7f + languageName: node + linkType: hard + "@ark/schema@npm:0.26.0": version: 0.26.0 resolution: "@ark/schema@npm:0.26.0" @@ -99,6 +132,27 @@ __metadata: languageName: node linkType: hard +"@arrirpc/schema@npm:^0.79.0": + version: 0.79.0 + resolution: "@arrirpc/schema@npm:0.79.0" + dependencies: + "@arrirpc/type-defs": "npm:0.79.0" + "@standard-schema/spec": "npm:1.0.0" + scule: "npm:^1.3.0" + uncrypto: "npm:^0.1.3" + checksum: bd7c1387e5db37938efa288e0d2a62221c1b21f0621c337039c49820f1676e3c108764af979b981b82cf2462b9ab914a146a5eb6122f2e7b074b5859415485d7 + languageName: node + linkType: hard + +"@arrirpc/type-defs@npm:0.79.0": + version: 0.79.0 + resolution: "@arrirpc/type-defs@npm:0.79.0" + dependencies: + scule: "npm:^1.3.0" + checksum: a61ef40c8385ca134258757f35281897e43a09b26ae8823d814b978f5a61ca10dbd1ec5106ad436ef572d698e5fd58560f083cbe08eb34015cc8f0fed70a6a85 + languageName: node + linkType: hard + "@asteasolutions/zod-to-openapi@npm:^7.3.0": version: 7.3.0 resolution: "@asteasolutions/zod-to-openapi@npm:7.3.0" @@ -1826,6 +1880,22 @@ __metadata: languageName: unknown linkType: soft +"@hono/arri-validator@workspace:packages/arri-validator": + version: 0.0.0-use.local + resolution: "@hono/arri-validator@workspace:packages/arri-validator" + dependencies: + "@arethetypeswrong/cli": "npm:^0.18.1" + "@arrirpc/schema": "npm:^0.79.0" + publint: "npm:^0.3.12" + tsup: "npm:^8.5.0" + typescript: "npm:^5.8.3" + vitest: "npm:^3.1.3" + peerDependencies: + "@arrirpc/schema": ^0.79.0 + hono: ">=4.0.0" + languageName: unknown + linkType: soft + "@hono/auth-js@workspace:packages/auth-js": version: 0.0.0-use.local resolution: "@hono/auth-js@workspace:packages/auth-js" @@ -4302,6 +4372,18 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/expect@npm:3.1.3" + dependencies: + "@vitest/spy": "npm:3.1.3" + "@vitest/utils": "npm:3.1.3" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 3a61e5526ed57491c9c230cb592849a2c15e6b4376bfaec4f623ac75fdcf5c24c322949cfb5362136fc8be5eb19be88d094917ea5f700bd3da0ea0c68ee4a8d9 + languageName: node + linkType: hard + "@vitest/mocker@npm:3.0.9": version: 3.0.9 resolution: "@vitest/mocker@npm:3.0.9" @@ -4321,6 +4403,25 @@ __metadata: languageName: node linkType: hard +"@vitest/mocker@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/mocker@npm:3.1.3" + dependencies: + "@vitest/spy": "npm:3.1.3" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 6e6a62e27aa6cd146d14ae64eb9acfc0f49e7479ca426af1fb4df362456aa3456abf29731247659032e4bfb7ac9482fca1d1c7e1501e1a186eb211221e1f613a + languageName: node + linkType: hard + "@vitest/pretty-format@npm:3.0.9, @vitest/pretty-format@npm:^3.0.9": version: 3.0.9 resolution: "@vitest/pretty-format@npm:3.0.9" @@ -4330,6 +4431,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:3.1.3, @vitest/pretty-format@npm:^3.1.3": + version: 3.1.3 + resolution: "@vitest/pretty-format@npm:3.1.3" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: eba164d2c0b2babbcf6bb054da3b326d08cc3a0289ade3c64309bfe5e7c3124cd4d45a60b2f673cf4f5b3a97381fb7af7009780a5d9665afdf7f8263fa34c068 + languageName: node + linkType: hard + "@vitest/runner@npm:3.0.9": version: 3.0.9 resolution: "@vitest/runner@npm:3.0.9" @@ -4340,6 +4450,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/runner@npm:3.1.3" + dependencies: + "@vitest/utils": "npm:3.1.3" + pathe: "npm:^2.0.3" + checksum: f03c26e72657242ce68a93b46ee8a4e6fa1a290850be608988622a3efef744ffadc0436123acafe61977608b287b1637f4f781d27107ee0c33937c54f547159d + languageName: node + linkType: hard + "@vitest/snapshot@npm:3.0.9": version: 3.0.9 resolution: "@vitest/snapshot@npm:3.0.9" @@ -4351,6 +4471,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/snapshot@npm:3.1.3" + dependencies: + "@vitest/pretty-format": "npm:3.1.3" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 60b70c1d878c3d9a4fe3464d14be2318a7a3be24131beb801712735d5dcbc7db7b798f21c98c6fbad4998554992038b29655e1b6e2503242627f203fd89c97c3 + languageName: node + linkType: hard + "@vitest/spy@npm:3.0.9": version: 3.0.9 resolution: "@vitest/spy@npm:3.0.9" @@ -4360,6 +4491,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/spy@npm:3.1.3" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 6a8c187069827c56f3492f212ccf76c797fe52392849948af736a0f579e4533fa91041d829e2574b252af4aaadec066ca0714450d6457b31526153978bc55192 + languageName: node + linkType: hard + "@vitest/utils@npm:3.0.9": version: 3.0.9 resolution: "@vitest/utils@npm:3.0.9" @@ -4371,6 +4511,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:3.1.3": + version: 3.1.3 + resolution: "@vitest/utils@npm:3.1.3" + dependencies: + "@vitest/pretty-format": "npm:3.1.3" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 1c4ea711b87a8b2c7dc2da91f20427dccc34c0d1d0e81b8142780d24b6caa3c724e8287f7e01e9e875262b6bb912d55711fb99e66f718ba30cc21706a335829d + languageName: node + linkType: hard + "abbrev@npm:^2.0.0": version: 2.0.0 resolution: "abbrev@npm:2.0.0" @@ -6450,6 +6601,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": version: 1.1.1 resolution: "es-object-atoms@npm:1.1.1" @@ -7300,6 +7458,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.2.1": + version: 1.2.1 + resolution: "expect-type@npm:1.2.1" + checksum: b775c9adab3c190dd0d398c722531726cdd6022849b4adba19dceab58dda7e000a7c6c872408cd73d665baa20d381eca36af4f7b393a4ba60dd10232d1fb8898 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.2 resolution: "exponential-backoff@npm:3.1.2" @@ -7456,6 +7621,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.4": + version: 6.4.4 + resolution: "fdir@npm:6.4.4" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 6ccc33be16945ee7bc841e1b4178c0b4cf18d3804894cb482aa514651c962a162f96da7ffc6ebfaf0df311689fb70091b04dd6caffe28d56b9ebdc0e7ccadfdd + languageName: node + linkType: hard + "fecha@npm:^4.2.0": version: 4.2.3 resolution: "fecha@npm:4.2.3" @@ -7659,6 +7836,17 @@ __metadata: languageName: node linkType: hard +"fix-dts-default-cjs-exports@npm:^1.0.0": + version: 1.0.1 + resolution: "fix-dts-default-cjs-exports@npm:1.0.1" + dependencies: + magic-string: "npm:^0.30.17" + mlly: "npm:^1.7.4" + rollup: "npm:^4.34.8" + checksum: 61a3cbe32b6c29df495ef3aded78199fe9dbb52e2801c899fe76d9ca413d3c8c51f79986bac83f8b4b2094ebde883ddcfe47b68ce469806ba13ca6ed4e7cd362 + languageName: node + linkType: hard + "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -9510,6 +9698,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.1": + version: 11.1.0 + resolution: "lru-cache@npm:11.1.0" + checksum: 85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -11167,6 +11362,13 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^1.1.0": + version: 1.3.0 + resolution: "package-manager-detector@npm:1.3.0" + checksum: b4b54a81a3230edd66564a59ff6a2233086961e36ba91a28a0f6d6932a8dec36618ace50e8efec9c4d8c6aa9828e98814557a39fb6b106c161434ccb44a80e1c + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -11759,6 +11961,20 @@ __metadata: languageName: node linkType: hard +"publint@npm:^0.3.12": + version: 0.3.12 + resolution: "publint@npm:0.3.12" + dependencies: + "@publint/pack": "npm:^0.1.2" + package-manager-detector: "npm:^1.1.0" + picocolors: "npm:^1.1.1" + sade: "npm:^1.8.1" + bin: + publint: src/cli.js + checksum: facd7d06236ef0ed5555ecbc671b46734847b3dde35611fbd143a175fc940d4f82d147f469bd806243e646c55c9dd06cd4a6de10d08404f6ff2b5942df70dd43 + languageName: node + linkType: hard + "publint@npm:^0.3.9": version: 0.3.9 resolution: "publint@npm:0.3.9" @@ -12675,6 +12891,13 @@ __metadata: languageName: node linkType: hard +"scule@npm:^1.3.0": + version: 1.3.0 + resolution: "scule@npm:1.3.0" + checksum: 5d1736daa10622c420f2aa74e60d3c722e756bfb139fa784ae5c66669fdfe92932d30ed5072e4ce3107f9c3053e35ad73b2461cb18de45b867e1d4dea63f8823 + languageName: node + linkType: hard + "semver-diff@npm:^3.1.1": version: 3.1.1 resolution: "semver-diff@npm:3.1.1" @@ -13197,6 +13420,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.9.0": + version: 3.9.0 + resolution: "std-env@npm:3.9.0" + checksum: 4a6f9218aef3f41046c3c7ecf1f98df00b30a07f4f35c6d47b28329bc2531eef820828951c7d7b39a1c5eb19ad8a46e3ddfc7deb28f0a2f3ceebee11bab7ba50 + languageName: node + linkType: hard + "stoppable@npm:1.1.0": version: 1.1.0 resolution: "stoppable@npm:1.1.0" @@ -13653,6 +13883,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.13": + version: 0.2.13 + resolution: "tinyglobby@npm:0.2.13" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: ef07dfaa7b26936601d3f6d999f7928a4d1c6234c5eb36896bb88681947c0d459b7ebe797022400e555fe4b894db06e922b95d0ce60cb05fd827a0a66326b18c + languageName: node + linkType: hard + "tinypool@npm:^1.0.2": version: 1.0.2 resolution: "tinypool@npm:1.0.2" @@ -13876,6 +14116,48 @@ __metadata: languageName: node linkType: hard +"tsup@npm:^8.5.0": + version: 8.5.0 + resolution: "tsup@npm:8.5.0" + dependencies: + bundle-require: "npm:^5.1.0" + cac: "npm:^6.7.14" + chokidar: "npm:^4.0.3" + consola: "npm:^3.4.0" + debug: "npm:^4.4.0" + esbuild: "npm:^0.25.0" + fix-dts-default-cjs-exports: "npm:^1.0.0" + joycon: "npm:^3.1.1" + picocolors: "npm:^1.1.1" + postcss-load-config: "npm:^6.0.1" + resolve-from: "npm:^5.0.0" + rollup: "npm:^4.34.8" + source-map: "npm:0.8.0-beta.0" + sucrase: "npm:^3.35.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.11" + tree-kill: "npm:^1.2.2" + peerDependencies: + "@microsoft/api-extractor": ^7.36.0 + "@swc/core": ^1 + postcss: ^8.4.12 + typescript: ">=4.5.0" + peerDependenciesMeta: + "@microsoft/api-extractor": + optional: true + "@swc/core": + optional: true + postcss: + optional: true + typescript: + optional: true + bin: + tsup: dist/cli-default.js + tsup-node: dist/cli-node.js + checksum: 2eddc1138ad992a2e67d826e92e0b0c4f650367355866c77df8368ade9489e0a8bf2b52b352e97fec83dc690af05881c29c489af27acb86ac2cef38b0d029087 + languageName: node + linkType: hard + "tsyringe@npm:^4.8.0": version: 4.8.0 resolution: "tsyringe@npm:4.8.0" @@ -13982,6 +14264,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 + languageName: node + linkType: hard + "typescript@npm:~5.6.3": version: 5.6.3 resolution: "typescript@npm:5.6.3" @@ -14012,6 +14304,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 92ea03509e06598948559ddcdd8a4ae5a7ab475766d5589f1b796f5731b3d631a4c7ddfb86a3bd44d58d10102b132cd4b4994dda9b63e6273c66d77d6a271dbd + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A~5.6.3#optional!builtin": version: 5.6.3 resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=e012d7" @@ -14067,6 +14369,13 @@ __metadata: languageName: node linkType: hard +"uncrypto@npm:^0.1.3": + version: 0.1.3 + resolution: "uncrypto@npm:0.1.3" + checksum: 74a29afefd76d5b77bedc983559ceb33f5bbc8dada84ff33755d1e3355da55a4e03a10e7ce717918c436b4dfafde1782e799ebaf2aadd775612b49f7b5b2998e + languageName: node + linkType: hard + "undici-types@npm:~6.19.2": version: 6.19.8 resolution: "undici-types@npm:6.19.8" @@ -14527,6 +14836,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:3.1.3": + version: 3.1.3 + resolution: "vite-node@npm:3.1.3" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.7.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0" + bin: + vite-node: vite-node.mjs + checksum: d69a1e52361bc0af22d1178db61674ef768cfd3c5610733794bb1e7a36af113da287dd89662a1ad57fd4f6c3360ca99678f5428ba837f239df4091d7891f2e4c + languageName: node + linkType: hard + "vite@npm:^5": version: 5.4.15 resolution: "vite@npm:5.4.15" @@ -14675,6 +14999,60 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^3.1.3": + version: 3.1.3 + resolution: "vitest@npm:3.1.3" + dependencies: + "@vitest/expect": "npm:3.1.3" + "@vitest/mocker": "npm:3.1.3" + "@vitest/pretty-format": "npm:^3.1.3" + "@vitest/runner": "npm:3.1.3" + "@vitest/snapshot": "npm:3.1.3" + "@vitest/spy": "npm:3.1.3" + "@vitest/utils": "npm:3.1.3" + chai: "npm:^5.2.0" + debug: "npm:^4.4.0" + expect-type: "npm:^1.2.1" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + std-env: "npm:^3.9.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.13" + tinypool: "npm:^1.0.2" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0" + vite-node: "npm:3.1.3" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.1.3 + "@vitest/ui": 3.1.3 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 954b3579a2d925606df7f78e367ae64eab52c8c5ba2bb2fed94d335a06c910202a4ce080bb02d8148c8b4782488c6d229e963617be8d0c7da96a1c944dd291d7 + languageName: node + linkType: hard + "wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1"