@travetto/web 6.0.0-rc.4 → 6.0.0-rc.6

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 CHANGED
@@ -377,7 +377,7 @@ Out of the box, the web framework comes with a few interceptors, and more are co
377
377
  1. global - Intended to run outside of the request flow - [AsyncContextInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/context.ts#L13)
378
378
  1. terminal - Handles once request and response are finished building - [LoggingInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/logging.ts#L28), [RespondInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/respond.ts#L12)
379
379
  1. pre-request - Prepares the request for running - [TrustProxyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/trust-proxy.ts#L23)
380
- 1. request - Handles inbound request, validation, and body preparation - [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53), [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34), [BodyParseInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body-parse.ts#L61), [CookiesInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookies.ts#L56)
380
+ 1. request - Handles inbound request, validation, and body preparation - [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53), [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34), [BodyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body.ts#L57), [CookieInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookie.ts#L60)
381
381
  1. response - Prepares outbound response - [CompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/compress.ts#L50), [CorsInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cors.ts#L51), [EtagInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/etag.ts#L34), [ResponseCacheInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/response-cache.ts#L30)
382
382
  1. application - Lives outside of the general request/response behavior, [Web Auth](https://github.com/travetto/travetto/tree/main/module/auth-web#readme "Web authentication integration support for the Travetto framework") uses this for login and logout flows.
383
383
 
@@ -460,8 +460,8 @@ export class DecompressConfig {
460
460
  }
461
461
  ```
462
462
 
463
- #### CookiesInterceptor
464
- [CookiesInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookies.ts#L56) is responsible for processing inbound cookie headers and populating the appropriate data on the request, as well as sending the appropriate response data
463
+ #### CookieInterceptor
464
+ [CookieInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookie.ts#L60) is responsible for processing inbound cookie headers and populating the appropriate data on the request, as well as sending the appropriate response data
465
465
 
466
466
  **Code: Cookies Config**
467
467
  ```typescript
@@ -473,7 +473,7 @@ export class CookieConfig implements CookieSetOptions {
473
473
  /**
474
474
  * Are they signed
475
475
  */
476
- signed = true;
476
+ signed?: boolean;
477
477
  /**
478
478
  * Supported only via http (not in JS)
479
479
  */
@@ -490,20 +490,24 @@ export class CookieConfig implements CookieSetOptions {
490
490
  /**
491
491
  * Is the cookie only valid for https
492
492
  */
493
- secure?: boolean = false;
493
+ secure?: boolean;
494
494
  /**
495
495
  * The domain of the cookie
496
496
  */
497
497
  domain?: string;
498
+ /**
499
+ * The default path of the cookie
500
+ */
501
+ path: string = '/';
498
502
  }
499
503
  ```
500
504
 
501
- #### BodyParseInterceptor
502
- [BodyParseInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body-parse.ts#L61) handles the inbound request, and converting the body payload into an appropriate format.
505
+ #### BodyInterceptor
506
+ [BodyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body.ts#L57) handles the inbound request, and converting the body payload into an appropriate format.
503
507
 
504
- **Code: Body Parse Config**
508
+ **Code: Body Config**
505
509
  ```typescript
506
- export class BodyParseConfig {
510
+ export class WebBodyConfig {
507
511
  /**
508
512
  * Parse request body
509
513
  */
@@ -523,10 +527,6 @@ export class BodyParseConfig {
523
527
 
524
528
  @Ignore()
525
529
  _limit: number | undefined;
526
-
527
- postConstruct(): void {
528
- this._limit = WebCommonUtil.parseByteSize(this.limit);
529
- }
530
530
  }
531
531
  ```
532
532
 
package/__index__.ts CHANGED
@@ -24,7 +24,7 @@ export * from './src/registry/visitor.ts';
24
24
  export * from './src/registry/types.ts';
25
25
 
26
26
  export * from './src/interceptor/accept.ts';
27
- export * from './src/interceptor/body-parse.ts';
27
+ export * from './src/interceptor/body.ts';
28
28
  export * from './src/interceptor/cors.ts';
29
29
  export * from './src/interceptor/cookie.ts';
30
30
  export * from './src/interceptor/compress.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/web",
3
- "version": "6.0.0-rc.4",
3
+ "version": "6.0.0-rc.6",
4
4
  "description": "Declarative api for Web Applications with support for the dependency injection.",
5
5
  "keywords": [
6
6
  "web",
@@ -37,13 +37,12 @@
37
37
  "@types/negotiator": "^0.6.3",
38
38
  "find-my-way": "^9.3.0",
39
39
  "fresh": "^0.5.2",
40
- "iconv-lite": "^0.6.3",
41
40
  "keygrip": "^1.1.0",
42
41
  "negotiator": "^1.0.0"
43
42
  },
44
43
  "peerDependencies": {
45
44
  "@travetto/cli": "^6.0.0-rc.3",
46
- "@travetto/test": "^6.0.0-rc.2",
45
+ "@travetto/test": "^6.0.0-rc.3",
47
46
  "@travetto/transformer": "^6.0.0-rc.3"
48
47
  },
49
48
  "peerDependenciesMeta": {
package/src/context.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AsyncContextValue, AsyncContext } from '@travetto/context';
2
2
  import { Inject, Injectable } from '@travetto/di';
3
- import { AppError, castTo, Class, toConcrete } from '@travetto/runtime';
3
+ import { AppError, castTo, Class } from '@travetto/runtime';
4
4
 
5
5
  import { WebRequest } from './types/request.ts';
6
6
 
@@ -21,7 +21,7 @@ export class WebAsyncContext {
21
21
  }
22
22
 
23
23
  postConstruct(): void {
24
- this.registerSource(toConcrete<WebRequest>(), () => this.#request.get());
24
+ this.registerSource(WebRequest, () => this.#request.get());
25
25
  }
26
26
 
27
27
  withContext<T>(request: WebRequest, next: () => Promise<T>): Promise<T> {
@@ -24,10 +24,10 @@ export interface BodyContentParser {
24
24
  };
25
25
 
26
26
  /**
27
- * Web body parse configuration
27
+ * Web body configuration
28
28
  */
29
- @Config('web.bodyParse')
30
- export class BodyParseConfig {
29
+ @Config('web.body')
30
+ export class WebBodyConfig {
31
31
  /**
32
32
  * Parse request body
33
33
  */
@@ -47,25 +47,21 @@ export class BodyParseConfig {
47
47
 
48
48
  @Ignore()
49
49
  _limit: number | undefined;
50
-
51
- postConstruct(): void {
52
- this._limit = WebCommonUtil.parseByteSize(this.limit);
53
- }
54
50
  }
55
51
 
56
52
 
57
53
  /**
58
- * Parses the body input content
54
+ * Verifies content length, decodes character encodings, and parses body input string via the content type
59
55
  */
60
56
  @Injectable()
61
- export class BodyParseInterceptor implements WebInterceptor<BodyParseConfig> {
57
+ export class BodyInterceptor implements WebInterceptor<WebBodyConfig> {
62
58
 
63
59
  dependsOn = [AcceptInterceptor, DecompressInterceptor];
64
60
  category: WebInterceptorCategory = 'request';
65
61
  parsers: Record<string, BodyContentParser> = {};
66
62
 
67
63
  @Inject()
68
- config: BodyParseConfig;
64
+ config: WebBodyConfig;
69
65
 
70
66
  async postConstruct(): Promise<void> {
71
67
  // Load all the parser types
@@ -75,11 +71,11 @@ export class BodyParseInterceptor implements WebInterceptor<BodyParseConfig> {
75
71
  }
76
72
  }
77
73
 
78
- applies({ endpoint, config }: WebInterceptorContext<BodyParseConfig>): boolean {
74
+ applies({ endpoint, config }: WebInterceptorContext<WebBodyConfig>): boolean {
79
75
  return config.applies && endpoint.allowsBody;
80
76
  }
81
77
 
82
- async filter({ request, config, next }: WebChainedContext<BodyParseConfig>): Promise<WebResponse> {
78
+ async filter({ request, config, next }: WebChainedContext<WebBodyConfig>): Promise<WebResponse> {
83
79
  const input = request.body;
84
80
 
85
81
  if (!WebBodyUtil.isRaw(input)) {
@@ -88,8 +84,8 @@ export class BodyParseInterceptor implements WebInterceptor<BodyParseConfig> {
88
84
 
89
85
  const lengthRead = +(request.headers.get('Content-Length') || '');
90
86
  const length = Number.isNaN(lengthRead) ? undefined : lengthRead;
87
+ const limit = config._limit ??= WebCommonUtil.parseByteSize(config.limit);
91
88
 
92
- const limit = config._limit ?? Number.MAX_SAFE_INTEGER;
93
89
  if (length && length > limit) {
94
90
  throw WebError.for('Request entity too large', 413, { length, limit });
95
91
  }
package/src/util/body.ts CHANGED
@@ -1,5 +1,4 @@
1
- import iconv from 'iconv-lite';
2
-
1
+ import { TextDecoder } from 'node:util';
3
2
  import { Readable } from 'node:stream';
4
3
  import { buffer as toBuffer } from 'node:stream/consumers';
5
4
 
@@ -187,27 +186,33 @@ export class WebBodyUtil {
187
186
  static async readText(input: Readable | Buffer, limit: number, encoding?: string): Promise<{ text: string, read: number }> {
188
187
  encoding ??= (Buffer.isBuffer(input) ? undefined : input.readableEncoding) ?? 'utf-8';
189
188
 
190
- if (!iconv.encodingExists(encoding)) {
189
+ let decoder: TextDecoder;
190
+ try {
191
+ decoder = new TextDecoder(encoding);
192
+ } catch {
191
193
  throw WebError.for('Specified Encoding Not Supported', 415, { encoding });
192
194
  }
193
195
 
194
196
  if (Buffer.isBuffer(input)) {
195
- return { text: iconv.decode(input, encoding), read: input.byteLength };
197
+ if (input.byteLength > limit) {
198
+ throw WebError.for('Request Entity Too Large', 413, { received: input.byteLength, limit });
199
+ }
200
+ return { text: decoder.decode(input), read: input.byteLength };
196
201
  }
197
202
 
198
203
  let received = Buffer.isBuffer(input) ? input.byteOffset : 0;
199
- const decoder = iconv.getDecoder(encoding);
200
204
  const all: string[] = [];
201
205
 
202
206
  try {
203
- for await (const chunk of input.iterator({ destroyOnReturn: false })) {
204
- received += Buffer.isBuffer(chunk) ? chunk.byteLength : (typeof chunk === 'string' ? chunk.length : chunk.length);
207
+ for await (const chunk of castTo<AsyncIterable<string | Buffer>>(input.iterator({ destroyOnReturn: false }))) {
208
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf8');
209
+ received += buffer.byteLength;
205
210
  if (received > limit) {
206
211
  throw WebError.for('Request Entity Too Large', 413, { received, limit });
207
212
  }
208
- all.push(decoder.write(chunk));
213
+ all.push(decoder.decode(buffer, { stream: true }));
209
214
  }
210
- all.push(decoder.end() ?? '');
215
+ all.push(decoder.decode(Buffer.alloc(0), { stream: false }));
211
216
  return { text: all.join(''), read: received };
212
217
  } catch (err) {
213
218
  if (err instanceof Error && err.name === 'AbortError') {
@@ -44,21 +44,26 @@ export class WebTransformer {
44
44
  // If non-regex
45
45
  if (arg && ts.isStringLiteral(arg)) {
46
46
  const literal = LiteralUtil.toLiteral(arg);
47
- if (typeof literal !== 'string') {
48
- throw new Error(`Unexpected literal type: ${literal}`);
49
- }
50
- // If param name matches path param, default to @Path
47
+ // If param name matches path param, default to @PathParam
51
48
  detectedParamType = new RegExp(`:${name}\\b`).test(literal) ? 'PathParam' : 'QueryParam';
52
49
  } else {
53
50
  // Default to query for empty or regex endpoints
54
51
  detectedParamType = 'QueryParam';
55
52
  }
56
- } else if (epDec.ident.getText() !== 'All') { // Treat all separate
53
+ } else {
57
54
  // Treat as schema, and see if endpoint supports a body for default behavior on untyped
58
55
  detectedParamType = epDec.targets?.includes('@travetto/web:HttpRequestBody') ? 'Body' : 'QueryParam';
59
56
  config.name = '';
60
57
  }
61
58
 
59
+ if (paramType.key === 'managed') {
60
+ if (paramType.name === 'WebResponse') {
61
+ throw new Error(`${paramType.name} must be registered using @ContextParam`);
62
+ } else if (paramType.name === 'WebRequest') {
63
+ throw new Error(`${paramType.name} is an invalid endpoint parameter`);
64
+ }
65
+ }
66
+
62
67
  node = SchemaTransformUtil.computeField(state, node, config);
63
68
 
64
69
  const modifiers = (node.modifiers ?? []).filter(x => x !== pDec);