@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 +21 -0
- package/README.md +115 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.mjs +594 -0
- package/dist/runtime/App.d.mts +12 -0
- package/dist/runtime/App.mjs +38 -0
- package/dist/runtime/RouteHandler.d.mts +4 -0
- package/dist/runtime/RouteHandler.mjs +9 -0
- package/dist/runtime/Storage.d.mts +119 -0
- package/dist/runtime/Storage.mjs +129 -0
- package/package.json +47 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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,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
|
+
}
|