@loutrejs/loutre 0.1.0 → 0.3.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 (43) hide show
  1. package/README.md +69 -0
  2. package/dist/adapters/aws-lambda.js +2 -0
  3. package/dist/adapters/bun.d.ts +3 -1
  4. package/dist/adapters/bun.js +90 -33
  5. package/dist/adapters/cloudflare-workers.js +2 -0
  6. package/dist/adapters/deno.d.ts +3 -1
  7. package/dist/adapters/deno.js +99 -33
  8. package/dist/adapters/electron.js +2 -0
  9. package/dist/application/openapi.d.ts +6 -0
  10. package/dist/application/openapi.js +14 -19
  11. package/dist/core/contract.d.ts +47 -7
  12. package/dist/core/contract.js +59 -10
  13. package/dist/generated/version.d.ts +1 -0
  14. package/dist/generated/version.js +2 -0
  15. package/dist/graph/compiler.d.ts +4 -0
  16. package/dist/graph/compiler.js +228 -0
  17. package/dist/graph/graph.js +65 -40
  18. package/dist/graph/index.d.ts +2 -1
  19. package/dist/graph/index.js +2 -1
  20. package/dist/graph/ir.d.ts +24 -8
  21. package/dist/http/application.js +5 -5
  22. package/dist/http/basic-auth.js +1 -1
  23. package/dist/http/client.d.ts +72 -0
  24. package/dist/http/client.js +258 -0
  25. package/dist/http/cors.js +4 -4
  26. package/dist/http/definitions.d.ts +13 -5
  27. package/dist/http/definitions.js +6 -2
  28. package/dist/http/index.d.ts +1 -0
  29. package/dist/http/index.js +1 -0
  30. package/dist/message-port/index.d.ts +12 -4
  31. package/dist/message-port/index.js +13 -6
  32. package/dist/presentation.d.ts +33 -0
  33. package/dist/presentation.js +233 -0
  34. package/dist/runtime/capability.js +1 -1
  35. package/dist/runtime/di.js +2 -2
  36. package/dist/runtime/engine.d.ts +3 -0
  37. package/dist/runtime/engine.js +38 -0
  38. package/dist/runtime/index.d.ts +2 -0
  39. package/dist/runtime/index.js +2 -0
  40. package/dist/runtime/pipeline.js +13 -13
  41. package/dist/runtime/server-url.d.ts +1 -0
  42. package/dist/runtime/server-url.js +5 -0
  43. package/package.json +5 -1
@@ -21,6 +21,7 @@ export interface DependencyNodeIR {
21
21
  readonly kind: 'class' | 'token' | 'factory' | 'conditional' | 'environment' | 'arguments' | 'implementation' | 'layer' | 'task' | 'framework';
22
22
  readonly scope?: 'application' | 'transient';
23
23
  readonly module?: string;
24
+ readonly visibility?: 'private' | 'exported';
24
25
  }
25
26
  export interface DependencyEdgeIR {
26
27
  readonly from: string;
@@ -57,18 +58,33 @@ export interface LayerIR {
57
58
  readonly response?: Readonly<Record<string, unknown>>;
58
59
  }[];
59
60
  }
61
+ export type ContractId = `contract:${number}`;
62
+ export type ImplementationId = `implementation:${number}`;
63
+ export interface ContractProtocolIR {
64
+ readonly name: string;
65
+ readonly dispatchKey: string | null;
66
+ readonly interaction: string;
67
+ }
68
+ export interface ContractProcedureIR {
69
+ readonly name: string;
70
+ readonly protocols: readonly ContractProtocolIR[];
71
+ }
72
+ export interface ContractIR {
73
+ readonly id: ContractId;
74
+ readonly procedures: readonly ContractProcedureIR[];
75
+ }
60
76
  export interface PipelineIR {
61
- readonly contract: string;
77
+ readonly contract: ContractId;
62
78
  readonly procedure: string;
63
79
  readonly protocol: string;
64
80
  readonly layers: readonly LayerIR[];
65
81
  }
66
82
  export interface ImplementationIR {
67
- readonly contract: string;
68
- readonly procedure: string;
83
+ readonly id: ImplementationId;
84
+ readonly name: string;
85
+ readonly contract: ContractId;
69
86
  readonly protocol: string;
70
- readonly implementation: string;
71
- readonly method: string;
87
+ readonly procedures: readonly string[];
72
88
  }
