@loutrejs/loutre 0.7.0 → 0.8.1

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 (48) hide show
  1. package/dist/adapters/bun.d.ts +2 -4
  2. package/dist/adapters/bun.js +36 -28
  3. package/dist/adapters/cloudflare-workers.d.ts +2 -4
  4. package/dist/adapters/cloudflare-workers.js +6 -11
  5. package/dist/adapters/server-transports.d.ts +4 -0
  6. package/dist/adapters/server-transports.js +18 -0
  7. package/dist/adapters/websocket-native.d.ts +24 -0
  8. package/dist/adapters/websocket-native.js +150 -0
  9. package/dist/core/extension.d.ts +13 -1
  10. package/dist/core/extension.js +19 -0
  11. package/dist/core/index.d.ts +1 -1
  12. package/dist/core/model/modules.js +13 -17
  13. package/dist/core/model.js +14 -4
  14. package/dist/core/module.d.ts +3 -3
  15. package/dist/devtools/capsule.js +13 -11
  16. package/dist/devtools/values.js +10 -13
  17. package/dist/generated/version.d.ts +1 -1
  18. package/dist/generated/version.js +1 -1
  19. package/dist/graph/model.js +37 -40
  20. package/dist/http/auth.d.ts +2 -2
  21. package/dist/http/auth.js +9 -0
  22. package/dist/http/client.js +5 -5
  23. package/dist/http/extension.d.ts +37 -10
  24. package/dist/http/extension.js +117 -39
  25. package/dist/http/index.d.ts +3 -2
  26. package/dist/http/index.js +1 -1
  27. package/dist/http/openapi.d.ts +3 -4
  28. package/dist/http/openapi.js +45 -26
  29. package/dist/http/request-head.d.ts +6 -0
  30. package/dist/http/request-head.js +1 -0
  31. package/dist/http/route-validation.d.ts +9 -0
  32. package/dist/http/route-validation.js +18 -0
  33. package/dist/http/runtime/response.js +10 -10
  34. package/dist/runtime/di.js +40 -39
  35. package/dist/runtime/engine.d.ts +1 -1
  36. package/dist/runtime/engine.js +1 -2
  37. package/dist/runtime/node-capabilities.d.ts +1 -1
  38. package/dist/runtime/node-capabilities.js +2 -1
  39. package/dist/tasks/extension.js +103 -114
  40. package/dist/websocket/driver.d.ts +30 -0
  41. package/dist/websocket/driver.js +96 -0
  42. package/dist/websocket/extension.d.ts +33 -12
  43. package/dist/websocket/extension.js +60 -44
  44. package/dist/websocket/index.d.ts +3 -1
  45. package/dist/websocket/index.js +1 -0
  46. package/package.json +3 -6
  47. package/dist/adapters/deno.d.ts +0 -35
  48. package/dist/adapters/deno.js +0 -231
@@ -1,2 +1,2 @@
1
1
  // package manifestとのずれを避けるため、scripts/generate-version.mjsがbuild前に更新する。
2
- export const LOUTRE_VERSION = '0.7.0';
2
+ export const LOUTRE_VERSION = '0.8.1';
@@ -1,4 +1,5 @@
1
1
  import { diagnostic, tokenName, } from '../core/index.js';
2
+ import { match } from 'ts-pattern';
2
3
  export function projectApplicationModel(model) {
3
4
  const diagnostics = [...model.diagnostics];
4
5
  const projectedExecutions = new Map();
@@ -21,46 +22,42 @@ export function projectApplicationModel(model) {
21
22
  });
22
23
  }
