@loutrejs/loutre 0.1.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 (103) hide show
  1. package/dist/adapters/aws-lambda.d.ts +52 -0
  2. package/dist/adapters/aws-lambda.js +114 -0
  3. package/dist/adapters/bun.d.ts +20 -0
  4. package/dist/adapters/bun.js +82 -0
  5. package/dist/adapters/cloudflare-workers.d.ts +18 -0
  6. package/dist/adapters/cloudflare-workers.js +55 -0
  7. package/dist/adapters/deno.d.ts +28 -0
  8. package/dist/adapters/deno.js +106 -0
  9. package/dist/adapters/electron.d.ts +27 -0
  10. package/dist/adapters/electron.js +52 -0
  11. package/dist/application/binding.d.ts +33 -0
  12. package/dist/application/binding.js +292 -0
  13. package/dist/application/host.d.ts +8 -0
  14. package/dist/application/host.js +14 -0
  15. package/dist/application/index.d.ts +62 -0
  16. package/dist/application/index.js +11 -0
  17. package/dist/application/openapi.d.ts +28 -0
  18. package/dist/application/openapi.js +362 -0
  19. package/dist/application/queue.d.ts +3 -0
  20. package/dist/application/queue.js +4 -0
  21. package/dist/application/scheduler.d.ts +2 -0
  22. package/dist/application/scheduler.js +45 -0
  23. package/dist/core/args.d.ts +16 -0
  24. package/dist/core/args.js +29 -0
  25. package/dist/core/context-key.d.ts +14 -0
  26. package/dist/core/context-key.js +14 -0
  27. package/dist/core/contract.d.ts +54 -0
  28. package/dist/core/contract.js +30 -0
  29. package/dist/core/env.d.ts +16 -0
  30. package/dist/core/env.js +29 -0
  31. package/dist/core/error.d.ts +24 -0
  32. package/dist/core/error.js +25 -0
  33. package/dist/core/execution.d.ts +78 -0
  34. package/dist/core/execution.js +61 -0
  35. package/dist/core/implementation.d.ts +34 -0
  36. package/dist/core/implementation.js +30 -0
  37. package/dist/core/index.d.ts +15 -0
  38. package/dist/core/index.js +15 -0
  39. package/dist/core/injection.d.ts +28 -0
  40. package/dist/core/injection.js +35 -0
  41. package/dist/core/layer.d.ts +109 -0
  42. package/dist/core/layer.js +52 -0
  43. package/dist/core/lifecycle.d.ts +34 -0
  44. package/dist/core/lifecycle.js +3 -0
  45. package/dist/core/module.d.ts +53 -0
  46. package/dist/core/module.js +31 -0
  47. package/dist/core/provider.d.ts +57 -0
  48. package/dist/core/provider.js +63 -0
  49. package/dist/core/runtime-input.d.ts +11 -0
  50. package/dist/core/runtime-input.js +1 -0
  51. package/dist/core/schema.d.ts +13 -0
  52. package/dist/core/schema.js +20 -0
  53. package/dist/core/token.d.ts +16 -0
  54. package/dist/core/token.js +13 -0
  55. package/dist/graph/graph.d.ts +17 -0
  56. package/dist/graph/graph.js +1078 -0
  57. package/dist/graph/index.d.ts +2 -0
  58. package/dist/graph/index.js +2 -0
  59. package/dist/graph/ir.d.ts +159 -0
  60. package/dist/graph/ir.js +1 -0
  61. package/dist/http/application.d.ts +18 -0
  62. package/dist/http/application.js +506 -0
  63. package/dist/http/basic-auth.d.ts +37 -0
  64. package/dist/http/basic-auth.js +67 -0
  65. package/dist/http/cors.d.ts +17 -0
  66. package/dist/http/cors.js +171 -0
  67. package/dist/http/definitions.d.ts +217 -0
  68. package/dist/http/definitions.js +65 -0
  69. package/dist/http/hosted-protocol.d.ts +4 -0
  70. package/dist/http/hosted-protocol.js +4 -0
  71. package/dist/http/index.d.ts +7 -0
  72. package/dist/http/index.js +6 -0
  73. package/dist/http/params.d.ts +2 -0
  74. package/dist/http/params.js +19 -0
  75. package/dist/http/path.d.ts +55 -0
  76. package/dist/http/path.js +88 -0
  77. package/dist/http/request-body.d.ts +4 -0
  78. package/dist/http/request-body.js +6 -0
  79. package/dist/http/validate.d.ts +20 -0
  80. package/dist/http/validate.js +6 -0
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.js +2 -0
  83. package/dist/message-port/environment.d.ts +2 -0
  84. package/dist/message-port/environment.js +4 -0
  85. package/dist/message-port/index.d.ts +65 -0
  86. package/dist/message-port/index.js +182 -0
  87. package/dist/runtime/application.d.ts +37 -0
  88. package/dist/runtime/application.js +324 -0
  89. package/dist/runtime/capability.d.ts +16 -0
  90. package/dist/runtime/capability.js +18 -0
  91. package/dist/runtime/di.d.ts +37 -0
  92. package/dist/runtime/di.js +441 -0
  93. package/dist/runtime/error.d.ts +10 -0
  94. package/dist/runtime/error.js +15 -0
  95. package/dist/runtime/index.d.ts +7 -0
  96. package/dist/runtime/index.js +7 -0
  97. package/dist/runtime/logger.d.ts +36 -0
  98. package/dist/runtime/logger.js +207 -0
  99. package/dist/runtime/node-capabilities.d.ts +5 -0
  100. package/dist/runtime/node-capabilities.js +18 -0
  101. package/dist/runtime/pipeline.d.ts +11 -0
  102. package/dist/runtime/pipeline.js +161 -0
  103. package/package.json +81 -0
