@buenojs/bueno 0.8.0
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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Injection Container
|
|
3
|
+
*
|
|
4
|
+
* Provides inversion of control for managing dependencies
|
|
5
|
+
* with support for constructor injection, factories, and scopes.
|
|
6
|
+
* Supports circular dependency resolution via forward references.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Provider } from "../types";
|
|
10
|
+
import {
|
|
11
|
+
type ForwardRef,
|
|
12
|
+
forwardRef,
|
|
13
|
+
isForwardRef,
|
|
14
|
+
resolveForwardRef,
|
|
15
|
+
} from "./forward-ref";
|
|
16
|
+
|
|
17
|
+
export type { Provider } from "../types";
|
|
18
|
+
export { type ForwardRef, forwardRef, isForwardRef, resolveForwardRef };
|
|
19
|
+
|
|
20
|
+
// ============= Token Factory =============
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Token type - represents an injection token
|
|
24
|
+
*/
|
|
25
|
+
export type Token<T = unknown> = symbol & { readonly __type?: T };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a typed injection token (symbol-based)
|
|
29
|
+
*/
|
|
30
|
+
export function createToken<T>(description: string): Token<T> {
|
|
31
|
+
return Symbol(description) as Token<T>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Token function - creates a typed injection token (symbol-based)
|
|
36
|
+
*/
|
|
37
|
+
export const Token = createToken;
|
|
38
|
+
|
|
39
|
+
// ============= Provider Resolution =============
|
|
40
|
+
|
|
41
|
+
interface ResolvedProvider<T = unknown> {
|
|
42
|
+
provider: Provider<T>;
|
|
43
|
+
instance?: T;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============= Circular Dependency Detection =============
|
|
47
|
+
|
|
48
|
+
class ResolutionStack {
|
|
49
|
+
private stack = new Set<Token>();
|
|
50
|
+
/**
|
|
51
|
+
* Track tokens that are being lazily resolved for circular dependencies.
|
|
52
|
+
* These tokens have a proxy placeholder that will be resolved later.
|
|
53
|
+
*/
|
|
54
|
+
private lazyResolutions = new Map<Token, { resolved: boolean; instance?: unknown }>();
|
|
55
|
+
|
|
56
|
+
push(token: Token): void {
|
|
57
|
+
if (this.stack.has(token)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Circular dependency detected: Token already in resolution stack",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
this.stack.add(token);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pop(token: Token): void {
|
|
66
|
+
this.stack.delete(token);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
has(token: Token): boolean {
|
|
70
|
+
return this.stack.has(token);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Mark a token as being lazily resolved (for circular dependency support)
|
|
75
|
+
*/
|
|
76
|
+
markLazy(token: Token, placeholder: { resolved: boolean; instance?: unknown }): void {
|
|
77
|
+
this.lazyResolutions.set(token, placeholder);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a token has a lazy resolution placeholder
|
|
82
|
+
*/
|
|
83
|
+
hasLazy(token: Token): boolean {
|
|
84
|
+
return this.lazyResolutions.has(token);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the lazy resolution placeholder for a token
|
|
89
|
+
*/
|
|
90
|
+
getLazy(token: Token): { resolved: boolean; instance?: unknown } | undefined {
|
|
91
|
+
return this.lazyResolutions.get(token);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Clear lazy resolution for a token after it's fully resolved
|
|
96
|
+
*/
|
|
97
|
+
clearLazy(token: Token): void {
|
|
98
|
+
this.lazyResolutions.delete(token);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============= Container =============
|
|
103
|
+
|
|
104
|
+
export class Container {
|
|
105
|
+
private providers = new Map<Token, ResolvedProvider>();
|
|
106
|
+
private resolutionStack = new ResolutionStack();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Register a single provider
|
|
110
|
+
*/
|
|
111
|
+
register<T>(provider: Provider<T>): void {
|
|
112
|
+
if (this.providers.has(provider.token as Token)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Provider already registered for token: ${String(provider.token)}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.providers.set(provider.token as Token, {
|
|
119
|
+
provider: {
|
|
120
|
+
...provider,
|
|
121
|
+
scope: provider.scope ?? "singleton",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register multiple providers at once
|
|
128
|
+
*/
|
|
129
|
+
registerAll(providers: Provider[]): void {
|
|
130
|
+
for (const provider of providers) {
|
|
131
|
+
this.register(provider);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a token is registered
|
|
137
|
+
*/
|
|
138
|
+
has(token: Token): boolean {
|
|
139
|
+
return this.providers.has(token);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve a token to its value
|
|
144
|
+
*/
|
|
145
|
+
resolve<T>(token: Token<T>): T {
|
|
146
|
+
const resolved = this.providers.get(token as Token);
|
|
147
|
+
|
|
148
|
+
if (!resolved) {
|
|
149
|
+
throw new Error(`No provider registered for token: ${String(token)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { provider } = resolved;
|
|
153
|
+
|
|
154
|
+
// Check for circular dependencies - return a lazy proxy instead of throwing
|
|
155
|
+
if (this.resolutionStack.has(token as Token)) {
|
|
156
|
+
return this.createLazyProxy<T>(token as Token, resolved);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Singleton: return cached instance if available
|
|
160
|
+
if (provider.scope === "singleton" && resolved.instance !== undefined) {
|
|
161
|
+
return resolved.instance as T;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Track resolution for circular dependency detection
|
|
165
|
+
this.resolutionStack.push(token as Token);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const instance = this.createInstance(provider);
|
|
169
|
+
|
|
170
|
+
// Cache singleton instances
|
|
171
|
+
if (provider.scope === "singleton") {
|
|
172
|
+
resolved.instance = instance;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return instance as T;
|
|
176
|
+
} finally {
|
|
177
|
+
this.resolutionStack.pop(token as Token);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create a lazy proxy for circular dependency resolution.
|
|
183
|
+
* The proxy will resolve the actual instance on first property access.
|
|
184
|
+
*/
|
|
185
|
+
private createLazyProxy<T>(token: Token, resolved: ResolvedProvider): T {
|
|
186
|
+
// Check if we already have a lazy placeholder for this token
|
|
187
|
+
const existingLazy = this.resolutionStack.getLazy(token);
|
|
188
|
+
if (existingLazy && existingLazy.instance) {
|
|
189
|
+
return existingLazy.instance as T;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Create a placeholder that will be resolved later
|
|
193
|
+
const placeholder: { resolved: boolean; instance?: T } = {
|
|
194
|
+
resolved: false,
|
|
195
|
+
};
|
|
196
|
+
this.resolutionStack.markLazy(token, placeholder);
|
|
197
|
+
|
|
198
|
+
// Create a proxy that lazily resolves the dependency
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
const proxy = new Proxy({} as any, {
|
|
201
|
+
get: (target: T, prop: string | symbol): unknown => {
|
|
202
|
+
// If already resolved, return the cached value
|
|
203
|
+
if (placeholder.resolved && placeholder.instance) {
|
|
204
|
+
const value = (placeholder.instance as Record<string | symbol, unknown>)[prop];
|
|
205
|
+
return typeof value === 'function' ? value.bind(placeholder.instance) : value;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Resolve the actual instance
|
|
209
|
+
// At this point, the circular dependency chain has completed
|
|
210
|
+
// and we can safely get the instance from the resolved provider
|
|
211
|
+
if (resolved.instance !== undefined) {
|
|
212
|
+
placeholder.instance = resolved.instance as T;
|
|
213
|
+
placeholder.resolved = true;
|
|
214
|
+
} else {
|
|
215
|
+
// If not yet cached, we need to wait for the resolution to complete
|
|
216
|
+
// This happens when the proxy is accessed during construction
|
|
217
|
+
// Return a function that will resolve later
|
|
218
|
+
if (prop === 'then') {
|
|
219
|
+
// Make the proxy thenable for async contexts
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Try to get the value from the resolved instance
|
|
225
|
+
if (placeholder.instance) {
|
|
226
|
+
const value = (placeholder.instance as Record<string | symbol, unknown>)[prop];
|
|
227
|
+
return typeof value === 'function' ? value.bind(placeholder.instance) : value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Return a no-op function for method calls during construction
|
|
231
|
+
return () => undefined;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
set: (target: T, prop: string | symbol, value: unknown): boolean => {
|
|
235
|
+
if (placeholder.instance) {
|
|
236
|
+
(placeholder.instance as Record<string | symbol, unknown>)[prop] = value;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
has: (target: T, prop: string | symbol): boolean => {
|
|
243
|
+
if (placeholder.instance) {
|
|
244
|
+
return prop in (placeholder.instance as object);
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
placeholder.instance = proxy;
|
|
251
|
+
return proxy;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create an instance from a provider
|
|
256
|
+
*/
|
|
257
|
+
private createInstance<T>(provider: Provider<T>): T {
|
|
258
|
+
// useValue: return the value directly
|
|
259
|
+
if (provider.useValue !== undefined) {
|
|
260
|
+
return provider.useValue as T;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// useFactory: call factory with injected dependencies
|
|
264
|
+
if (provider.useFactory) {
|
|
265
|
+
const deps = this.resolveDeps(provider.inject ?? []);
|
|
266
|
+
return provider.useFactory(...deps) as T;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// useClass: instantiate with injected dependencies
|
|
270
|
+
if (provider.useClass) {
|
|
271
|
+
const deps = this.resolveDeps(provider.inject ?? []);
|
|
272
|
+
// Use new with proper typing for abstract class constructor
|
|
273
|
+
return new (provider.useClass as new (...args: unknown[]) => T)(...deps);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Invalid provider configuration for token: ${String(provider.token)}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Resolve an array of dependency tokens (including ForwardRef support)
|
|
283
|
+
*/
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
|
+
private resolveDeps(tokens: any[]): unknown[] {
|
|
286
|
+
return tokens.map((tokenOrRef) => {
|
|
287
|
+
// Resolve forward reference if needed
|
|
288
|
+
const token = resolveForwardRef(tokenOrRef);
|
|
289
|
+
return this.resolve(token);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Clear all registrations
|
|
295
|
+
*/
|
|
296
|
+
clear(): void {
|
|
297
|
+
this.providers.clear();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get all registered tokens
|
|
302
|
+
*/
|
|
303
|
+
getTokens(): Token[] {
|
|
304
|
+
return Array.from(this.providers.keys());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Create a child container (for request-scoped containers)
|
|
309
|
+
*/
|
|
310
|
+
createChild(): Container {
|
|
311
|
+
const child = new Container();
|
|
312
|
+
// Copy singleton providers to child
|
|
313
|
+
for (const [token, resolved] of this.providers) {
|
|
314
|
+
if (resolved.provider.scope === "singleton") {
|
|
315
|
+
child.providers.set(token, resolved);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return child;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ============= Decorator Helpers =============
|
|
323
|
+
|
|
324
|
+
// WeakMap for container metadata storage
|
|
325
|
+
const containerMetadata = new WeakMap<object, Map<string, unknown>>();
|
|
326
|
+
|
|
327
|
+
function setContainerMetadata(
|
|
328
|
+
target: object,
|
|
329
|
+
key: string,
|
|
330
|
+
value: unknown,
|
|
331
|
+
): void {
|
|
332
|
+
if (!containerMetadata.has(target)) {
|
|
333
|
+
containerMetadata.set(target, new Map());
|
|
334
|
+
}
|
|
335
|
+
containerMetadata.get(target)?.set(key, value);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getContainerMetadata<T>(target: object, key: string): T | undefined {
|
|
339
|
+
return containerMetadata.get(target)?.get(key) as T | undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Helper to create injectable class decorator
|
|
344
|
+
*/
|
|
345
|
+
export function Injectable(token?: Token): ClassDecorator {
|
|
346
|
+
return <TFunction extends Function>(target: TFunction): TFunction => {
|
|
347
|
+
setContainerMetadata(target as object, "injectable", true);
|
|
348
|
+
if (token) {
|
|
349
|
+
setContainerMetadata(target as object, "token", token);
|
|
350
|
+
}
|
|
351
|
+
return target;
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Helper to create parameter injection metadata.
|
|
357
|
+
* Supports both Token and ForwardRef<Token> for circular dependency resolution.
|
|
358
|
+
*
|
|
359
|
+
* @param token - The injection token or a forward reference to the token
|
|
360
|
+
* @returns A parameter decorator that registers the injection metadata
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* // Regular injection
|
|
365
|
+
* constructor(@Inject(MY_TOKEN) private service: MyService) {}
|
|
366
|
+
*
|
|
367
|
+
* // Forward reference for circular dependency
|
|
368
|
+
* constructor(@Inject(forwardRef(() => ServiceB)) private serviceB: ServiceB) {}
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
export function Inject(token: Token | ForwardRef<Token>): ParameterDecorator {
|
|
372
|
+
return (
|
|
373
|
+
target: unknown,
|
|
374
|
+
propertyKey: string | symbol | undefined,
|
|
375
|
+
parameterIndex: number,
|
|
376
|
+
) => {
|
|
377
|
+
const targetObj = target as object;
|
|
378
|
+
const existingTokens: Array<Token | ForwardRef<Token>> =
|
|
379
|
+
getContainerMetadata<Array<Token | ForwardRef<Token>>>(targetObj, "inject:tokens") ?? [];
|
|
380
|
+
existingTokens[parameterIndex] = token;
|
|
381
|
+
setContainerMetadata(targetObj, "inject:tokens", existingTokens);
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Export getter for use by modules
|
|
386
|
+
export { getContainerMetadata as getInjectTokens };
|