23
24
  function projectCoreNode(node) {
24
- switch (node.kind) {
25
- case 'module':
26
- return {
27
- id: node.id,
28
- kind: node.kind,
29
- ...(node.source === undefined ? {} : { source: node.source }),
30
- ...(node.name === undefined ? {} : { name: node.name }),
31
- ...(node.description === undefined
32
- ? {}
33
- : { attributes: { description: node.description } }),
34
- };
35
- case 'provider':
36
- return {
37
- id: node.id,
38
- kind: node.kind,
39
- name: tokenName(node.token),
40
- module: node.moduleId,
41
- ...(node.source === undefined ? {} : { source: node.source }),
42
- attributes: {
43
- providerKind: node.provider.kind,
44
- scope: node.provider.scope,
45
- },
46
- };
47
- case 'lifecycle':
48
- return {
49
- id: node.id,
50
- kind: node.kind,
51
- name: node.phase,
52
- module: node.moduleId,
53
- ...(node.source === undefined ? {} : { source: node.source }),
54
- };
55
- case 'framework':
56
- return {
57
- id: node.id,
58
- kind: node.kind,
59
- name: node.name,
60
- ...(node.source === undefined ? {} : { source: node.source }),
61
- attributes: { frameworkKind: node.frameworkKind },
62
- };
63
- }
25
+ return match(node)
26
+ .with({ kind: 'module' }, (candidate) => ({
27
+ id: candidate.id,
28
+ kind: candidate.kind,
29
+ ...(candidate.source === undefined ? {} : { source: candidate.source }),
30
+ ...(candidate.name === undefined ? {} : { name: candidate.name }),
31
+ ...(candidate.description === undefined
32
+ ? {}
33
+ : { attributes: { description: candidate.description } }),
34
+ }))
35
+ .with({ kind: 'provider' }, (candidate) => ({
36
+ id: candidate.id,
37
+ kind: candidate.kind,
38
+ name: tokenName(candidate.token),
39
+ module: candidate.moduleId,
40
+ ...(candidate.source === undefined ? {} : { source: candidate.source }),
41
+ attributes: {
42
+ providerKind: candidate.provider.kind,
43
+ scope: candidate.provider.scope,
44
+ },
45
+ }))
46
+ .with({ kind: 'lifecycle' }, (candidate) => ({
47
+ id: candidate.id,
48
+ kind: candidate.kind,
49
+ name: candidate.phase,
50
+ module: candidate.moduleId,
51
+ ...(candidate.source === undefined ? {} : { source: candidate.source }),
52
+ }))
53
+ .with({ kind: 'framework' }, (candidate) => ({
54
+ id: candidate.id,
55
+ kind: candidate.kind,
56
+ name: candidate.name,
57
+ ...(candidate.source === undefined ? {} : { source: candidate.source }),
58
+ attributes: { frameworkKind: candidate.frameworkKind },
59
+ }))
60
+ .exhaustive();
64
61
  }
65
62
  function projectExtensionGroup(group, diagnostics) {
66
63
  return group.executions.map((execution) => {
@@ -30,7 +30,7 @@ export interface BasicAuthDefinition<TContribution extends object, TResponse ext
30
30
  }
31
31
  export interface BasicAuthRuntime<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
32
32
  authenticate(credentials: BasicAuthCredentials): TContribution | null | undefined | Promise<TContribution | null | undefined>;
33
- unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody>;
33
+ unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody> | Response;
34
34
  }
35
35
  export declare function basicAuth<const TContribution extends object, const TResponse extends string, TUnauthorizedBody>(definition: BasicAuthDefinition<TContribution, TResponse, TUnauthorizedBody>): HttpMiddleware<TContribution, HttpMiddlewareContext, AuthenticationShortCircuit<TResponse, TUnauthorizedBody>>;
36
36
  export interface BearerAuthDefinition<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
@@ -40,7 +40,7 @@ export interface BearerAuthDefinition<TContribution extends object, TResponse ex
40
40
  }
41
41
  export interface BearerAuthRuntime<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
42
42
  authenticate(token: string): TContribution | null | undefined | Promise<TContribution | null | undefined>;
43
- unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody>;
43
+ unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody> | Response;
44
44
  }
45
45
  export declare function bearerAuth<const TContribution extends object, const TResponse extends string, TUnauthorizedBody>(definition: BearerAuthDefinition<TContribution, TResponse, TUnauthorizedBody>): HttpMiddleware<TContribution, HttpMiddlewareContext, AuthenticationShortCircuit<TResponse, TUnauthorizedBody>>;
46
46
  export {};
package/dist/http/auth.js CHANGED
@@ -40,6 +40,15 @@ export function bearerAuth(definition) {
40
40
  });
41
41
  }