@@ -0,0 +1,55 @@
1
+ type AsciiLowercase = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
2
+ type AsciiLetter = AsciiLowercase | Uppercase<AsciiLowercase>;
3
+ type AsciiDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
4
+ type ParamNameStart = AsciiLetter | '_';
5
+ type ParamNameContinuation = ParamNameStart | AsciiDigit;
6
+ type IsParamNameRest<TName extends string> = TName extends '' ? true : TName extends `${ParamNameContinuation}${infer TRest}` ? IsParamNameRest<TRest> : false;
7
+ type IsParamName<TName extends string> = TName extends `${ParamNameStart}${infer TRest}` ? IsParamNameRest<TRest> : false;
8
+ type IsStaticSegment<TSegment extends string> = TSegment extends '' ? false : TSegment extends `${string}{${string}` | `${string}}${string}` ? false : TSegment extends `${string}?${string}` | `${string}#${string}` ? false : true;
9
+ type ValidatePathSegments<TSegments extends string, TSeen extends string = never> = TSegments extends `${infer THead}/${infer TTail}` ? ValidatePathSegment<THead, TSeen> extends infer TResult ? TResult extends {
10
+ readonly valid: true;
11
+ readonly seen: infer TNext extends string;
12
+ } ? ValidatePathSegments<TTail, TNext> : false : false : ValidatePathSegment<TSegments, TSeen> extends {
13
+ readonly valid: true;
14
+ } ? true : false;
15
+ type ValidatePathSegment<TSegment extends string, TSeen extends string> = TSegment extends `{${infer TName}}` ? IsParamName<TName> extends true ? TName extends TSeen ? {
16
+ readonly valid: false;
17
+ } : {
18
+ readonly valid: true;
19
+ readonly seen: TSeen | TName;
20
+ } : {
21
+ readonly valid: false;
22
+ } : IsStaticSegment<TSegment> extends true ? {
23
+ readonly valid: true;
24
+ readonly seen: TSeen;
25
+ } : {
26
+ readonly valid: false;
27
+ };
28
+ export type IsValidHttpPath<TPath extends string> = string extends TPath ? false : TPath extends '/' ? true : TPath extends `/${infer TSegments}` ? ValidatePathSegments<TSegments> : false;
29
+ type PathParamNamesFromSegments<TSegments extends string> = TSegments extends `${infer THead}/${infer TTail}` ? PathParamName<THead> | PathParamNamesFromSegments<TTail> : PathParamName<TSegments>;
30
+ type PathParamName<TSegment extends string> = TSegment extends `{${infer TName}}` ? TName : never;
31
+ export type PathParamNames<TPath extends string> = TPath extends `/${infer TSegments}` ? PathParamNamesFromSegments<TSegments> : never;
32
+ export type RawPathParams<TPath extends string> = Readonly<{
33
+ [TName in PathParamNames<TPath>]: string;
34
+ }>;
35
+ type NormalizeHttpPathSegments<TSegments extends string> = TSegments extends `${infer THead}/${infer TTail}` ? `${NormalizeHttpPathSegment<THead>}/${NormalizeHttpPathSegments<TTail>}` : NormalizeHttpPathSegment<TSegments>;
36
+ type NormalizeHttpPathSegment<TSegment extends string> = TSegment extends `{${string}}` ? '{}' : TSegment;
37
+ export type NormalizeHttpPath<TPath extends string> = TPath extends '/' ? '/' : string extends TPath ? string : TPath extends `/${infer TSegments}` ? `/${NormalizeHttpPathSegments<TSegments>}` : never;
38
+ export type HttpDispatchKey<TMethod extends string, TPath extends string> = `http:${Uppercase<TMethod>}:${NormalizeHttpPath<TPath>}`;
39
+ export type HttpPathSegment = {
40
+ readonly kind: 'static';
41
+ readonly value: string;
42
+ } | {
43
+ readonly kind: 'param';
44
+ readonly name: string;
45
+ };
46
+ export declare function parseHttpPath(path: string): readonly HttpPathSegment[];
47
+ export declare function normalizeHttpPath(segments: readonly HttpPathSegment[]): string;
48
+ export declare function createHttpDispatchKey(method: string, segments: readonly HttpPathSegment[]): string;
49
+ export declare function matchHttpPath(segments: readonly HttpPathSegment[], pathname: string): Record<string, string> | undefined;
50
+ export declare function compareHttpPathSpecificity(left: readonly HttpPathSegment[], right: readonly HttpPathSegment[]): number;
51
+ export declare class HttpPathDecodeError extends Error {
52
+ readonly cause: unknown;
53
+ constructor(cause: unknown);
54
+ }
55
+ export {};
@@ -0,0 +1,88 @@
1
+ const PARAM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2
+ export function parseHttpPath(path) {
3
+ if (path === '/')
4
+ return Object.freeze([]);
5
+ if (!path.startsWith('/')) {
6
+ throw new Error(`Invalid HTTP path: ${path}`);
7
+ }
8
+ if (path.endsWith('/') || path.includes('//')) {
9
+ throw new Error(`Invalid HTTP path segment: ${path}`);
10
+ }
11
+ if (path.includes('?') || path.includes('#')) {
12
+ throw new Error(`HTTP path must not contain a query or fragment: ${path}`);
13
+ }
14
+ const names = new Set();
15
+ const segments = path
16
+ .slice(1)
17
+ .split('/')
18
+ .map((segment) => {
19
+ if (segment.startsWith('{') || segment.endsWith('}')) {
20
+ const match = /^\{([^{}]*)\}$/.exec(segment);
21
+ const name = match?.[1];
22
+ if (!name || !PARAM_NAME_PATTERN.test(name)) {
23
+ throw new Error(`Invalid HTTP path parameter: ${segment}`);
24
+ }
25
+ if (names.has(name)) {
26
+ throw new Error(`Duplicate HTTP path parameter: ${name}`);
27
+ }
28
+ names.add(name);
29
+ return Object.freeze({ kind: 'param', name });
30
+ }
31
+ if (segment.includes('{') || segment.includes('}')) {
32
+ throw new Error(`Inline HTTP path parameters are not supported: ${segment}`);
33
+ }
34
+ return Object.freeze({ kind: 'static', value: segment });
35
+ });
36
+ return Object.freeze(segments);
37
+ }
38
+ export function normalizeHttpPath(segments) {
39
+ if (segments.length === 0)
40
+ return '/';
41
+ return `/${segments
42
+ .map((segment) => (segment.kind === 'static' ? segment.value : '{}'))
43
+ .join('/')}`;
44
+ }
45
+ export function createHttpDispatchKey(method, segments) {
46
+ return `http:${method.toUpperCase()}:${normalizeHttpPath(segments)}`;
47
+ }
48
+ export function matchHttpPath(segments, pathname) {
49
+ const requestSegments = pathname === '/' ? [] : pathname.slice(1).split('/');
50
+ if (requestSegments.length !== segments.length)
51
+ return undefined;
52
+ const params = {};
53
+ for (let index = 0; index < segments.length; index += 1) {
54
+ const routeSegment = segments[index];
55
+ const requestSegment = requestSegments[index];
56
+ if (routeSegment.kind === 'static') {
57
+ if (routeSegment.value !== requestSegment)
58
+ return undefined;
59
+ continue;
60
+ }
61
+ try {
62
+ params[routeSegment.name] = decodeURIComponent(requestSegment);
63
+ }
64
+ catch (error) {
65
+ throw new HttpPathDecodeError(error);
66
+ }
67
+ }
68
+ return params;
69
+ }
70
+ export function compareHttpPathSpecificity(left, right) {
71
+ const length = Math.min(left.length, right.length);
72
+ for (let index = 0; index < length; index += 1) {
73
+ const leftSegment = left[index];
74
+ const rightSegment = right[index];
75
+ if (leftSegment.kind === rightSegment.kind)
76
+ continue;
77
+ return leftSegment.kind === 'static' ? -1 : 1;
78
+ }
79
+ return right.length - left.length;
80
+ }
81
+ export class HttpPathDecodeError extends Error {
82
+ cause;
83
+ constructor(cause) {
84
+ super('HTTP path decode failed', { cause });
85
+ this.cause = cause;
86
+ this.name = 'HttpPathDecodeError';
87
+ }
88
+ }
@@ -0,0 +1,4 @@
1
+ import type { StandardSchemaV1 } from '../core/index.js';
2
+ import type { HttpRequestBodyDefinition } from './definitions.js';
3
+ export declare function httpRequestBodySchema(body: HttpRequestBodyDefinition): StandardSchemaV1;
4
+ export declare function httpRequestBodyContentType(body: HttpRequestBodyDefinition): string;
@@ -0,0 +1,6 @@
1
+ export function httpRequestBodySchema(body) {
2
+ return body.schema;
3
+ }
4
+ export function httpRequestBodyContentType(body) {
5
+ return body.contentType;
6
+ }
@@ -0,0 +1,20 @@
1
+ import { cors } from './cors.js';
2
+ export declare const validate: Readonly<{
3
+ params: import("../index.js").ValidationLayerDescriptor & {
4
+ readonly part: "params";
5
+ readonly name: "validate.params";
6
+ };
7
+ query: import("../index.js").ValidationLayerDescriptor & {
8
+ readonly part: "query";
9
+ readonly name: "validate.query";
10
+ };
11
+ headers: import("../index.js").ValidationLayerDescriptor & {
12
+ readonly part: "headers";
13
+ readonly name: "validate.headers";
14
+ };
15
+ body: import("../index.js").ValidationLayerDescriptor & {
16
+ readonly part: "body";
17
+ readonly name: "validate.body";
18
+ };
19
+ cors: typeof cors;
20
+ }>;
@@ -0,0 +1,6 @@
1
+ import { cors } from './cors.js';
2
+ import { validate as inputValidation } from './definitions.js';
3
+ export const validate = Object.freeze({
4
+ ...inputValidation,
5
+ cors,
6
+ });
@@ -0,0 +1,2 @@
1
+ export * from './core/index.js';
2
+ export * from './application/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './core/index.js';
2
+ export * from './application/index.js';
@@ -0,0 +1,2 @@
1
+ import type { MessagePortProtocolExecution } from './index.js';
2
+ export declare function initializeMessagePortExecution(application: MessagePortProtocolExecution, environmentSource: unknown): Promise<void>;
@@ -0,0 +1,4 @@
1
+ import { initializeWithEnvironment } from '../runtime/index.js';
2
+ export function initializeMessagePortExecution(application, environmentSource) {
3
+ return initializeWithEnvironment(application, environmentSource);
4
+ }
@@ -0,0 +1,65 @@
1
+ import { type ContextProvidedBeforeTerminal, type IsValidProtocolPipeline, type ContractDefinition, type PipelineItem, type ProtocolDescriptor, type SchemaOutput, type StandardSchemaV1, type TerminalLayerDescriptor } from '../core/index.js';
2
+ import type { ApplicationGraphIR } from '../graph/index.js';
3
+ import { ApplicationRuntime, Logger } from '../runtime/index.js';
4
+ export interface MessagePortResponseDefinition {
5
+ readonly body: StandardSchemaV1;
6
+ readonly stream?: 'server';
7
+ }
8
+ export interface MessagePortProtocolDefinition {
9
+ readonly interaction?: 'unary' | 'server-stream';
10
+ readonly responses: Readonly<Record<string, MessagePortResponseDefinition>>;
11
+ readonly pipeline: readonly PipelineItem[];
12
+ }
13
+ export interface MessagePortProtocol<TDefinition extends MessagePortProtocolDefinition = MessagePortProtocolDefinition> extends ProtocolDescriptor<'messagePort', MessagePortHandlerContextDefinition<TDefinition>, LogicalMessagePortResult> {
14
+ readonly definition: TDefinition;
15
+ }
16
+ declare function defineMessagePort<const TDefinition extends MessagePortProtocolDefinition>(definition: TDefinition & MessagePortPipelineConstraint<TDefinition>): MessagePortProtocol<TDefinition>;
17
+ type MessagePortPipelineConstraint<TDefinition extends MessagePortProtocolDefinition> = IsValidProtocolPipeline<TDefinition['pipeline'], 'messagePort'> extends true ? unknown : {
18
+ readonly pipeline: never;
19
+ };
20
+ export declare const messagePort: typeof defineMessagePort & {
21
+ protocol: 'messagePort';
22
+ handler: TerminalLayerDescriptor<"messagePort">;
23
+ };
24
+ export interface LogicalMessagePortResult<TVariant extends string = string, TValue = unknown> {
25
+ readonly kind: 'message-port-result';
26
+ readonly variant: TVariant;
27
+ readonly value: TValue;
28
+ }
29
+ type ProceduresForMessagePort<TContract extends ContractDefinition> = {
30
+ [K in keyof TContract['procedures']]: 'messagePort' extends keyof TContract['procedures'][K]['protocols'] ? K : never;
31
+ }[keyof TContract['procedures']] & string;
32
+ type ProtocolAt<TContract extends ContractDefinition, TProcedure extends keyof TContract['procedures']> = TContract['procedures'][TProcedure]['protocols']['messagePort' & keyof TContract['procedures'][TProcedure]['protocols']] extends infer TProtocol ? TProtocol extends MessagePortProtocol ? TProtocol : never : never;
33
+ type MessageHelpers<TProtocol extends MessagePortProtocol> = {
34
+ [TVariant in keyof TProtocol['definition']['responses'] & string]: (value: TProtocol['definition']['responses'][TVariant] extends {
35
+ readonly stream: 'server';
36
+ } ? AsyncIterable<SchemaOutput<TProtocol['definition']['responses'][TVariant]['body']>> : SchemaOutput<TProtocol['definition']['responses'][TVariant]['body']>) => LogicalMessagePortResult;
37
+ };
38
+ type MessagePortHandlerContextDefinition<TDefinition extends MessagePortProtocolDefinition> = {
39
+ readonly input: unknown;
40
+ readonly message: MessageHelpers<MessagePortProtocol<TDefinition>>;
41
+ readonly logger: Logger;
42
+ } & ContextProvidedBeforeTerminal<TDefinition['pipeline']>;
43
+ export type MessagePortHandlerContext<TProtocol extends MessagePortProtocol> = MessagePortHandlerContextDefinition<TProtocol['definition']>;
44
+ export type HandlerOf<TContract extends ContractDefinition, TProtocol extends 'messagePort', TProcedures extends ProceduresForMessagePort<TContract> = ProceduresForMessagePort<TContract>> = TProtocol extends 'messagePort' ? {
45
+ [K in TProcedures]: (context: MessagePortHandlerContext<ProtocolAt<TContract, K>>) => LogicalMessagePortResult | Promise<LogicalMessagePortResult>;
46
+ } : never;
47
+ export type MessageContextOf<THandler, TProcedure extends keyof THandler> = THandler[TProcedure] extends (context: infer TContext, ...args: any[]) => any ? TContext : never;
48
+ export interface MessagePortProtocolExecution {
49
+ readonly graph: ApplicationGraphIR;
50
+ initialize(): Promise<void>;
51
+ shutdown(signal?: string): Promise<void>;
52
+ invoke(procedure: string, input?: unknown): Promise<LogicalMessagePortResult>;
53
+ }
54
+ export declare function createMessagePortExecution(options: {
55
+ readonly runtime: ApplicationRuntime;
56
+ readonly graph: ApplicationGraphIR;
57
+ readonly logger?: Logger;
58
+ }): MessagePortProtocolExecution;
59
+ export interface MessagePortLike {
60
+ postMessage(value: unknown): void;
61
+ addEventListener(type: string, listener: (event: any) => void): void;
62
+ start?(): void;
63
+ }
64
+ export declare function attachMessagePort(application: MessagePortProtocolExecution, port: MessagePortLike): void;
65
+ export {};
@@ -0,0 +1,182 @@
1
+ import { validateSchema, } from '../core/index.js';
2
+ import { executePipeline, Logger, normalizeUnknownError, } from '../runtime/index.js';
3
+ const handler = Object.freeze({
4
+ kind: 'terminal',
5
+ name: 'messagePort.handler',
6
+ role: 'terminal',
7
+ protocol: 'messagePort',
8
+ });
9
+ function defineMessagePort(definition) {
10
+ return {
11
+ kind: 'protocol',
12
+ protocol: 'messagePort',
13
+ interaction: definition.interaction ?? 'unary',
14
+ dispatchKey: null,
15
+ definition,
16
+ };
17
+ }
18
+ export const messagePort = Object.assign(defineMessagePort, {
19
+ protocol: 'messagePort',
20
+ handler,
21
+ });
22
+ export function createMessagePortExecution(options) {
23
+ const graph = options.graph;
24
+ const applicationLogger = options.logger ?? new Logger();
25
+ const runtime = options.runtime;
26
+ const logger = applicationLogger.child({ protocol: 'messagePort' });
27
+ const routes = collectRoutes(runtime.graph.modules);
28
+ let initialization;
29
+ const initialize = () => (initialization ??= runtime.initialize());
30
+ return {
31
+ graph,
32
+ initialize,
33
+ shutdown: (signal) => runtime.shutdown(signal),
34
+ async invoke(procedure, input) {
35
+ const startedAt = Date.now();
36
+ let invocationLogger = logger.child({
37
+ procedure,
38
+ executionId: crypto.randomUUID(),
39
+ });
40
+ try {
41
+ return await runtime.execute(async () => {
42
+ const route = routes.find((candidate) => candidate.procedure === procedure);
43
+ if (!route)
44
+ throw new Error(`MessagePort procedureがありません: ${procedure}`);
45
+ invocationLogger = invocationLogger.child({
46
+ source: `${route.implementation.name}.${route.procedure}`,
47
+ });
48
+ const message = Object.fromEntries(Object.keys(route.protocol.definition.responses).map((variant) => [
49
+ variant,
50
+ (value) => ({
51
+ kind: 'message-port-result',
52
+ variant,
53
+ value,
54
+ }),
55
+ ]));
56
+ const context = {
57
+ input,
58
+ message,
59
+ logger: invocationLogger,
60
+ };
61
+ const result = await executePipeline(route.protocol.definition.pipeline, {
62
+ context,
63
+ layer: (descriptor) => runtime.container.layerRuntime(descriptor),
64
+ validate: () => undefined,
65
+ terminal: async (_layer, terminalContext) => {
66
+ const target = runtime.container.implementationRuntime(route.implementation);
67
+ const method = target[route.procedure];
68
+ if (typeof method !== 'function')
69
+ throw new Error('Handler methodがありません');
70
+ return Reflect.apply(method, target, [
71
+ terminalContext,
72
+ ]);
73
+ },
74
+ });
75
+ const finalized = await finalize(route.protocol.definition, result);
76
+ invocationLogger.info('MessagePort invocation completed', {
77
+ event: 'message_port.invocation.completed',
78
+ durationMs: Math.max(0, Date.now() - startedAt),
79
+ });
80
+ return finalized;
81
+ });
82
+ }
83
+ catch (error) {
84
+ const normalized = normalizeUnknownError(error, invocationLogger.context);
85
+ invocationLogger.error('Unhandled application error', {
86
+ event: 'application.error',
87
+ durationMs: Math.max(0, Date.now() - startedAt),
88
+ error: {
89
+ code: normalized.code,
90
+ id: normalized.errorId,
91
+ name: error instanceof Error ? error.name : typeof error,
92
+ message: normalized.message,
93
+ ...(normalized.stack === undefined
94
+ ? {}
95
+ : { stack: normalized.stack }),
96
+ ...(normalized.cause === undefined
97
+ ? {}
98
+ : { cause: normalized.cause }),
99
+ },
100
+ });
101
+ throw error;
102
+ }
103
+ },
104
+ };
105
+ }
106
+ async function finalize(definition, result) {
107
+ const response = definition.responses[result.variant];
108
+ if (!response)
109
+ throw new Error(`未宣言variantです: ${result.variant}`);
110
+ if (response.stream === 'server') {
111
+ if (!isAsyncIterable(result.value)) {
112
+ throw new Error('server-stream resultにはAsyncIterableが必要です');
113
+ }
114
+ const source = result.value;
115
+ return {
116
+ ...result,
117
+ value: (async function* () {
118
+ for await (const value of source) {
119
+ yield await validateSchema(response.body, value);
120
+ }
121
+ })(),
122
+ };
123
+ }
124
+ return { ...result, value: await validateSchema(response.body, result.value) };
125
+ }
126
+ function collectRoutes(modules) {
127
+ return modules.flatMap((module) => (module.definition.implementations ?? []).flatMap((implementation) => {
128
+ if (implementation.protocol !== 'messagePort')
129
+ return [];
130
+ return implementation.procedures.map((procedure) => ({
131
+ procedure,
132
+ protocol: implementation.contract.procedures[procedure].protocols
133
+ .messagePort,
134
+ implementation,
135
+ }));
136
+ }));
137
+ }
138
+ export function attachMessagePort(application, port) {
139
+ port.addEventListener('message', async (event) => {
140
+ const request = event.data;
141
+ try {
142
+ const result = await application.invoke(request.procedure, request.input);
143
+ if (isAsyncIterable(result.value)) {
144
+ for await (const value of result.value) {
145
+ port.postMessage({
146
+ id: request.id,
147
+ variant: result.variant,
148
+ value,
149
+ done: false,
150
+ });
151
+ }
152
+ port.postMessage({
153
+ id: request.id,
154
+ variant: result.variant,
155
+ done: true,
156
+ });
157
+ }
158
+ else {
159
+ port.postMessage({
160
+ id: request.id,
161
+ variant: result.variant,
162
+ value: result.value,
163
+ done: true,
164
+ });
165
+ }
166
+ }
167
+ catch (error) {
168
+ port.postMessage({
169
+ id: request.id,
170
+ error: error instanceof Error ? error.message : String(error),
171
+ done: true,
172
+ });
173
+ }
174
+ });
175
+ port.start?.();
176
+ }
177
+ function isAsyncIterable(value) {
178
+ return (typeof value === 'object' &&
179
+ value !== null &&
180
+ Symbol.asyncIterator in value &&
181
+ typeof value[Symbol.asyncIterator] === 'function');
182
+ }
@@ -0,0 +1,37 @@
1
+ import { type ArgsClass, type EnvClass, type ModuleInstance, type ModuleTemplate, type TaskArguments, type TaskDescriptor, type TaskOutput } from '../core/index.js';
2
+ import { Container, type RuntimeModuleGraph } from './di.js';
3
+ import { Logger } from './logger.js';
4
+ export interface ApplicationRuntimeOptions {
5
+ readonly logger?: Logger;
6
+ readonly environmentSource?: unknown;
7
+ readonly arguments?: ArgsClass;
8
+ readonly argumentsSource?: unknown;
9
+ readonly tasks?: readonly TaskDescriptor<any, any>[];
10
+ readonly publicTasks?: readonly TaskDescriptor<any, any>[];
11
+ }
12
+ export interface InitializableApplication {
13
+ initialize(): Promise<void>;
14
+ }
15
+ export declare function initializeWithEnvironment(application: InitializableApplication, environmentSource: unknown): Promise<void>;
16
+ export declare class EnvironmentBindingError extends Error {
17
+ readonly code = "LUTRE_ENV_003";
18
+ constructor(environment: EnvClass, cause: unknown);
19
+ }
20
+ export declare class ArgumentsBindingError extends Error {
21
+ readonly code = "LUTRE_ARGS_003";
22
+ constructor(argumentsContract: ArgsClass, cause: unknown);
23
+ }
24
+ export declare class ApplicationRuntime {
25
+ #private;
26
+ readonly graph: RuntimeModuleGraph;
27
+ readonly container: Container;
28
+ constructor(roots: readonly (ModuleInstance | ModuleTemplate<void>)[], options?: ApplicationRuntimeOptions);
29
+ initialize(): Promise<void>;
30
+ run<TTask extends TaskDescriptor<any, any>>(task: TTask, ...args: TaskArguments<TTask>): Promise<TaskOutput<TTask>>;
31
+ /** public run()ではTrigger専用Taskを拒否するため、Trigger Host専用経路を分ける。 */
32
+ runTask<TTask extends TaskDescriptor<any, any>>(task: TTask, ...args: TaskArguments<TTask>): Promise<TaskOutput<TTask>>;
33
+ execute<T>(execution: () => T | Promise<T>): Promise<T>;
34
+ stopAcceptingExecutions(): void;
35
+ shutdown(signal?: string): Promise<void>;
36
+ }
37
+ export declare function createApplicationRuntime(roots: readonly (ModuleInstance | ModuleTemplate<void>)[], options?: ApplicationRuntimeOptions): ApplicationRuntime;