@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.
- package/README.md +69 -0
- package/dist/adapters/aws-lambda.js +2 -0
- package/dist/adapters/bun.d.ts +3 -1
- package/dist/adapters/bun.js +90 -33
- package/dist/adapters/cloudflare-workers.js +2 -0
- package/dist/adapters/deno.d.ts +3 -1
- package/dist/adapters/deno.js +99 -33
- package/dist/adapters/electron.js +2 -0
- package/dist/application/openapi.d.ts +6 -0
- package/dist/application/openapi.js +14 -19
- package/dist/core/contract.d.ts +47 -7
- package/dist/core/contract.js +59 -10
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/graph/compiler.d.ts +4 -0
- package/dist/graph/compiler.js +228 -0
- package/dist/graph/graph.js +65 -40
- package/dist/graph/index.d.ts +2 -1
- package/dist/graph/index.js +2 -1
- package/dist/graph/ir.d.ts +24 -8
- package/dist/http/application.js +5 -5
- package/dist/http/basic-auth.js +1 -1
- package/dist/http/client.d.ts +72 -0
- package/dist/http/client.js +258 -0
- package/dist/http/cors.js +4 -4
- package/dist/http/definitions.d.ts +13 -5
- package/dist/http/definitions.js +6 -2
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/message-port/index.d.ts +12 -4
- package/dist/message-port/index.js +13 -6
- package/dist/presentation.d.ts +33 -0
- package/dist/presentation.js +233 -0
- package/dist/runtime/capability.js +1 -1
- package/dist/runtime/di.js +2 -2
- package/dist/runtime/engine.d.ts +3 -0
- package/dist/runtime/engine.js +38 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/pipeline.js +13 -13
- package/dist/runtime/server-url.d.ts +1 -0
- package/dist/runtime/server-url.js +5 -0
- 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
|
|
14
|
+
super(`Runtime is missing required capabilities: ${check.missing.join(', ')}`);
|
|
15
15
|
this.check = check;
|
|
16
16
|
this.name = 'MissingCapabilitiesError';
|
|
17
17
|
}
|
package/dist/runtime/di.js
CHANGED
|
@@ -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:
|
|
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(
|
|
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
|
+
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/runtime/index.js
CHANGED
|
@@ -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';
|
package/dist/runtime/pipeline.js
CHANGED
|
@@ -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
|
|
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}
|
|
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}
|
|
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}
|
|
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(
|
|
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(
|
|
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
|
-
?
|
|
122
|
-
:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loutrejs/loutre",
|
|
3
|
-
"version": "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"
|