42
42
  function authenticationFailure(failure, challenge) {
43
+ if (failure instanceof Response) {
44
+ const headers = new Headers(failure.headers);
45
+ headers.set('www-authenticate', challenge);
46
+ return new Response(failure.body, {
47
+ status: failure.status,
48
+ statusText: failure.statusText,
49
+ headers,
50
+ });
51
+ }
43
52
  return {
44
53
  kind: 'http-result',
45
54
  response: failure.response,
@@ -1,5 +1,6 @@
1
1
  import { SchemaValidationError, validateSchema, } from '../core/index.js';
2
2
  import { parseHttpPath } from './path.js';
3
+ import { match } from 'ts-pattern';
3
4
  export class HttpClientResponseError extends Error {
4
5
  status;
5
6
  method;
@@ -84,11 +85,10 @@ async function decodeResponse(procedure, definition, response) {
84
85
  throw new HttpClientResponseError(response.status, definition.method, definition.path, procedure, `${target} returned a response that does not match its Contract`, lastValidationError ? { cause: lastValidationError } : undefined);
85
86
  }
86
87
  function responseHeadersSchema(headers) {
87
- if (isStandardSchema(headers))
88
- return headers;
89
- if (isResponseHeadersWithDefaults(headers))
90
- return headers.schema;
91
- return undefined;
88
+ return match(headers)
89
+ .when(isStandardSchema, (schema) => schema)
90
+ .when(isResponseHeadersWithDefaults, (definition) => definition.schema)
91
+ .otherwise(() => undefined);
92
92
  }
93
93
  function isStandardSchema(value) {
94
94
  return typeof value === 'object' && value !== null && '~standard' in value;
@@ -1,13 +1,11 @@
1
- import { type ExecutionDefinition, type ExecutionExtension, type ExecutionExtensionDrainContext, type GenericLayer, type RuntimeCapabilityBinding, type SchemaInput, type SchemaOutput, type StandardSchemaV1, type SourceLocation, type Type } from '../core/index.js';
1
+ import { type ExecutionDefinition, type ExecutionContextView, type ExecutionExtension, type ExecutionExtensionDrainContext, type GenericLayer, type RuntimeCapabilityBinding, type SchemaInput, type SchemaOutput, type StandardSchemaV1, type SourceLocation, type Type } from '../core/index.js';
2
2
  import { type HttpPathSegment, type PathParamNames } from './path.js';
3
+ import type { HttpRequestHeadDefinition } from './request-head.js';
3
4
  export interface HttpServerDriver {
4
5
  readonly runtime: string;
5
6
  }
6
7
  export declare const HTTP_SERVER: import("../core/extension.js").RuntimeCapability<HttpServerDriver>;
7
- export interface HttpExecutionRequestDefinition {
8
- readonly params?: Readonly<Record<string, StandardSchemaV1>>;
9
- readonly query?: StandardSchemaV1;
10
- readonly headers?: StandardSchemaV1;
8
+ export interface HttpExecutionRequestDefinition extends HttpRequestHeadDefinition {
11
9
  readonly body?: StandardSchemaV1;
12
10
  }
13
11
  export type HttpHeaderValue = string | readonly string[];
@@ -56,7 +54,8 @@ export interface HttpExecutionRouteDefinition {
56
54
  readonly middlewares?: readonly HttpRouteMiddleware[];
57
55
  readonly interaction?: 'unary' | 'server-stream';
58
56
  }
59
- type AnyHttpMiddleware = GenericLayer<any, any, HttpExecutionResult>;
57
+ type HttpMiddlewareOutcome = HttpExecutionResult | Response;
58
+ type AnyHttpMiddleware = GenericLayer<any, any, HttpMiddlewareOutcome>;
60
59
  type HttpRouteMiddleware = AnyHttpMiddleware | HttpValidationMiddleware;
61
60
  export interface HttpContractBranchDefinition {
62
61
  readonly path?: string;
@@ -67,7 +66,7 @@ export interface HttpContractBranchDefinition {
67
66
  export type HttpContractNodeDefinition = HttpExecutionRouteDefinition | HttpContractBranchDefinition;
68
67
  export type HttpContractRouteTree = Readonly<Record<string, HttpContractNodeDefinition>>;
69
68
  declare const httpMiddlewareShortCircuit: unique symbol;
70
- export type HttpMiddleware<TContribution extends object = object, TContext extends object = HttpMiddlewareContext, TShortCircuit extends HttpExecutionResult = never> = GenericLayer<TContext, TContribution, HttpExecutionResult> & {
69
+ export type HttpMiddleware<TContribution extends object = object, TContext extends object = HttpMiddlewareContext, TShortCircuit extends HttpExecutionResult = never> = GenericLayer<TContext, TContribution, HttpMiddlewareOutcome> & {
71
70
  readonly [httpMiddlewareShortCircuit]?: TShortCircuit;
72
71
  };
73
72
  export interface HttpMiddlewareContext {
@@ -123,10 +122,13 @@ type IsHttpContractTreeInheritanceValid<TTree extends HttpContractRouteTree, TRe
123
122
  type HttpMiddlewareState<TRoute extends HttpExecutionRouteDefinition> = TRoute extends {
124
123
  readonly middlewares: infer TMiddlewares extends readonly HttpRouteMiddleware[];
125
124
  } ? UnionToIntersection<MiddlewareContribution<TMiddlewares[number]>> : {};
125
+ declare const httpContractBrand: unique symbol;
126
126
  export interface HttpContract<TRoutes extends Readonly<Record<string, HttpExecutionRouteDefinition>> = Readonly<Record<string, HttpExecutionRouteDefinition>>> {
127
127
  readonly kind: 'http-contract';
128
128
  readonly routes: TRoutes;
129
+ readonly [httpContractBrand]: true;
129
130
  }
131
+ export declare function isHttpContract(value: unknown): value is HttpContract;
130
132
  type RequestValue<TRequest extends HttpExecutionRequestDefinition | undefined, TPart extends keyof HttpExecutionRequestDefinition, TFallback> = TRequest extends HttpExecutionRequestDefinition ? TRequest[TPart] extends StandardSchemaV1 ? SchemaOutput<TRequest[TPart]> : TFallback : TFallback;
131
133
  type RawHttpQuery = Readonly<Record<string, string | readonly string[] | undefined>>;
132
134
  type RawHttpRequestHeaders = Readonly<Record<string, string | undefined>>;
@@ -198,6 +200,28 @@ export interface HttpImplementationDefinition<TContract extends HttpContract = H
198
200
  readonly contract: TContract;
199
201
  readonly factory: () => HttpHandlers<TContract>;
200
202
  }
203
+ export interface HttpRawRouteDefinition {
204
+ readonly method: string;
205
+ readonly path: string;
206
+ readonly middlewares?: readonly AnyHttpMiddleware[];
207
+ readonly protocol?: string;
208
+ }
209
+ export interface HttpRawExecutionContext<TState = Readonly<Record<string, unknown>>> {
210
+ readonly request: Request;
211
+ readonly signal: AbortSignal;
212
+ readonly state: Readonly<TState>;
213
+ readonly execution: ExecutionContextView;
214
+ }
215
+ type RawMiddlewareState<TMiddlewares extends readonly AnyHttpMiddleware[]> = UnionToIntersection<MiddlewareContribution<TMiddlewares[number]>> & object;
216
+ export interface HttpRawDefinition<TMiddlewares extends readonly AnyHttpMiddleware[] = readonly AnyHttpMiddleware[]> {
217
+ readonly name?: string;
218
+ readonly route: HttpRawRouteDefinition & {
219
+ readonly middlewares?: TMiddlewares;
220
+ };
221
+ readonly factory: () => (context: HttpRawExecutionContext<RawMiddlewareState<TMiddlewares>>) => Response | Promise<Response>;
222
+ }
223
+ export type HttpRawExecutionDefinition = HttpRawDefinition & ExecutionDefinition<typeof httpExecutionExtension>;
224
+ export declare function defineHttpRaw<const TMiddlewares extends readonly AnyHttpMiddleware[] = readonly []>(definition: HttpRawDefinition<TMiddlewares>): HttpRawDefinition<TMiddlewares> & ExecutionDefinition<typeof httpExecutionExtension>;
201
225
  interface CompiledHttpRoute {
202
226
  readonly name: string;
203
227
  readonly method: string;
@@ -207,10 +231,12 @@ interface CompiledHttpRoute {
207
231
  readonly definition: HttpExecutionRouteDefinition;
208
232
  readonly middlewares: readonly AnyHttpMiddleware[];
209
233
  readonly source?: SourceLocation;
234
+ readonly raw?: boolean;
235
+ readonly protocol?: string;
210
236
  }
211
237
  interface CompiledHttpExecution {
212
238
  readonly routes: readonly CompiledHttpRoute[];
213
- readonly factory: () => Record<string, (context: HttpExecutionContext) => HttpExecutionResult | Promise<HttpExecutionResult>>;
239
+ readonly factory: () => Record<string, (context: HttpExecutionContext) => HttpMiddlewareOutcome | Promise<HttpMiddlewareOutcome>>;
214
240
  }
215
241
  export interface HttpExtensionRuntime {
216
242
  fetch(request: Request): Promise<Response>;
@@ -220,7 +246,7 @@ export interface HttpHostApi {
220
246
  fetch(request: Request): Promise<Response>;
221
247
  }
222
248
  declare const httpExecutionExtensionIdentity: unique symbol;
223
- export declare const httpExecutionExtension: ExecutionExtension<HttpImplementationDefinition & ExecutionDefinition, CompiledHttpExecution, 'http', HttpHostApi, HttpExtensionRuntime> & {
249
+ export declare const httpExecutionExtension: ExecutionExtension<(HttpImplementationDefinition | HttpRawDefinition) & ExecutionDefinition, CompiledHttpExecution, 'http', HttpHostApi, HttpExtensionRuntime> & {
224
250
  readonly [httpExecutionExtensionIdentity]: true;
225
251
  };
226
252
  export type HttpExecutionDefinition<TContract extends HttpContract = HttpContract> = HttpImplementationDefinition<TContract> & ExecutionDefinition<typeof httpExecutionExtension>;
@@ -328,6 +354,7 @@ export declare const validate: Readonly<{
328
354
  }>;
329
355
  }>;
330
356
  export declare const executionHttp: Readonly<{
357
+ raw: typeof defineHttpRaw;
331
358
  contract: typeof defineHttpContract;
332
359
  implementation: typeof defineHttpImplementation;
333
360
  middleware: typeof defineHttpMiddleware;
@@ -338,7 +365,7 @@ export declare const executionHttp: Readonly<{
338
365
  }>;
339
366
  }>;
340
367
  error: typeof httpError;
341
- extension: ExecutionExtension<HttpImplementationDefinition<HttpContract<Readonly<Record<string, HttpExecutionRouteDefinition>>>> & ExecutionDefinition<import("../core/extension.js").AnyExecutionExtension>, CompiledHttpExecution, "http", HttpHostApi, HttpExtensionRuntime> & {
368
+ extension: ExecutionExtension<(HttpImplementationDefinition<HttpContract<Readonly<Record<string, HttpExecutionRouteDefinition>>>> | HttpRawDefinition<readonly AnyHttpMiddleware[]>) & ExecutionDefinition<import("../core/extension.js").AnyExecutionExtension>, CompiledHttpExecution, "http", HttpHostApi, HttpExtensionRuntime> & {
342
369
  readonly [httpExecutionExtensionIdentity]: true;
343
370
  };
344
371
  serverCapability: import("../core/extension.js").RuntimeCapability<HttpServerDriver>;
@@ -1,18 +1,35 @@
1
1
  import { collectInjectedDependencies, getSourceLocation, inheritSourceLocation, composeLayers, defineExecution, defineExecutionExtension, defineLayer, runInInjectionContext, runtimeCapability, SchemaValidationError, validateSchema, } from '../core/index.js';
2
2
  import { createCorsActualResponseHeaders, createCorsPreflightResponseHeaders, } from './cors-internal.js';
3
3
  import { assertValidHttpMethod, compareHttpPathSpecificity, createHttpDispatchKey, HttpPathDecodeError, matchHttpPath, parseHttpPath, } from './path.js';
4
+ import { findHttpRouteConflicts } from './route-validation.js';
4
5
  import { IngressGate } from '../runtime/ingress-gate.js';
5
6
  import { AsyncIteratorCleanupDeadlineError, AsyncIteratorLifecycle, } from '../runtime/async-iterator-lifecycle.js';
6
7
  import { decodeBody, decodeQuery, HttpInputDecodeError, HttpInputValidationError, HttpUnsupportedMediaTypeError, validatedContentType, validateRequestHeaders, } from './runtime/request.js';
7
8
  import { applyFrameworkHeadersToResponse, applyFrameworkResponseHeaders, applyResponseHeaders, isResponseHeadersWithDefaults, isStandardSchema, mergeResponseHeaders, responseHeadersDefaults, responseHeadersSchema, validateResponseHeaders, } from './runtime/response.js';
9
+ import { match as patternMatch } from 'ts-pattern';
8
10
  export const HTTP_SERVER = runtimeCapability('http.server');
11
+ const httpContractBrand = Symbol.for('loutre.http-contract');
12
+ export function isHttpContract(value) {
13
+ if (typeof value !== 'object' || value === null)
14
+ return false;
15
+ const candidate = value;
16
+ return (candidate.kind === 'http-contract' &&
17
+ candidate[httpContractBrand] === true &&
18
+ typeof candidate.routes === 'object' &&
19
+ candidate.routes !== null);
20
+ }
21
+ export function defineHttpRaw(definition) {
22
+ return defineExecution(httpExecutionExtension, { ...definition, raw: true });
23
+ }
9
24
  export const httpExecutionExtension = defineExecutionExtension({
10
25
  kind: 'execution-extension',
11
26
  abiVersion: '1',
12
27
  name: 'loutre:http',
13
28
  compile(definition, context) {
14
- const contractSource = getSourceLocation(definition.contract);
15
- const routes = Object.entries(definition.contract.routes).map(([name, route]) => compileHttpRoute(name, route, contractSource));
29
+ const raw = 'route' in definition;
30
+ const routes = raw
31
+ ? [compileRawRoute(definition.route)]
32
+ : Object.entries(definition.contract.routes).map(([name, route]) => compileHttpRoute(name, route, getSourceLocation(definition.contract)));
16
33
  const id = definition.name || `${context.moduleId}.http.${context.definitionIndex}`;
17
34
  const dependencies = new Set(collectInjectedDependencies({
18
35
  kind: 'execution',
@@ -43,25 +60,22 @@ export const httpExecutionExtension = defineExecutionExtension({
43
60
  ],
44
61
  compiled: Object.freeze({
45
62
  routes: Object.freeze(routes),
46
- factory: definition.factory,
63
+ factory: (raw
64
+ ? () => ({ raw: definition.factory() })
65
+ : definition.factory),
47
66
  }),
48
67
  };
49
68
  },
50
69
  validate({ executions }) {
51
- const dispatches = new Map();
52
- return executions.flatMap((execution) => execution.compiled.routes.flatMap((route) => {
53
- const owner = dispatches.get(route.dispatch);
54
- if (owner) {
55
- return [
56
- {
57
- code: 'LUTRE_HTTP_DUPLICATE_ROUTE',
58
- message: `${route.method} ${route.path} conflicts with ${owner}.`,
59
- path: execution.id,
60
- },
61
- ];
62
- }
63
- dispatches.set(route.dispatch, execution.id);
64
- return [];
70
+ const routes = executions.flatMap((execution) => execution.compiled.routes.map((route) => ({
71
+ method: route.method,
72
+ path: route.path,
73
+ owner: execution.id,
74
+ })));
75
+ return findHttpRouteConflicts(routes).map(({ existing, route }) => ({
76
+ code: 'LUTRE_HTTP_DUPLICATE_ROUTE',
77
+ message: `${route.method} ${route.path} conflicts with ${existing.owner}.`,
78
+ path: route.owner,
65
79
  }));
66
80
  },
67
81
  createRuntime(context) {
@@ -81,10 +95,16 @@ export const httpExecutionExtension = defineExecutionExtension({
81
95
  ? {}
82
96
  : { source: getSourceLocation(middleware) }),
83
97
  })),
84
- responses: Object.fromEntries(Object.entries(route.definition.responses).map(([name, response]) => [
85
- name,
86
- { status: response.status },
87
- ])),
98
+ ...(route.raw
99
+ ? {
100
+ raw: true,
101
+ ...(route.protocol === undefined
102
+ ? {}
103
+ : { protocol: route.protocol }),
104
+ }
105
+ : {
106
+ responses: Object.fromEntries(Object.entries(route.definition.responses).map(([name, response]) => [name, { status: response.status }])),
107
+ }),
88
108
  })),
89
109
  }),
90
110
  host: {
@@ -101,12 +121,14 @@ export function httpError(definition, map) {
101
121
  }
102
122
  export function defineHttpContract(routes) {
103
123
  const resolvedRoutes = resolveHttpContractRoutes(routes);
104
- for (const [name, route] of Object.entries(resolvedRoutes)) {
105
- compileHttpRoute(name, route);
106
- }
124
+ const canonicalRoutes = Object.fromEntries(Object.entries(resolvedRoutes).map(([name, route]) => [
125
+ name,
126
+ compileHttpRoute(name, route).definition,
127
+ ]));
107
128
  return Object.freeze({
108
129
  kind: 'http-contract',
109
- routes: Object.freeze(resolvedRoutes),
130
+ [httpContractBrand]: true,
131
+ routes: Object.freeze(canonicalRoutes),
110
132
  });
111
133
  }
112
134
  function resolveHttpContractRoutes(tree) {
@@ -186,6 +208,7 @@ export const validate = Object.freeze({
186
208
  body: bodyValidation,
187
209
  });
188
210
  export const executionHttp = Object.freeze({
211
+ raw: defineHttpRaw,
189
212
  contract: defineHttpContract,
190
213
  implementation: defineHttpImplementation,
191
214
  middleware: defineHttpMiddleware,
@@ -203,7 +226,29 @@ export function withHttpFrameworkHeaders(result, headers) {
203
226
  [httpFrameworkHeaders]: { ...current, ...headers },
204
227
  };
205
228
  }
229
+ function compileRawRoute(route) {
230
+ if (route.method !== '*')
231
+ assertValidHttpMethod(route.method);
232
+ const segments = parseHttpPath(route.path);
233
+ return Object.freeze({
234
+ name: 'raw',
235
+ method: route.method.toUpperCase(),
236
+ path: route.path,
237
+ segments,
238
+ dispatch: createHttpDispatchKey(route.method, segments),
239
+ definition: Object.freeze({
240
+ method: route.method,
241
+ path: route.path,
242
+ responses: Object.freeze({}),
243
+ }),
244
+ middlewares: Object.freeze([...(route.middlewares ?? [])]),
245
+ raw: true,
246
+ ...(route.protocol === undefined ? {} : { protocol: route.protocol }),
247
+ });
248
+ }
206
249
  function compileHttpRoute(name, route, routeSourceFallback) {
250
+ if (route.method === '*')
251
+ throw new TypeError('method: *はhttp.raw()でのみ利用できます。');
207
252
  assertValidHttpMethod(route.method);
208
253
  const segments = parseHttpPath(route.path);
209
254
  assertValidHttpRouteDefinition(route, segments);
@@ -281,15 +326,15 @@ function compileHttpMiddlewares(route) {
281
326
  return Object.freeze(compiled);
282
327
  }
283
328
  function compileHttpValidationMiddleware(middleware, route) {
284
- switch (middleware.part) {
285
- case 'body': {
286
- const schema = route.request?.body;
287
- if (!schema) {
288
- throw new TypeError(`HTTP ${route.method} ${route.path} uses validate.body but declares no request body.`);
289
- }
290
- return createBodyValidationMiddleware(schema);
329
+ return patternMatch(middleware)
330
+ .with({ part: 'body' }, () => {
331
+ const schema = route.request?.body;
332
+ if (!schema) {
333
+ throw new TypeError(`HTTP ${route.method} ${route.path} uses validate.body but declares no request body.`);
291
334
  }
292
- }
335
+ return createBodyValidationMiddleware(schema);
336
+ })
337
+ .exhaustive();
293
338
  }
294
339
  function createBodyValidationMiddleware(schema) {
295
340
  return defineLayer({
@@ -562,14 +607,33 @@ function createHttpExtensionRuntime(executions, applicationRuntime) {
562
607
  kind: 'http.handler',
563
608
  name: handlerName,
564
609
  graphNodeId: match.executionId,
565
- }, {
566
- input: middlewareContext.input,
567
- invoke: (input) => replayHttpHandler(applicationRuntime, handler, match, input, middlewareContext.state),
568
- });
610
+ }, match.route.raw
611
+ ? undefined
612
+ : {
613
+ input: middlewareContext.input,
614
+ invoke: (input) => replayHttpHandler(applicationRuntime, handler, match, input, middlewareContext.state),
615
+ });
569
616
  const invoke = () => handler({
617
+ ...(match.route.raw
618
+ ? {
619
+ request,
620
+ execution: {
621
+ signal: lease.signal,
622
+ annotate: (attributes) => lease.annotate?.(attributes),
623
+ beginOperation: (metadata) => {
624
+ const begin = () => applicationRuntime.beginOperation?.(metadata) ?? {
625
+ complete() { },
626
+ };
627
+ return lease.run ? lease.run(begin) : begin();
628
+ },
629
+ },
630
+ }
631
+ : {}),
570
632
  input: middlewareContext.input,
571
633
  response: middlewareContext.response,
572
- signal: middlewareContext.signal,
634
+ signal: match.route.raw
635
+ ? request.signal
636
+ : middlewareContext.signal,
573
637
  state: middlewareContext.state,
574
638
  });
575
639
  let result;
@@ -596,6 +660,20 @@ function createHttpExtensionRuntime(executions, applicationRuntime) {
596
660
  const result = await (lease.run ? lease.run(dispatch) : dispatch());
597
661
  executionResult = result;
598
662
  hasExecutionResult = true;
663
+ if (result instanceof Response) {
664
+ if (result.body) {
665
+ const stream = createLeasedReadableStream(result.body, lease.signal, finishExecution);
666
+ adoptResponseStream(stream);
667
+ return complete(new Response(stream.stream, {
668
+ status: result.status,
669
+ statusText: result.statusText,
670
+ headers: result.headers,
671
+ }));
672
+ }
673
+ return complete(result);
674
+ }
675
+ if (match.route.raw)
676
+ throw new TypeError('Raw handlerとmiddlewareはResponseを返す必要があります。');
599
677
  const finalized = await finalizeHttpResult(match.route.definition, result, lease.signal, finishExecution);
600
678
  if (finalized.streamControl)
601
679
  adoptResponseStream(finalized.streamControl);
@@ -634,7 +712,7 @@ function createHttpExtensionRuntime(executions, applicationRuntime) {
634
712
  }
635
713
  function findRuntimeHttpRoute(routes, method, pathname) {
636
714
  for (const candidate of routes) {
637
- if (candidate.route.method !== method)
715
+ if (candidate.route.method !== method && candidate.route.method !== '*')
638
716
  continue;
639
717
  const params = matchHttpPath(candidate.route.segments, pathname);
640
718
  if (!params)
@@ -4,7 +4,8 @@ export { cors } from './cors.js';
4
4
  export type { CorsLayerDescriptor, CorsOptions, CorsOrigin } from './cors.js';
5
5
  export { basicAuth, bearerAuth } from './auth.js';
6
6
  export type { BasicAuthContext, BasicAuthCredentials, BasicAuthDefinition, BasicAuthUnauthorized, BasicAuthRuntime, BearerAuthContext, BearerAuthDefinition, BearerAuthRuntime, BearerAuthUnauthorized, HttpAuthenticationFailure, } from './auth.js';
7
- export { bindHttpServer, defineHttpContract, defineHttpImplementation, defineHttpMiddleware, httpError, executionHttp as http, httpExecutionExtension, HTTP_SERVER, validate, } from './extension.js';
8
- export type { HttpContract, HttpContractBranchDefinition, HttpContractNodeDefinition, HttpContractRouteTree, HttpExecutionContext, HttpExecutionDefinition, HttpExecutionRequestDefinition, HttpExecutionResponseDefinition, HttpExecutionResult, HttpExecutionRouteDefinition, HttpErrorMapping, HttpErrorMatcher, HttpExtensionRuntime, HttpHandlers, HttpHeaderValue, HttpHeaders, HttpHostApi, HttpImplementationDefinition, HttpMiddleware, HttpMiddlewareContext, HttpResponseHeadersDefinition, HttpResponseResult, HttpResponseHeadersWithDefaults, HttpServerDriver, HttpValidationMiddleware, HttpValidationPart, ResolvedHttpContractRoutes, } from './extension.js';
7
+ export { bindHttpServer, defineHttpContract, defineHttpImplementation, defineHttpMiddleware, defineHttpRaw, httpError, executionHttp as http, httpExecutionExtension, isHttpContract, HTTP_SERVER, validate, } from './extension.js';
8
+ export type { HttpRawDefinition, HttpRawRouteDefinition, HttpRawExecutionContext, HttpRawExecutionDefinition, HttpContract, HttpContractBranchDefinition, HttpContractNodeDefinition, HttpContractRouteTree, HttpExecutionContext, HttpExecutionDefinition, HttpExecutionRequestDefinition, HttpExecutionResponseDefinition, HttpExecutionResult, HttpExecutionRouteDefinition, HttpErrorMapping, HttpErrorMatcher, HttpExtensionRuntime, HttpHandlers, HttpHeaderValue, HttpHeaders, HttpHostApi, HttpImplementationDefinition, HttpMiddleware, HttpMiddlewareContext, HttpResponseHeadersDefinition, HttpResponseResult, HttpResponseHeadersWithDefaults, HttpServerDriver, HttpValidationMiddleware, HttpValidationPart, ResolvedHttpContractRoutes, } from './extension.js';
9
9
  export { compareHttpPathSpecificity, HttpPathDecodeError, matchHttpPath, normalizeHttpPath, parseHttpPath, } from './path.js';
10
10
  export type { HttpPathSegment, PathParamNames } from './path.js';
11
+ export type { HttpRequestHeadDefinition } from './request-head.js';
@@ -1,5 +1,5 @@
1
1
  export { createHttpClient, fetchHttpTransport, HttpClientResponseError, } from './client.js';
2
2
  export { cors } from './cors.js';
3
3
  export { basicAuth, bearerAuth } from './auth.js';
4
- export { bindHttpServer, defineHttpContract, defineHttpImplementation, defineHttpMiddleware, httpError, executionHttp as http, httpExecutionExtension, HTTP_SERVER, validate, } from './extension.js';
4
+ export { bindHttpServer, defineHttpContract, defineHttpImplementation, defineHttpMiddleware, defineHttpRaw, httpError, executionHttp as http, httpExecutionExtension, isHttpContract, HTTP_SERVER, validate, } from './extension.js';
5
5
  export { compareHttpPathSpecificity, HttpPathDecodeError, matchHttpPath, normalizeHttpPath, parseHttpPath, } from './path.js';
@@ -1,5 +1,5 @@
1
1
  import { type ApplicationModel } from '../core/index.js';
2
- import { type HttpExecutionResponseDefinition, type HttpExecutionRouteDefinition } from './extension.js';
2
+ import { type HttpContract, type HttpExecutionResponseDefinition, type HttpExecutionRouteDefinition } from './extension.js';
3
3
  export interface OpenApiInfo {
4
4
  readonly title: string;
5
5
  readonly version: string;
@@ -37,7 +37,6 @@ export interface HttpOperationTarget {
37
37
  };
38
38
  readonly procedure: string;
39
39
  }
40
- export declare function generateOpenApi(source: ApplicationModel | {
41
- readonly model: ApplicationModel;
42
- }, options: GenerateOpenApiOptions): OpenApiDocument;
40
+ export type OpenApiSource = ApplicationModel | HttpContract;
41
+ export declare function generateOpenApi(source: OpenApiSource, options: GenerateOpenApiOptions): OpenApiDocument;
43
42
  export {};