@vercube/nitro 1.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025-present - Vercube
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @vercube/nitro
2
+
3
+ Nitro integration for the [Vercube](https://github.com/vercube/vercube) framework. This package wires up Vercube's decorator-based controllers, services and middleware into a [Nitro](https://nitro.unjs.io/) server module, so you can use the full Vercube DI/decorator system without any extra glue code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @vercube/nitro
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Add the plugin to your `nitro.config.ts`:
14
+
15
+ ```ts
16
+ import { vercubeNitro } from '@vercube/nitro';
17
+
18
+ export default defineNitroConfig({
19
+ modules: [
20
+ vercubeNitro({
21
+ // optional path to a boot file that runs before the app starts
22
+ setupFile: './src/boot/boot.ts',
23
+ }),
24
+ ],
25
+ });
26
+ ```
27
+
28
+ ### Boot file
29
+
30
+ The `setupFile` is a regular TypeScript module that exports a default function. It receives the Vercube `App` instance and is the right place to configure your DI container, register providers, or run any startup logic.
31
+
32
+ ```ts
33
+ // src/boot/boot.ts
34
+ import type { App } from '@vercube/core';
35
+
36
+ export default async function boot(app: App) {
37
+ // configure the app here
38
+ }
39
+ ```
40
+
41
+ ### Controllers
42
+
43
+ Annotate your classes with `@Controller` and HTTP method decorators. The plugin discovers them automatically at build time.
44
+
45
+ ```ts
46
+ import { Controller, Get, Post } from '@vercube/core';
47
+
48
+ @Controller('/users')
49
+ export class UserController {
50
+ @Get('/')
51
+ list() {
52
+ return [{ id: 1, name: 'Alice' }];
53
+ }
54
+
55
+ @Post('/')
56
+ create() {
57
+ return { id: 2, name: 'Bob' };
58
+ }
59
+ }
60
+ ```
61
+
62
+ Place your controllers in the `api/` or `routes/` directory (configurable via `nitro.config.ts` options `apiDir` / `routesDir`).
63
+
64
+ ### Services
65
+
66
+ Services decorated with `@Injectable` are discovered and registered in the DI container automatically. Place them anywhere inside the directories listed in `scanDirs`.
67
+
68
+ ```ts
69
+ import { Injectable } from '@vercube/di';
70
+
71
+ @Injectable()
72
+ export class UserService {
73
+ findAll() {
74
+ return [];
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Middleware
80
+
81
+ Classes that extend `BaseMiddleware` are picked up and registered automatically.
82
+
83
+ ```ts
84
+ import { BaseMiddleware } from '@vercube/core';
85
+
86
+ export class AuthMiddleware extends BaseMiddleware {
87
+ async onRequest(event: any) {
88
+ // validate token, etc.
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Storage
94
+
95
+ `@vercube/nitro` ships a `NitroStorageManager` that wraps Nitro's built-in storage layer. Configure your storage drivers in `nitro.config.ts` under `storage`, then inject `StorageManager` in your services as usual.
96
+
97
+ ```ts
98
+ // nitro.config.ts
99
+ export default defineNitroConfig({
100
+ storage: {
101
+ cache: { driver: 'memory' },
102
+ },
103
+ });
104
+ ```
105
+
106
+ ## Options
107
+
108
+ | Option | Type | Default | Description |
109
+ | ----------- | ---------- | ------- | ----------------------------------------------------- |
110
+ | `setupFile` | `string` | - | Path to the boot file executed before the app starts |
111
+ | `scanDirs` | `string[]` | `[]` | Extra directories to scan for services and middleware |
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,32 @@
1
+ import { createNitroApp } from "./runtime/App.mjs";
2
+ import { App } from "@vercube/core";
3
+ import { NitroModule } from "nitro/types";
4
+
5
+ //#region src/plugin/VercubePlugin.d.ts
6
+ interface PluginOptions {
7
+ scanDirs?: string[];
8
+ /**
9
+ * Path to a file that exports a default function for customizing the Vercube app
10
+ * before the DI container is flushed. Use this to bind tokens, override services,
11
+ * or perform any setup that auto-discovery cannot handle.
12
+ *
13
+ * The file must export a default function matching: `(app: App) => void | Promise<void>`
14
+ *
15
+ * @example
16
+ * // nitro.config.ts
17
+ * vercubeNitro({ setupFile: './src/container.ts' })
18
+ *
19
+ * // src/container.ts
20
+ * import type { App } from '@vercube/core'
21
+ * export default async (app: App) => {
22
+ * app.container.bind(DatabaseToken, PostgresDatabase)
23
+ * }
24
+ */
25
+ setupFile?: string;
26
+ }
27
+ declare function vercubeNitro(options?: PluginOptions): NitroModule;
28
+ //#endregion
29
+ //#region src/helpers/helpers.d.ts
30
+ declare function useVercubeApp(): App | undefined;
31
+ //#endregion
32
+ export { createNitroApp, useVercubeApp, vercubeNitro };
package/dist/index.mjs ADDED
@@ -0,0 +1,594 @@
1
+ import { createNitroApp } from "./runtime/App.mjs";
2
+ import { destroyContainer } from "@vercube/di";
3
+ import { defu } from "defu";
4
+ import { join, relative, resolve } from "pathe";
5
+ import { watch } from "chokidar";
6
+ import { readFileSync } from "node:fs";
7
+ import { glob } from "tinyglobby";
8
+ import { parseSync } from "oxc-parser";
9
+ //#region src/helpers/helpers.ts
10
+ function useVercubeApp() {
11
+ return globalThis.__vercubeApp__;
12
+ }
13
+ //#endregion
14
+ //#region src/validators/BundlerValidator.ts
15
+ /**
16
+ * Validates the bundler options for the nitro instance
17
+ * @param nitro - The nitro instance
18
+ * @returns void
19
+ */
20
+ function validateBundler(nitro) {
21
+ if (nitro.options.builder !== "rolldown") throw new Error("Vercube Nitro module requires the rolldown builder");
22
+ }
23
+ //#endregion
24
+ //#region src/validators/TypescriptValidator.ts
25
+ /**
26
+ * Validates the typescript options for experimentalDecorators
27
+ * @param nitro - The nitro instance
28
+ * @returns void
29
+ */
30
+ function validateTypescript(nitro) {
31
+ const tsConfig = nitro.options.typescript;
32
+ if (typeof tsConfig?.tsConfig === "string") {
33
+ if (!JSON.parse(readFileSync(tsConfig.tsConfig, "utf8")).compilerOptions?.experimentalDecorators) throw new Error(`Vercube requires "experimentalDecorators" to be enabled in your tsconfig (${tsConfig.tsConfig}). Please add "experimentalDecorators": true to your compilerOptions.`);
34
+ } else if (typeof tsConfig?.tsConfig === "object" && tsConfig.tsConfig !== null) tsConfig.tsConfig.compilerOptions = {
35
+ ...tsConfig.tsConfig.compilerOptions,
36
+ experimentalDecorators: true
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/_internal/scan.ts
41
+ const GLOB_SCAN_PATTERN = "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}";
42
+ async function scanFiles(nitro, name) {
43
+ return await Promise.all(nitro.options.scanDirs.map((dir) => scanDir(nitro, dir, name))).then((r) => r.flat());
44
+ }
45
+ async function scanDir(nitro, dir, name) {
46
+ return (await glob(join(name, GLOB_SCAN_PATTERN), {
47
+ cwd: dir,
48
+ dot: true,
49
+ absolute: true
50
+ }).catch((error) => {
51
+ if (error?.code === "ENOTDIR") {
52
+ nitro.logger.warn(`Ignoring \`${join(dir, name)}\`. It must be a directory.`);
53
+ return [];
54
+ }
55
+ throw error;
56
+ })).map((fullPath) => {
57
+ return {
58
+ fullPath,
59
+ path: relative(join(dir, name), fullPath)
60
+ };
61
+ }).sort((a, b) => a.path.localeCompare(b.path));
62
+ }
63
+ //#endregion
64
+ //#region src/build/Routes.ts
65
+ /**
66
+ * Mapping of decorator names to their corresponding uppercase HTTP method strings.
67
+ * Used for O(1) lookup and simultaneous method name resolution.
68
+ */
69
+ const HTTP_METHODS = {
70
+ Get: "GET",
71
+ Post: "POST",
72
+ Put: "PUT",
73
+ Delete: "DELETE",
74
+ Patch: "PATCH",
75
+ Options: "OPTIONS",
76
+ Head: "HEAD"
77
+ };
78
+ /**
79
+ * Pre-compiled regular expression for extracting named route parameters.
80
+ * Matches path segments prefixed with ':' (e.g. ':id', ':slug').
81
+ * Uses the global flag for iterative matching via `exec`.
82
+ */
83
+ const PARAM_RE = /:([^/]+)/g;
84
+ /**
85
+ * Internal module path used for generated import statements.
86
+ */
87
+ const IMPORT_SOURCE = "#internal/vercube-route-plugin";
88
+ /**
89
+ * Reads a controller file from disk and returns all routes defined in classes
90
+ * decorated with `Controller` and methods decorated with HTTP decorators (Get, Post, etc.).
91
+ * @param route - File info (path and fullPath) to analyze.
92
+ * @returns List of `RouteInfo` for every route in the file.
93
+ */
94
+ async function transformRoute(route) {
95
+ return extractRoutes(readFileSync(route.fullPath, "utf8")).map((parsedRoute) => ({
96
+ ...parsedRoute,
97
+ ...route
98
+ }));
99
+ }
100
+ /**
101
+ * Extracts all route definitions from the given TypeScript/JavaScript source code.
102
+ *
103
+ * Parses the source using `oxc-parser` and traverses the resulting AST to find
104
+ * classes decorated with `@Controller(path)`. For each such class, it inspects
105
+ * method definitions for HTTP method decorators (`@Get`, `@Post`, `@Put`, `@Delete`,
106
+ * `@Patch`, `@Options`, `@Head`) and constructs full route paths by concatenating
107
+ * the controller base path with the method-level path.
108
+ *
109
+ * Generates appropriate import statements based on the export style:
110
+ * - `export default class Foo` → `import Foo from '...'`
111
+ * - `export class Foo` → `import { Foo } from '...'`
112
+ *
113
+ * Supports classes declared via `export class`, `export default class`,
114
+ * and plain `class` declarations.
115
+ *
116
+ * @param code - The raw TypeScript or JavaScript source code string to analyze.
117
+ * @returns An array of {@link RouteInfo} objects representing all discovered routes.
118
+ */
119
+ function extractRoutes(code) {
120
+ const ast = parseSync("file.ts", code).program;
121
+ const routes = [];
122
+ for (const node of ast.body) {
123
+ const classInfo = getClassNode$2(node);
124
+ if (!classInfo) continue;
125
+ const { classNode, isDefault } = classInfo;
126
+ const basePath = extractDecoratorArg(classNode.decorators, "Controller");
127
+ if (basePath === null) continue;
128
+ const className = classNode.id?.name;
129
+ if (!className) continue;
130
+ const importStatement = isDefault ? `import ${className} from '${IMPORT_SOURCE}';` : `import { ${className} } from '${IMPORT_SOURCE}';`;
131
+ for (const member of classNode.body?.body ?? []) {
132
+ if (member.type !== "MethodDefinition") continue;
133
+ for (const decorator of member.decorators ?? []) {
134
+ const expr = decorator.expression;
135
+ if (expr?.type !== "CallExpression") continue;
136
+ const method = HTTP_METHODS[expr.callee?.name];
137
+ if (!method) continue;
138
+ const arg = expr.arguments?.[0];
139
+ const fullRoute = normalizePath(basePath + (arg?.type === "Literal" && typeof arg.value === "string" ? arg.value : ""));
140
+ routes.push({
141
+ import: importStatement,
142
+ importClassName: className,
143
+ route: fullRoute,
144
+ method,
145
+ fullPath: "",
146
+ path: "",
147
+ params: extractParams(fullRoute)
148
+ });
149
+ }
150
+ }
151
+ }
152
+ return routes;
153
+ }
154
+ /**
155
+ * Unwraps export declarations to retrieve the underlying class node,
156
+ * along with metadata about the export style.
157
+ *
158
+ * Handles the following AST patterns:
159
+ * - `ExportDefaultDeclaration` wrapping a `ClassDeclaration` → `isDefault: true`
160
+ * - `ExportNamedDeclaration` wrapping a `ClassDeclaration` → `isDefault: false`
161
+ * - Direct `ClassDeclaration` or `Class` nodes → `isDefault: false`
162
+ *
163
+ * @param node - An AST node from the program body to inspect.
164
+ * @returns An object containing the class AST node and whether it is a default export, or `null` if not a class.
165
+ */
166
+ function getClassNode$2(node) {
167
+ if (node.type === "ExportDefaultDeclaration") {
168
+ const decl = node.declaration;
169
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
170
+ classNode: decl,
171
+ isDefault: true
172
+ };
173
+ }
174
+ if (node.type === "ExportNamedDeclaration") {
175
+ const decl = node.declaration;
176
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
177
+ classNode: decl,
178
+ isDefault: false
179
+ };
180
+ }
181
+ if (node?.type === "ClassDeclaration" || node?.type === "Class") return {
182
+ classNode: node,
183
+ isDefault: false
184
+ };
185
+ return null;
186
+ }
187
+ /**
188
+ * Searches a list of decorators for a `CallExpression` matching the given name
189
+ * and extracts its first string argument.
190
+ *
191
+ * This is used to retrieve the path argument from decorators like `@Controller('/api/foo')`.
192
+ * If the decorator is found but has no string argument, an empty string is returned.
193
+ * If no matching decorator is found, `null` is returned.
194
+ *
195
+ * @param decorators - The array of decorator AST nodes to search, or `undefined` if none exist.
196
+ * @param name - The decorator function name to match against (e.g. 'Controller').
197
+ * @returns The string argument value, an empty string if no argument is provided, or `null` if the decorator is not found.
198
+ */
199
+ function extractDecoratorArg(decorators, name) {
200
+ if (!decorators) return null;
201
+ for (const dec of decorators) {
202
+ const expr = dec.expression;
203
+ if (expr?.type === "CallExpression" && expr.callee?.name === name) {
204
+ const arg = expr.arguments?.[0];
205
+ if (arg?.type === "Literal" && typeof arg.value === "string") return arg.value;
206
+ return "";
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ /**
212
+ * Normalizes a route path by removing duplicate slashes and ensuring
213
+ * the path starts with a single leading slash.
214
+ *
215
+ * Splits the path on '/' separators, filters out empty segments,
216
+ * and rejoins with single '/' separators.
217
+ *
218
+ * @param path - The raw concatenated path string to normalize.
219
+ * @returns A cleaned path string with a leading slash and no duplicate separators.
220
+ */
221
+ function normalizePath(path) {
222
+ return "/" + path.split("/").filter(Boolean).join("/");
223
+ }
224
+ /**
225
+ * Extracts named route parameters from a path string.
226
+ *
227
+ * Scans the path for segments prefixed with ':' using a pre-compiled
228
+ * regular expression and returns an array of parameter names with
229
+ * the ':' prefix stripped.
230
+ *
231
+ * Resets the regex `lastIndex` after each invocation to ensure
232
+ * consistent behavior across repeated calls.
233
+ *
234
+ * @param route - The normalized route path to scan for parameters.
235
+ * @returns An array of parameter name strings, empty if none are found.
236
+ */
237
+ function extractParams(route) {
238
+ const params = [];
239
+ let match;
240
+ while (match = PARAM_RE.exec(route)) params.push(match[1]);
241
+ PARAM_RE.lastIndex = 0;
242
+ return params;
243
+ }
244
+ //#endregion
245
+ //#region src/setup/Routes.ts
246
+ async function setupRoutes(nitro) {
247
+ /**
248
+ * Scan files in the api and routes directories
249
+ * @param nitro - The nitro instance
250
+ * @returns void
251
+ */
252
+ const routes = await getTransformedRoutes(nitro);
253
+ nitro.options.handlers = clearRoutes(nitro.options.handlers);
254
+ nitro.options.handlers.push(...routes.map((route) => ({
255
+ route: route.route,
256
+ method: route.method,
257
+ handler: `@vercube/nitro/runtime/handler`,
258
+ lazy: true
259
+ })));
260
+ nitro.options.ignore = [...new Set([...nitro.options.ignore ?? [], ...routes.map((route) => route.fullPath.replace(nitro.options.rootDir, "").replace(String(nitro.options?.serverDir), "").replace("src/", ""))])];
261
+ nitro.routing.sync();
262
+ }
263
+ /**
264
+ * Gets the transformed routes from the routes directory
265
+ * @param nitro - The nitro instance
266
+ * @returns The transformed routes
267
+ */
268
+ async function getTransformedRoutes(nitro) {
269
+ const routes = await scanRoutes(nitro);
270
+ return (await Promise.all(routes.map(async (route) => await transformRoute(route))).then((r) => r.flat())).map((route) => ({
271
+ ...route,
272
+ import: route.import.replace(IMPORT_SOURCE, route.fullPath)
273
+ }));
274
+ }
275
+ /**
276
+ * Scans the routes directory and returns the file info of the routes
277
+ * @param nitro - The nitro instance
278
+ * @returns The file info of the routes
279
+ */
280
+ async function scanRoutes(nitro) {
281
+ return Promise.all([scanFiles(nitro, nitro.options.apiDir || "api"), scanFiles(nitro, nitro.options.routesDir || "routes")]).then((r) => r.flat());
282
+ }
283
+ /**
284
+ * Clears the routes from the handlers
285
+ * @param handlers - The handlers
286
+ * @returns void
287
+ */
288
+ function clearRoutes(handlers) {
289
+ return handlers.filter((handler) => handler.handler !== "@vercube/nitro/runtime/handler");
290
+ }
291
+ //#endregion
292
+ //#region src/setup/Hooks.ts
293
+ function setupHooks(nitro, options) {
294
+ /**
295
+ * Validates the bundler and typescript options for the nitro instance
296
+ * @param nitro - The nitro instance
297
+ * @returns void
298
+ */
299
+ nitro.hooks.hook("build:before", async () => {
300
+ validateBundler(nitro);
301
+ validateTypescript(nitro);
302
+ await setupRoutes(nitro);
303
+ });
304
+ nitro.hooks.hook("compiled", async () => {
305
+ await setupRoutes(nitro);
306
+ nitro.routing.sync();
307
+ });
308
+ /**
309
+ * In dev mode, watch controller files for content changes.
310
+ * Nitro's built-in watcher only reacts to add/unlink events (not `change`),
311
+ * and Rollup only watches files it directly imports. Controller files
312
+ * decorated with @Controller are not imported by Rollup, so their changes
313
+ * are invisible to Nitro. This watcher bridges that gap.
314
+ */
315
+ if (nitro.options.dev) {
316
+ const watcher = watch(nitro.options.scanDirs.flatMap((dir) => [
317
+ join(dir, nitro.options.apiDir || "api"),
318
+ join(dir, nitro.options.routesDir || "routes"),
319
+ join(dir, "middleware"),
320
+ ...options?.scanDirs ?? []
321
+ ]), { ignoreInitial: true }).on("change", async () => {
322
+ await nitro.hooks.callHook("compiled", nitro);
323
+ });
324
+ nitro.hooks.hook("close", () => watcher.close());
325
+ }
326
+ /**
327
+ * Destroys the Vercube container
328
+ * @param nitro - The nitro instance
329
+ * @returns void
330
+ */
331
+ nitro.hooks.hook("close", async () => {
332
+ const app = useVercubeApp();
333
+ if (!app) return;
334
+ destroyContainer(app.container);
335
+ });
336
+ }
337
+ //#endregion
338
+ //#region src/build/Middleware.ts
339
+ /**
340
+ * Internal module path used as a placeholder in generated import statements.
341
+ * Replaced with the actual file path during setup.
342
+ */
343
+ const MIDDLEWARE_IMPORT_SOURCE = "#internal/vercube-middleware-source";
344
+ /**
345
+ * Reads a file from disk and returns all classes extending `BaseMiddleware` found within it.
346
+ */
347
+ async function transformMiddleware(file) {
348
+ return extractMiddlewares(readFileSync(file.fullPath, "utf8")).map((m) => ({
349
+ ...m,
350
+ ...file
351
+ }));
352
+ }
353
+ /**
354
+ * Extracts all class definitions that extend `BaseMiddleware` from the given source code.
355
+ *
356
+ * Parses the source using `oxc-parser` and traverses the AST to find classes
357
+ * whose `superClass` resolves to the identifier `BaseMiddleware`. Generates
358
+ * appropriate import statements based on whether the class is a default or named export.
359
+ *
360
+ * @param code - The raw TypeScript or JavaScript source code string to analyze.
361
+ * @returns An array of {@link MiddlewareInfo} objects for all discovered middleware classes.
362
+ */
363
+ function extractMiddlewares(code) {
364
+ const ast = parseSync("file.ts", code).program;
365
+ const middlewares = [];
366
+ for (const node of ast.body) {
367
+ const classInfo = getClassNode$1(node);
368
+ if (!classInfo) continue;
369
+ const { classNode, isDefault } = classInfo;
370
+ if (!extendsBaseMiddleware(classNode)) continue;
371
+ const className = classNode.id?.name;
372
+ if (!className) continue;
373
+ const importStatement = isDefault ? `import ${className} from '${MIDDLEWARE_IMPORT_SOURCE}';` : `import { ${className} } from '${MIDDLEWARE_IMPORT_SOURCE}';`;
374
+ middlewares.push({
375
+ import: importStatement,
376
+ importClassName: className,
377
+ fullPath: "",
378
+ path: ""
379
+ });
380
+ }
381
+ return middlewares;
382
+ }
383
+ /**
384
+ * Returns true if the class node extends `BaseMiddleware`.
385
+ */
386
+ function extendsBaseMiddleware(classNode) {
387
+ return classNode.superClass?.type === "Identifier" && classNode.superClass.name === "BaseMiddleware";
388
+ }
389
+ /**
390
+ * Unwraps export declarations to retrieve the underlying class node,
391
+ * along with metadata about the export style.
392
+ */
393
+ function getClassNode$1(node) {
394
+ if (node.type === "ExportDefaultDeclaration") {
395
+ const decl = node.declaration;
396
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
397
+ classNode: decl,
398
+ isDefault: true
399
+ };
400
+ }
401
+ if (node.type === "ExportNamedDeclaration") {
402
+ const decl = node.declaration;
403
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
404
+ classNode: decl,
405
+ isDefault: false
406
+ };
407
+ }
408
+ if (node?.type === "ClassDeclaration" || node?.type === "Class") return {
409
+ classNode: node,
410
+ isDefault: false
411
+ };
412
+ return null;
413
+ }
414
+ //#endregion
415
+ //#region src/setup/Middleware.ts
416
+ /**
417
+ * Gets the transformed middleware from the middleware directory.
418
+ */
419
+ async function getTransformedMiddlewares(nitro) {
420
+ const files = await scanMiddlewares(nitro);
421
+ return (await Promise.all(files.map((file) => transformMiddleware(file))).then((r) => r.flat())).map((middleware) => ({
422
+ ...middleware,
423
+ import: middleware.import.replace(MIDDLEWARE_IMPORT_SOURCE, middleware.fullPath)
424
+ }));
425
+ }
426
+ /**
427
+ * Scans the middleware directory for classes extending `BaseMiddleware` and
428
+ * excludes those files from Nitro's native middleware handling.
429
+ */
430
+ async function scanMiddlewares(nitro) {
431
+ const files = await scanFiles(nitro, "middleware");
432
+ nitro.options.ignore = [...new Set([...nitro.options.ignore ?? [], ...files.map((file) => file.fullPath.replace(nitro.options.rootDir, "").replace(String(nitro.options?.serverDir), "").replace("src/", ""))])];
433
+ return files;
434
+ }
435
+ //#endregion
436
+ //#region src/build/Services.ts
437
+ /**
438
+ * Internal module path used as a placeholder in generated import statements.
439
+ * Replaced with the actual file path during setup.
440
+ */
441
+ const SERVICE_IMPORT_SOURCE = "#internal/vercube-service-source";
442
+ /**
443
+ * Reads a file from disk and returns all `@Injectable`-decorated classes found within it.
444
+ */
445
+ async function transformService(file) {
446
+ return extractServices(readFileSync(file.fullPath, "utf8")).map((s) => ({
447
+ ...s,
448
+ ...file
449
+ }));
450
+ }
451
+ /**
452
+ * Extracts all `@Injectable`-decorated class definitions from the given source code.
453
+ *
454
+ * Parses the source using `oxc-parser` and traverses the AST to find classes
455
+ * annotated with `@Injectable()`. Generates appropriate import statements based
456
+ * on whether the class is a default or named export.
457
+ *
458
+ * @param code - The raw TypeScript or JavaScript source code string to analyze.
459
+ * @returns An array of {@link ServiceInfo} objects for all discovered injectable classes.
460
+ */
461
+ function extractServices(code) {
462
+ const ast = parseSync("file.ts", code).program;
463
+ const services = [];
464
+ for (const node of ast.body) {
465
+ const classInfo = getClassNode(node);
466
+ if (!classInfo) continue;
467
+ const { classNode, isDefault } = classInfo;
468
+ if (!hasDecorator(classNode.decorators, "Injectable")) continue;
469
+ const className = classNode.id?.name;
470
+ if (!className) continue;
471
+ const importStatement = isDefault ? `import ${className} from '${SERVICE_IMPORT_SOURCE}';` : `import { ${className} } from '${SERVICE_IMPORT_SOURCE}';`;
472
+ services.push({
473
+ import: importStatement,
474
+ importClassName: className,
475
+ fullPath: "",
476
+ path: ""
477
+ });
478
+ }
479
+ return services;
480
+ }
481
+ /**
482
+ * Unwraps export declarations to retrieve the underlying class node,
483
+ * along with metadata about the export style.
484
+ */
485
+ function getClassNode(node) {
486
+ if (node.type === "ExportDefaultDeclaration") {
487
+ const decl = node.declaration;
488
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
489
+ classNode: decl,
490
+ isDefault: true
491
+ };
492
+ }
493
+ if (node.type === "ExportNamedDeclaration") {
494
+ const decl = node.declaration;
495
+ if (decl?.type === "ClassDeclaration" || decl?.type === "Class") return {
496
+ classNode: decl,
497
+ isDefault: false
498
+ };
499
+ }
500
+ if (node?.type === "ClassDeclaration" || node?.type === "Class") return {
501
+ classNode: node,
502
+ isDefault: false
503
+ };
504
+ return null;
505
+ }
506
+ /**
507
+ * Returns true if the given decorators array contains a decorator matching `name`.
508
+ * Supports both `@Name` identifier and `@Name()` call expression styles.
509
+ */
510
+ function hasDecorator(decorators, name) {
511
+ if (!decorators) return false;
512
+ for (const dec of decorators) {
513
+ const expr = dec.expression;
514
+ if (expr?.type === "CallExpression" && expr.callee?.name === name) return true;
515
+ if (expr?.type === "Identifier" && expr.name === name) return true;
516
+ }
517
+ return false;
518
+ }
519
+ //#endregion
520
+ //#region src/setup/Services.ts
521
+ /**
522
+ * Scans the project source directory for `@Injectable`-decorated classes and returns
523
+ * their transformed service info with resolved import paths.
524
+ */
525
+ async function getTransformedServices(nitro, scanDirs) {
526
+ const files = await scanServices(nitro, scanDirs);
527
+ return (await Promise.all(files.map((file) => transformService(file))).then((r) => r.flat())).map((service) => ({
528
+ ...service,
529
+ import: service.import.replace(SERVICE_IMPORT_SOURCE, service.fullPath)
530
+ }));
531
+ }
532
+ /**
533
+ * Scans the root of each scanDir for `@Injectable`-decorated service files.
534
+ * scanDirs already points to the server source root (e.g. `src/`), so we scan
535
+ * from `.` to cover all subdirectories without adding an extra path segment.
536
+ */
537
+ async function scanServices(nitro, scanDirs) {
538
+ return Promise.all(scanDirs?.map((dir) => scanFiles(nitro, dir)) ?? [scanFiles(nitro, ".")]).then((r) => r.flat());
539
+ }
540
+ //#endregion
541
+ //#region src/plugin/VercubePlugin.ts
542
+ const defaultOptions = { scanDirs: [
543
+ "api",
544
+ "routes",
545
+ "services",
546
+ "repositories"
547
+ ] };
548
+ function vercubeNitro(options) {
549
+ options = defu(options, defaultOptions);
550
+ return {
551
+ name: "@vercube/nitro",
552
+ setup: async (nitro) => {
553
+ setupHooks(nitro, options);
554
+ const routeMap = /* @__PURE__ */ new Map();
555
+ for (const route of await getTransformedRoutes(nitro)) routeMap.set(route.path, route);
556
+ const routes = [...routeMap.values()];
557
+ const serviceMap = /* @__PURE__ */ new Map();
558
+ for (const service of await getTransformedServices(nitro, options?.scanDirs)) serviceMap.set(`${service.fullPath}:${service.importClassName}`, service);
559
+ const services = [...serviceMap.values()];
560
+ await getTransformedMiddlewares(nitro);
561
+ const routeClassNames = new Set(routes.map((r) => r.importClassName));
562
+ const uniqueServices = services.filter((s) => !routeClassNames.has(s.importClassName));
563
+ const setupFilePath = options?.setupFile ? resolve(nitro.options.rootDir, options.setupFile) : null;
564
+ nitro.options.virtual["#internal/vercube-route-plugin"] = `
565
+ import { definePlugin } from 'nitro';
566
+ import { createNitroApp } from '@vercube/nitro';
567
+ ${setupFilePath ? `import __vercubeSetup__ from '${setupFilePath}';` : ""}
568
+
569
+ ${routes.map((route) => route.import).join("\n")}
570
+ ${uniqueServices.map((service) => service.import).join("\n")}
571
+
572
+ export default definePlugin(async (nitroApp) => {
573
+ const app = await createNitroApp(${JSON.stringify(nitro.options)});
574
+ nitroApp.__vercubeApp__ = app;
575
+ globalThis.__vercubeApp__ = app;
576
+
577
+ // bind routes to container
578
+ ${routes.map((route) => `app.container.bind(${route.importClassName});`).join("\n")}
579
+
580
+ // bind services to container
581
+ ${uniqueServices.map((service) => `app.container.bind(${service.importClassName});`).join("\n")}
582
+
583
+ ${setupFilePath ? `await __vercubeSetup__(app);` : ""}
584
+
585
+ app.container.flushQueue();
586
+
587
+ });
588
+ `;
589
+ nitro.options.plugins.push("#internal/vercube-route-plugin");
590
+ }
591
+ };
592
+ }
593
+ //#endregion
594
+ export { createNitroApp, useVercubeApp, vercubeNitro };
@@ -0,0 +1,12 @@
1
+ import { App } from "@vercube/core";
2
+ import { NitroOptions } from "nitro/types";
3
+
4
+ //#region src/runtime/App.d.ts
5
+ /**
6
+ * Create a Vercube application from a Nitro instance.
7
+ * @param nitro - The Nitro instance.
8
+ * @returns The Vercube application.
9
+ */
10
+ declare function createNitroApp(nitroOpts: NitroOptions): Promise<App>;
11
+ //#endregion
12
+ export { createNitroApp };
@@ -0,0 +1,38 @@
1
+ import { NitroStorageManager } from "./Storage.mjs";
2
+ import { createApp } from "@vercube/core";
3
+ import { initializeContainer } from "@vercube/di";
4
+ import { StorageManager } from "@vercube/storage";
5
+ //#region src/runtime/App.ts
6
+ /**
7
+ * Create a Vercube application from a Nitro instance.
8
+ * @param nitro - The Nitro instance.
9
+ * @returns The Vercube application.
10
+ */
11
+ async function createNitroApp(nitroOpts) {
12
+ const app = await createApp({ cfg: {
13
+ logLevel: getLogLevel(nitroOpts.logLevel),
14
+ production: !nitroOpts.dev,
15
+ dev: nitroOpts.dev,
16
+ runtime: { ...nitroOpts.runtimeConfig }
17
+ } });
18
+ app.container.bind(StorageManager, NitroStorageManager);
19
+ initializeContainer(app.container);
20
+ return app;
21
+ }
22
+ /**
23
+ * Get the Vercube log level from a Nitro log level.
24
+ * @param logLevel - The Nitro log level.
25
+ * @returns The Vercube log level.
26
+ */
27
+ function getLogLevel(logLevel) {
28
+ switch (logLevel) {
29
+ case 0: return "debug";
30
+ case 1: return "error";
31
+ case 2: return "warn";
32
+ case 3: return "info";
33
+ default:
34
+ case 4: return "debug";
35
+ }
36
+ }
37
+ //#endregion
38
+ export { createNitroApp };
@@ -0,0 +1,4 @@
1
+ //#region src/runtime/RouteHandler.d.ts
2
+ declare const _default: import("nitro/h3").EventHandlerWithFetch<import("nitro/h3").EventHandlerRequest, unknown>;
3
+ //#endregion
4
+ export { _default as default };
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler } from "nitro/h3";
2
+ //#region src/runtime/RouteHandler.ts
3
+ var RouteHandler_default = defineEventHandler({ fetch: async (event) => {
4
+ const app = globalThis.__vercubeApp__;
5
+ if (!app) throw new Error("Vercube app is not initialized");
6
+ return app.fetch(event);
7
+ } });
8
+ //#endregion
9
+ export { RouteHandler_default as default };
@@ -0,0 +1,119 @@
1
+ import { Storage, StorageManager, StorageTypes } from "@vercube/storage";
2
+
3
+ //#region src/runtime/Storage.d.ts
4
+ /**
5
+ * Manages multiple storage instances and provides a unified interface for storage operations.
6
+ * Each storage instance is identified by a unique name and implements the Storage interface.
7
+ * This class handles initialization, registration, and delegation of storage operations.
8
+ */
9
+ declare class NitroStorageManager extends StorageManager {
10
+ /**
11
+ * Mounts a new storage instance with the specified name
12
+ * @param {StorageTypes.Mount} params - Mount parameters containing name and storage implementation
13
+ * @param {string} [params.name] - Optional name for the storage instance, defaults to 'default'
14
+ * @param {IOC.Newable<Storage>} params.storage - Storage implementation to mount
15
+ * @returns {Promise<void>} A promise that resolves when mounting is complete
16
+ */
17
+ mount<T extends Storage<unknown>>(_opts: StorageTypes.Mount<T>): Promise<void>;
18
+ /**
19
+ * Retrieves a registered storage instance by name
20
+ * @param {string} name - Name of the storage instance to retrieve
21
+ * @returns {Storage | undefined} The storage instance if found, undefined otherwise
22
+ */
23
+ getStorage(name?: string): Storage | undefined;
24
+ /**
25
+ * Retrieves an item from the specified storage
26
+ * @template T - Type of the stored value
27
+ * @param {StorageTypes.GetItem} params - Parameters for retrieving an item
28
+ * @param {string} [params.storage] - Name of the storage to retrieve from, defaults to 'default'
29
+ * @param {string} params.key - Key of the item to retrieve
30
+ * @returns {Promise<T | null>} A promise that resolves with the stored value or null if not found
31
+ */
32
+ getItem<T = unknown>({
33
+ storage,
34
+ key
35
+ }: StorageTypes.GetItem): Promise<T | null>;
36
+ /**
37
+ * Retrieves multiple items from the specified storage
38
+ * @template T - Type of the stored value
39
+ * @param {StorageTypes.GetItems} params - Parameters for retrieving multiple items
40
+ * @param {string} [params.storage] - Name of the storage to retrieve from, defaults to 'default'
41
+ * @param {string[]} params.keys - Keys of the items to retrieve
42
+ * @returns {Promise<T[]>} A promise that resolves with the stored values or empty array if not found
43
+ */
44
+ getItems<T = unknown>({
45
+ storage,
46
+ keys
47
+ }: StorageTypes.GetItems): Promise<T[]>;
48
+ /**
49
+ * Stores an item in the specified storage
50
+ * @template T - Type of the value to store
51
+ * @param {StorageTypes.SetItem<T>} params - Parameters for storing an item
52
+ * @param {string} [params.storage] - Name of the storage to store in, defaults to 'default'
53
+ * @param {string} params.key - Key under which to store the value
54
+ * @param {T} params.value - Value to store
55
+ * @returns {Promise<void>} A promise that resolves when the value is stored
56
+ */
57
+ setItem<T = unknown, U = unknown>({
58
+ storage,
59
+ key,
60
+ value,
61
+ options
62
+ }: StorageTypes.SetItem<T, U>): Promise<void>;
63
+ /**
64
+ * Deletes an item from the specified storage
65
+ * @param {StorageTypes.DeleteItem} params - Parameters for deleting an item
66
+ * @param {string} [params.storage] - Name of the storage to delete from, defaults to 'default'
67
+ * @param {string} params.key - Key of the item to delete
68
+ * @returns {Promise<void>} A promise that resolves when the item is deleted
69
+ */
70
+ deleteItem({
71
+ storage,
72
+ key
73
+ }: StorageTypes.DeleteItem): Promise<void>;
74
+ /**
75
+ * Checks if an item exists in the specified storage
76
+ * @param {StorageTypes.HasItem} params - Parameters for checking item existence
77
+ * @param {string} [params.storage] - Name of the storage to check, defaults to 'default'
78
+ * @param {string} params.key - Key to check for
79
+ * @returns {Promise<boolean>} A promise that resolves to true if the item exists, false otherwise
80
+ */
81
+ hasItem({
82
+ storage,
83
+ key
84
+ }: StorageTypes.HasItem): Promise<boolean>;
85
+ /**
86
+ * Retrieves all keys from the specified storage
87
+ * @param {StorageTypes.GetKeys} params - Parameters for retrieving keys
88
+ * @param {string} [params.storage] - Name of the storage to get keys from, defaults to 'default'
89
+ * @returns {Promise<string[]>} A promise that resolves with an array of all keys
90
+ */
91
+ getKeys({
92
+ storage
93
+ }: StorageTypes.GetKeys): Promise<string[]>;
94
+ /**
95
+ * Clears all items from the specified storage
96
+ * @param {StorageTypes.Clear} params - Parameters for clearing storage
97
+ * @param {string} [params.storage] - Name of the storage to clear, defaults to 'default'
98
+ * @returns {Promise<void>} A promise that resolves when the storage is cleared
99
+ */
100
+ clear({
101
+ storage
102
+ }: StorageTypes.Clear): Promise<void>;
103
+ /**
104
+ * Gets the number of items in the specified storage
105
+ * @param {StorageTypes.Size} params - Parameters for getting storage size
106
+ * @param {string} [params.storage] - Name of the storage to get size of, defaults to 'default'
107
+ * @returns {Promise<number>} A promise that resolves with the number of items
108
+ */
109
+ size({
110
+ storage
111
+ }: StorageTypes.Size): Promise<number>;
112
+ /**
113
+ * Initializes the storage manager
114
+ * @returns {Promise<void>} A promise that resolves when the storage manager is initialized
115
+ */
116
+ protected init(): Promise<void>;
117
+ }
118
+ //#endregion
119
+ export { NitroStorageManager };
@@ -0,0 +1,129 @@
1
+ import { StorageManager } from "@vercube/storage";
2
+ import { useStorage } from "nitro/storage";
3
+ //#region src/runtime/Storage.ts
4
+ /**
5
+ * Manages multiple storage instances and provides a unified interface for storage operations.
6
+ * Each storage instance is identified by a unique name and implements the Storage interface.
7
+ * This class handles initialization, registration, and delegation of storage operations.
8
+ */
9
+ var NitroStorageManager = class extends StorageManager {
10
+ /**
11
+ * Mounts a new storage instance with the specified name
12
+ * @param {StorageTypes.Mount} params - Mount parameters containing name and storage implementation
13
+ * @param {string} [params.name] - Optional name for the storage instance, defaults to 'default'
14
+ * @param {IOC.Newable<Storage>} params.storage - Storage implementation to mount
15
+ * @returns {Promise<void>} A promise that resolves when mounting is complete
16
+ */
17
+ async mount(_opts) {
18
+ this.gLogger?.warn("NitroStorageManager::mount", "mounting storage is not supported for Nitro. Use options.storage in nitro.config.ts instead");
19
+ }
20
+ /**
21
+ * Retrieves a registered storage instance by name
22
+ * @param {string} name - Name of the storage instance to retrieve
23
+ * @returns {Storage | undefined} The storage instance if found, undefined otherwise
24
+ */
25
+ getStorage(name = "") {
26
+ const storage = useStorage(name) || useStorage();
27
+ return {
28
+ getItem: storage.getItem.bind(storage),
29
+ getItems: async (keys) => (await storage.getItems(keys)).map((item) => item.value),
30
+ setItem: storage.setItem.bind(storage),
31
+ hasItem: storage.hasItem.bind(storage),
32
+ getKeys: storage.getKeys.bind(storage),
33
+ clear: storage.clear.bind(storage),
34
+ deleteItem: storage.removeItem.bind(storage),
35
+ size: () => {
36
+ this.gLogger?.warn("NitroStorageManager::size", "getting size is not supported for Nitro Storage.");
37
+ return 0;
38
+ }
39
+ };
40
+ }
41
+ /**
42
+ * Retrieves an item from the specified storage
43
+ * @template T - Type of the stored value
44
+ * @param {StorageTypes.GetItem} params - Parameters for retrieving an item
45
+ * @param {string} [params.storage] - Name of the storage to retrieve from, defaults to 'default'
46
+ * @param {string} params.key - Key of the item to retrieve
47
+ * @returns {Promise<T | null>} A promise that resolves with the stored value or null if not found
48
+ */
49
+ async getItem({ storage, key }) {
50
+ return this.getStorage(storage)?.getItem(key) ?? null;
51
+ }
52
+ /**
53
+ * Retrieves multiple items from the specified storage
54
+ * @template T - Type of the stored value
55
+ * @param {StorageTypes.GetItems} params - Parameters for retrieving multiple items
56
+ * @param {string} [params.storage] - Name of the storage to retrieve from, defaults to 'default'
57
+ * @param {string[]} params.keys - Keys of the items to retrieve
58
+ * @returns {Promise<T[]>} A promise that resolves with the stored values or empty array if not found
59
+ */
60
+ async getItems({ storage, keys }) {
61
+ return this.getStorage(storage)?.getItems(keys) ?? [];
62
+ }
63
+ /**
64
+ * Stores an item in the specified storage
65
+ * @template T - Type of the value to store
66
+ * @param {StorageTypes.SetItem<T>} params - Parameters for storing an item
67
+ * @param {string} [params.storage] - Name of the storage to store in, defaults to 'default'
68
+ * @param {string} params.key - Key under which to store the value
69
+ * @param {T} params.value - Value to store
70
+ * @returns {Promise<void>} A promise that resolves when the value is stored
71
+ */
72
+ async setItem({ storage, key, value, options }) {
73
+ this.getStorage(storage)?.setItem(key, value, options);
74
+ }
75
+ /**
76
+ * Deletes an item from the specified storage
77
+ * @param {StorageTypes.DeleteItem} params - Parameters for deleting an item
78
+ * @param {string} [params.storage] - Name of the storage to delete from, defaults to 'default'
79
+ * @param {string} params.key - Key of the item to delete
80
+ * @returns {Promise<void>} A promise that resolves when the item is deleted
81
+ */
82
+ async deleteItem({ storage, key }) {
83
+ this.getStorage(storage)?.deleteItem(key);
84
+ }
85
+ /**
86
+ * Checks if an item exists in the specified storage
87
+ * @param {StorageTypes.HasItem} params - Parameters for checking item existence
88
+ * @param {string} [params.storage] - Name of the storage to check, defaults to 'default'
89
+ * @param {string} params.key - Key to check for
90
+ * @returns {Promise<boolean>} A promise that resolves to true if the item exists, false otherwise
91
+ */
92
+ async hasItem({ storage, key }) {
93
+ return this.getStorage(storage)?.hasItem(key) ?? false;
94
+ }
95
+ /**
96
+ * Retrieves all keys from the specified storage
97
+ * @param {StorageTypes.GetKeys} params - Parameters for retrieving keys
98
+ * @param {string} [params.storage] - Name of the storage to get keys from, defaults to 'default'
99
+ * @returns {Promise<string[]>} A promise that resolves with an array of all keys
100
+ */
101
+ async getKeys({ storage }) {
102
+ return this.getStorage(storage)?.getKeys() ?? [];
103
+ }
104
+ /**
105
+ * Clears all items from the specified storage
106
+ * @param {StorageTypes.Clear} params - Parameters for clearing storage
107
+ * @param {string} [params.storage] - Name of the storage to clear, defaults to 'default'
108
+ * @returns {Promise<void>} A promise that resolves when the storage is cleared
109
+ */
110
+ async clear({ storage }) {
111
+ this.getStorage(storage)?.clear();
112
+ }
113
+ /**
114
+ * Gets the number of items in the specified storage
115
+ * @param {StorageTypes.Size} params - Parameters for getting storage size
116
+ * @param {string} [params.storage] - Name of the storage to get size of, defaults to 'default'
117
+ * @returns {Promise<number>} A promise that resolves with the number of items
118
+ */
119
+ async size({ storage }) {
120
+ return this.getStorage(storage)?.size() ?? 0;
121
+ }
122
+ /**
123
+ * Initializes the storage manager
124
+ * @returns {Promise<void>} A promise that resolves when the storage manager is initialized
125
+ */
126
+ async init() {}
127
+ };
128
+ //#endregion
129
+ export { NitroStorageManager };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@vercube/nitro",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Nitro module for Vercube framework",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/vercube/vercube.git",
8
+ "directory": "packages/nitro"
9
+ },
10
+ "license": "MIT",
11
+ "sideEffects": false,
12
+ "type": "module",
13
+ "main": "./dist/index.mjs",
14
+ "module": "./dist/index.mjs",
15
+ "exports": {
16
+ ".": "./dist/index.mjs",
17
+ "./package.json": "./package.json",
18
+ "./runtime/handler": "./dist/runtime/RouteHandler.mjs"
19
+ },
20
+ "types": "./dist/index.d.mts",
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "keywords": [
26
+ "vercube",
27
+ "nitro",
28
+ "framework"
29
+ ],
30
+ "dependencies": {
31
+ "chokidar": "5.0.0",
32
+ "defu": "6.1.7",
33
+ "nitro": "3.0.260610-beta",
34
+ "oxc-parser": "0.135.0",
35
+ "pathe": "2.0.3",
36
+ "tinyglobby": "0.2.17",
37
+ "@vercube/core": "1.0.0-beta.1",
38
+ "@vercube/di": "1.0.0-beta.1",
39
+ "@vercube/storage": "1.0.0-beta.1"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsdown --config ./tsdown.config.ts --config-loader=unrun"
46
+ }
47
+ }