@di-framework/di-framework-http 0.0.0-prerelease.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +512 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +144 -0
- package/dist/src/decorators.d.ts +10 -0
- package/dist/src/openapi.d.ts +7 -0
- package/dist/src/registry.d.ts +21 -0
- package/dist/src/typed-router.d.ts +53 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# @di-framework/di-framework-http
|
|
2
|
+
|
|
3
|
+
Lightweight TypeScript decorators and a type-safe router for [itty-router](https://github.com/kwhitley/itty-router). Includes a build-time OpenAPI 3.1 generator. Ported from `itty-decorators`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-Safe Routing**: `TypedRouter` provides full TypeScript support for request bodies, response types, and context.
|
|
8
|
+
- **Auto JSON Enforcement**: Automatically validates `Content-Type: application/json` for mutation methods (POST, PUT, PATCH).
|
|
9
|
+
- **Multipart Support**: Opt into `multipart/form-data` handling with `Multipart<T>` and `{ multipart: true }`.
|
|
10
|
+
- **Declarative Metadata**: Use `@Controller` and `@Endpoint` decorators to document your API logic directly in code.
|
|
11
|
+
- **DI Integration**: `@Controller` composes the core DI `@Container` decorator, so controllers are auto-registered and can use `@Component` injection and `useContainer().resolve(...)`.
|
|
12
|
+
- **OpenAPI 3.1 Support**: Generate a complete OpenAPI specification from your code at build time.
|
|
13
|
+
- **Minimal Footprint**: Built on top of the ultra-light `itty-router`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install the HTTP package and the core DI framework (peer dependency)
|
|
19
|
+
bun add @di-framework/di-framework-http @di-framework/di-framework
|
|
20
|
+
# or
|
|
21
|
+
npm install @di-framework/di-framework-http @di-framework/di-framework
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Create a Controller (DI-aware)
|
|
27
|
+
|
|
28
|
+
Annotate your API logic using decorators and the `TypedRouter`. Controllers are automatically registered with the DI container, so you can inject services and resolve the controller instance.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import {
|
|
32
|
+
TypedRouter,
|
|
33
|
+
json,
|
|
34
|
+
type RequestSpec,
|
|
35
|
+
type ResponseSpec,
|
|
36
|
+
type Json,
|
|
37
|
+
Controller,
|
|
38
|
+
Endpoint,
|
|
39
|
+
} from "@di-framework/di-framework-http";
|
|
40
|
+
import { Component, Container } from "@di-framework/di-framework/decorators";
|
|
41
|
+
import { useContainer } from "@di-framework/di-framework/container";
|
|
42
|
+
|
|
43
|
+
const router = TypedRouter();
|
|
44
|
+
|
|
45
|
+
type EchoPayload = { message: string };
|
|
46
|
+
type EchoResponse = { echoed: string; timestamp: string };
|
|
47
|
+
|
|
48
|
+
// Example DI-managed service
|
|
49
|
+
@Container()
|
|
50
|
+
export class LoggerService {
|
|
51
|
+
log(msg: string) {
|
|
52
|
+
console.log(msg);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Controller()
|
|
57
|
+
export class EchoController {
|
|
58
|
+
// Because @Controller composes the core @Container decorator, this class is
|
|
59
|
+
// automatically registered with the DI container. We can inject services.
|
|
60
|
+
@Component(LoggerService)
|
|
61
|
+
private logger!: LoggerService;
|
|
62
|
+
|
|
63
|
+
echoMessage(message: string): EchoResponse {
|
|
64
|
+
this.logger.log(`Echoing: ${message}`);
|
|
65
|
+
return { echoed: message, timestamp: new Date().toISOString() };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Endpoint({
|
|
69
|
+
summary: "Echo a message",
|
|
70
|
+
description: "Returns the provided message with a server timestamp.",
|
|
71
|
+
responses: {
|
|
72
|
+
"200": { description: "Successful echo" },
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
static post = router.post<
|
|
76
|
+
RequestSpec<Json<EchoPayload>>,
|
|
77
|
+
ResponseSpec<EchoResponse>
|
|
78
|
+
>("/echo", (req) => {
|
|
79
|
+
// Demonstrate auto DI registration: resolve the controller instance from
|
|
80
|
+
// the global container without any manual registration.
|
|
81
|
+
const controller = useContainer().resolve(EchoController);
|
|
82
|
+
return json(controller.echoMessage(req.content.message));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Add a simple GET route
|
|
87
|
+
router.get("/", () => json({ message: "API is healthy" }));
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
fetch: (request: Request, env: any, ctx: any) =>
|
|
91
|
+
router.fetch(request, env, ctx),
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. Multipart File Uploads
|
|
96
|
+
|
|
97
|
+
Use `Multipart<T>` and `{ multipart: true }` to accept `multipart/form-data` instead of JSON. The handler receives `req.content` typed as `FormData`.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import {
|
|
101
|
+
TypedRouter,
|
|
102
|
+
json,
|
|
103
|
+
type RequestSpec,
|
|
104
|
+
type ResponseSpec,
|
|
105
|
+
type Multipart,
|
|
106
|
+
} from "@di-framework/di-framework-http";
|
|
107
|
+
|
|
108
|
+
const router = TypedRouter();
|
|
109
|
+
|
|
110
|
+
type UploadPayload = { files: File[] };
|
|
111
|
+
type UploadResult = { filenames: string[] };
|
|
112
|
+
|
|
113
|
+
router.post<RequestSpec<Multipart<UploadPayload>>, ResponseSpec<UploadResult>>(
|
|
114
|
+
"/upload",
|
|
115
|
+
(req) => {
|
|
116
|
+
// req.content is typed as FormData
|
|
117
|
+
const files = req.content.getAll("files") as File[];
|
|
118
|
+
return json({ filenames: files.map((f) => f.name) });
|
|
119
|
+
},
|
|
120
|
+
{ multipart: true },
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### OpenAPI Generation
|
|
125
|
+
|
|
126
|
+
`@di-framework/di-framework-http` provides a built-in CLI and a registry to generate OpenAPI specs from your controllers.
|
|
127
|
+
|
|
128
|
+
#### Using the CLI
|
|
129
|
+
|
|
130
|
+
The easiest way to generate a spec is using the provided CLI tool.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Generate openapi.json from your controllers
|
|
134
|
+
bun x di-framework-http generate --controllers ./src/index.ts
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Options:**
|
|
138
|
+
|
|
139
|
+
- `--controllers <path>`: (Required) Path to the file that imports all your decorated controllers.
|
|
140
|
+
- `--output <path>`: (Optional) Path to save the generated JSON (default: `openapi.json`).
|
|
141
|
+
|
|
142
|
+
#### Manual Generation
|
|
143
|
+
|
|
144
|
+
You can also generate the spec programmatically using the `generateOpenAPI` function and the default `registry`:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import registry, { generateOpenAPI } from "@di-framework/di-framework-http";
|
|
148
|
+
import "./controllers/MyController"; // Import to trigger registration
|
|
149
|
+
|
|
150
|
+
const spec = generateOpenAPI(
|
|
151
|
+
{
|
|
152
|
+
title: "My API",
|
|
153
|
+
version: "1.0.0",
|
|
154
|
+
},
|
|
155
|
+
registry,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
console.log(JSON.stringify(spec, null, 2));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
If you need full control, you can iterate the `registry` manually:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import registry from "@di-framework/di-framework-http";
|
|
165
|
+
|
|
166
|
+
for (const target of registry.getTargets()) {
|
|
167
|
+
// target is the decorated class
|
|
168
|
+
// target[methodName].isEndpoint will be true
|
|
169
|
+
// target[methodName].metadata contains your @Endpoint info
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
### `TypedRouter<Args[]>()`
|
|
176
|
+
|
|
177
|
+
A proxy for `itty-router` that enables type-safe method definitions.
|
|
178
|
+
|
|
179
|
+
- `Args`: An array of types representing additional arguments passed to `fetch` (e.g., `[Env, ExecutionContext]`).
|
|
180
|
+
|
|
181
|
+
### `json<T>(data: T, init?: ResponseInit)`
|
|
182
|
+
|
|
183
|
+
A typed wrapper around itty-router's `json` helper.
|
|
184
|
+
|
|
185
|
+
### `Json<T>` / `Multipart<T>`
|
|
186
|
+
|
|
187
|
+
Body spec markers used with `RequestSpec<>` to declare the expected content type. `Json<T>` types `req.content` as `T`; `Multipart<T>` types it as `FormData`. Multipart routes require passing `{ multipart: true }` as the third argument to the route method.
|
|
188
|
+
|
|
189
|
+
### `@Controller(options?)`
|
|
190
|
+
|
|
191
|
+
Composed decorator that:
|
|
192
|
+
|
|
193
|
+
- Marks a class for inclusion in the OpenAPI registry; and
|
|
194
|
+
- Registers the class with the core DI container (same instance as `@di-framework/di-framework`).
|
|
195
|
+
|
|
196
|
+
**Options:** `{ singleton?: boolean; container?: DIContainer }`
|
|
197
|
+
|
|
198
|
+
### `@Endpoint(metadata)`
|
|
199
|
+
|
|
200
|
+
Method or property decorator that attaches OpenAPI metadata.
|
|
201
|
+
|
|
202
|
+
- `summary`: Short summary of the operation.
|
|
203
|
+
- `description`: Verbose explanation.
|
|
204
|
+
- `requestBody`: OpenAPI Request Body object.
|
|
205
|
+
- `responses`: OpenAPI Responses object.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __export = (target, all) => {
|
|
19
|
+
for (var name in all)
|
|
20
|
+
__defProp(target, name, {
|
|
21
|
+
get: all[name],
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
set: (newValue) => all[name] = () => newValue
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
28
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
29
|
+
|
|
30
|
+
// src/registry.ts
|
|
31
|
+
var exports_registry = {};
|
|
32
|
+
__export(exports_registry, {
|
|
33
|
+
default: () => registry_default,
|
|
34
|
+
Registry: () => Registry
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
class Registry {
|
|
38
|
+
targets = new Set;
|
|
39
|
+
addTarget(target) {
|
|
40
|
+
this.targets.add(target);
|
|
41
|
+
}
|
|
42
|
+
getTargets() {
|
|
43
|
+
return this.targets;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var registry, registry_default;
|
|
47
|
+
var init_registry = __esm(() => {
|
|
48
|
+
registry = new Registry;
|
|
49
|
+
registry_default = registry;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// src/typed-router.ts
|
|
53
|
+
import {
|
|
54
|
+
Router,
|
|
55
|
+
withContent,
|
|
56
|
+
json as ittyJson
|
|
57
|
+
} from "itty-router";
|
|
58
|
+
function json(data, init) {
|
|
59
|
+
return ittyJson(data, init);
|
|
60
|
+
}
|
|
61
|
+
function TypedRouter(opts) {
|
|
62
|
+
const r = Router(opts);
|
|
63
|
+
function enforceJson(req) {
|
|
64
|
+
const ct = (req.headers.get("content-type") ?? "").toLowerCase();
|
|
65
|
+
if (!ct.includes("application/json") && !ct.includes("+json")) {
|
|
66
|
+
return ittyJson({ error: "Content-Type must be application/json" }, { status: 415 });
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
async function withFormData(req) {
|
|
71
|
+
try {
|
|
72
|
+
req.content = await req.formData();
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
const methodsToProxy = [
|
|
76
|
+
"get",
|
|
77
|
+
"post",
|
|
78
|
+
"put",
|
|
79
|
+
"delete",
|
|
80
|
+
"patch",
|
|
81
|
+
"head",
|
|
82
|
+
"options"
|
|
83
|
+
];
|
|
84
|
+
const wrapper = new Proxy(r, {
|
|
85
|
+
get(target, prop, receiver) {
|
|
86
|
+
if (typeof prop === "string" && methodsToProxy.includes(prop)) {
|
|
87
|
+
return (path, controller, options) => {
|
|
88
|
+
const handler = (...args) => {
|
|
89
|
+
const req = args[0];
|
|
90
|
+
const extraArgs = args.slice(1);
|
|
91
|
+
if (prop === "post" || prop === "put" || prop === "patch") {
|
|
92
|
+
if (!options?.multipart) {
|
|
93
|
+
const ctErr = enforceJson(req);
|
|
94
|
+
if (ctErr)
|
|
95
|
+
return ctErr;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return controller(req, ...extraArgs);
|
|
99
|
+
};
|
|
100
|
+
const middleware = options?.multipart ? withFormData : withContent;
|
|
101
|
+
target[prop](path, middleware, handler);
|
|
102
|
+
const routeInfo = {
|
|
103
|
+
path,
|
|
104
|
+
method: prop,
|
|
105
|
+
handler
|
|
106
|
+
};
|
|
107
|
+
Object.assign(handler, routeInfo);
|
|
108
|
+
return handler;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const value = Reflect.get(target, prop, receiver);
|
|
112
|
+
if (typeof value === "function") {
|
|
113
|
+
return (...args) => {
|
|
114
|
+
const result = value.apply(target, args);
|
|
115
|
+
return result === target ? wrapper : result;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return wrapper;
|
|
122
|
+
}
|
|
123
|
+
// src/decorators.ts
|
|
124
|
+
init_registry();
|
|
125
|
+
|
|
126
|
+
// ../di-framework/dist/container.js
|
|
127
|
+
var INJECT_METADATA_KEY = "di:inject";
|
|
128
|
+
var DESIGN_PARAM_TYPES_KEY = "design:paramtypes";
|
|
129
|
+
var TELEMETRY_METADATA_KEY = "di:telemetry";
|
|
130
|
+
var TELEMETRY_LISTENER_METADATA_KEY = "di:telemetry-listener";
|
|
131
|
+
var metadataStore = new Map;
|
|
132
|
+
function defineMetadata(key, value, target) {
|
|
133
|
+
if (!metadataStore.has(target)) {
|
|
134
|
+
metadataStore.set(target, new Map);
|
|
135
|
+
}
|
|
136
|
+
metadataStore.get(target).set(key, value);
|
|
137
|
+
}
|
|
138
|
+
function getMetadata(key, target) {
|
|
139
|
+
return metadataStore.get(target)?.get(key);
|
|
140
|
+
}
|
|
141
|
+
function getOwnMetadata(key, target) {
|
|
142
|
+
return getMetadata(key, target);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class Container {
|
|
146
|
+
services = new Map;
|
|
147
|
+
resolutionStack = new Set;
|
|
148
|
+
listeners = new Map;
|
|
149
|
+
register(serviceClass, options = { singleton: true }) {
|
|
150
|
+
const name = serviceClass.name;
|
|
151
|
+
this.services.set(name, {
|
|
152
|
+
type: serviceClass,
|
|
153
|
+
singleton: options.singleton ?? true
|
|
154
|
+
});
|
|
155
|
+
this.services.set(serviceClass, {
|
|
156
|
+
type: serviceClass,
|
|
157
|
+
singleton: options.singleton ?? true
|
|
158
|
+
});
|
|
159
|
+
this.emit("registered", {
|
|
160
|
+
key: serviceClass,
|
|
161
|
+
singleton: options.singleton ?? true,
|
|
162
|
+
kind: "class"
|
|
163
|
+
});
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
registerFactory(name, factory, options = { singleton: true }) {
|
|
167
|
+
this.services.set(name, {
|
|
168
|
+
type: factory,
|
|
169
|
+
singleton: options.singleton ?? true
|
|
170
|
+
});
|
|
171
|
+
this.emit("registered", {
|
|
172
|
+
key: name,
|
|
173
|
+
singleton: options.singleton ?? true,
|
|
174
|
+
kind: "factory"
|
|
175
|
+
});
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
resolve(serviceClass) {
|
|
179
|
+
const key = typeof serviceClass === "string" ? serviceClass : serviceClass;
|
|
180
|
+
const keyStr = typeof serviceClass === "string" ? serviceClass : serviceClass.name;
|
|
181
|
+
if (this.resolutionStack.has(key)) {
|
|
182
|
+
throw new Error(`Circular dependency detected while resolving ${keyStr}. Stack: ${Array.from(this.resolutionStack).join(" -> ")} -> ${keyStr}`);
|
|
183
|
+
}
|
|
184
|
+
const definition = this.services.get(key);
|
|
185
|
+
if (!definition) {
|
|
186
|
+
throw new Error(`Service '${keyStr}' is not registered in the DI container`);
|
|
187
|
+
}
|
|
188
|
+
const wasCached = definition.singleton && !!definition.instance;
|
|
189
|
+
if (definition.singleton && definition.instance) {
|
|
190
|
+
this.emit("resolved", {
|
|
191
|
+
key,
|
|
192
|
+
instance: definition.instance,
|
|
193
|
+
singleton: true,
|
|
194
|
+
fromCache: true
|
|
195
|
+
});
|
|
196
|
+
return definition.instance;
|
|
197
|
+
}
|
|
198
|
+
this.resolutionStack.add(key);
|
|
199
|
+
try {
|
|
200
|
+
const instance = this.instantiate(definition.type);
|
|
201
|
+
if (definition.singleton) {
|
|
202
|
+
definition.instance = instance;
|
|
203
|
+
}
|
|
204
|
+
this.emit("resolved", {
|
|
205
|
+
key,
|
|
206
|
+
instance,
|
|
207
|
+
singleton: definition.singleton,
|
|
208
|
+
fromCache: wasCached
|
|
209
|
+
});
|
|
210
|
+
return instance;
|
|
211
|
+
} finally {
|
|
212
|
+
this.resolutionStack.delete(key);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
construct(serviceClass, overrides = {}) {
|
|
216
|
+
const keyStr = serviceClass.name;
|
|
217
|
+
if (this.resolutionStack.has(serviceClass)) {
|
|
218
|
+
throw new Error(`Circular dependency detected while constructing ${keyStr}. Stack: ${Array.from(this.resolutionStack).join(" -> ")} -> ${keyStr}`);
|
|
219
|
+
}
|
|
220
|
+
this.resolutionStack.add(serviceClass);
|
|
221
|
+
try {
|
|
222
|
+
const instance = this.instantiate(serviceClass, overrides);
|
|
223
|
+
this.emit("constructed", { key: serviceClass, instance, overrides });
|
|
224
|
+
return instance;
|
|
225
|
+
} finally {
|
|
226
|
+
this.resolutionStack.delete(serviceClass);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
has(serviceClass) {
|
|
230
|
+
return this.services.has(serviceClass);
|
|
231
|
+
}
|
|
232
|
+
clear() {
|
|
233
|
+
const count = this.services.size;
|
|
234
|
+
this.services.clear();
|
|
235
|
+
this.emit("cleared", { count });
|
|
236
|
+
}
|
|
237
|
+
getServiceNames() {
|
|
238
|
+
const names = new Set;
|
|
239
|
+
this.services.forEach((_, key) => {
|
|
240
|
+
if (typeof key === "string") {
|
|
241
|
+
names.add(key);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
return Array.from(names);
|
|
245
|
+
}
|
|
246
|
+
fork(options = {}) {
|
|
247
|
+
const clone = new Container;
|
|
248
|
+
this.services.forEach((def, key) => {
|
|
249
|
+
clone.services.set(key, {
|
|
250
|
+
...def,
|
|
251
|
+
instance: options.carrySingletons ? def.instance : undefined
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
return clone;
|
|
255
|
+
}
|
|
256
|
+
on(event, listener) {
|
|
257
|
+
if (!this.listeners.has(event)) {
|
|
258
|
+
this.listeners.set(event, new Set);
|
|
259
|
+
}
|
|
260
|
+
this.listeners.get(event).add(listener);
|
|
261
|
+
return () => this.off(event, listener);
|
|
262
|
+
}
|
|
263
|
+
off(event, listener) {
|
|
264
|
+
this.listeners.get(event)?.delete(listener);
|
|
265
|
+
}
|
|
266
|
+
emit(event, payload) {
|
|
267
|
+
const listeners = this.listeners.get(event);
|
|
268
|
+
if (!listeners || listeners.size === 0)
|
|
269
|
+
return;
|
|
270
|
+
listeners.forEach((listener) => {
|
|
271
|
+
try {
|
|
272
|
+
listener(payload);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.error(`[Container] listener for '${event}' threw`, err);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
instantiate(type, overrides = {}) {
|
|
279
|
+
if (typeof type !== "function") {
|
|
280
|
+
throw new Error("Service type must be a constructor or factory function");
|
|
281
|
+
}
|
|
282
|
+
if (!this.isClass(type)) {
|
|
283
|
+
return type();
|
|
284
|
+
}
|
|
285
|
+
const paramTypes = getMetadata(DESIGN_PARAM_TYPES_KEY, type) || [];
|
|
286
|
+
const paramNames = this.getConstructorParamNames(type);
|
|
287
|
+
const dependencies = [];
|
|
288
|
+
const injectMetadata = getOwnMetadata(INJECT_METADATA_KEY, type) || {};
|
|
289
|
+
const paramCount = Math.max(paramTypes.length, paramNames.length);
|
|
290
|
+
for (let i = 0;i < paramCount; i++) {
|
|
291
|
+
if (Object.prototype.hasOwnProperty.call(overrides, i)) {
|
|
292
|
+
dependencies.push(overrides[i]);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const paramType = paramTypes[i];
|
|
296
|
+
const paramName = paramNames[i];
|
|
297
|
+
const paramInjectTarget = injectMetadata[`param_${i}`];
|
|
298
|
+
if (paramInjectTarget) {
|
|
299
|
+
dependencies.push(this.resolve(paramInjectTarget));
|
|
300
|
+
} else if (paramType && paramType !== Object) {
|
|
301
|
+
if (this.has(paramType)) {
|
|
302
|
+
dependencies.push(this.resolve(paramType));
|
|
303
|
+
} else if (this.has(paramType.name)) {
|
|
304
|
+
dependencies.push(this.resolve(paramType.name));
|
|
305
|
+
} else {
|
|
306
|
+
throw new Error(`Cannot resolve dependency of type ${paramType.name} for parameter '${paramName}' in ${type.name}`);
|
|
307
|
+
}
|
|
308
|
+
} else {}
|
|
309
|
+
}
|
|
310
|
+
const instance = new type(...dependencies);
|
|
311
|
+
this.applyTelemetry(instance, type);
|
|
312
|
+
const injectProperties = getMetadata(INJECT_METADATA_KEY, type) || {};
|
|
313
|
+
const protoInjectProperties = getMetadata(INJECT_METADATA_KEY, type.prototype) || {};
|
|
314
|
+
const allInjectProperties = {
|
|
315
|
+
...injectProperties,
|
|
316
|
+
...protoInjectProperties
|
|
317
|
+
};
|
|
318
|
+
Object.entries(allInjectProperties).forEach(([propName, targetType]) => {
|
|
319
|
+
if (!propName.startsWith("param_") && targetType) {
|
|
320
|
+
try {
|
|
321
|
+
instance[propName] = this.resolve(targetType);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.warn(`Failed to inject property '${propName}' on ${type.name}:`, error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return instance;
|
|
328
|
+
}
|
|
329
|
+
applyTelemetry(instance, constructor) {
|
|
330
|
+
const className = constructor.name;
|
|
331
|
+
const listenerMethods = getMetadata(TELEMETRY_LISTENER_METADATA_KEY, constructor.prototype) || [];
|
|
332
|
+
listenerMethods.forEach((methodName) => {
|
|
333
|
+
const method = instance[methodName];
|
|
334
|
+
if (typeof method === "function") {
|
|
335
|
+
this.on("telemetry", (payload) => {
|
|
336
|
+
try {
|
|
337
|
+
method.call(instance, payload);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error(`[Container] TelemetryListener '${className}.${methodName}' threw`, err);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
const telemetryMethods = getMetadata(TELEMETRY_METADATA_KEY, constructor.prototype) || {};
|
|
345
|
+
Object.entries(telemetryMethods).forEach(([methodName, options]) => {
|
|
346
|
+
const originalMethod = instance[methodName];
|
|
347
|
+
if (typeof originalMethod === "function") {
|
|
348
|
+
const self = this;
|
|
349
|
+
instance[methodName] = function(...args) {
|
|
350
|
+
const startTime = Date.now();
|
|
351
|
+
const emit = (result, error) => {
|
|
352
|
+
const payload = {
|
|
353
|
+
className,
|
|
354
|
+
methodName,
|
|
355
|
+
args,
|
|
356
|
+
startTime,
|
|
357
|
+
endTime: Date.now(),
|
|
358
|
+
result,
|
|
359
|
+
error
|
|
360
|
+
};
|
|
361
|
+
if (options.logging) {
|
|
362
|
+
const duration = payload.endTime - payload.startTime;
|
|
363
|
+
const status = error ? `ERROR: ${error.message || error}` : "SUCCESS";
|
|
364
|
+
console.log(`[Telemetry] ${className}.${methodName} - ${status} (${duration}ms)`);
|
|
365
|
+
}
|
|
366
|
+
self.emit("telemetry", payload);
|
|
367
|
+
};
|
|
368
|
+
try {
|
|
369
|
+
const result = originalMethod.apply(this, args);
|
|
370
|
+
if (result instanceof Promise) {
|
|
371
|
+
return result.then((val) => {
|
|
372
|
+
emit(val);
|
|
373
|
+
return val;
|
|
374
|
+
}).catch((err) => {
|
|
375
|
+
emit(undefined, err);
|
|
376
|
+
throw err;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
emit(result);
|
|
380
|
+
return result;
|
|
381
|
+
} catch (err) {
|
|
382
|
+
emit(undefined, err);
|
|
383
|
+
throw err;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
isClass(func) {
|
|
390
|
+
return typeof func === "function" && func.prototype && func.prototype.constructor === func;
|
|
391
|
+
}
|
|
392
|
+
getConstructorParamNames(target) {
|
|
393
|
+
const funcStr = target.toString();
|
|
394
|
+
const match = funcStr.match(/constructor\s*\(([^)]*)\)/);
|
|
395
|
+
if (!match || !match[1])
|
|
396
|
+
return [];
|
|
397
|
+
const paramsStr = match[1];
|
|
398
|
+
return paramsStr.split(",").map((param) => {
|
|
399
|
+
const trimmed = param.trim();
|
|
400
|
+
const withoutDefault = trimmed.split("=")[0] || "";
|
|
401
|
+
const withoutType = withoutDefault.split(":")[0] || "";
|
|
402
|
+
return withoutType.trim();
|
|
403
|
+
}).filter((param) => param);
|
|
404
|
+
}
|
|
405
|
+
extractParamTypesFromSource(target) {
|
|
406
|
+
const funcStr = target.toString();
|
|
407
|
+
const decoratorMatch = funcStr.match(/__decorate\(\[\s*(?:\w+\s*\([^)]*\),?\s*)*__param\((\d+),\s*(\w+)\([^)]*\)\)/g);
|
|
408
|
+
if (decoratorMatch) {
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
var container = new Container;
|
|
415
|
+
function useContainer() {
|
|
416
|
+
return container;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ../di-framework/dist/decorators.js
|
|
420
|
+
var INJECTABLE_METADATA_KEY = "di:injectable";
|
|
421
|
+
function Container2(options = {}) {
|
|
422
|
+
return function(constructor) {
|
|
423
|
+
const container2 = options.container ?? useContainer();
|
|
424
|
+
const singleton = options.singleton ?? true;
|
|
425
|
+
defineMetadata(INJECTABLE_METADATA_KEY, true, constructor);
|
|
426
|
+
container2.register(constructor, { singleton });
|
|
427
|
+
return constructor;
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/decorators.ts
|
|
432
|
+
function Controller(options = {}) {
|
|
433
|
+
const container2 = Container2(options);
|
|
434
|
+
return function(target) {
|
|
435
|
+
target.isController = true;
|
|
436
|
+
registry_default.addTarget(target);
|
|
437
|
+
container2(target);
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function Endpoint(metadata) {
|
|
441
|
+
return function(target, propertyKey) {
|
|
442
|
+
if (propertyKey) {
|
|
443
|
+
const property = target[propertyKey];
|
|
444
|
+
const constructor = typeof target === "function" ? target : target.constructor;
|
|
445
|
+
registry_default.addTarget(constructor);
|
|
446
|
+
property.isEndpoint = true;
|
|
447
|
+
if (metadata) {
|
|
448
|
+
property.metadata = metadata;
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
target.isEndpoint = true;
|
|
452
|
+
if (metadata) {
|
|
453
|
+
target.metadata = metadata;
|
|
454
|
+
}
|
|
455
|
+
registry_default.addTarget(target);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
// src/openapi.ts
|
|
460
|
+
init_registry();
|
|
461
|
+
function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
462
|
+
const spec = {
|
|
463
|
+
openapi: "3.1.0",
|
|
464
|
+
info: {
|
|
465
|
+
title: options.title || "Generated API",
|
|
466
|
+
version: options.version || "1.0.0",
|
|
467
|
+
description: options.description || "API documentation generated by @di-framework/di-framework-http."
|
|
468
|
+
},
|
|
469
|
+
paths: {},
|
|
470
|
+
components: {
|
|
471
|
+
schemas: {}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const targets = registryToUse.getTargets();
|
|
475
|
+
for (const target of targets) {
|
|
476
|
+
for (const key of Object.getOwnPropertyNames(target)) {
|
|
477
|
+
const property = target[key];
|
|
478
|
+
if (property && property.isEndpoint) {
|
|
479
|
+
const path = property.path || "/unknown";
|
|
480
|
+
const method = property.method || "get";
|
|
481
|
+
if (!spec.paths[path]) {
|
|
482
|
+
spec.paths[path] = {};
|
|
483
|
+
}
|
|
484
|
+
spec.paths[path][method] = {
|
|
485
|
+
summary: property.metadata?.summary || key,
|
|
486
|
+
description: property.metadata?.description,
|
|
487
|
+
operationId: `${target.name}.${key}`,
|
|
488
|
+
requestBody: property.metadata?.requestBody,
|
|
489
|
+
responses: property.metadata?.responses || {
|
|
490
|
+
"200": {
|
|
491
|
+
description: "OK"
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return spec;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// index.ts
|
|
502
|
+
init_registry();
|
|
503
|
+
init_registry();
|
|
504
|
+
export {
|
|
505
|
+
registry_default as registry,
|
|
506
|
+
json,
|
|
507
|
+
generateOpenAPI,
|
|
508
|
+
TypedRouter,
|
|
509
|
+
Registry,
|
|
510
|
+
Endpoint,
|
|
511
|
+
Controller
|
|
512
|
+
};
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// src/registry.ts
|
|
32
|
+
var exports_registry = {};
|
|
33
|
+
__export(exports_registry, {
|
|
34
|
+
default: () => registry_default,
|
|
35
|
+
Registry: () => Registry
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
class Registry {
|
|
39
|
+
targets = new Set;
|
|
40
|
+
addTarget(target) {
|
|
41
|
+
this.targets.add(target);
|
|
42
|
+
}
|
|
43
|
+
getTargets() {
|
|
44
|
+
return this.targets;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var registry, registry_default;
|
|
48
|
+
var init_registry = __esm(() => {
|
|
49
|
+
registry = new Registry;
|
|
50
|
+
registry_default = registry;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/cli.ts
|
|
54
|
+
import fs from "fs";
|
|
55
|
+
import path from "path";
|
|
56
|
+
|
|
57
|
+
// src/openapi.ts
|
|
58
|
+
init_registry();
|
|
59
|
+
function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
60
|
+
const spec = {
|
|
61
|
+
openapi: "3.1.0",
|
|
62
|
+
info: {
|
|
63
|
+
title: options.title || "Generated API",
|
|
64
|
+
version: options.version || "1.0.0",
|
|
65
|
+
description: options.description || "API documentation generated by @di-framework/di-framework-http."
|
|
66
|
+
},
|
|
67
|
+
paths: {},
|
|
68
|
+
components: {
|
|
69
|
+
schemas: {}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const targets = registryToUse.getTargets();
|
|
73
|
+
for (const target of targets) {
|
|
74
|
+
for (const key of Object.getOwnPropertyNames(target)) {
|
|
75
|
+
const property = target[key];
|
|
76
|
+
if (property && property.isEndpoint) {
|
|
77
|
+
const path = property.path || "/unknown";
|
|
78
|
+
const method = property.method || "get";
|
|
79
|
+
if (!spec.paths[path]) {
|
|
80
|
+
spec.paths[path] = {};
|
|
81
|
+
}
|
|
82
|
+
spec.paths[path][method] = {
|
|
83
|
+
summary: property.metadata?.summary || key,
|
|
84
|
+
description: property.metadata?.description,
|
|
85
|
+
operationId: `${target.name}.${key}`,
|
|
86
|
+
requestBody: property.metadata?.requestBody,
|
|
87
|
+
responses: property.metadata?.responses || {
|
|
88
|
+
"200": {
|
|
89
|
+
description: "OK"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return spec;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/cli.ts
|
|
100
|
+
var args = process.argv.slice(2);
|
|
101
|
+
var command = args[0];
|
|
102
|
+
if (command === "generate") {
|
|
103
|
+
const outputArg = args.indexOf("--output");
|
|
104
|
+
const outputPath = outputArg !== -1 ? args[outputArg + 1] : "openapi.json";
|
|
105
|
+
const controllersArg = args.indexOf("--controllers");
|
|
106
|
+
if (controllersArg === -1) {
|
|
107
|
+
console.error("Error: --controllers path is required");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const controllersPath = path.resolve(process.cwd(), args[controllersArg + 1]);
|
|
111
|
+
async function run() {
|
|
112
|
+
try {
|
|
113
|
+
const controllersPathResolved = path.resolve(process.cwd(), args[controllersArg + 1]);
|
|
114
|
+
const imported = await import(controllersPathResolved);
|
|
115
|
+
let registryToUse = imported.default || imported.registry;
|
|
116
|
+
if (!registryToUse || typeof registryToUse.getTargets !== "function") {
|
|
117
|
+
try {
|
|
118
|
+
const dfHttp = await import("@di-framework/di-framework-http");
|
|
119
|
+
registryToUse = dfHttp.default || dfHttp.registry;
|
|
120
|
+
} catch {
|
|
121
|
+
const regModule = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
122
|
+
registryToUse = regModule.default;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const spec = generateOpenAPI({
|
|
126
|
+
title: "API Documentation"
|
|
127
|
+
}, registryToUse);
|
|
128
|
+
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
|
|
129
|
+
console.log(`Successfully generated OpenAPI spec at ${outputPath}`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Error generating OpenAPI spec: ${error.message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
run();
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`
|
|
138
|
+
Usage: di-framework-http generate --controllers <path-to-controllers> [options]
|
|
139
|
+
|
|
140
|
+
Options:
|
|
141
|
+
--controllers <path> Path to a file that imports all your decorated controllers
|
|
142
|
+
--output <path> Path to save the generated JSON (default: openapi.json)
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function Controller(options?: {
|
|
2
|
+
singleton?: boolean;
|
|
3
|
+
container?: any;
|
|
4
|
+
}): (target: any) => void;
|
|
5
|
+
export declare function Endpoint(metadata?: {
|
|
6
|
+
summary?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
requestBody?: any;
|
|
9
|
+
responses?: Record<string, any>;
|
|
10
|
+
}): (target: any, propertyKey?: string) => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface EndpointMetadata {
|
|
2
|
+
summary?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
requestBody?: any;
|
|
5
|
+
responses?: Record<string, any>;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
export interface RegisteredEndpoint {
|
|
9
|
+
target: any;
|
|
10
|
+
propertyKey: string;
|
|
11
|
+
path: string;
|
|
12
|
+
method: string;
|
|
13
|
+
metadata: EndpointMetadata;
|
|
14
|
+
}
|
|
15
|
+
export declare class Registry {
|
|
16
|
+
private targets;
|
|
17
|
+
addTarget(target: any): void;
|
|
18
|
+
getTargets(): Set<any>;
|
|
19
|
+
}
|
|
20
|
+
declare const registry: Registry;
|
|
21
|
+
export default registry;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Router, type IRequest } from "itty-router";
|
|
2
|
+
/** Marker for body "shape + content-type" */
|
|
3
|
+
export type Json<T> = {
|
|
4
|
+
readonly __kind: "json";
|
|
5
|
+
readonly __type?: T;
|
|
6
|
+
};
|
|
7
|
+
export type Multipart<T> = {
|
|
8
|
+
readonly __kind: "multipart";
|
|
9
|
+
readonly __type?: T;
|
|
10
|
+
};
|
|
11
|
+
/** Spec types you’ll use in generics */
|
|
12
|
+
export type RequestSpec<BodySpec = unknown> = {
|
|
13
|
+
readonly __req?: BodySpec;
|
|
14
|
+
};
|
|
15
|
+
export type ResponseSpec<Body = unknown> = {
|
|
16
|
+
readonly __res?: Body;
|
|
17
|
+
};
|
|
18
|
+
/** Map a BodySpec to the actual req.content type */
|
|
19
|
+
type ContentOf<BodySpec> = BodySpec extends Json<infer T> ? T : BodySpec extends Multipart<infer _T> ? FormData : unknown;
|
|
20
|
+
/** The actual request type your handlers receive */
|
|
21
|
+
export type TypedRequest<ReqSpec> = IRequest & {
|
|
22
|
+
content: ContentOf<ReqSpec extends RequestSpec<infer B> ? B : never>;
|
|
23
|
+
};
|
|
24
|
+
/** Typed response helper (phantom only) */
|
|
25
|
+
export type TypedResponse<ResSpec> = globalThis.Response & {
|
|
26
|
+
readonly __typedRes?: ResSpec;
|
|
27
|
+
};
|
|
28
|
+
/** HandlerController type derived from the Request/Response specs */
|
|
29
|
+
export type HandlerController<ReqSpec, ResSpec, Args extends any[] = any[]> = (req: TypedRequest<ReqSpec>, ...args: Args) => TypedResponse<ResSpec> | Promise<TypedResponse<ResSpec>>;
|
|
30
|
+
/** A typed json() that returns a Response annotated with Response<T> */
|
|
31
|
+
export declare function json<T>(data: T, init?: ResponseInit): TypedResponse<ResponseSpec<T>>;
|
|
32
|
+
export type RouteOptions = {
|
|
33
|
+
multipart?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type TypedRoute<Args extends any[] = any[]> = <ReqSpec = RequestSpec<unknown>, ResSpec = ResponseSpec<unknown>>(path: string, controller: HandlerController<ReqSpec, ResSpec, Args>, options?: RouteOptions) => TypedRouterType<Args> & {
|
|
36
|
+
path: string;
|
|
37
|
+
method: string;
|
|
38
|
+
reqSpec: ReqSpec;
|
|
39
|
+
resSpec: ResSpec;
|
|
40
|
+
};
|
|
41
|
+
export type TypedRouterType<Args extends any[] = any[]> = {
|
|
42
|
+
get: TypedRoute<Args>;
|
|
43
|
+
post: TypedRoute<Args>;
|
|
44
|
+
put: TypedRoute<Args>;
|
|
45
|
+
delete: TypedRoute<Args>;
|
|
46
|
+
patch: TypedRoute<Args>;
|
|
47
|
+
head: TypedRoute<Args>;
|
|
48
|
+
options: TypedRoute<Args>;
|
|
49
|
+
fetch: (request: Request, ...args: Args) => Promise<Response>;
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
};
|
|
52
|
+
export declare function TypedRouter<Args extends any[] = any[]>(opts?: Parameters<typeof Router>[0]): TypedRouterType<Args>;
|
|
53
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@di-framework/di-framework-http",
|
|
3
|
+
"version": "0.0.0-prerelease.4",
|
|
4
|
+
"description": "Extends di-framework with HTTP features",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"private": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"bun": "./index.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"author": "github.com/geoffsee",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": "https://github.com/geoffsee/di-framework",
|
|
19
|
+
"bin": {
|
|
20
|
+
"di-framework-http": "./dist/src/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "rm -rf dist && bun build ./index.ts ./src/cli.ts --outdir ./dist --target node --external itty-router && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
29
|
+
"prepublishOnly": "npm run build",
|
|
30
|
+
"dev": "bun ../examples/http-router/index.ts",
|
|
31
|
+
"test": "bun test"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"itty-router": "^5.0.22"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "latest",
|
|
38
|
+
"typescript": "^5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@di-framework/di-framework": "0.0.0-prerelease-0",
|
|
42
|
+
"typescript": "^5"
|
|
43
|
+
}
|
|
44
|
+
}
|