@loutrejs/loutre 0.5.0 → 0.7.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.
Files changed (188) hide show
  1. package/README.md +63 -32
  2. package/dist/adapters/aws-lambda.d.ts +5 -3
  3. package/dist/adapters/aws-lambda.js +53 -23
  4. package/dist/adapters/bun.d.ts +7 -4
  5. package/dist/adapters/bun.js +52 -42
  6. package/dist/adapters/cloudflare-workers.d.ts +5 -3
  7. package/dist/adapters/cloudflare-workers.js +23 -28
  8. package/dist/adapters/deno.d.ts +7 -4
  9. package/dist/adapters/deno.js +94 -57
  10. package/dist/adapters/electron.d.ts +13 -5
  11. package/dist/adapters/electron.js +76 -28
  12. package/dist/application/definition.d.ts +31 -0
  13. package/dist/application/definition.js +16 -0
  14. package/dist/application/index.d.ts +4 -63
  15. package/dist/application/index.js +2 -11
  16. package/dist/application/kernel-internal.d.ts +2 -0
  17. package/dist/application/kernel-internal.js +10 -0
  18. package/dist/application/kernel.d.ts +22 -0
  19. package/dist/application/kernel.js +65 -0
  20. package/dist/core/diagnostic.d.ts +9 -0
  21. package/dist/core/diagnostic.js +9 -0
  22. package/dist/core/extension.d.ts +118 -0
  23. package/dist/core/extension.js +73 -0
  24. package/dist/core/generic-layer.d.ts +26 -0
  25. package/dist/core/generic-layer.js +116 -0
  26. package/dist/core/index.d.ts +32 -15
  27. package/dist/core/index.js +15 -15
  28. package/dist/core/injection.d.ts +4 -13
  29. package/dist/core/injection.js +63 -0
  30. package/dist/core/instrumentation.d.ts +36 -0
  31. package/dist/core/model/context.d.ts +18 -0
  32. package/dist/core/model/context.js +34 -0
  33. package/dist/core/model/lifecycle.d.ts +5 -0
  34. package/dist/core/model/lifecycle.js +44 -0
  35. package/dist/core/model/modules.d.ts +2 -0
  36. package/dist/core/model/modules.js +101 -0
  37. package/dist/core/model/providers.d.ts +4 -0
  38. package/dist/core/model/providers.js +74 -0
  39. package/dist/core/model/snapshot.d.ts +6 -0
  40. package/dist/core/model/snapshot.js +31 -0
  41. package/dist/core/model/types.d.ts +78 -0
  42. package/dist/core/model/types.js +1 -0
  43. package/dist/core/model/validation.d.ts +4 -0
  44. package/dist/core/model/validation.js +40 -0
  45. package/dist/core/model/visibility.d.ts +4 -0
  46. package/dist/core/model/visibility.js +20 -0
  47. package/dist/core/model.d.ts +10 -0
  48. package/dist/core/model.js +346 -0
  49. package/dist/core/module.d.ts +8 -13
  50. package/dist/core/source-location.d.ts +8 -0
  51. package/dist/core/source-location.js +31 -0
  52. package/dist/core/token-internal.d.ts +3 -0
  53. package/dist/core/token-internal.js +4 -0
  54. package/dist/core/type.d.ts +7 -0
  55. package/dist/core/type.js +4 -0
  56. package/dist/devtools/capsule.d.ts +51 -0
  57. package/dist/devtools/capsule.js +245 -0
  58. package/dist/devtools/index.d.ts +11 -0
  59. package/dist/devtools/index.js +6 -0
  60. package/dist/devtools/inspector.d.ts +57 -0
  61. package/dist/devtools/inspector.js +1 -0
  62. package/dist/devtools/module.d.ts +18 -0
  63. package/dist/devtools/module.js +26 -0
  64. package/dist/devtools/observer.d.ts +77 -0
  65. package/dist/devtools/observer.js +10 -0
  66. package/dist/devtools/protocol.d.ts +1 -0
  67. package/dist/devtools/protocol.js +1 -0
  68. package/dist/devtools/provider-instrumentation.d.ts +14 -0
  69. package/dist/devtools/provider-instrumentation.js +117 -0
  70. package/dist/devtools/runtime.d.ts +20 -0
  71. package/dist/devtools/runtime.js +519 -0
  72. package/dist/devtools/values.d.ts +35 -0
  73. package/dist/devtools/values.js +175 -0
  74. package/dist/generated/version.d.ts +1 -1
  75. package/dist/generated/version.js +1 -1
  76. package/dist/graph/index.d.ts +2 -3
  77. package/dist/graph/index.js +1 -3
  78. package/dist/graph/model.d.ts +34 -0
  79. package/dist/graph/model.js +127 -0
  80. package/dist/http/auth.d.ts +46 -0
  81. package/dist/http/auth.js +96 -0
  82. package/dist/http/client.d.ts +28 -31
  83. package/dist/http/client.js +58 -44
  84. package/dist/http/cors-internal.d.ts +29 -0
  85. package/dist/http/cors-internal.js +160 -0
  86. package/dist/http/cors.d.ts +5 -8
  87. package/dist/http/cors.js +7 -165
  88. package/dist/http/extension.d.ts +348 -0
  89. package/dist/http/extension.js +1061 -0
  90. package/dist/http/index.d.ts +9 -7
  91. package/dist/http/index.js +5 -7
  92. package/dist/{application → http}/openapi.d.ts +12 -3
  93. package/dist/{application → http}/openapi.js +164 -102
  94. package/dist/http/path.d.ts +1 -42
  95. package/dist/http/path.js +1 -2
  96. package/dist/http/runtime/request.d.ts +17 -0
  97. package/dist/http/runtime/request.js +109 -0
  98. package/dist/http/runtime/response.d.ts +18 -0
  99. package/dist/http/runtime/response.js +108 -0
  100. package/dist/index.d.ts +4 -0
  101. package/dist/index.js +2 -0
  102. package/dist/message-port/extension.d.ts +66 -0
  103. package/dist/message-port/extension.js +411 -0
  104. package/dist/message-port/index.d.ts +4 -73
  105. package/dist/message-port/index.js +2 -189
  106. package/dist/message-port/transport.d.ts +11 -0
  107. package/dist/message-port/transport.js +45 -0
  108. package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
  109. package/dist/runtime/async-iterator-lifecycle.js +95 -0
  110. package/dist/runtime/capability.d.ts +11 -11
  111. package/dist/runtime/capability.js +3 -3
  112. package/dist/runtime/di.d.ts +5 -21
  113. package/dist/runtime/di.js +22 -289
  114. package/dist/runtime/execution-tracker.d.ts +8 -0
  115. package/dist/runtime/execution-tracker.js +54 -0
  116. package/dist/runtime/index.d.ts +15 -9
  117. package/dist/runtime/index.js +9 -9
  118. package/dist/runtime/ingress-gate.d.ts +7 -0
  119. package/dist/runtime/ingress-gate.js +33 -0
  120. package/dist/runtime/kernel.d.ts +25 -0
  121. package/dist/runtime/kernel.js +447 -0
  122. package/dist/runtime/logger.d.ts +2 -0
  123. package/dist/runtime/logger.js +2 -0
  124. package/dist/runtime/node-capabilities.d.ts +1 -1
  125. package/dist/runtime/node-capabilities.js +1 -1
  126. package/dist/runtime/server-port.d.ts +2 -0
  127. package/dist/runtime/server-port.js +11 -0
  128. package/dist/tasks/extension.d.ts +144 -0
  129. package/dist/tasks/extension.js +525 -0
  130. package/dist/tasks/index.d.ts +2 -0
  131. package/dist/tasks/index.js +1 -0
  132. package/dist/tasks/runtime/cron.d.ts +6 -0
  133. package/dist/tasks/runtime/cron.js +89 -0
  134. package/dist/websocket/errors.d.ts +9 -0
  135. package/dist/websocket/errors.js +18 -0
  136. package/dist/websocket/extension.d.ts +154 -0
  137. package/dist/websocket/extension.js +533 -0
  138. package/dist/websocket/index.d.ts +3 -0
  139. package/dist/websocket/index.js +2 -0
  140. package/package.json +16 -16
  141. package/dist/application/binding.d.ts +0 -33
  142. package/dist/application/binding.js +0 -295
  143. package/dist/application/host.d.ts +0 -8
  144. package/dist/application/host.js +0 -14
  145. package/dist/application/queue.d.ts +0 -3
  146. package/dist/application/queue.js +0 -4
  147. package/dist/application/scheduler.d.ts +0 -2
  148. package/dist/application/scheduler.js +0 -45
  149. package/dist/core/context-key.d.ts +0 -16
  150. package/dist/core/context-key.js +0 -17
  151. package/dist/core/contract-internal.d.ts +0 -22
  152. package/dist/core/contract-internal.js +0 -61
  153. package/dist/core/contract.d.ts +0 -96
  154. package/dist/core/contract.js +0 -88
  155. package/dist/core/execution.d.ts +0 -78
  156. package/dist/core/execution.js +0 -61
  157. package/dist/core/implementation.d.ts +0 -44
  158. package/dist/core/implementation.js +0 -35
  159. package/dist/core/layer.d.ts +0 -137
  160. package/dist/core/layer.js +0 -81
  161. package/dist/graph/compiler.d.ts +0 -4
  162. package/dist/graph/compiler.js +0 -228
  163. package/dist/graph/graph.d.ts +0 -18
  164. package/dist/graph/graph.js +0 -1118
  165. package/dist/graph/ir.d.ts +0 -175
  166. package/dist/http/application.d.ts +0 -18
  167. package/dist/http/application.js +0 -515
  168. package/dist/http/basic-auth.d.ts +0 -37
  169. package/dist/http/basic-auth.js +0 -67
  170. package/dist/http/definitions.d.ts +0 -363
  171. package/dist/http/definitions.js +0 -310
  172. package/dist/http/hosted-protocol.d.ts +0 -4
  173. package/dist/http/hosted-protocol.js +0 -4
  174. package/dist/http/internal.d.ts +0 -8
  175. package/dist/http/internal.js +0 -1
  176. package/dist/http/params.d.ts +0 -2
  177. package/dist/http/params.js +0 -19
  178. package/dist/http/request-body.d.ts +0 -4
  179. package/dist/http/request-body.js +0 -6
  180. package/dist/http/validate.d.ts +0 -20
  181. package/dist/http/validate.js +0 -6
  182. package/dist/message-port/environment.d.ts +0 -2
  183. package/dist/message-port/environment.js +0 -4
  184. package/dist/runtime/application.d.ts +0 -38
  185. package/dist/runtime/application.js +0 -339
  186. package/dist/runtime/pipeline.d.ts +0 -11
  187. package/dist/runtime/pipeline.js +0 -161
  188. /package/dist/{graph/ir.js → core/instrumentation.js} +0 -0
