nuxt-procedures 0.1.2
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 +203 -0
- package/dist/module.d.mts +9 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +113 -0
- package/dist/runtime/create-caller.d.ts +16 -0
- package/dist/runtime/create-caller.js +23 -0
- package/dist/runtime/define-procedure.d.ts +22 -0
- package/dist/runtime/define-procedure.js +17 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/types.d.mts +3 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Nuxt Procedures
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
Nuxt module to define and easily consume your backend API in a validated and
|
|
9
|
+
type-safe way using procedures and Zod schemas.
|
|
10
|
+
|
|
11
|
+
- [🏀 Online playground](https://stackblitz.com/github/andresberrios/nuxt-procedures?file=playground%2Fapp.vue)
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Type-Safe API Layer:** End-to-end type safety for your API calls.
|
|
16
|
+
- **Zod Validation:** Use Zod schemas to validate inputs and outputs.
|
|
17
|
+
- **Automatic API Client:** An `apiClient` is automatically generated based on your procedures.
|
|
18
|
+
- **`useFetch` Integration:** Seamlessly integrates with Nuxt's `useFetch` for easy data fetching in your components.
|
|
19
|
+
- **`superjson` Support:** Automatically handles serialization of complex data types.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Quick Install (Recommended)
|
|
24
|
+
|
|
25
|
+
Install and configure the module with a single command:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx nuxi module add nuxt-procedures
|
|
29
|
+
```
|
|
30
|
+
This will add `nuxt-procedures` to your `package.json` and `nuxt.config.ts`.
|
|
31
|
+
|
|
32
|
+
You also need to install its peer dependency, `zod`:
|
|
33
|
+
```bash
|
|
34
|
+
npm install zod
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manual Install
|
|
38
|
+
|
|
39
|
+
1. Install the packages:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install nuxt-procedures zod
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Add the module to your `nuxt.config.ts`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
export default defineNuxtConfig({
|
|
49
|
+
modules: ['nuxt-procedures'],
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### 1. Define Procedures
|
|
56
|
+
|
|
57
|
+
Create `.ts` files in your `server/api` directory. The module will automatically create a corresponding client for each file.
|
|
58
|
+
|
|
59
|
+
#### Simple Example
|
|
60
|
+
|
|
61
|
+
For a simple input and output, you can use Zod schemas directly.
|
|
62
|
+
|
|
63
|
+
`server/api/hello.ts`:
|
|
64
|
+
```typescript
|
|
65
|
+
import { z } from 'zod'
|
|
66
|
+
|
|
67
|
+
export default defineProcedure({
|
|
68
|
+
input: z.string(),
|
|
69
|
+
output: z.string(),
|
|
70
|
+
handler: async ({ input }) => {
|
|
71
|
+
return `Hello, ${input}!`
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Complex Example
|
|
77
|
+
|
|
78
|
+
For more complex scenarios, `z.object` is the way to go. This is useful for things like form submissions or creating database entries.
|
|
79
|
+
|
|
80
|
+
`server/api/users/create.ts`:
|
|
81
|
+
```typescript
|
|
82
|
+
import { z } from 'zod'
|
|
83
|
+
|
|
84
|
+
export default defineProcedure({
|
|
85
|
+
input: z.object({
|
|
86
|
+
name: z.string(),
|
|
87
|
+
email: z.string().email(),
|
|
88
|
+
role: z.enum(['admin', 'user']).default('user'),
|
|
89
|
+
}),
|
|
90
|
+
output: z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
name: z.string(),
|
|
93
|
+
email: z.string(),
|
|
94
|
+
}),
|
|
95
|
+
handler: async ({ input, event }) => {
|
|
96
|
+
// The event param gives you access to the request context and Nuxt utilities
|
|
97
|
+
// For example, you can access request headers
|
|
98
|
+
const headers = getRequestHeaders(event)
|
|
99
|
+
console.log(headers)
|
|
100
|
+
|
|
101
|
+
// In a real app, you would create a user in your database
|
|
102
|
+
// The following is just an example of how you could get a db client
|
|
103
|
+
const db = await useDB(event)
|
|
104
|
+
const newUser = await db.user.create({
|
|
105
|
+
data: input,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return newUser
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2. Use the `apiClient`
|
|
114
|
+
|
|
115
|
+
The module automatically generates an `apiClient` that you can use in your components or pages. The structure of the `apiClient` mirrors your `server/api` directory.
|
|
116
|
+
|
|
117
|
+
#### `useCall`
|
|
118
|
+
|
|
119
|
+
The `useCall` method is a wrapper around Nuxt's `useFetch` and is the recommended way to call procedures from your Vue components.
|
|
120
|
+
|
|
121
|
+
Calling the simple `hello` procedure:
|
|
122
|
+
```vue
|
|
123
|
+
<script setup lang="ts">
|
|
124
|
+
const { data: greeting, pending } = await apiClient.hello.useCall('World')
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<template>
|
|
128
|
+
<p v-if="pending">Loading...</p>
|
|
129
|
+
<p v-else>{{ greeting }}</p>
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Calling the complex `users/create` procedure:
|
|
134
|
+
```vue
|
|
135
|
+
<script setup lang="ts">
|
|
136
|
+
const { data: newUser, execute } = apiClient.users.create.useCall({
|
|
137
|
+
name: 'Andres',
|
|
138
|
+
email: 'andres@example.com',
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// `execute` can be called later, e.g. in a form submission handler
|
|
142
|
+
// const submitForm = () => execute()
|
|
143
|
+
</script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### `call`
|
|
147
|
+
|
|
148
|
+
The `call` method makes a direct API call and is useful for calling procedures from server-side code or when you don't need the features of `useFetch`.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Calling the simple procedure
|
|
152
|
+
const greeting = await apiClient.hello.call('World')
|
|
153
|
+
// greeting is "Hello, World!"
|
|
154
|
+
|
|
155
|
+
// Calling the complex procedure
|
|
156
|
+
const newUser = await apiClient.users.create.call({
|
|
157
|
+
name: 'Andres',
|
|
158
|
+
email: 'andres@example.com',
|
|
159
|
+
})
|
|
160
|
+
// newUser is { id: '...', name: 'Andres', email: 'andres@example.com' }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Contribution
|
|
164
|
+
|
|
165
|
+
<details>
|
|
166
|
+
<summary>Local development</summary>
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Install dependencies
|
|
170
|
+
npm install
|
|
171
|
+
|
|
172
|
+
# Generate type stubs
|
|
173
|
+
npm run dev:prepare
|
|
174
|
+
|
|
175
|
+
# Develop with the playground
|
|
176
|
+
npm run dev
|
|
177
|
+
|
|
178
|
+
# Build the playground
|
|
179
|
+
npm run dev:build
|
|
180
|
+
|
|
181
|
+
# Run ESLint
|
|
182
|
+
npm run lint
|
|
183
|
+
|
|
184
|
+
# Run Vitest
|
|
185
|
+
npm run test
|
|
186
|
+
npm run test:watch
|
|
187
|
+
|
|
188
|
+
# Release new version
|
|
189
|
+
npm run release
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
<!-- Badges -->
|
|
195
|
+
|
|
196
|
+
[npm-version-src]: https://img.shields.io/npm/v/nuxt-procedures/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
197
|
+
[npm-version-href]: https://npmjs.com/package/nuxt-procedures
|
|
198
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-procedures.svg?style=flat&colorA=020420&colorB=00DC82
|
|
199
|
+
[npm-downloads-href]: https://npm.chart.dev/nuxt-procedures
|
|
200
|
+
[license-src]: https://img.shields.io/npm/l/nuxt-procedures.svg?style=flat&colorA=020420&colorB=00DC82
|
|
201
|
+
[license-href]: https://npmjs.com/package/nuxt-procedures
|
|
202
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
203
|
+
[nuxt-href]: https://nuxt.com
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { join, parse, sep } from 'node:path';
|
|
2
|
+
import { defineNuxtModule, createResolver, addServerImports, addTemplate, updateTemplates, addImports, resolveFiles } from '@nuxt/kit';
|
|
3
|
+
import { camelCase } from 'scule';
|
|
4
|
+
|
|
5
|
+
const toCode = Symbol("toCode");
|
|
6
|
+
const toList = Symbol("toList");
|
|
7
|
+
class Procedure {
|
|
8
|
+
constructor(file, url) {
|
|
9
|
+
this.file = file;
|
|
10
|
+
this.url = url;
|
|
11
|
+
}
|
|
12
|
+
id = "proc" + Math.random().toString(36).slice(2);
|
|
13
|
+
[toCode]() {
|
|
14
|
+
return `createCaller<typeof ${this.id}>("${this.url}")`;
|
|
15
|
+
}
|
|
16
|
+
[toList]() {
|
|
17
|
+
return [this];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
class Router {
|
|
21
|
+
[toCode]() {
|
|
22
|
+
return `{
|
|
23
|
+
${Object.entries(this).map(([key, value]) => `"${camelCase(key)}": ${value[toCode]()}`).join(",\n")}
|
|
24
|
+
}`;
|
|
25
|
+
}
|
|
26
|
+
[toList]() {
|
|
27
|
+
return Object.values(this).flatMap((v) => v[toList]());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const module = defineNuxtModule({
|
|
31
|
+
meta: {
|
|
32
|
+
name: "nuxt-procedures",
|
|
33
|
+
configKey: "procedures"
|
|
34
|
+
},
|
|
35
|
+
// Default configuration options of the Nuxt module
|
|
36
|
+
defaults: {
|
|
37
|
+
enabled: true
|
|
38
|
+
},
|
|
39
|
+
async setup(options, nuxt) {
|
|
40
|
+
if (!options.enabled) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { resolve } = createResolver(import.meta.url);
|
|
44
|
+
addServerImports([
|
|
45
|
+
{
|
|
46
|
+
from: resolve("./runtime/define-procedure.ts"),
|
|
47
|
+
name: "defineProcedure"
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
const createCallerPath = resolve("./runtime/create-caller.ts");
|
|
51
|
+
const generatedClient = addTemplate({
|
|
52
|
+
filename: "nuxt-procedures/api-client.ts",
|
|
53
|
+
getContents: () => generateClientCode(nuxt, createCallerPath),
|
|
54
|
+
write: true
|
|
55
|
+
});
|
|
56
|
+
nuxt.hook("builder:watch", async (event, path) => {
|
|
57
|
+
if (path.includes("api")) {
|
|
58
|
+
updateTemplates({
|
|
59
|
+
filter: (t) => t.filename === generatedClient.filename
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const imports = [{ from: generatedClient.dst, name: "apiClient" }];
|
|
64
|
+
addImports(imports);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
async function buildRouterStructure(nuxt) {
|
|
68
|
+
const procedureDirs = nuxt.options._layers.map(
|
|
69
|
+
(l) => (
|
|
70
|
+
// join(l.config.serverDir ?? "server", "procedures")
|
|
71
|
+
join(l.config.serverDir ?? "server", "api")
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
const router = new Router();
|
|
75
|
+
for (const procDir of procedureDirs) {
|
|
76
|
+
const files = await resolveFiles(procDir, "**/*.ts");
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const { name, dir } = parse(file);
|
|
79
|
+
const namespace = dir.slice(procDir.length + 1);
|
|
80
|
+
const url = namespace === "" ? `/${name}` : `/${namespace}/${name}`;
|
|
81
|
+
if (namespace === "") {
|
|
82
|
+
router[name] = new Procedure(file, url);
|
|
83
|
+
} else {
|
|
84
|
+
const parts = namespace.split(sep);
|
|
85
|
+
let subRouter = router;
|
|
86
|
+
for (const part of parts) {
|
|
87
|
+
if (!subRouter[part] || subRouter[part] instanceof Procedure) {
|
|
88
|
+
subRouter[part] = new Router();
|
|
89
|
+
}
|
|
90
|
+
subRouter = subRouter[part];
|
|
91
|
+
}
|
|
92
|
+
subRouter[name] = new Procedure(file, url);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return router;
|
|
97
|
+
}
|
|
98
|
+
function withoutExtension(file) {
|
|
99
|
+
return file.slice(0, -parse(file).ext.length);
|
|
100
|
+
}
|
|
101
|
+
async function generateClientCode(nuxt, createCallerPath) {
|
|
102
|
+
const router = await buildRouterStructure(nuxt);
|
|
103
|
+
const procedures = router[toList]();
|
|
104
|
+
return `
|
|
105
|
+
import { createCaller } from "${withoutExtension(createCallerPath)}";
|
|
106
|
+
|
|
107
|
+
${procedures.map((p) => `import type ${p.id} from "${withoutExtension(p.file)}";`).join("\n")}
|
|
108
|
+
|
|
109
|
+
export const apiClient = ${router[toCode]()};
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { module as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { Procedure } from './define-procedure.js';
|
|
3
|
+
import { useFetch } from '#imports';
|
|
4
|
+
type InferProcedureInput<P> = P extends Procedure<infer I, any> ? I extends z.ZodUndefined ? unknown : z.infer<I> : never;
|
|
5
|
+
type InferProcedureOutput<P> = P extends Procedure<any, infer O> ? O extends z.ZodTypeAny ? z.infer<O> : unknown : never;
|
|
6
|
+
type Call<P extends Procedure<any, any>> = [unknown] extends [
|
|
7
|
+
InferProcedureInput<P>
|
|
8
|
+
] ? () => Promise<InferProcedureOutput<P>> : (input: InferProcedureInput<P>) => Promise<InferProcedureOutput<P>>;
|
|
9
|
+
type UseCall<P extends Procedure<any, any>> = [unknown] extends [
|
|
10
|
+
InferProcedureInput<P>
|
|
11
|
+
] ? () => ReturnType<typeof useFetch<InferProcedureOutput<P>>> : (input: InferProcedureInput<P>) => ReturnType<typeof useFetch<InferProcedureOutput<P>>>;
|
|
12
|
+
export declare function createCaller<P extends Procedure<any, any>>(url: string): {
|
|
13
|
+
call: Call<P>;
|
|
14
|
+
useCall: UseCall<P>;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import superjson from "superjson";
|
|
2
|
+
import { useFetch, useRequestHeaders } from "#imports";
|
|
3
|
+
export function createCaller(url) {
|
|
4
|
+
return {
|
|
5
|
+
call: (input) => {
|
|
6
|
+
return $fetch("/api" + url, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
body: superjson.serialize(input),
|
|
9
|
+
parseResponse: superjson.parse,
|
|
10
|
+
headers: useRequestHeaders(["cookie"])
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
useCall: (input) => {
|
|
14
|
+
return useFetch("/api" + url, {
|
|
15
|
+
key: `${url}: ${JSON.stringify(input)}`,
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: superjson.serialize(input),
|
|
18
|
+
parseResponse: superjson.parse,
|
|
19
|
+
headers: useRequestHeaders(["cookie"])
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EventHandler, H3Event } from 'h3';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
export interface Procedure<I extends z.ZodTypeAny = z.ZodUndefined, O extends z.ZodTypeAny = z.ZodVoid> extends EventHandler {
|
|
4
|
+
__procedureMetadata: {
|
|
5
|
+
inputSchema: I;
|
|
6
|
+
outputSchema: O;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
type Awaitable<T> = T | Promise<T>;
|
|
10
|
+
type HandlerArgs<I> = I extends undefined ? {
|
|
11
|
+
event: H3Event;
|
|
12
|
+
} : {
|
|
13
|
+
input: I;
|
|
14
|
+
event: H3Event;
|
|
15
|
+
};
|
|
16
|
+
type Handler<I extends z.ZodTypeAny = z.ZodUndefined, O extends z.ZodTypeAny = z.ZodVoid> = (args: HandlerArgs<z.infer<I>>) => Awaitable<z.infer<O>>;
|
|
17
|
+
export declare function defineProcedure<I extends z.ZodTypeAny = z.ZodUndefined, O extends z.ZodTypeAny = z.ZodVoid>(args: {
|
|
18
|
+
input?: I;
|
|
19
|
+
output?: O;
|
|
20
|
+
handler: Handler<I, O>;
|
|
21
|
+
}): Procedure<I, O>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import superjson from "superjson";
|
|
3
|
+
export function defineProcedure({
|
|
4
|
+
input: inputSchema,
|
|
5
|
+
output: outputSchema = z.void(),
|
|
6
|
+
handler
|
|
7
|
+
}) {
|
|
8
|
+
const h = defineEventHandler(async (event) => {
|
|
9
|
+
const input = inputSchema ? inputSchema.parse(superjson.deserialize(await readBody(event))) : void 0;
|
|
10
|
+
const outputRaw = await handler(inputSchema ? { input, event } : { event });
|
|
11
|
+
const output = outputSchema.parse(outputRaw);
|
|
12
|
+
return superjson.serialize(output);
|
|
13
|
+
});
|
|
14
|
+
const procedure = h;
|
|
15
|
+
procedure.__procedureMetadata = { inputSchema, outputSchema };
|
|
16
|
+
return procedure;
|
|
17
|
+
}
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-procedures",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Nuxt module to define and easily consume your backend API in a validated and type-safe way using procedures and Zod schemas.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/andresberrios/nuxt-procedures.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/types.d.mts",
|
|
14
|
+
"import": "./dist/module.mjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/module.mjs",
|
|
18
|
+
"typesVersions": {
|
|
19
|
+
"*": {
|
|
20
|
+
".": [
|
|
21
|
+
"./dist/types.d.mts"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"prepack": "nuxt-module-build build",
|
|
30
|
+
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
31
|
+
"dev:build": "nuxi build playground",
|
|
32
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
33
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest watch",
|
|
37
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@nuxt/kit": "^4.0.1",
|
|
41
|
+
"scule": "^1.3.0",
|
|
42
|
+
"superjson": "^2.2.2"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@nuxt/devtools": "^2.6.2",
|
|
49
|
+
"@nuxt/eslint-config": "^1.7.1",
|
|
50
|
+
"@nuxt/module-builder": "^1.0.2",
|
|
51
|
+
"@nuxt/schema": "^4.0.1",
|
|
52
|
+
"@nuxt/test-utils": "^3.19.2",
|
|
53
|
+
"@types/node": "latest",
|
|
54
|
+
"changelogen": "^0.6.2",
|
|
55
|
+
"eslint": "^9.32.0",
|
|
56
|
+
"nuxt": "^4.0.1",
|
|
57
|
+
"typescript": "~5.8.3",
|
|
58
|
+
"vitest": "^3.2.4",
|
|
59
|
+
"vue-tsc": "^3.0.4"
|
|
60
|
+
}
|
|
61
|
+
}
|