@carno.js/core 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +188 -188
  3. package/dist/Carno.js +46 -26
  4. package/dist/Carno.mjs +46 -26
  5. package/dist/bun/index.js +4 -4
  6. package/dist/bun/index.js.map +29 -29
  7. package/package.json +2 -2
  8. package/src/Carno.ts +718 -673
  9. package/src/DefaultRoutes.ts +34 -34
  10. package/src/cache/CacheDriver.ts +50 -50
  11. package/src/cache/CacheService.ts +139 -139
  12. package/src/cache/MemoryDriver.ts +104 -104
  13. package/src/cache/RedisDriver.ts +116 -116
  14. package/src/compiler/JITCompiler.ts +167 -167
  15. package/src/container/Container.ts +168 -168
  16. package/src/context/Context.ts +130 -130
  17. package/src/cors/CorsHandler.ts +145 -145
  18. package/src/decorators/Controller.ts +63 -63
  19. package/src/decorators/Inject.ts +16 -16
  20. package/src/decorators/Middleware.ts +22 -22
  21. package/src/decorators/Service.ts +18 -18
  22. package/src/decorators/methods.ts +58 -58
  23. package/src/decorators/params.ts +47 -47
  24. package/src/events/Lifecycle.ts +97 -97
  25. package/src/exceptions/HttpException.ts +99 -99
  26. package/src/index.ts +95 -95
  27. package/src/metadata.ts +46 -46
  28. package/src/middleware/CarnoMiddleware.ts +14 -14
  29. package/src/router/RadixRouter.ts +225 -225
  30. package/src/testing/TestHarness.ts +185 -185
  31. package/src/utils/Metadata.ts +43 -43
  32. package/src/utils/parseQuery.ts +161 -161
  33. package/src/validation/ValibotAdapter.ts +95 -95
  34. package/src/validation/ValidatorAdapter.ts +69 -69
  35. package/src/validation/ZodAdapter.ts +102 -102
  36. package/dist/Carno.d.js +0 -14
  37. package/dist/Carno.d.mjs +0 -1
  38. package/dist/DefaultRoutes.d.js +0 -13
  39. package/dist/DefaultRoutes.d.mjs +0 -0
  40. package/dist/cache/CacheDriver.d.js +0 -13
  41. package/dist/cache/CacheDriver.d.mjs +0 -0
  42. package/dist/cache/CacheService.d.js +0 -13
  43. package/dist/cache/CacheService.d.mjs +0 -0
  44. package/dist/cache/MemoryDriver.d.js +0 -13
  45. package/dist/cache/MemoryDriver.d.mjs +0 -0
  46. package/dist/cache/RedisDriver.d.js +0 -13
  47. package/dist/cache/RedisDriver.d.mjs +0 -0
  48. package/dist/compiler/JITCompiler.d.js +0 -13
  49. package/dist/compiler/JITCompiler.d.mjs +0 -0
  50. package/dist/container/Container.d.js +0 -13
  51. package/dist/container/Container.d.mjs +0 -0
  52. package/dist/context/Context.d.js +0 -13
  53. package/dist/context/Context.d.mjs +0 -0
  54. package/dist/cors/CorsHandler.d.js +0 -13
  55. package/dist/cors/CorsHandler.d.mjs +0 -0
  56. package/dist/decorators/Controller.d.js +0 -13
  57. package/dist/decorators/Controller.d.mjs +0 -0
  58. package/dist/decorators/Inject.d.js +0 -13
  59. package/dist/decorators/Inject.d.mjs +0 -0
  60. package/dist/decorators/Middleware.d.js +0 -13
  61. package/dist/decorators/Middleware.d.mjs +0 -0
  62. package/dist/decorators/Service.d.js +0 -13
  63. package/dist/decorators/Service.d.mjs +0 -0
  64. package/dist/decorators/methods.d.js +0 -13
  65. package/dist/decorators/methods.d.mjs +0 -0
  66. package/dist/decorators/params.d.js +0 -13
  67. package/dist/decorators/params.d.mjs +0 -0
  68. package/dist/events/Lifecycle.d.js +0 -13
  69. package/dist/events/Lifecycle.d.mjs +0 -0
  70. package/dist/exceptions/HttpException.d.js +0 -13
  71. package/dist/exceptions/HttpException.d.mjs +0 -0
  72. package/dist/index.d.js +0 -130
  73. package/dist/index.d.mjs +0 -78
  74. package/dist/metadata.d.js +0 -13
  75. package/dist/metadata.d.mjs +0 -0
  76. package/dist/middleware/CarnoMiddleware.d.js +0 -13
  77. package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
  78. package/dist/router/RadixRouter.d.js +0 -13
  79. package/dist/router/RadixRouter.d.mjs +0 -0
  80. package/dist/testing/TestHarness.d.js +0 -13
  81. package/dist/testing/TestHarness.d.mjs +0 -0
  82. package/dist/utils/Metadata.d.js +0 -13
  83. package/dist/utils/Metadata.d.mjs +0 -0
  84. package/dist/utils/parseQuery.d.js +0 -13
  85. package/dist/utils/parseQuery.d.mjs +0 -0
  86. package/dist/validation/ValibotAdapter.d.js +0 -13
  87. package/dist/validation/ValibotAdapter.d.mjs +0 -0
  88. package/dist/validation/ValidatorAdapter.d.js +0 -13
  89. package/dist/validation/ValidatorAdapter.d.mjs +0 -0
  90. package/dist/validation/ZodAdapter.d.js +0 -13
  91. package/dist/validation/ZodAdapter.d.mjs +0 -0
  92. package/src/Carno.d.ts +0 -135
  93. package/src/DefaultRoutes.d.ts +0 -19
  94. package/src/cache/CacheDriver.d.ts +0 -43
  95. package/src/cache/CacheService.d.ts +0 -89
  96. package/src/cache/MemoryDriver.d.ts +0 -32
  97. package/src/cache/RedisDriver.d.ts +0 -34
  98. package/src/compiler/JITCompiler.d.ts +0 -36
  99. package/src/container/Container.d.ts +0 -38
  100. package/src/context/Context.d.ts +0 -36
  101. package/src/cors/CorsHandler.d.ts +0 -47
  102. package/src/decorators/Controller.d.ts +0 -13
  103. package/src/decorators/Inject.d.ts +0 -6
  104. package/src/decorators/Middleware.d.ts +0 -5
  105. package/src/decorators/Service.d.ts +0 -9
  106. package/src/decorators/methods.d.ts +0 -7
  107. package/src/decorators/params.d.ts +0 -13
  108. package/src/events/Lifecycle.d.ts +0 -54
  109. package/src/exceptions/HttpException.d.ts +0 -43
  110. package/src/index.d.ts +0 -42
  111. package/src/metadata.d.ts +0 -41
  112. package/src/middleware/CarnoMiddleware.d.ts +0 -12
  113. package/src/router/RadixRouter.d.ts +0 -19
  114. package/src/testing/TestHarness.d.ts +0 -71
  115. package/src/utils/Metadata.d.ts +0 -20
  116. package/src/utils/parseQuery.d.ts +0 -23
  117. package/src/validation/ValibotAdapter.d.ts +0 -30
  118. package/src/validation/ValidatorAdapter.d.ts +0 -54
  119. package/src/validation/ZodAdapter.d.ts +0 -35