73
89
  export interface CapabilityIR {
74
90
  readonly name: string;
@@ -79,9 +95,9 @@ export interface ProtocolExecutionRootIR {
79
95
  readonly id: `protocol:${string}`;
80
96
  readonly kind: 'protocol';
81
97
  readonly protocol: string;
82
- readonly contract: string;
98
+ readonly contract: ContractId;
83
99
  readonly procedure: string;
84
- readonly implementation: string;
100
+ readonly implementation: ImplementationId;
85
101
  }
86
102
  export interface ApplicationArgumentsIR {
87
103
  readonly name: string;
@@ -136,7 +152,7 @@ export interface ApplicationGraphIR {
136
152
  readonly providers: readonly ProviderIR[];
137
153
  readonly tokens: readonly TokenIR[];
138
154
  readonly contextKeys: readonly ContextKeyIR[];
139
- readonly contracts: readonly string[];
155
+ readonly contracts: readonly ContractIR[];
140
156
  readonly pipelines: readonly PipelineIR[];
141
157
  readonly implementations: readonly ImplementationIR[];
142
158
  readonly tasks: readonly TaskIR[];
@@ -301,7 +301,7 @@ async function finalizeResponse(definition, result, signal) {
301
301
  const responseHeaders = await validateResponseHeaders(response.headers, result.headers);
302
302
  if (response.stream === 'server') {
303
303
  if (!isAsyncIterable(result.body)) {
304
- throw new Error('server-stream responseにはAsyncIterableが必要です');
304
+ throw new Error('Server-stream response requires an AsyncIterable');
305
305
  }
306
306
  const encoder = new TextEncoder();
307
307
  const iterator = result.body[Symbol.asyncIterator]();
@@ -315,7 +315,7 @@ async function finalizeResponse(definition, result, signal) {
315
315
  finished = true;
316
316
  signal.removeEventListener('abort', abort);
317
317
  void iterator.return?.(signal.reason).finally(() => {
318
- controller.error(signal.reason ?? new Error('HTTP requestが中断されました'));
318
+ controller.error(signal.reason ?? new Error('HTTP request was aborted'));
319
319
  });
320
320
  };
321
321
  if (signal.aborted)
@@ -368,7 +368,7 @@ async function finalizeResponse(definition, result, signal) {
368
368
  async function validateResponseHeaders(schema, headers) {
369
369
  if (!schema) {
370
370
  if (headers !== undefined) {
371
- throw new Error('未宣言のHTTP response headerが返されました');
371
+ throw new Error('Undeclared HTTP response header was returned');
372
372
  }
373
373
  return undefined;
374
374
  }
@@ -376,7 +376,7 @@ async function validateResponseHeaders(schema, headers) {
376
376
  if (validated === undefined)
377
377
  return undefined;
378
378
  if (!isHttpHeaders(validated)) {
379
- throw new Error('HTTP response header schemaの出力が不正です');
379
+ throw new Error('HTTP response header schema produced an invalid value');
380
380
  }
381
381
  return validated;
382
382
  }
@@ -458,7 +458,7 @@ async function mapDeclaredError(definition, error) {
458
458
  if (typeof result !== 'object' ||
459
459
  result === null ||
460
460
  !('body' in result)) {
461
- throw new Error('HTTP error mappingはbodyを返す必要があります');
461
+ throw new Error('HTTP error mapping must return a body');
462
462
  }
463
463
  return {
464
464
  kind: 'http-result',
@@ -40,7 +40,7 @@ function unauthorizedResult(unauthorized, challenge) {
40
40
  }
41
41
  function formatBasicChallenge(realm) {
42
42
  if (realm.length === 0 || /[\u0000-\u001f\u007f]/.test(realm)) {
43
- throw new TypeError('Basic認証のrealmには空文字列または制御文字を使用できません');
43
+ throw new TypeError('Basic authentication realm cannot be empty or contain control characters');
44
44
  }
45
45
  const escaped = realm.replace(/[\\"]/g, '\\$&');
46
46
  return `Basic realm="${escaped}", charset="UTF-8"`;
@@ -0,0 +1,72 @@
1
+ import { type ContractDefinition, type ProcedureDefinition, type SchemaInput, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
2
+ import type { HttpHeaders, HttpParamsSchemas, HttpProtocol, HttpProtocolDefinition, HttpRequestBodyDefinition, HttpResponseDefinition } from './definitions.js';
3
+ import { type PathParamNames, type RawPathParams } from './path.js';
4
+ export interface HttpClientTransportRequest {
5
+ readonly method: string;
6
+ readonly path: string;
7
+ readonly query?: unknown;
8
+ readonly headers?: unknown;
9
+ readonly body?: unknown;
10
+ readonly contentType?: string;
11
+ }
12
+ export interface HttpClientTransportResponse {
13
+ readonly status: number;
14
+ readonly body: unknown;
15
+ readonly headers?: HttpHeaders;
16
+ }
17
+ export type HttpClientTransport = (request: HttpClientTransportRequest) => Promise<HttpClientTransportResponse>;
18
+ type HttpProtocolOf<TProcedure extends ProcedureDefinition> = TProcedure['protocols'] extends {
19
+ readonly http: infer TProtocol;
20
+ } ? TProtocol extends HttpProtocol<infer TDefinition> ? HttpProtocol<TDefinition> : never : never;
21
+ type HttpProcedureNames<TContract extends ContractDefinition> = {
22
+ [TProcedure in keyof TContract['procedures'] & string]: HttpProtocolOf<TContract['procedures'][TProcedure]> extends never ? never : TProcedure;
23
+ }[keyof TContract['procedures'] & string];
24
+ type HttpDefinitionOf<TContract extends ContractDefinition, TProcedure extends HttpProcedureNames<TContract>> = HttpProtocolOf<TContract['procedures'][TProcedure]>['definition'];
25
+ type RequestBodySchema<TBody> = TBody extends HttpRequestBodyDefinition<infer TSchema> ? TSchema : never;
26
+ type PathParamsInput<TDefinition extends HttpProtocolDefinition> = PathParamNames<TDefinition['path']> extends never ? never : TDefinition['request'] extends {
27
+ readonly params: infer TSchemas extends HttpParamsSchemas;
28
+ } ? Readonly<{
29
+ [TName in keyof TSchemas]: SchemaInput<TSchemas[TName]>;
30
+ }> : RawPathParams<TDefinition['path']>;
31
+ type RequestPartInput<TDefinition extends HttpProtocolDefinition, TPart extends 'query' | 'headers'> = TDefinition['request'] extends infer TRequest ? TRequest extends Record<TPart, infer TSchema extends StandardSchemaV1> ? SchemaInput<TSchema> : never : never;
32
+ type RequestBodyInput<TDefinition extends HttpProtocolDefinition> = TDefinition['request'] extends {
33
+ readonly body: infer TBody extends HttpRequestBodyDefinition;
34
+ } ? SchemaInput<RequestBodySchema<TBody>> : never;
35
+ type RequestField<TName extends string, TValue> = [TValue] extends [never] ? object : undefined extends TValue ? {
36
+ readonly [TKey in TName]?: Exclude<TValue, undefined>;
37
+ } : {
38
+ readonly [TKey in TName]: TValue;
39
+ };
40
+ export type HttpClientRequest<TDefinition extends HttpProtocolDefinition> = RequestField<'params', PathParamsInput<TDefinition>> & RequestField<'query', RequestPartInput<TDefinition, 'query'>> & RequestField<'headers', RequestPartInput<TDefinition, 'headers'>> & RequestField<'body', RequestBodyInput<TDefinition>>;
41
+ type ResponseBodyOutput<TResponse extends HttpResponseDefinition> = TResponse extends {
42
+ readonly stream: 'server';
43
+ } ? AsyncIterable<SchemaOutput<TResponse['body']>> : SchemaOutput<TResponse['body']>;
44
+ type ResponseHeadersOutput<TResponse extends HttpResponseDefinition> = TResponse extends {
45
+ readonly headers: infer THeaders extends StandardSchemaV1;
46
+ } ? SchemaOutput<THeaders> : HttpHeaders;
47
+ export type HttpClientResponse<TDefinition extends HttpProtocolDefinition> = {
48
+ [TVariant in keyof TDefinition['responses'] & string]: NonNullable<TDefinition['responses'][TVariant]> extends infer TResponse extends HttpResponseDefinition ? {
49
+ readonly status: TResponse['status'];
50
+ readonly body: ResponseBodyOutput<TResponse>;
51
+ readonly headers: ResponseHeadersOutput<TResponse>;
52
+ } : never;
53
+ }[keyof TDefinition['responses'] & string];
54
+ type HttpClientMethod<TDefinition extends HttpProtocolDefinition> = keyof HttpClientRequest<TDefinition> extends never ? () => Promise<HttpClientResponse<TDefinition>> : (request: HttpClientRequest<TDefinition>) => Promise<HttpClientResponse<TDefinition>>;
55
+ export type HttpClient<TContract extends ContractDefinition> = {
56
+ readonly [TProcedure in HttpProcedureNames<TContract>]: HttpClientMethod<HttpDefinitionOf<TContract, TProcedure>>;
57
+ };
58
+ export declare class HttpClientResponseError extends Error {
59
+ readonly status: number;
60
+ readonly method: string;
61
+ readonly path: string;
62
+ readonly procedure: string;
63
+ constructor(status: number, method: string, path: string, procedure: string, message: string, options?: ErrorOptions);
64
+ }
65
+ export declare function createHttpClient<const TContract extends ContractDefinition>(contract: TContract, transport: HttpClientTransport): HttpClient<TContract>;
66
+ export interface FetchHttpTransportOptions {
67
+ readonly baseUrl: string | URL;
68
+ readonly fetch?: typeof globalThis.fetch;
69
+ readonly headers?: HeadersInit;
70
+ }
71
+ export declare function fetchHttpTransport(options: FetchHttpTransportOptions): HttpClientTransport;
72
+ export {};
@@ -0,0 +1,258 @@
1
+ import { SchemaValidationError, validateSchema, } from '../core/index.js';
2
+ import { parseHttpPath, } from './path.js';
3
+ export class HttpClientResponseError extends Error {
4
+ status;
5
+ method;
6
+ path;
7
+ procedure;
8
+ constructor(status, method, path, procedure, message, options) {
9
+ super(message, options);
10
+ this.status = status;
11
+ this.method = method;
12
+ this.path = path;
13
+ this.procedure = procedure;
14
+ this.name = 'HttpClientResponseError';
15
+ }
16
+ }
17
+ export function createHttpClient(contract, transport) {
18
+ const client = {};
19
+ for (const [procedureName, procedure] of Object.entries(contract.procedures)) {
20
+ const definition = httpDefinitionOf(procedure);
21
+ if (!definition)
22
+ continue;
23
+ client[procedureName] = async (request = {}) => {
24
+ const response = await transport({
25
+ method: definition.method,
26
+ path: interpolatePath(definition.path, request.params),
27
+ ...(request.query === undefined ? {} : { query: request.query }),
28
+ ...(request.headers === undefined ? {} : { headers: request.headers }),
29
+ ...(request.body === undefined ? {} : { body: request.body }),
30
+ ...(definition.request?.body === undefined
31
+ ? {}
32
+ : { contentType: definition.request.body.contentType }),
33
+ });
34
+ return decodeResponse(procedureName, definition, response);
35
+ };
36
+ }
37
+ return Object.freeze(client);
38
+ }
39
+ function httpDefinitionOf(procedure) {
40
+ const protocol = procedure.protocols.http;
41
+ if (!protocol ||
42
+ protocol.protocol !== 'http' ||
43
+ !('definition' in protocol)) {
44
+ return undefined;
45
+ }
46
+ return protocol.definition;
47
+ }
48
+ function interpolatePath(path, params) {
49
+ const segments = parseHttpPath(path);
50
+ if (segments.length === 0)
51
+ return '/';
52
+ return `/${segments
53
+ .map((segment) => {
54
+ if (segment.kind === 'static')
55
+ return segment.value;
56
+ const value = params?.[segment.name];
57
+ if (value === undefined) {
58
+ throw new Error(`Missing HTTP path parameter: ${segment.name}`);
59
+ }
60
+ return encodeURIComponent(String(value));
61
+ })
62
+ .join('/')}`;
63
+ }
64
+ async function decodeResponse(procedure, definition, response) {
65
+ const target = describeHttpTarget(definition, procedure);
66
+ const candidates = Object.values(definition.responses).filter((candidate) => candidate.status === response.status);
67
+ if (candidates.length === 0) {
68
+ throw new HttpClientResponseError(response.status, definition.method, definition.path, procedure, `${target} returned undeclared HTTP status ${response.status}`);
69
+ }
70
+ let lastValidationError;
71
+ for (const candidate of candidates) {
72
+ try {
73
+ const body = candidate.stream
74
+ ? validatedResponseStream(candidate.body, response.body, procedure, definition, response.status)
75
+ : await validateSchema(candidate.body, response.body);
76
+ const headers = candidate.headers
77
+ ? await validateSchema(candidate.headers, response.headers ?? {})
78
+ : (response.headers ?? {});
79
+ return Object.freeze({ status: response.status, body, headers });
80
+ }
81
+ catch (error) {
82
+ if (!(error instanceof SchemaValidationError))
83
+ throw error;
84
+ lastValidationError = error;
85
+ }
86
+ }
87
+ throw new HttpClientResponseError(response.status, definition.method, definition.path, procedure, `${target} returned a response that does not match its Contract`, lastValidationError ? { cause: lastValidationError } : undefined);
88
+ }
89
+ function validatedResponseStream(schema, value, procedure, definition, status) {
90
+ const target = describeHttpTarget(definition, procedure);
91
+ if (!isAsyncIterable(value)) {
92
+ throw new HttpClientResponseError(status, definition.method, definition.path, procedure, `${target} returned a non-stream body for a server-stream response`);
93
+ }
94
+ return {
95
+ async *[Symbol.asyncIterator]() {
96
+ try {
97
+ for await (const item of value)
98
+ yield await validateSchema(schema, item);
99
+ }
100
+ catch (error) {
101
+ throw new HttpClientResponseError(status, definition.method, definition.path, procedure, `${target} returned an invalid server-stream item`, { cause: error });
102
+ }
103
+ },
104
+ };
105
+ }
106
+ function describeHttpTarget(definition, procedure) {
107
+ return `${definition.method.toUpperCase()} ${definition.path} (${procedure})`;
108
+ }
109
+ function isAsyncIterable(value) {
110
+ return (typeof value === 'object' &&
111
+ value !== null &&
112
+ Symbol.asyncIterator in value &&
113
+ typeof value[Symbol.asyncIterator] === 'function');
114
+ }
115
+ export function fetchHttpTransport(options) {
116
+ const fetchImplementation = options.fetch ?? globalThis.fetch;
117
+ return async (request) => {
118
+ const url = new URL(request.path, options.baseUrl);
119
+ appendQuery(url.searchParams, request.query);
120
+ const headers = new Headers(options.headers);
121
+ appendHeaders(headers, request.headers);
122
+ if (request.contentType && !headers.has('content-type')) {
123
+ headers.set('content-type', request.contentType);
124
+ }
125
+ const response = await fetchImplementation(url, {
126
+ method: request.method,
127
+ headers,
128
+ ...(request.body === undefined
129
+ ? {}
130
+ : { body: encodeBody(request.body, request.contentType) }),
131
+ });
132
+ return {
133
+ status: response.status,
134
+ body: await decodeFetchBody(response),
135
+ headers: Object.fromEntries(response.headers.entries()),
136
+ };
137
+ };
138
+ }
139
+ function appendQuery(searchParams, query) {
140
+ if (query === undefined || query === null)
141
+ return;
142
+ if (query instanceof URLSearchParams) {
143
+ for (const [key, value] of query)
144
+ searchParams.append(key, value);
145
+ return;
146
+ }
147
+ if (typeof query !== 'object') {
148
+ throw new TypeError('HTTP client query must be an object or URLSearchParams');
149
+ }
150
+ for (const [key, value] of Object.entries(query)) {
151
+ if (value === undefined)
152
+ continue;
153
+ if (Array.isArray(value)) {
154
+ for (const item of value)
155
+ searchParams.append(key, String(item));
156
+ }
157
+ else {
158
+ searchParams.append(key, String(value));
159
+ }
160
+ }
161
+ }
162
+ function appendHeaders(headers, value) {
163
+ if (value === undefined || value === null)
164
+ return;
165
+ if (value instanceof Headers) {
166
+ for (const [name, header] of value)
167
+ headers.set(name, header);
168
+ return;
169
+ }
170
+ if (typeof value !== 'object') {
171
+ throw new TypeError('HTTP client headers must be an object or Headers');
172
+ }
173
+ for (const [name, header] of Object.entries(value)) {
174
+ if (header === undefined)
175
+ continue;
176
+ if (Array.isArray(header)) {
177
+ headers.set(name, header.map(String).join(', '));
178
+ }
179
+ else {
180
+ headers.set(name, String(header));
181
+ }
182
+ }
183
+ }
184
+ function encodeBody(body, contentType) {
185
+ const mediaType = normalizeMediaType(contentType);
186
+ if (mediaType === 'application/json' || mediaType?.endsWith('+json')) {
187
+ return JSON.stringify(body);
188
+ }
189
+ return body;
190
+ }
191
+ async function decodeFetchBody(response) {
192
+ if (response.status === 204 || response.status === 205)
193
+ return undefined;
194
+ const mediaType = normalizeMediaType(response.headers.get('content-type'));
195
+ if (mediaType === 'text/event-stream') {
196
+ if (!response.body) {
197
+ throw new Error('Server-stream HTTP response has no body');
198
+ }
199
+ return decodeServerSentEvents(response.body);
200
+ }
201
+ if (mediaType === 'application/json' || mediaType?.endsWith('+json')) {
202
+ return response.json();
203
+ }
204
+ return response.text();
205
+ }
206
+ function normalizeMediaType(value) {
207
+ const mediaType = value?.split(';', 1)[0]?.trim().toLowerCase();
208
+ return mediaType || undefined;
209
+ }
210
+ async function* decodeServerSentEvents(stream) {
211
+ const reader = stream.getReader();
212
+ const decoder = new TextDecoder();
213
+ let buffer = '';
214
+ let completed = false;
215
+ try {
216
+ while (true) {
217
+ const next = await reader.read();
218
+ if (next.done) {
219
+ completed = true;
220
+ buffer += decoder.decode();
221
+ break;
222
+ }
223
+ buffer += decoder.decode(next.value, { stream: true });
224
+ buffer = normalizeEventStreamNewlines(buffer);
225
+ let boundary = buffer.indexOf('\n\n');
226
+ while (boundary >= 0) {
227
+ const event = buffer.slice(0, boundary);
228
+ buffer = buffer.slice(boundary + 2);
229
+ const data = event
230
+ .split('\n')
231
+ .filter((line) => line.startsWith('data:'))
232
+ .map((line) => line.slice('data:'.length).trimStart())
233
+ .join('\n');
234
+ if (data.length > 0)
235
+ yield JSON.parse(data);
236
+ boundary = buffer.indexOf('\n\n');
237
+ }
238
+ }
239
+ buffer = normalizeEventStreamNewlines(buffer).trim();
240
+ if (buffer.length > 0) {
241
+ const data = buffer
242
+ .split('\n')
243
+ .filter((line) => line.startsWith('data:'))
244
+ .map((line) => line.slice('data:'.length).trimStart())
245
+ .join('\n');
246
+ if (data.length > 0)
247
+ yield JSON.parse(data);
248
+ }
249
+ }
250
+ finally {
251
+ if (!completed)
252
+ await reader.cancel();
253
+ reader.releaseLock();
254
+ }
255
+ }
256
+ function normalizeEventStreamNewlines(value) {
257
+ return value.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
258
+ }
package/dist/http/cors.js CHANGED
@@ -123,12 +123,12 @@ function normalizeOrigin(origin, credentials) {
123
123
  }
124
124
  for (const value of origins) {
125
125
  if (value.length === 0 || /[\u0000-\u001f\u007f]/.test(value)) {
126
- throw new TypeError('CORS originには空文字列または制御文字を使用できません');
126
+ throw new TypeError('CORS origin cannot be empty or contain control characters');
127
127
  }
128
128
  }
129
129
  if (origins.includes('*')) {
130
130
  if (credentials) {
131
- throw new TypeError('credentialsを有効にする場合、CORS originにワイルドカードは使用できません');
131
+ throw new TypeError('CORS origin cannot be a wildcard when credentials are enabled');
132
132
  }
133
133
  return Object.freeze({ kind: 'wildcard' });
134
134
  }
@@ -141,7 +141,7 @@ function normalizeTokens(values, option, uppercase) {
141
141
  return Object.freeze(values.map((value) => {
142
142
  const token = value.trim();
143
143
  if (!httpToken.test(token)) {
144
- throw new TypeError(`CORS ${option}に不正なHTTP tokenが含まれています`);
144
+ throw new TypeError(`CORS ${option} contains an invalid HTTP token`);
145
145
  }
146
146
  return uppercase ? token.toUpperCase() : token;
147
147
  }));
@@ -150,7 +150,7 @@ function normalizeMaxAge(maxAge) {
150
150
  if (maxAge === undefined)
151
151
  return undefined;
152
152
  if (!Number.isSafeInteger(maxAge) || maxAge < 0) {
153
- throw new TypeError('CORS maxAgeには0以上の安全な整数を指定してください');
153
+ throw new TypeError('CORS maxAge must be a non-negative safe integer');
154
154
  }
155
155
  return maxAge;
156
156
  }
@@ -1,4 +1,4 @@
1
- import type { ContractDefinition, ContextProvidedBeforeTerminal, HasValidationBeforeTerminal, IsValidProtocolPipeline, PipelineItem, ProtocolDescriptor, SchemaInput, SchemaOutput, ShortCircuitDeclarationsOf, ShortCircuitResultOf, StandardSchemaV1, TerminalLayerDescriptor, ValidationLayerDescriptor } from '../core/index.js';
1
+ import type { ContractDefinition, ContextProvidedBeforeTerminal, HasValidationBeforeTerminal, IsValidProtocolPipeline, PipelineItem, ProtocolDescriptor, ProtocolFactory, ProtocolGroup, SchemaInput, SchemaOutput, ShortCircuitDeclarationsOf, ShortCircuitResultOf, StandardSchemaV1, TerminalLayerDescriptor, ValidationLayerDescriptor } from '../core/index.js';
2
2
  import type { Logger } from '../runtime/index.js';
3
3
  import { type HttpDispatchKey, type IsValidHttpPath, type PathParamNames, type RawPathParams } from './path.js';
4
4
  export type HttpParamsSchemas = Readonly<Record<string, StandardSchemaV1>>;
@@ -133,11 +133,19 @@ type AreErrorMappingsCompatible<TResponses extends HttpProtocolDefinition['respo
133
133
  [TVariant in keyof TResponses]: IsErrorMappingCompatible<TResponses[TVariant]>;
134
134
  }[keyof TResponses] ? false : true;
135
135
  declare function defineHttp<const TDefinition extends HttpProtocolDefinition>(definition: TDefinition & HttpPipelineConstraint<TDefinition> & HttpResponseConstraint<TDefinition> & HttpPathConstraint<TDefinition>): HttpProtocol<TDefinition>;
136
- export declare const http: typeof defineHttp & {
137
- protocol: 'http';
138
- controller: TerminalLayerDescriptor<"http">;
139
- error: typeof httpError;
136
+ type HttpProtocolGroup<TDefinitions extends Record<string, HttpProtocolDefinition>> = ProtocolGroup<'http', {
137
+ [K in keyof TDefinitions]: HttpProtocol<TDefinitions[K]>;
138
+ }>;
139
+ type HttpDefinitionsConstraint<TDefinitions extends Record<string, HttpProtocolDefinition>> = {
140
+ [K in keyof TDefinitions]: TDefinitions[K] & HttpPipelineConstraint<TDefinitions[K]> & HttpResponseConstraint<TDefinitions[K]> & HttpPathConstraint<TDefinitions[K]>;
140
141
  };
142
+ export interface HttpProtocolFactory extends ProtocolFactory<'http'> {
143
+ <const TDefinitions extends Record<string, HttpProtocolDefinition>>(definitions: TDefinitions & HttpDefinitionsConstraint<TDefinitions>): HttpProtocolGroup<TDefinitions>;
144
+ readonly route: typeof defineHttp;
145
+ readonly controller: TerminalLayerDescriptor<'http'>;
146
+ readonly error: typeof httpError;
147
+ }
148
+ export declare const http: HttpProtocolFactory;
141
149
  export declare const validate: Readonly<{
142
150
  params: ValidationLayerDescriptor & {
143
151
  readonly part: "params";
@@ -1,4 +1,4 @@
1
- import { childPipelineOf } from '../core/index.js';
1
+ import { childPipelineOf, protocolGroup } from '../core/index.js';
2
2
  import { createHttpDispatchKey, parseHttpPath, } from './path.js';
3
3
  const controller = Object.freeze({
4
4
  kind: 'terminal',
@@ -37,8 +37,12 @@ function hasParamsValidation(pipeline) {
37
37
  (item.kind === 'layer' &&
38
38
  hasParamsValidation(childPipelineOf(item) ?? [])));
39
39
  }
40
- export const http = Object.assign(defineHttp, {
40
+ function defineHttpGroup(definitions) {
41
+ return protocolGroup('http', Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, defineHttp(definition)])));
42
+ }
43
+ export const http = Object.assign(defineHttpGroup, {
41
44
  protocol: 'http',
45
+ route: defineHttp,
42
46
  controller,
43
47
  error: httpError,
44
48
  });
@@ -1,5 +1,6 @@
1
1
  export * from './application.js';
2
2
  export * from './basic-auth.js';
3
+ export * from './client.js';
3
4
  export type { CorsLayerDescriptor, CorsOptions, CorsOrigin } from './cors.js';
4
5
  export * from './definitions.js';
5
6
  export { http } from './hosted-protocol.js';
@@ -1,5 +1,6 @@
1
1
  export * from './application.js';
2
2
  export * from './basic-auth.js';
3
+ export * from './client.js';
3
4
  export * from './definitions.js';
4
5
  export { http } from './hosted-protocol.js';
5
6
  export * from './request-body.js';
@@ -1,4 +1,4 @@
1
- import { type ContextProvidedBeforeTerminal, type IsValidProtocolPipeline, type ContractDefinition, type PipelineItem, type ProtocolDescriptor, type SchemaOutput, type StandardSchemaV1, type TerminalLayerDescriptor } from '../core/index.js';
1
+ import { type ContextProvidedBeforeTerminal, type IsValidProtocolPipeline, type ContractDefinition, type PipelineItem, type ProtocolDescriptor, type ProtocolFactory, type ProtocolGroup, type SchemaOutput, type StandardSchemaV1, type TerminalLayerDescriptor } from '../core/index.js';
2
2
  import type { ApplicationGraphIR } from '../graph/index.js';
3
3
  import { ApplicationRuntime, Logger } from '../runtime/index.js';
4
4
  export interface MessagePortResponseDefinition {
@@ -17,10 +17,18 @@ declare function defineMessagePort<const TDefinition extends MessagePortProtocol
17
17
  type MessagePortPipelineConstraint<TDefinition extends MessagePortProtocolDefinition> = IsValidProtocolPipeline<TDefinition['pipeline'], 'messagePort'> extends true ? unknown : {
18
18
  readonly pipeline: never;
19
19
  };
20
- export declare const messagePort: typeof defineMessagePort & {
21
- protocol: 'messagePort';
22
- handler: TerminalLayerDescriptor<"messagePort">;
20
+ type MessagePortProtocolGroup<TDefinitions extends Record<string, MessagePortProtocolDefinition>> = ProtocolGroup<'messagePort', {
21
+ [K in keyof TDefinitions]: MessagePortProtocol<TDefinitions[K]>;
22
+ }>;
23
+ type MessagePortDefinitionsConstraint<TDefinitions extends Record<string, MessagePortProtocolDefinition>> = {
24
+ [K in keyof TDefinitions]: TDefinitions[K] & MessagePortPipelineConstraint<TDefinitions[K]>;
23
25
  };
26
+ export interface MessagePortProtocolFactory extends ProtocolFactory<'messagePort'> {
27
+ <const TDefinitions extends Record<string, MessagePortProtocolDefinition>>(definitions: TDefinitions & MessagePortDefinitionsConstraint<TDefinitions>): MessagePortProtocolGroup<TDefinitions>;
28
+ readonly route: typeof defineMessagePort;
29
+ readonly handler: TerminalLayerDescriptor<'messagePort'>;
30
+ }
31
+ export declare const messagePort: MessagePortProtocolFactory;
24
32
  export interface LogicalMessagePortResult<TVariant extends string = string, TValue = unknown> {
25
33
  readonly kind: 'message-port-result';
26
34
  readonly variant: TVariant;
@@ -1,4 +1,4 @@
1
- import { validateSchema, } from '../core/index.js';
1
+ import { validateSchema, protocolGroup, } from '../core/index.js';
2
2
  import { executePipeline, Logger, normalizeUnknownError, } from '../runtime/index.js';
3
3
  const handler = Object.freeze({
4
4
  kind: 'terminal',
@@ -15,8 +15,15 @@ function defineMessagePort(definition) {
15
15
  definition,
16
16
  };
17
17
  }
18
- export const messagePort = Object.assign(defineMessagePort, {
18
+ function defineMessagePortGroup(definitions) {
19
+ return protocolGroup('messagePort', Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
20
+ name,
21
+ defineMessagePort(definition),
22
+ ])));
23
+ }
24
+ export const messagePort = Object.assign(defineMessagePortGroup, {
19
25
  protocol: 'messagePort',
26
+ route: defineMessagePort,
20
27
  handler,
21
28
  });
22
29
  export function createMessagePortExecution(options) {
@@ -41,7 +48,7 @@ export function createMessagePortExecution(options) {
41
48
  return await runtime.execute(async () => {
42
49
  const route = routes.find((candidate) => candidate.procedure === procedure);
43
50
  if (!route)
44
- throw new Error(`MessagePort procedureがありません: ${procedure}`);
51
+ throw new Error(`MessagePort procedure not found: ${procedure}`);
45
52
  invocationLogger = invocationLogger.child({
46
53
  source: `${route.implementation.name}.${route.procedure}`,
47
54
  });
@@ -66,7 +73,7 @@ export function createMessagePortExecution(options) {
66
73
  const target = runtime.container.implementationRuntime(route.implementation);
67
74
  const method = target[route.procedure];
68
75
  if (typeof method !== 'function')
69
- throw new Error('Handler methodがありません');
76
+ throw new Error('Handler method is missing');
70
77
  return Reflect.apply(method, target, [
71
78
  terminalContext,
72
79
  ]);
@@ -106,10 +113,10 @@ export function createMessagePortExecution(options) {
106
113
  async function finalize(definition, result) {
107
114
  const response = definition.responses[result.variant];
108
115
  if (!response)
109
- throw new Error(`未宣言variantです: ${result.variant}`);
116
+ throw new Error(`Undeclared variant: ${result.variant}`);
110
117
  if (response.stream === 'server') {
111
118
  if (!isAsyncIterable(result.value)) {
112
- throw new Error('server-stream resultにはAsyncIterableが必要です');
119
+ throw new Error('Server-stream result requires an AsyncIterable');
113
120
  }
114
121
  const source = result.value;
115
122
  return {