@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
@@ -0,0 +1,33 @@
1
+ import { LOUTRE_VERSION } from './generated/version.js';
2
+ export interface StartupPresentationInfo {
3
+ readonly version: string;
4
+ }
5
+ export interface StartupStatusInfo {
6
+ readonly server: string;
7
+ readonly runtime: string;
8
+ readonly environment: string;
9
+ readonly startupDurationMs: number;
10
+ }
11
+ export interface StartupPresentationSession {
12
+ ready(info: StartupStatusInfo): void;
13
+ }
14
+ export interface StartupPresentationStartOptions {
15
+ readonly terminal: PresentationRenderOptions;
16
+ readonly write: (value: string) => void;
17
+ }
18
+ export { LOUTRE_VERSION };
19
+ export interface PresentationRenderOptions {
20
+ readonly isTTY: boolean;
21
+ readonly color: boolean;
22
+ readonly columns?: number;
23
+ }
24
+ export interface PresentationTerminalOutput {
25
+ readonly isTTY?: boolean;
26
+ readonly columns?: number;
27
+ readonly getColorDepth: (environment?: Readonly<Record<string, string | undefined>>) => number;
28
+ }
29
+ export declare function detectPresentationTerminal(output: PresentationTerminalOutput, environment: Readonly<Record<string, string | undefined>>): PresentationRenderOptions;
30
+ export declare function startStartupPresentation(info: StartupPresentationInfo, options: StartupPresentationStartOptions): StartupPresentationSession;
31
+ export declare function renderLoutreBrand(options: PresentationRenderOptions): string;
32
+ export declare function renderStartupPrelude(info: StartupPresentationInfo, options: PresentationRenderOptions): string;
33
+ export declare function renderStartupStatus(info: StartupStatusInfo, options: PresentationRenderOptions): string;
@@ -0,0 +1,233 @@
1
+ import { LOUTRE_VERSION } from './generated/version.js';
2
+ export { LOUTRE_VERSION };
3
+ export function detectPresentationTerminal(output, environment) {
4
+ const isTTY = output.isTTY === true;
5
+ const colorDisabled = environment.NO_COLOR !== undefined ||
6
+ environment.NODE_DISABLE_COLORS !== undefined;
7
+ const columns = output.columns;
8
+ return {
9
+ isTTY,
10
+ color: isTTY && !colorDisabled && output.getColorDepth(environment) >= 24,
11
+ ...(columns !== undefined && columns > 0 ? { columns } : {}),
12
+ };
13
+ }
14
+ export function startStartupPresentation(info, options) {
15
+ options.write(renderStartupPrelude(info, options.terminal));
16
+ return {
17
+ ready(status) {
18
+ options.write(renderStartupStatus(status, options.terminal));
19
+ },
20
+ };
21
+ }
22
+ const colors = {
23
+ otterLight: [244, 211, 164],
24
+ otterSand: [224, 179, 119],
25
+ otterCaramel: [198, 139, 75],
26
+ otterBrown: [167, 105, 58],
27
+ otterDeep: [112, 66, 45],
28
+ lavender: [192, 132, 252],
29
+ lavenderSoft: [196, 181, 253],
30
+ label: [103, 232, 249],
31
+ value: [74, 222, 128],
32
+ text: [148, 163, 184],
33
+ frame: [71, 85, 105],
34
+ };
35
+ const logoGlyphs = [
36
+ ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
37
+ [
38
+ ' ██████╗ ',
39
+ '██╔═══██╗',
40
+ '██║ ██║',
41
+ '██║ ██║',
42
+ '╚██████╔╝',
43
+ ' ╚═════╝ ',
44
+ ],
45
+ [
46
+ '██╗ ██╗',
47
+ '██║ ██║',
48
+ '██║ ██║',
49
+ '██║ ██║',
50
+ '╚██████╔╝',
51
+ ' ╚═════╝ ',
52
+ ],
53
+ [
54
+ '████████╗',
55
+ '╚══██╔══╝',
56
+ ' ██║ ',
57
+ ' ██║ ',
58
+ ' ██║ ',
59
+ ' ╚═╝ ',
60
+ ],
61
+ ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
62
+ ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
63
+ ];
64
+ const logoColors = [
65
+ colors.otterLight,
66
+ colors.otterSand,
67
+ colors.otterCaramel,
68
+ colors.otterBrown,
69
+ colors.otterDeep,
70
+ colors.otterCaramel,
71
+ ];
72
+ const minimumContentWidth = 68;
73
+ const metadataIndent = ' ';
74
+ const mascot = 'ʕ•ᴥ•ʔ';
75
+ export function renderLoutreBrand(options) {
76
+ if (!options.isTTY)
77
+ return 'Loutre';
78
+ if (options.columns !== undefined && options.columns < logoWidth()) {
79
+ return `${mascot} Loutre`;
80
+ }
81
+ const logo = renderLogo(options.color);
82
+ const brand = options.color
83
+ ? `${paint(colors.lavender, mascot)} ${paint(colors.lavenderSoft, 'Loutre')}`
84
+ : `${mascot} Loutre`;
85
+ return [...logo.styled, '', brand].join('\n');
86
+ }
87
+ export function renderStartupPrelude(info, options) {
88
+ if (!options.isTTY)
89
+ return renderCompactPrelude(info);
90
+ const layout = createStartupLayout(info);
91
+ if (options.columns !== undefined && options.columns < layout.frameWidth) {
92
+ return renderCompactPrelude(info);
93
+ }
94
+ return renderRichPrelude(info, layout.contentWidth, options.color);
95
+ }
96
+ export function renderStartupStatus(info, options) {
97
+ if (!options.isTTY)
98
+ return renderCompactStatus(info);
99
+ const layout = createStartupLayout(info);
100
+ if (options.columns !== undefined && options.columns < layout.frameWidth) {
101
+ return renderCompactStatus(info);
102
+ }
103
+ return renderRichStatus(info, layout.contentWidth, options.color);
104
+ }
105
+ function renderCompactPrelude(info) {
106
+ return `Loutre ${info.version}`;
107
+ }
108
+ function renderCompactStatus(info) {
109
+ return [
110
+ `Server: ${info.server}`,
111
+ `Runtime: ${info.runtime}`,
112
+ `Environment: ${info.environment}`,
113
+ `Ready in ${formatDuration(info.startupDurationMs)}`,
114
+ ].join('\n');
115
+ }
116
+ function renderRichPrelude(info, contentWidth, color) {
117
+ const layout = createFrameLayout(contentWidth, color);
118
+ const logo = renderLogo(color);
119
+ const brand = `${mascot} version ${info.version}`;
120
+ const styledBrand = `${paintWhen(color, colors.lavender, mascot)} ${paintWhen(color, colors.lavenderSoft, `version ${info.version}`)}`;
121
+ return [
122
+ layout.topBorder,
123
+ layout.row(''),
124
+ ...logo.plain.map((line, index) => layout.centered(line, logo.styled[index])),
125
+ layout.row(''),
126
+ layout.centered(brand, styledBrand),
127
+ layout.row(''),
128
+ ].join('\n');
129
+ }
130
+ function renderRichStatus(info, contentWidth, color) {
131
+ const layout = createFrameLayout(contentWidth, color);
132
+ const labels = ['Server', 'Runtime', 'Environment'];
133
+ const labelWidth = Math.max(...labels.map(displayWidth));
134
+ const metadata = [
135
+ ['Server', info.server],
136
+ ['Runtime', info.runtime],
137
+ ['Environment', info.environment],
138
+ ];
139
+ return [
140
+ ...metadata.map(([label, value]) => {
141
+ const gap = ' '.repeat(labelWidth - displayWidth(label) + 3);
142
+ const plain = `${metadataIndent}${label}${gap}${value}`;
143
+ const styled = `${metadataIndent}${paintWhen(color, colors.label, label)}${gap}${paintWhen(color, colors.value, value)}`;
144
+ return layout.row(plain, styled);
145
+ }),
146
+ layout.row(''),
147
+ layout.bottomBorder,
148
+ '',
149
+ ` ${paintWhen(color, colors.value, '✓ Ready')} ${paintWhen(color, colors.text, `in ${formatDuration(info.startupDurationMs)}`)}`,
150
+ ].join('\n');
151
+ }
152
+ function createStartupLayout(info) {
153
+ const labelWidth = displayWidth('Environment');
154
+ const metadata = ('server' in info ? [info.server, info.runtime, info.environment] : []).map((value) => displayWidth(metadataIndent) + labelWidth + 3 + displayWidth(value));
155
+ const contentWidth = Math.max(minimumContentWidth, logoWidth(), 'version' in info
156
+ ? displayWidth(`${mascot} version ${info.version}`)
157
+ : minimumContentWidth, ...metadata);
158
+ return { contentWidth, frameWidth: contentWidth + 4 };
159
+ }
160
+ function createFrameLayout(contentWidth, color) {
161
+ const frame = (value) => paintWhen(color, colors.frame, value);
162
+ const row = (plain, styled = plain) => `${frame('│')} ${styled}${' '.repeat(contentWidth - displayWidth(plain))} ${frame('│')}`;
163
+ const centered = (plain, styled = plain) => {
164
+ const remaining = contentWidth - displayWidth(plain);
165
+ const left = Math.floor(remaining / 2);
166
+ return row(`${' '.repeat(left)}${plain}`, `${' '.repeat(left)}${styled}`);
167
+ };
168
+ const border = frame('─'.repeat(contentWidth + 2));
169
+ return {
170
+ topBorder: `${frame('╭')}${border}${frame('╮')}`,
171
+ bottomBorder: `${frame('╰')}${border}${frame('╯')}`,
172
+ row,
173
+ centered,
174
+ };
175
+ }
176
+ function renderLogo(color) {
177
+ const widths = logoGlyphs.map((glyph) => Math.max(...glyph.map(displayWidth)));
178
+ const plain = [];
179
+ const styled = [];
180
+ for (let row = 0; row < logoGlyphs[0].length; row += 1) {
181
+ const plainParts = [];
182
+ const styledParts = [];
183
+ for (let letter = 0; letter < logoGlyphs.length; letter += 1) {
184
+ const value = logoGlyphs[letter][row];
185
+ const padded = `${value}${' '.repeat(widths[letter] - displayWidth(value))}`;
186
+ const rgb = logoColors[letter];
187
+ plainParts.push(padded);
188
+ styledParts.push(paintWhen(color, rgb, padded));
189
+ }
190
+ plain.push(plainParts.join(' '));
191
+ styled.push(styledParts.join(' '));
192
+ }
193
+ return { plain, styled };
194
+ }
195
+ function logoWidth() {
196
+ const widths = logoGlyphs.map((glyph) => Math.max(...glyph.map(displayWidth)));
197
+ return widths.reduce((sum, width) => sum + width, logoGlyphs.length - 1);
198
+ }
199
+ function paintWhen(color, rgb, value) {
200
+ return color ? paint(rgb, value) : value;
201
+ }
202
+ function paint(rgb, value) {
203
+ return `\u001B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${value}\u001B[0m`;
204
+ }
205
+ function formatDuration(durationMs) {
206
+ return `${Math.max(0, Math.round(durationMs))} ms`;
207
+ }
208
+ function displayWidth(value) {
209
+ let width = 0;
210
+ const ansiControlSequence = new RegExp(`${String.fromCodePoint(0x1b)}\\[[0-?]*[ -/]*[@-~]`, 'g');
211
+ for (const character of value.replace(ansiControlSequence, '')) {
212
+ const codePoint = character.codePointAt(0);
213
+ if (/\p{Mark}/u.test(character))
214
+ continue;
215
+ width += isFullWidth(codePoint) ? 2 : 1;
216
+ }
217
+ return width;
218
+ }
219
+ function isFullWidth(codePoint) {
220
+ return (codePoint >= 0x1100 &&
221
+ (codePoint <= 0x115f ||
222
+ codePoint === 0x2329 ||
223
+ codePoint === 0x232a ||
224
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
225
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
226
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
227
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
228
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
229
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
230
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
231
+ (codePoint >= 0x1b000 && codePoint <= 0x1ffff) ||
232
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)));
233
+ }
@@ -11,7 +11,7 @@ export function checkCapabilities(required, runtime) {
11
11
  export class MissingCapabilitiesError extends Error {
12
12
  check;
13
13
  constructor(check) {
14
- super(`Runtimeに必要なCapabilityがありません: ${check.missing.join(', ')}`);
14
+ super(`Runtime is missing required capabilities: ${check.missing.join(', ')}`);
15
15
  this.check = check;
16
16
  this.name = 'MissingCapabilitiesError';
17
17
  }
@@ -222,7 +222,7 @@ export class Container {
222
222
  const cycle = [...lineage.slice(cycleStart), token]
223
223
  .map(tokenName)
224
224
  .join(' -> ');
225
- throw new DependencyResolutionError(`LUTRE_DI_CYCLE: 循環依存を検出しました: ${cycle}`);
225
+ throw new DependencyResolutionError(`LUTRE_DI_CYCLE: Circular dependency detected: ${cycle}`);
226
226
  }
227
227
  const provider = this.#providers.get(token);
228
228
  if (!provider) {
@@ -286,7 +286,7 @@ export class Container {
286
286
  const selected = input[provider.select.key];
287
287
  const implementation = provider.mapping[selected];
288
288
  if (!implementation) {
289
- throw new DependencyResolutionError(`${provider.select.key}=${String(selected)}に対応するconditional Providerがありません`);
289
+ throw new DependencyResolutionError(`No conditional Provider matches ${provider.select.key}=${String(selected)}`);
290
290
  }
291
291
  return this.#instantiate(implementation, lineage);
292
292
  }
@@ -0,0 +1,3 @@
1
+ export type RuntimeEngine = 'node' | 'bun' | 'deno' | 'cloudflare-workers' | 'aws-lambda' | 'electron' | 'unknown';
2
+ export declare function detectRuntimeEngine(): RuntimeEngine;
3
+ export declare function assertRuntimeEngine(expected: Exclude<RuntimeEngine, 'unknown'>): void;
@@ -0,0 +1,38 @@
1
+ const runtimeLabels = {
2
+ node: 'Node.js',
3
+ bun: 'Bun',
4
+ deno: 'Deno',
5
+ 'cloudflare-workers': 'Cloudflare Workers',
6
+ 'aws-lambda': 'AWS Lambda',
7
+ electron: 'Electron',
8
+ unknown: 'an unknown runtime',
9
+ };
10
+ export function detectRuntimeEngine() {
11
+ const globals = globalThis;
12
+ if (globals.Bun !== undefined ||
13
+ globals.process?.versions?.bun !== undefined) {
14
+ return 'bun';
15
+ }
16
+ if (globals.Deno !== undefined)
17
+ return 'deno';
18
+ if (globals.navigator?.userAgent === 'Cloudflare-Workers' ||
19
+ globals.WebSocketPair !== undefined) {
20
+ return 'cloudflare-workers';
21
+ }
22
+ if (globals.process?.versions?.electron !== undefined)
23
+ return 'electron';
24
+ if (globals.process?.env?.AWS_EXECUTION_ENV?.startsWith('AWS_Lambda_nodejs')) {
25
+ return 'aws-lambda';
26
+ }
27
+ if (globals.process?.release?.name === 'node' ||
28
+ globals.process?.versions?.node !== undefined) {
29
+ return 'node';
30
+ }
31
+ return 'unknown';
32
+ }
33
+ export function assertRuntimeEngine(expected) {
34
+ const actual = detectRuntimeEngine();
35
+ if (actual === expected)
36
+ return;
37
+ throw new Error(`LUTRE_RUNTIME_MISMATCH: Expected ${runtimeLabels[expected]}, detected ${runtimeLabels[actual]}.`);
38
+ }
@@ -2,6 +2,8 @@ export * from './application.js';
2
2
  export * from './capability.js';
3
3
  export * from './di.js';
4
4
  export * from './error.js';
5
+ export * from './engine.js';
5
6
  export * from './logger.js';
6
7
  export * from './pipeline.js';
7
8
  export * from './node-capabilities.js';
9
+ export * from './server-url.js';
@@ -2,6 +2,8 @@ export * from './application.js';
2
2
  export * from './capability.js';
3
3
  export * from './di.js';
4
4
  export * from './error.js';
5
+ export * from './engine.js';
5
6
  export * from './logger.js';
6
7
  export * from './pipeline.js';
7
8
  export * from './node-capabilities.js';
9
+ export * from './server-url.js';
@@ -9,7 +9,7 @@ export async function executePipeline(pipeline, hooks) {
9
9
  const context = hooks.context;
10
10
  const flow = await executeSegment(pipeline, 0, hooks, context, new Set());
11
11
  if (flow.kind === 'continue') {
12
- throw new Error('Pipelineがresultを生成しませんでした');
12
+ throw new Error('Pipeline did not produce a result');
13
13
  }
14
14
  return flow.result;
15
15
  }
@@ -48,7 +48,7 @@ async function executeLayer(layer, runtime, continuation, context, availableKeys
48
48
  const next = (...arguments_) => {
49
49
  calls += 1;
50
50
  if (calls > 1) {
51
- contractError = new LayerContractError(`LUTRE_LAYER_NEXT_REENTRY: Layer ${layer.name}のnext()は1回だけ実行できます`);
51
+ contractError = new LayerContractError(`LUTRE_LAYER_NEXT_REENTRY: Layer ${layer.name} can call next() only once`);
52
52
  return Promise.reject(contractError);
53
53
  }
54
54
  applyProvidedContext(layer, context, availableKeys, arguments_);
@@ -90,7 +90,7 @@ async function executeLayer(layer, runtime, continuation, context, availableKeys
90
90
  throw runtimeError;
91
91
  if (isShortCircuit(runtimeResult)) {
92
92
  if (calls > 0) {
93
- throw new LayerContractError(`LUTRE_LAYER_SHORT_CIRCUIT_AFTER_NEXT: Layer ${layer.name}はnext()後にshortCircuitできません`);
93
+ throw new LayerContractError(`LUTRE_LAYER_SHORT_CIRCUIT_AFTER_NEXT: Layer ${layer.name} cannot shortCircuit after next()`);
94
94
  }
95
95
  return {
96
96
  kind: 'complete',
@@ -98,28 +98,28 @@ async function executeLayer(layer, runtime, continuation, context, availableKeys
98
98
  };
99
99
  }
100
100
  if (calls === 0) {
101
- throw new LayerContractError(`LUTRE_LAYER_NEXT_SKIPPED: Layer ${layer.name}はnext()を1回実行する必要があります`);
101
+ throw new LayerContractError(`LUTRE_LAYER_NEXT_SKIPPED: Layer ${layer.name} must call next() exactly once`);
102
102
  }
103
103
  return continuationFlow;
104
104
  }
105
105
  function assertRequiredContext(layerName, requiredKeys, availableKeys) {
106
106
  for (const required of requiredKeys) {
107
107
  if (!availableKeys.has(required)) {
108
- throw new LayerContractError(`${layerName}が必要とするContext Key ${contextKeyName(required)}は利用できません`);
108
+ throw new LayerContractError(`Context Key ${contextKeyName(required)} required by ${layerName} is unavailable`);
109
109
  }
110
110
  }
111
111
  }
112
112
  function applyProvidedContext(layer, context, availableKeys, arguments_) {
113
113
  if (arguments_.length > 1) {
114
- throw new LayerContractError(`${layer.name}のnext()へ渡せるContext objectは1つだけです`);
114
+ throw new LayerContractError(`Layer ${layer.name} can pass only one Context object to next()`);
115
115
  }
116
116
  if (layer.provides.length === 0) {
117
117
  if (arguments_.length > 0) {
118
118
  const provided = arguments_[0];
119
119
  const property = firstProperty(provided);
120
120
  throw new LayerContractError(property === undefined
121
- ? `${layer.name}はContextをprovideすると宣言していません`
122
- : `${layer.name}が未宣言のContext property ${property}をprovideしました`);
121
+ ? `Layer ${layer.name} does not declare any provided Context`
122
+ : `Layer ${layer.name} provided undeclared Context property ${property}`);
123
123
  }
124
124
  return;
125
125
  }
@@ -127,26 +127,26 @@ function applyProvidedContext(layer, context, availableKeys, arguments_) {
127
127
  if (typeof provided !== 'object' ||
128
128
  provided === null ||
129
129
  Array.isArray(provided)) {
130
- throw new LayerContractError(`${layer.name}はprovidesで宣言したContextをnext(object)で渡す必要があります`);
130
+ throw new LayerContractError(`Layer ${layer.name} must pass its declared Context to next(object)`);
131
131
  }
132
132
  const additions = provided;
133
133
  const declaredNames = new Set(layer.provides.map((key) => key.name));
134
134
  for (const property of Object.keys(additions)) {
135
135
  if (!declaredNames.has(property)) {
136
- throw new LayerContractError(`${layer.name}が未宣言のContext property ${property}をprovideしました`);
136
+ throw new LayerContractError(`Layer ${layer.name} provided undeclared Context property ${property}`);
137
137
  }
138
138
  }
139
139
  const providedNames = new Set();
140
140
  for (const key of layer.provides) {
141
141
  if (providedNames.has(key.name)) {
142
- throw new LayerContractError(`${layer.name}がContext property ${key.name}を重複して宣言しました`);
142
+ throw new LayerContractError(`Layer ${layer.name} declared duplicate Context property ${key.name}`);
143
143
  }
144
144
  providedNames.add(key.name);
145
145
  if (!Object.hasOwn(additions, key.name)) {
146
- throw new LayerContractError(`${layer.name}が宣言したContext Key ${contextKeyName(key)}をprovideしませんでした`);
146
+ throw new LayerContractError(`Layer ${layer.name} did not provide declared Context Key ${contextKeyName(key)}`);
147
147
  }
148
148
  if (Object.hasOwn(context, key.name)) {
149
- throw new LayerContractError(`${layer.name}は既存のContext property ${key.name}を上書きできません`);
149
+ throw new LayerContractError(`Layer ${layer.name} cannot overwrite existing Context property ${key.name}`);
150
150
  }
151
151
  }
152
152
  Object.assign(context, additions);
@@ -0,0 +1 @@
1
+ export declare function serverUrl(hostname: string | undefined, port: number): string;
@@ -0,0 +1,5 @@
1
+ export function serverUrl(hostname, port) {
2
+ const host = hostname ?? 'localhost';
3
+ const formattedHost = host.includes(':') ? `[${host}]` : host;
4
+ return `http://${formattedHost}:${port}`;
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loutrejs/loutre",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "files": [
@@ -24,6 +24,10 @@
24
24
  "types": "./dist/application/openapi.d.ts",
25
25
  "import": "./dist/application/openapi.js"
26
26
  },
27
+ "./presentation": {
28
+ "types": "./dist/presentation.d.ts",
29
+ "import": "./dist/presentation.js"
30
+ },
27
31
  "./graph": {
28
32
  "types": "./dist/graph/index.d.ts",
29
33
  "import": "./dist/graph/index.js"