@@ -1,6 +1,5 @@
1
1
  import { SchemaValidationError, validateSchema, } from '../core/index.js';
2
- import { contractOfBinding } from '../core/contract-internal.js';
3
- import { parseHttpPath, } from './path.js';
2
+ import { parseHttpPath } from './path.js';
4
3
  export class HttpClientResponseError extends Error {
5
4
  status;
6
5
  method;
@@ -15,13 +14,9 @@ export class HttpClientResponseError extends Error {
15
14
  this.name = 'HttpClientResponseError';
16
15
  }
17
16
  }
18
- export function createHttpClient(binding, transport) {
19
- const contract = contractOfBinding(binding);
17
+ export function createHttpClient(contract, transport) {
20
18
  const client = {};
21
- for (const [procedureName, procedure] of Object.entries(contract.procedures)) {
22
- const definition = httpDefinitionOf(procedure);
23
- if (!definition)
24
- continue;
19
+ for (const [procedureName, definition] of Object.entries(contract.routes)) {
25
20
  client[procedureName] = async (request = {}) => {
26
21
  const response = await transport({
27
22
  method: definition.method,
@@ -29,24 +24,12 @@ export function createHttpClient(binding, transport) {
29
24
  ...(request.query === undefined ? {} : { query: request.query }),
30
25
  ...(request.headers === undefined ? {} : { headers: request.headers }),
31
26
  ...(request.body === undefined ? {} : { body: request.body }),
32
- ...(definition.request?.body === undefined
33
- ? {}
34
- : { contentType: definition.request.body.contentType }),
35
27
  });
36
28
  return decodeResponse(procedureName, definition, response);
37
29
  };
38
30
  }
39
31
  return Object.freeze(client);
40
32
  }
41
- function httpDefinitionOf(procedure) {
42
- const protocol = procedure.protocols.http;
43
- if (!protocol ||
44
- protocol.protocol !== 'http' ||
45
- !('definition' in protocol)) {
46
- return undefined;
47
- }
48
- return protocol.definition;
49
- }
50
33
  function interpolatePath(path, params) {
51
34
  const segments = parseHttpPath(path);
52
35
  if (segments.length === 0)
@@ -72,11 +55,23 @@ async function decodeResponse(procedure, definition, response) {
72
55
  let lastValidationError;
73
56
  for (const candidate of candidates) {
74
57
  try {
75
- const body = candidate.stream
76
- ? validatedResponseStream(candidate.body, response.body, procedure, definition, response.status)
77
- : await validateSchema(candidate.body, response.body);
78
- const headers = candidate.headers
79
- ? await validateSchema(candidate.headers, response.headers ?? {})
58
+ let body;
59
+ if (candidate.stream === 'server') {
60
+ if (!candidate.body)
61
+ continue;
62
+ body = validatedResponseStream(candidate.body, response.body, procedure, definition, response.status);
63
+ }
64
+ else if (candidate.body) {
65
+ body = await validateSchema(candidate.body, response.body);
66
+ }
67
+ else {
68
+ if (response.body !== undefined && response.body !== null)
69
+ continue;
70
+ body = undefined;
71
+ }
72
+ const headerSchema = responseHeadersSchema(candidate.headers);
73
+ const headers = headerSchema
74
+ ? await validateSchema(headerSchema, response.headers ?? {})
80
75
  : (response.headers ?? {});
81
76
  return Object.freeze({ status: response.status, body, headers });
82
77
  }
@@ -88,6 +83,22 @@ async function decodeResponse(procedure, definition, response) {
88
83
  }
89
84
  throw new HttpClientResponseError(response.status, definition.method, definition.path, procedure, `${target} returned a response that does not match its Contract`, lastValidationError ? { cause: lastValidationError } : undefined);
90
85
  }
86
+ function responseHeadersSchema(headers) {
87
+ if (isStandardSchema(headers))
88
+ return headers;
89
+ if (isResponseHeadersWithDefaults(headers))
90
+ return headers.schema;
91
+ return undefined;
92
+ }
93
+ function isStandardSchema(value) {
94
+ return typeof value === 'object' && value !== null && '~standard' in value;
95
+ }
96
+ function isResponseHeadersWithDefaults(value) {
97
+ return (typeof value === 'object' &&
98
+ value !== null &&
99
+ 'schema' in value &&
100
+ isStandardSchema(value.schema));
101
+ }
91
102
  function validatedResponseStream(schema, value, procedure, definition, status) {
92
103
  const target = describeHttpTarget(definition, procedure);
93
104
  if (!isAsyncIterable(value)) {
@@ -121,15 +132,18 @@ export function fetchHttpTransport(options) {
121
132
  appendQuery(url.searchParams, request.query);
122
133
  const headers = new Headers(options.headers);
123
134
  appendHeaders(headers, request.headers);
124
- if (request.contentType && !headers.has('content-type')) {
125
- headers.set('content-type', request.contentType);
135
+ const contentType = headers.get('content-type') ?? undefined;
136
+ const body = request.body === undefined
137
+ ? undefined
138
+ : encodeBody(request.body, contentType);
139
+ if (body instanceof FormData &&
140
+ normalizeMediaType(contentType) === 'multipart/form-data') {
141
+ headers.delete('content-type');
126
142
  }
127
143
  const response = await fetchImplementation(url, {
128
144
  method: request.method,
129
145
  headers,
130
- ...(request.body === undefined
131
- ? {}
132
- : { body: encodeBody(request.body, request.contentType) }),
146
+ ...(body === undefined ? {} : { body }),
133
147
  });
134
148
  return {
135
149
  status: response.status,
@@ -191,7 +205,8 @@ function encodeBody(body, contentType) {
191
205
  return body;
192
206
  }
193
207
  async function decodeFetchBody(response) {
194
- if (response.status === 204 ||
208
+ if (response.body === null ||
209
+ response.status === 204 ||
195
210
  response.status === 205 ||
196
211
  response.status === 304) {
197
212
  return undefined;
@@ -217,16 +232,25 @@ async function* decodeServerSentEvents(stream) {
217
232
  const decoder = new TextDecoder();
218
233
  let buffer = '';
219
234
  let completed = false;
235
+ let pendingCarriageReturn = false;
236
+ const appendDecoded = (value, final) => {
237
+ let decoded = pendingCarriageReturn ? `\r${value}` : value;
238
+ pendingCarriageReturn = false;
239
+ if (!final && decoded.endsWith('\r')) {
240
+ pendingCarriageReturn = true;
241
+ decoded = decoded.slice(0, -1);
242
+ }
243
+ buffer += normalizeEventStreamNewlines(decoded);
244
+ };
220
245
  try {
221
246
  while (true) {
222
247
  const next = await reader.read();
223
248
  if (next.done) {
224
249
  completed = true;
225
- buffer += decoder.decode();
250
+ appendDecoded(decoder.decode(), true);
226
251
  break;
227
252
  }
228
- buffer += decoder.decode(next.value, { stream: true });
229
- buffer = normalizeEventStreamNewlines(buffer);
253
+ appendDecoded(decoder.decode(next.value, { stream: true }), false);
230
254
  let boundary = buffer.indexOf('\n\n');
231
255
  while (boundary >= 0) {
232
256
  const event = buffer.slice(0, boundary);
@@ -241,16 +265,6 @@ async function* decodeServerSentEvents(stream) {
241
265
  boundary = buffer.indexOf('\n\n');
242
266
  }
243
267
  }
244
- buffer = normalizeEventStreamNewlines(buffer).trim();
245
- if (buffer.length > 0) {
246
- const data = buffer
247
- .split('\n')
248
- .filter((line) => line.startsWith('data:'))
249
- .map((line) => line.slice('data:'.length).trimStart())
250
- .join('\n');
251
- if (data.length > 0)
252
- yield JSON.parse(data);
253
- }
254
268
  }
255
269
  finally {
256
270
  if (!completed)
@@ -0,0 +1,29 @@
1
+ export type CorsOrigin = string | readonly string[] | ((origin: string) => boolean | Promise<boolean>);
2
+ export interface CorsPolicyOptions {
3
+ readonly origin?: CorsOrigin;
4
+ readonly allowMethods?: readonly string[];
5
+ readonly allowHeaders?: readonly string[];
6
+ readonly exposeHeaders?: readonly string[];
7
+ readonly credentials?: boolean;
8
+ readonly maxAge?: number;
9
+ }
10
+ export type CorsPolicy = Readonly<{
11
+ origin: {
12
+ readonly kind: 'wildcard';
13
+ } | {
14
+ readonly kind: 'exact';
15
+ readonly origins: ReadonlySet<string>;
16
+ } | {
17
+ readonly kind: 'predicate';
18
+ readonly predicate: (origin: string) => boolean | Promise<boolean>;
19
+ };
20
+ allowMethods: readonly string[] | undefined;
21
+ allowHeaders: readonly string[] | undefined;
22
+ exposeHeaders: readonly string[];
23
+ credentials: boolean;
24
+ maxAge: number | undefined;
25
+ }>;
26
+ export declare function normalizeCorsPolicy(options: CorsPolicyOptions): CorsPolicy;
27
+ export declare function registerCorsPolicy(middleware: object, policy: CorsPolicy): void;
28
+ export declare function createCorsActualResponseHeaders(middlewares: readonly object[], request: Request): Promise<Headers | undefined>;
29
+ export declare function createCorsPreflightResponseHeaders(middlewares: readonly object[], request: Request, targetMethod: string): Promise<Headers | undefined>;
@@ -0,0 +1,160 @@
1
+ const policies = new WeakMap();
2
+ const httpToken = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
3
+ export function normalizeCorsPolicy(options) {
4
+ const credentials = options.credentials ?? false;
5
+ return Object.freeze({
6
+ origin: normalizeOrigin(options.origin ?? '*', credentials),
7
+ allowMethods: options.allowMethods === undefined
8
+ ? undefined
9
+ : normalizeTokens(options.allowMethods, 'allowMethods', true),
10
+ allowHeaders: options.allowHeaders === undefined
11
+ ? undefined
12
+ : normalizeTokens(options.allowHeaders, 'allowHeaders', false),
13
+ exposeHeaders: normalizeTokens(options.exposeHeaders ?? [], 'exposeHeaders', false),
14
+ credentials,
15
+ maxAge: normalizeMaxAge(options.maxAge),
16
+ });
17
+ }
18
+ export function registerCorsPolicy(middleware, policy) {
19
+ policies.set(middleware, policy);
20
+ }
21
+ export async function createCorsActualResponseHeaders(middlewares, request) {
22
+ const policy = corsPolicyOfMiddlewares(middlewares);
23
+ if (!policy)
24
+ return undefined;
25
+ const origin = request.headers.get('origin');
26
+ const headers = new Headers();
27
+ if (policy.origin.kind !== 'wildcard')
28
+ appendVary(headers, 'Origin');
29
+ if (origin === null)
30
+ return headers;
31
+ await applyOrigin(headers, policy, origin);
32
+ if (!headers.has('access-control-allow-origin'))
33
+ return headers;
34
+ if (policy.credentials) {
35
+ headers.set('access-control-allow-credentials', 'true');
36
+ }
37
+ if (policy.exposeHeaders.length > 0) {
38
+ headers.set('access-control-expose-headers', policy.exposeHeaders.join(', '));
39
+ }
40
+ return headers;
41
+ }
42
+ export async function createCorsPreflightResponseHeaders(middlewares, request, targetMethod) {
43
+ const policy = corsPolicyOfMiddlewares(middlewares);
44
+ if (!policy)
45
+ return undefined;
46
+ const headers = new Headers();
47
+ appendVary(headers, 'Access-Control-Request-Method');
48
+ const origin = request.headers.get('origin');
49
+ if (origin === null)
50
+ return headers;
51
+ await applyOrigin(headers, policy, origin);
52
+ if (!headers.has('access-control-allow-origin'))
53
+ return headers;
54
+ if (policy.credentials) {
55
+ headers.set('access-control-allow-credentials', 'true');
56
+ }
57
+ const methods = policy.allowMethods ?? [targetMethod.toUpperCase()];
58
+ if (methods.length > 0) {
59
+ headers.set('access-control-allow-methods', methods.join(', '));
60
+ }
61
+ if (policy.allowHeaders === undefined) {
62
+ const requested = request.headers.get('access-control-request-headers');
63
+ if (requested !== null && requested.trim().length > 0) {
64
+ headers.set('access-control-allow-headers', requested);
65
+ appendVary(headers, 'Access-Control-Request-Headers');
66
+ }
67
+ }
68
+ else if (policy.allowHeaders.length > 0) {
69
+ headers.set('access-control-allow-headers', policy.allowHeaders.join(', '));
70
+ }
71
+ if (policy.maxAge !== undefined) {
72
+ headers.set('access-control-max-age', String(policy.maxAge));
73
+ }
74
+ return headers;
75
+ }
76
+ function corsPolicyOfMiddlewares(middlewares) {
77
+ let found;
78
+ for (const middleware of middlewares) {
79
+ const policy = policies.get(middleware);
80
+ if (policy)
81
+ found = policy;
82
+ }
83
+ return found;
84
+ }
85
+ async function applyOrigin(headers, policy, origin) {
86
+ if (policy.origin.kind === 'wildcard') {
87
+ headers.set('access-control-allow-origin', '*');
88
+ return;
89
+ }
90
+ appendVary(headers, 'Origin');
91
+ const allowed = policy.origin.kind === 'exact'
92
+ ? policy.origin.origins.has(origin)
93
+ : await policy.origin.predicate(origin);
94
+ if (allowed)
95
+ headers.set('access-control-allow-origin', origin);
96
+ }
97
+ function normalizeOrigin(origin, credentials) {
98
+ if (typeof origin === 'function') {
99
+ return Object.freeze({ kind: 'predicate', predicate: origin });
100
+ }
101
+ const origins = typeof origin === 'string' ? [origin] : [...origin];
102
+ if (origins.length === 0) {
103
+ return Object.freeze({ kind: 'exact', origins: new Set() });
104
+ }
105
+ for (const value of origins) {
106
+ if (value.length === 0 || containsControlCharacter(value)) {
107
+ throw new TypeError('CORS origin cannot be empty or contain control characters');
108
+ }
109
+ }
110
+ if (origins.includes('*')) {
111
+ if (credentials) {
112
+ throw new TypeError('CORS origin cannot be a wildcard when credentials are enabled');
113
+ }
114
+ return Object.freeze({ kind: 'wildcard' });
115
+ }
116
+ return Object.freeze({
117
+ kind: 'exact',
118
+ origins: new Set(origins),
119
+ });
120
+ }
121
+ function containsControlCharacter(value) {
122
+ for (const character of value) {
123
+ const code = character.charCodeAt(0);
124
+ if (code <= 0x1f || code === 0x7f)
125
+ return true;
126
+ }
127
+ return false;
128
+ }
129
+ function normalizeTokens(values, option, uppercase) {
130
+ return Object.freeze(values.map((value) => {
131
+ const token = value.trim();
132
+ if (!httpToken.test(token)) {
133
+ throw new TypeError(`CORS ${option} contains an invalid HTTP token`);
134
+ }
135
+ return uppercase ? token.toUpperCase() : token;
136
+ }));
137
+ }
138
+ function normalizeMaxAge(maxAge) {
139
+ if (maxAge === undefined)
140
+ return undefined;
141
+ if (!Number.isSafeInteger(maxAge) || maxAge < 0) {
142
+ throw new TypeError('CORS maxAge must be a non-negative safe integer');
143
+ }
144
+ return maxAge;
145
+ }
146
+ function appendVary(headers, value) {
147
+ const current = headers.get('vary');
148
+ if (current === null) {
149
+ headers.set('vary', value);
150
+ return;
151
+ }
152
+ const values = current
153
+ .split(',')
154
+ .map((item) => item.trim())
155
+ .filter(Boolean);
156
+ if (!values.some((item) => item.toLowerCase() === value.toLowerCase())) {
157
+ values.push(value);
158
+ }
159
+ headers.set('vary', values.join(', '));
160
+ }
@@ -1,5 +1,6 @@
1
- import { type LayerDescriptor, type PipelineItem } from '../core/index.js';
2
- export type CorsOrigin = string | readonly string[] | ((origin: string) => boolean | Promise<boolean>);
1
+ import { type CorsOrigin } from './cors-internal.js';
2
+ import type { HttpExecutionResult, HttpMiddlewareContext } from './extension.js';
3
+ export type { CorsOrigin } from './cors-internal.js';
3
4
  export interface CorsOptions {
4
5
  readonly origin?: CorsOrigin;
5
6
  readonly allowMethods?: readonly string[];
@@ -9,9 +10,5 @@ export interface CorsOptions {
9
10
  readonly maxAge?: number;
10
11
  readonly name?: string;
11
12
  }
12
- export interface CorsLayerDescriptor extends LayerDescriptor<readonly [], readonly [], never, readonly [], string, 'framework', readonly [], object> {
13
- readonly role: 'framework';
14
- }
15
- export declare function cors(options?: CorsOptions): CorsLayerDescriptor;
16
- export declare function createCorsActualResponseHeaders(pipeline: readonly PipelineItem[], request: Request): Promise<Headers | undefined>;
17
- export declare function createCorsPreflightResponseHeaders(pipeline: readonly PipelineItem[], request: Request, targetMethod: string): Promise<Headers | undefined>;
13
+ export declare function cors(options?: CorsOptions): import("../core/generic-layer.js").GenericLayer<HttpMiddlewareContext, {}, HttpExecutionResult>;
14
+ export type CorsLayerDescriptor = ReturnType<typeof cors>;
package/dist/http/cors.js CHANGED
@@ -1,171 +1,13 @@
1
- import { childPipelineOf, layer, layerDefinitionOf, } from '../core/index.js';
2
- const policies = new WeakMap();
3
- const httpToken = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1
+ import { defineLayer } from '../core/index.js';
2
+ import { normalizeCorsPolicy, registerCorsPolicy, } from './cors-internal.js';
4
3
  export function cors(options = {}) {
5
- const policy = normalizeCorsOptions(options);
6
- const descriptor = layer({
4
+ const policy = normalizeCorsPolicy(options);
5
+ const middleware = defineLayer({
7
6
  name: options.name ?? 'cors',
8
- role: 'framework',
9
- factory: () => async (_ctx, next) => {
7
+ factory: () => async (_context, next) => {
10
8
  await next();
11
9
  },
12
10
  });
13
- policies.set(descriptor, policy);
14
- return descriptor;
15
- }
16
- export async function createCorsActualResponseHeaders(pipeline, request) {
17
- const policy = corsPolicyOfPipeline(pipeline);
18
- if (!policy)
19
- return undefined;
20
- const origin = request.headers.get('origin');
21
- const headers = new Headers();
22
- if (policy.origin.kind !== 'wildcard')
23
- appendVary(headers, 'Origin');
24
- if (origin === null)
25
- return headers;
26
- await applyOrigin(headers, policy, origin);
27
- if (!headers.has('access-control-allow-origin'))
28
- return headers;
29
- if (policy.credentials) {
30
- headers.set('access-control-allow-credentials', 'true');
31
- }
32
- if (policy.exposeHeaders.length > 0) {
33
- headers.set('access-control-expose-headers', policy.exposeHeaders.join(', '));
34
- }
35
- return headers;
36
- }
37
- export async function createCorsPreflightResponseHeaders(pipeline, request, targetMethod) {
38
- const policy = corsPolicyOfPipeline(pipeline);
39
- if (!policy)
40
- return undefined;
41
- const headers = new Headers();
42
- appendVary(headers, 'Access-Control-Request-Method');
43
- const origin = request.headers.get('origin');
44
- if (origin === null)
45
- return headers;
46
- await applyOrigin(headers, policy, origin);
47
- if (!headers.has('access-control-allow-origin'))
48
- return headers;
49
- if (policy.credentials) {
50
- headers.set('access-control-allow-credentials', 'true');
51
- }
52
- const methods = policy.allowMethods ?? [targetMethod.toUpperCase()];
53
- if (methods.length > 0) {
54
- headers.set('access-control-allow-methods', methods.join(', '));
55
- }
56
- if (policy.allowHeaders === undefined) {
57
- const requested = request.headers.get('access-control-request-headers');
58
- if (requested !== null && requested.trim().length > 0) {
59
- headers.set('access-control-allow-headers', requested);
60
- appendVary(headers, 'Access-Control-Request-Headers');
61
- }
62
- }
63
- else if (policy.allowHeaders.length > 0) {
64
- headers.set('access-control-allow-headers', policy.allowHeaders.join(', '));
65
- }
66
- if (policy.maxAge !== undefined) {
67
- headers.set('access-control-max-age', String(policy.maxAge));
68
- }
69
- return headers;
70
- }
71
- function corsPolicyOfPipeline(pipeline) {
72
- let found;
73
- const visit = (items) => {
74
- for (const item of items) {
75
- if (item.kind !== 'layer')
76
- continue;
77
- const definition = layerDefinitionOf(item);
78
- const policy = policies.get(definition);
79
- if (policy)
80
- found = policy;
81
- const child = childPipelineOf(item);
82
- if (child)
83
- visit(child);
84
- }
85
- };
86
- visit(pipeline);
87
- return found;
88
- }
89
- async function applyOrigin(headers, policy, origin) {
90
- if (policy.origin.kind === 'wildcard') {
91
- headers.set('access-control-allow-origin', '*');
92
- return;
93
- }
94
- appendVary(headers, 'Origin');
95
- const allowed = policy.origin.kind === 'exact'
96
- ? policy.origin.origins.has(origin)
97
- : await policy.origin.predicate(origin);
98
- if (allowed)
99
- headers.set('access-control-allow-origin', origin);
100
- }
101
- function normalizeCorsOptions(options) {
102
- const origin = normalizeOrigin(options.origin ?? '*', options.credentials ?? false);
103
- return Object.freeze({
104
- origin,
105
- allowMethods: options.allowMethods === undefined
106
- ? undefined
107
- : normalizeTokens(options.allowMethods, 'allowMethods', true),
108
- allowHeaders: options.allowHeaders === undefined
109
- ? undefined
110
- : normalizeTokens(options.allowHeaders, 'allowHeaders', false),
111
- exposeHeaders: normalizeTokens(options.exposeHeaders ?? [], 'exposeHeaders', false),
112
- credentials: options.credentials ?? false,
113
- maxAge: normalizeMaxAge(options.maxAge),
114
- });
115
- }
116
- function normalizeOrigin(origin, credentials) {
117
- if (typeof origin === 'function') {
118
- return Object.freeze({ kind: 'predicate', predicate: origin });
119
- }
120
- const origins = typeof origin === 'string' ? [origin] : [...origin];
121
- if (origins.length === 0) {
122
- return Object.freeze({ kind: 'exact', origins: new Set() });
123
- }
124
- for (const value of origins) {
125
- if (value.length === 0 || /[\u0000-\u001f\u007f]/.test(value)) {
126
- throw new TypeError('CORS origin cannot be empty or contain control characters');
127
- }
128
- }
129
- if (origins.includes('*')) {
130
- if (credentials) {
131
- throw new TypeError('CORS origin cannot be a wildcard when credentials are enabled');
132
- }
133
- return Object.freeze({ kind: 'wildcard' });
134
- }
135
- return Object.freeze({
136
- kind: 'exact',
137
- origins: new Set(origins),
138
- });
139
- }
140
- function normalizeTokens(values, option, uppercase) {
141
- return Object.freeze(values.map((value) => {
142
- const token = value.trim();
143
- if (!httpToken.test(token)) {
144
- throw new TypeError(`CORS ${option} contains an invalid HTTP token`);
145
- }
146
- return uppercase ? token.toUpperCase() : token;
147
- }));
148
- }
149
- function normalizeMaxAge(maxAge) {
150
- if (maxAge === undefined)
151
- return undefined;
152
- if (!Number.isSafeInteger(maxAge) || maxAge < 0) {
153
- throw new TypeError('CORS maxAge must be a non-negative safe integer');
154
- }
155
- return maxAge;
156
- }
157
- function appendVary(headers, value) {
158
- const current = headers.get('vary');
159
- if (current === null) {
160
- headers.set('vary', value);
161
- return;
162
- }
163
- const values = current
164
- .split(',')
165
- .map((item) => item.trim())
166
- .filter(Boolean);
167
- if (!values.some((item) => item.toLowerCase() === value.toLowerCase())) {
168
- values.push(value);
169
- }
170
- headers.set('vary', values.join(', '));
11
+ registerCorsPolicy(middleware, policy);
12
+ return middleware;
171
13
  }