@@ -1,168 +1,168 @@
1
- /**
2
- * Lightweight DI Container for Turbo.
3
- *
4
- * Features:
5
- * - Constructor injection via reflect-metadata
6
- * - Singleton scope by default
7
- * - Request scope support
8
- * - Lazy instantiation
9
- */
10
-
11
- export type Token<T = any> = new (...args: any[]) => T;
12
-
13
- export enum Scope {
14
- SINGLETON = 'singleton', // Always the same instance
15
- REQUEST = 'request', // New instance per request
16
- INSTANCE = 'instance' // New instance per dependency injection
17
- }
18
-
19
- export interface ProviderConfig<T = any> {
20
- token: Token<T>;
21
- useClass?: Token<T>;
22
- useValue?: T;
23
- scope?: Scope;
24
- }
25
-
26
- interface ProviderEntry {
27
- config: ProviderConfig;
28
- instance: any | null;
29
- }
30
-
31
- export class Container {
32
- private configs = new Map<Token, ProviderConfig>();
33
- private instances = new Map<Token, any>();
34
- private resolving = new Set<Token>();
35
-
36
- register<T>(config: ProviderConfig<T> | Token<T>): this {
37
- const normalized = this.normalizeConfig(config);
38
-
39
- this.configs.set(normalized.token, normalized);
40
-
41
- if (normalized.useValue !== undefined) {
42
- this.instances.set(normalized.token, normalized.useValue);
43
- }
44
-
45
- return this;
46
- }
47
-
48
- get<T>(token: Token<T>): T {
49
- const cached = this.instances.get(token);
50
-
51
- if (cached !== undefined) {
52
- return cached;
53
- }
54
-
55
- const res = this.resolveInternal(token);
56
- return res.instance;
57
- }
58
-
59
- has(token: Token): boolean {
60
- return this.configs.has(token);
61
- }
62
-
63
- /**
64
- * Resolves a token to return instance and its effective scope.
65
- */
66
- private resolveInternal<T>(token: Token<T>, requestLocals?: Map<Token, any>): { instance: T, scope: Scope } {
67
- // 1. Check Request Cache
68
- if (requestLocals?.has(token)) {
69
- return { instance: requestLocals.get(token), scope: Scope.REQUEST };
70
- }
71
-
72
- // 2. Check Singleton Cache
73
- const cached = this.instances.get(token);
74
- if (cached !== undefined) {
75
- return { instance: cached, scope: Scope.SINGLETON };
76
- }
77
-
78
- const config = this.configs.get(token);
79
-
80
- if (!config) {
81
- throw new Error(`Provider not found: ${token.name}`);
82
- }
83
-
84
- // 3. Create Instance with Scope Bubbling
85
- const creation = this.createInstance(config, requestLocals);
86
-
87
- // 4. Cache based on Effective Scope
88
- if (creation.scope === Scope.SINGLETON) {
89
- this.instances.set(token, creation.instance);
90
- } else if (creation.scope === Scope.REQUEST && requestLocals) {
91
- requestLocals.set(token, creation.instance);
92
- }
93
- // INSTANCE scope is never cached
94
-
95
- return creation;
96
- }
97
-
98
- private createInstance(config: ProviderConfig, requestLocals?: Map<Token, any>): { instance: any, scope: Scope } {
99
- const target = config.useClass ?? config.token;
100
-
101
- if (this.resolving.has(target)) {
102
- throw new Error(`Circular dependency detected: ${target.name}`);
103
- }
104
-
105
- this.resolving.add(target);
106
-
107
- try {
108
- const depsToken = this.getDependencies(target);
109
-
110
- if (depsToken.length === 0) {
111
- // No deps: Scope is as configured
112
- return { instance: new target(), scope: config.scope || Scope.SINGLETON };
113
- }
114
-
115
- const args: any[] = [];
116
- let effectiveScope = config.scope || Scope.SINGLETON;
117
-
118
- for (const depToken of depsToken) {
119
- const depResult = this.resolveInternal(depToken, requestLocals);
120
- args.push(depResult.instance);
121
-
122
- // Scope Bubbling Logic:
123
- // If a dependency is REQUEST scoped, the parent MUST become REQUEST scoped (if it was Singleton)
124
- // to avoid holding a stale reference to a request-bound instance.
125
- if (depResult.scope === Scope.REQUEST && effectiveScope === Scope.SINGLETON) {
126
- effectiveScope = Scope.REQUEST;
127
- }
128
-
129
- // Note: INSTANCE scope dependencies do not force bubbling because they are transient and safe to hold (usually),
130
- // unless semantic logic dictates otherwise. For now, strictly bubbling REQUEST scope.
131
- }
132
-
133
- return { instance: new target(...args), scope: effectiveScope };
134
- } finally {
135
- this.resolving.delete(target);
136
- }
137
- }
138
-
139
- private getDependencies(target: Token): Token[] {
140
- const types = Reflect.getMetadata('design:paramtypes', target) || [];
141
- return types.filter((t: any) => t && typeof t === 'function' && !this.isPrimitive(t));
142
- }
143
-
144
- private isPrimitive(type: any): boolean {
145
- return type === String || type === Number || type === Boolean || type === Object || type === Array || type === Symbol;
146
- }
147
-
148
- private normalizeConfig<T>(config: ProviderConfig<T> | Token<T>): ProviderConfig<T> {
149
- if (typeof config === 'function') {
150
- return {
151
- token: config,
152
- useClass: config,
153
- scope: Scope.SINGLETON
154
- };
155
- }
156
-
157
- return {
158
- ...config,
159
- useClass: config.useClass ?? config.token,
160
- scope: config.scope ?? Scope.SINGLETON
161
- };
162
- }
163
-
164
- clear(): void {
165
- this.configs.clear();
166
- this.instances.clear();
167
- }
168
- }
1
+ /**
2
+ * Lightweight DI Container for Turbo.
3
+ *
4
+ * Features:
5
+ * - Constructor injection via reflect-metadata
6
+ * - Singleton scope by default
7
+ * - Request scope support
8
+ * - Lazy instantiation
9
+ */
10
+
11
+ export type Token<T = any> = new (...args: any[]) => T;
12
+
13
+ export enum Scope {
14
+ SINGLETON = 'singleton', // Always the same instance
15
+ REQUEST = 'request', // New instance per request
16
+ INSTANCE = 'instance' // New instance per dependency injection
17
+ }
18
+
19
+ export interface ProviderConfig<T = any> {
20
+ token: Token<T>;
21
+ useClass?: Token<T>;
22
+ useValue?: T;
23
+ scope?: Scope;
24
+ }
25
+
26
+ interface ProviderEntry {
27
+ config: ProviderConfig;
28
+ instance: any | null;
29
+ }
30
+
31
+ export class Container {
32
+ private configs = new Map<Token, ProviderConfig>();
33
+ private instances = new Map<Token, any>();
34
+ private resolving = new Set<Token>();
35
+
36
+ register<T>(config: ProviderConfig<T> | Token<T>): this {
37
+ const normalized = this.normalizeConfig(config);
38
+
39
+ this.configs.set(normalized.token, normalized);
40
+
41
+ if (normalized.useValue !== undefined) {
42
+ this.instances.set(normalized.token, normalized.useValue);
43
+ }
44
+
45
+ return this;
46
+ }
47
+
48
+ get<T>(token: Token<T>): T {
49
+ const cached = this.instances.get(token);
50
+
51
+ if (cached !== undefined) {
52
+ return cached;
53
+ }
54
+
55
+ const res = this.resolveInternal(token);
56
+ return res.instance;
57
+ }
58
+
59
+ has(token: Token): boolean {
60
+ return this.configs.has(token);
61
+ }
62
+
63
+ /**
64
+ * Resolves a token to return instance and its effective scope.
65
+ */
66
+ private resolveInternal<T>(token: Token<T>, requestLocals?: Map<Token, any>): { instance: T, scope: Scope } {
67
+ // 1. Check Request Cache
68
+ if (requestLocals?.has(token)) {
69
+ return { instance: requestLocals.get(token), scope: Scope.REQUEST };
70
+ }
71
+
72
+ // 2. Check Singleton Cache
73
+ const cached = this.instances.get(token);
74
+ if (cached !== undefined) {
75
+ return { instance: cached, scope: Scope.SINGLETON };
76
+ }
77
+
78
+ const config = this.configs.get(token);
79
+
80
+ if (!config) {
81
+ throw new Error(`Provider not found: ${token.name}`);
82
+ }
83
+
84
+ // 3. Create Instance with Scope Bubbling
85
+ const creation = this.createInstance(config, requestLocals);
86
+
87
+ // 4. Cache based on Effective Scope
88
+ if (creation.scope === Scope.SINGLETON) {
89
+ this.instances.set(token, creation.instance);
90
+ } else if (creation.scope === Scope.REQUEST && requestLocals) {
91
+ requestLocals.set(token, creation.instance);
92
+ }
93
+ // INSTANCE scope is never cached
94
+
95
+ return creation;
96
+ }
97
+
98
+ private createInstance(config: ProviderConfig, requestLocals?: Map<Token, any>): { instance: any, scope: Scope } {
99
+ const target = config.useClass ?? config.token;
100
+
101
+ if (this.resolving.has(target)) {
102
+ throw new Error(`Circular dependency detected: ${target.name}`);
103
+ }
104
+
105
+ this.resolving.add(target);
106
+
107
+ try {
108
+ const depsToken = this.getDependencies(target);
109
+
110
+ if (depsToken.length === 0) {
111
+ // No deps: Scope is as configured
112
+ return { instance: new target(), scope: config.scope || Scope.SINGLETON };
113
+ }
114
+
115
+ const args: any[] = [];
116
+ let effectiveScope = config.scope || Scope.SINGLETON;
117
+
118
+ for (const depToken of depsToken) {
119
+ const depResult = this.resolveInternal(depToken, requestLocals);
120
+ args.push(depResult.instance);
121
+
122
+ // Scope Bubbling Logic:
123
+ // If a dependency is REQUEST scoped, the parent MUST become REQUEST scoped (if it was Singleton)
124
+ // to avoid holding a stale reference to a request-bound instance.
125
+ if (depResult.scope === Scope.REQUEST && effectiveScope === Scope.SINGLETON) {
126
+ effectiveScope = Scope.REQUEST;
127
+ }
128
+
129
+ // Note: INSTANCE scope dependencies do not force bubbling because they are transient and safe to hold (usually),
130
+ // unless semantic logic dictates otherwise. For now, strictly bubbling REQUEST scope.
131
+ }
132
+
133
+ return { instance: new target(...args), scope: effectiveScope };
134
+ } finally {
135
+ this.resolving.delete(target);
136
+ }
137
+ }
138
+
139
+ private getDependencies(target: Token): Token[] {
140
+ const types = Reflect.getMetadata('design:paramtypes', target) || [];
141
+ return types.filter((t: any) => t && typeof t === 'function' && !this.isPrimitive(t));
142
+ }
143
+
144
+ private isPrimitive(type: any): boolean {
145
+ return type === String || type === Number || type === Boolean || type === Object || type === Array || type === Symbol;
146
+ }
147
+
148
+ private normalizeConfig<T>(config: ProviderConfig<T> | Token<T>): ProviderConfig<T> {
149
+ if (typeof config === 'function') {
150
+ return {
151
+ token: config,
152
+ useClass: config,
153
+ scope: Scope.SINGLETON
154
+ };
155
+ }
156
+
157
+ return {
158
+ ...config,
159
+ useClass: config.useClass ?? config.token,
160
+ scope: config.scope ?? Scope.SINGLETON
161
+ };
162
+ }
163
+
164
+ clear(): void {
165
+ this.configs.clear();
166
+ this.instances.clear();
167
+ }
168
+ }
@@ -1,130 +1,130 @@
1
- /**
2
- * Request Context for Turbo.
3
- *
4
- * Lazy initialization for maximum performance:
5
- * - Query parsed only when accessed
6
- * - Body parsed only when needed
7
- * - Minimal allocations in hot path
8
- */
9
-
10
- import { parseQueryFromURL } from '../utils/parseQuery';
11
-
12
- const EMPTY_PARAMS: Record<string, string> = Object.freeze({}) as Record<string, string>;
13
-
14
- export class Context {
15
- readonly req: Request;
16
- params: Record<string, string>;
17
- locals: Record<string, any> = {};
18
-
19
- // Lazy fields - only allocated when accessed
20
- private _query: Record<string, string> | null = null;
21
- private _body: any;
22
- private _bodyParsed = false;
23
- private _url: URL | null = null;
24
- private _status = 0;
25
-
26
- constructor(req: Request, params: Record<string, string> = EMPTY_PARAMS) {
27
- this.req = req;
28
- this.params = params;
29
- }
30
-
31
- get status(): number {
32
- return this._status || 200;
33
- }
34
-
35
- set status(value: number) {
36
- this._status = value;
37
- }
38
-
39
- get url(): URL {
40
- if (!this._url) {
41
- this._url = new URL(this.req.url);
42
- }
43
-
44
- return this._url;
45
- }
46
-
47
- get query(): Record<string, string> {
48
- if (!this._query) {
49
- this._query = parseQueryFromURL(this.req.url);
50
- }
51
-
52
- return this._query;
53
- }
54
-
55
- get body(): any {
56
- return this._body;
57
- }
58
-
59
- async parseBody(): Promise<any> {
60
- if (this._bodyParsed) {
61
- return this._body;
62
- }
63
-
64
- this._bodyParsed = true;
65
- const contentType = this.req.headers.get('content-type') || '';
66
-
67
- if (contentType.includes('application/json')) {
68
- this._body = await this.req.json();
69
- } else if (contentType.includes('form')) {
70
- const formData = await this.req.formData();
71
- this._body = Object.fromEntries(formData);
72
- } else if (contentType.includes('text')) {
73
- this._body = await this.req.text();
74
- } else {
75
- this._body = await this.req.arrayBuffer();
76
- }
77
-
78
- return this._body;
79
- }
80
-
81
- get method(): string {
82
- return this.req.method;
83
- }
84
-
85
- get headers(): Headers {
86
- return this.req.headers;
87
- }
88
-
89
- get path(): string {
90
- return this.url.pathname;
91
- }
92
-
93
- json(data: any, status?: number): Response {
94
- if (status) this.status = status;
95
-
96
- return Response.json(data, { status: this.status });
97
- }
98
-
99
- text(data: string, status?: number): Response {
100
- if (status) this.status = status;
101
-
102
- return new Response(data, {
103
- status: this.status,
104
- headers: { 'Content-Type': 'text/plain' }
105
- });
106
- }
107
-
108
- html(data: string, status?: number): Response {
109
- if (status) this.status = status;
110
-
111
- return new Response(data, {
112
- status: this.status,
113
- headers: { 'Content-Type': 'text/html' }
114
- });
115
- }
116
-
117
- redirect(url: string, status: number = 302): Response {
118
- return Response.redirect(url, status);
119
- }
120
-
121
- /**
122
- * Creates a Context from a job (for queue processing).
123
- */
124
- static createFromJob(job: any): Context {
125
- const fakeRequest = new Request('http://localhost/job');
126
- const ctx = new Context(fakeRequest);
127
- ctx.locals.job = job;
128
- return ctx;
129
- }
130
- }
1
+ /**
2
+ * Request Context for Turbo.
3
+ *
4
+ * Lazy initialization for maximum performance:
5
+ * - Query parsed only when accessed
6
+ * - Body parsed only when needed
7
+ * - Minimal allocations in hot path
8
+ */
9
+
10
+ import { parseQueryFromURL } from '../utils/parseQuery';
11
+
12
+ const EMPTY_PARAMS: Record<string, string> = Object.freeze({}) as Record<string, string>;
13
+
14
+ export class Context {
15
+ readonly req: Request;
16
+ params: Record<string, string>;
17
+ locals: Record<string, any> = {};
18
+
19
+ // Lazy fields - only allocated when accessed
20
+ private _query: Record<string, string> | null = null;
21
+ private _body: any;
22
+ private _bodyParsed = false;
23
+ private _url: URL | null = null;
24
+ private _status = 0;
25
+
26
+ constructor(req: Request, params: Record<string, string> = EMPTY_PARAMS) {
27
+ this.req = req;
28
+ this.params = params;
29
+ }
30
+
31
+ get status(): number {
32
+ return this._status || 200;
33
+ }
34
+
35
+ set status(value: number) {
36
+ this._status = value;
37
+ }
38
+
39
+ get url(): URL {
40
+ if (!this._url) {
41
+ this._url = new URL(this.req.url);
42
+ }
43
+
44
+ return this._url;
45
+ }
46
+
47
+ get query(): Record<string, string> {
48
+ if (!this._query) {
49
+ this._query = parseQueryFromURL(this.req.url);
50
+ }
51
+
52
+ return this._query;
53
+ }
54
+
55
+ get body(): any {
56
+ return this._body;
57
+ }
58
+
59
+ async parseBody(): Promise<any> {
60
+ if (this._bodyParsed) {
61
+ return this._body;
62
+ }
63
+
64
+ this._bodyParsed = true;
65
+ const contentType = this.req.headers.get('content-type') || '';
66
+
67
+ if (contentType.includes('application/json')) {
68
+ this._body = await this.req.json();
69
+ } else if (contentType.includes('form')) {
70
+ const formData = await this.req.formData();
71
+ this._body = Object.fromEntries(formData);
72
+ } else if (contentType.includes('text')) {
73
+ this._body = await this.req.text();
74
+ } else {
75
+ this._body = await this.req.arrayBuffer();
76
+ }
77
+
78
+ return this._body;
79
+ }
80
+
81
+ get method(): string {
82
+ return this.req.method;
83
+ }
84
+
85
+ get headers(): Headers {
86
+ return this.req.headers;
87
+ }
88
+
89
+ get path(): string {
90
+ return this.url.pathname;
91
+ }
92
+
93
+ json(data: any, status?: number): Response {
94
+ if (status) this.status = status;
95
+
96
+ return Response.json(data, { status: this.status });
97
+ }
98
+
99
+ text(data: string, status?: number): Response {
100
+ if (status) this.status = status;
101
+
102
+ return new Response(data, {
103
+ status: this.status,
104
+ headers: { 'Content-Type': 'text/plain' }
105
+ });
106
+ }
107
+
108
+ html(data: string, status?: number): Response {
109
+ if (status) this.status = status;
110
+
111
+ return new Response(data, {
112
+ status: this.status,
113
+ headers: { 'Content-Type': 'text/html' }
114
+ });
115
+ }
116
+
117
+ redirect(url: string, status: number = 302): Response {
118
+ return Response.redirect(url, status);
119
+ }
120
+
121
+ /**
122
+ * Creates a Context from a job (for queue processing).
123
+ */
124
+ static createFromJob(job: any): Context {
125
+ const fakeRequest = new Request('http://localhost/job');
126
+ const ctx = new Context(fakeRequest);
127
+ ctx.locals.job = job;
128
+ return ctx;
129
+ }
130
+ }