@vyriy/server 0.3.0 → 0.3.4
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/AGENTS.md +59 -22
- package/README.md +32 -36
- package/index.d.ts +1 -2
- package/index.js +1 -2
- package/listener.d.ts +3 -2
- package/listener.js +9 -5
- package/package.json +5 -16
- package/server.d.ts +2 -1
- package/server.js +2 -1
- package/types.d.ts +2 -2
- package/static.d.ts +0 -8
- package/static.js +0 -120
package/AGENTS.md
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Project Agent Guide
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This repository follows a calm engineering style: changes should be explicit, reusable, typed, documented, tested, and easy to reason about.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use this guide as the default behavior for AI agents and contributors working in this repository. Prefer local package conventions when they are more specific than this document.
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
6
8
|
|
|
7
9
|
- Prefer simple modules over clever frameworks or hidden conventions.
|
|
8
|
-
- Keep package
|
|
10
|
+
- Keep package and project boundaries explicit.
|
|
11
|
+
- Avoid project-specific coupling in reusable code.
|
|
9
12
|
- Extract only proven reusable behavior.
|
|
10
13
|
- Keep public APIs small, typed, documented, and stable.
|
|
11
|
-
- Prefer SSR-friendly and SSG-friendly code paths.
|
|
12
|
-
- Keep integrations replaceable and avoid hard coupling to a CMS or runtime host.
|
|
13
|
-
- Prefer
|
|
14
|
+
- Prefer SSR-friendly and SSG-friendly code paths when working with frontend or shared code.
|
|
15
|
+
- Keep integrations replaceable and avoid hard coupling to a CMS, framework, vendor, or runtime host.
|
|
16
|
+
- Prefer infrastructure assumptions that are easy to deploy, observe, and replace.
|
|
17
|
+
- Prefer the option that is simpler to explain, easier to evolve, and calmer to maintain.
|
|
14
18
|
|
|
15
19
|
## File Shape
|
|
16
20
|
|
|
17
|
-
- Prefer one exported runtime method, component, or
|
|
21
|
+
- Prefer one exported runtime method, component, helper, or class per production file when it stays readable.
|
|
18
22
|
- Prefer one matching test file per production file, for example `feature.ts` and `feature.test.ts`.
|
|
19
23
|
- Use focused folders when behavior naturally splits into several related files.
|
|
20
24
|
- Keep `index.ts` as a public re-export surface only. Do not place implementation logic in it.
|
|
21
|
-
- Use
|
|
25
|
+
- Use relative import and export specifiers that match the package module style.
|
|
26
|
+
- Use `.js` relative specifiers in TypeScript source for ESM/NodeNext packages.
|
|
22
27
|
- Add `types.ts` when public shared types are part of the package contract.
|
|
23
28
|
- Keep constants near the code that owns them unless they are shared or clarify repeated behavior.
|
|
24
29
|
|
|
25
30
|
## Public Surface
|
|
26
31
|
|
|
27
|
-
- Every new public export
|
|
28
|
-
- Add or update
|
|
32
|
+
- Every new public export must be re-exported from the package or module public entry point.
|
|
33
|
+
- Add or update public-surface tests when exports change.
|
|
29
34
|
- Add JSDoc for public exports when behavior, parameters, return values, or usage expectations need explanation.
|
|
30
|
-
-
|
|
35
|
+
- Avoid exporting internal helpers only to make tests easier.
|
|
36
|
+
- Do not hand-maintain package `exports` maps unless the project has a real custom publishing need.
|
|
31
37
|
|
|
32
38
|
## Tests
|
|
33
39
|
|
|
34
|
-
- Tests use Jest and `@jest/globals`.
|
|
35
40
|
- Cover public behavior and meaningful edge cases.
|
|
36
41
|
- Prefer behavior-focused tests over private implementation lock-in.
|
|
42
|
+
- Keep tests deterministic.
|
|
43
|
+
- Avoid real network, filesystem, timers, browser, or cloud dependencies unless the behavior specifically requires them.
|
|
37
44
|
- When mocking modules, install mocks before loading the module under test.
|
|
38
|
-
- Use focused validation when changing
|
|
45
|
+
- Use focused validation when changing behavior.
|
|
46
|
+
|
|
47
|
+
Example validation commands:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn test
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For workspaces, prefer the project convention, for example:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
yarn workspace <package-name> test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For Jest-based packages, focused validation may look like:
|
|
39
60
|
|
|
40
61
|
```bash
|
|
41
62
|
yarn jest packages/<package> --runInBand --coverage=false
|
|
@@ -44,22 +65,38 @@ yarn jest packages/<package> --runInBand --coverage=false
|
|
|
44
65
|
## Documentation
|
|
45
66
|
|
|
46
67
|
- Keep `README.md` concise and usage-oriented.
|
|
47
|
-
- Start package READMEs with `#
|
|
68
|
+
- Start package READMEs with `# <package>`.
|
|
48
69
|
- Document real public exports, supported options, and examples that actually work.
|
|
49
|
-
-
|
|
50
|
-
-
|
|
70
|
+
- Update docs when public behavior changes.
|
|
71
|
+
- Keep generated docs wrappers, such as `doc.mdx`, aligned with the README when the project uses them.
|
|
72
|
+
- For component packages, include visual documentation or stories for supported states and common usage.
|
|
51
73
|
|
|
52
74
|
## Components
|
|
53
75
|
|
|
54
|
-
- Prefer lightweight React
|
|
76
|
+
- Prefer lightweight React components with TypeScript when working in React packages.
|
|
55
77
|
- Keep components SSR-friendly and avoid browser globals during render.
|
|
56
|
-
- Prefer composable props and
|
|
78
|
+
- Prefer composable props and predictable ergonomics.
|
|
57
79
|
- Put each public component in its own file with a matching test.
|
|
58
|
-
- Add
|
|
80
|
+
- Add stories or examples when a component has visual states, variants, or interaction states.
|
|
81
|
+
- Keep styling explicit and reusable. Avoid hidden theme assumptions unless they are part of the package contract.
|
|
59
82
|
|
|
60
83
|
## Change Discipline
|
|
61
84
|
|
|
62
85
|
- Keep changes scoped to the requested behavior.
|
|
63
86
|
- Avoid unrelated refactors and metadata churn.
|
|
64
|
-
- Sync implementation, tests,
|
|
65
|
-
-
|
|
87
|
+
- Sync implementation, tests, docs, examples, and public re-exports together.
|
|
88
|
+
- Do not introduce new dependencies unless they clearly reduce complexity or are already part of the project direction.
|
|
89
|
+
- Prefer small, reviewable changes over broad rewrites.
|
|
90
|
+
- Preserve existing conventions unless there is a clear reason to change them.
|
|
91
|
+
|
|
92
|
+
## Before Finishing
|
|
93
|
+
|
|
94
|
+
Check that the change is complete:
|
|
95
|
+
|
|
96
|
+
- Public exports are updated.
|
|
97
|
+
- Public-surface tests are updated when exports change.
|
|
98
|
+
- Matching unit tests exist for new behavior.
|
|
99
|
+
- README examples still match the real API.
|
|
100
|
+
- Visual docs, stories, or examples are updated for visible component behavior.
|
|
101
|
+
- TypeScript imports follow the package module style.
|
|
102
|
+
- No unrelated files, formatting churn, or generated artifacts were changed.
|
package/README.md
CHANGED
|
@@ -32,58 +32,54 @@ server(async () => ({
|
|
|
32
32
|
}));
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Use `api(...)` when you want the handler package wrappers. It keeps the same `(event, context)` Lambda handler shape.
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
|
-
import { api
|
|
38
|
+
import { api } from '@vyriy/handler';
|
|
39
39
|
import { server } from '@vyriy/server';
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
```
|
|
41
|
+
const handler = api(async (event) => ({
|
|
42
|
+
statusCode: 200,
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
ok: true,
|
|
45
|
+
path: event.path,
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
server(handler);
|
|
50
|
+
```
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
Run a Lambda response streaming handler locally:
|
|
55
53
|
|
|
56
54
|
```ts
|
|
57
|
-
import {
|
|
58
|
-
import { staticFiles } from '@vyriy/server/static';
|
|
55
|
+
import { streamServer } from '@vyriy/server';
|
|
59
56
|
|
|
60
|
-
|
|
57
|
+
streamServer(async (event, responseStream) => {
|
|
58
|
+
responseStream.setContentType?.('text/plain');
|
|
59
|
+
responseStream.write(`Path: ${event.path}\n`);
|
|
60
|
+
responseStream.end('Done');
|
|
61
|
+
});
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
The stream handler receives the API Gateway-style event first, the response stream second, and the Lambda context third.
|
|
65
|
+
|
|
66
|
+
Use `streamApi(...)` when you want the handler package wrappers. It keeps the same `(event, responseStream, context)` stream handler shape.
|
|
64
67
|
|
|
65
68
|
```ts
|
|
66
|
-
import {
|
|
69
|
+
import { streamApi } from '@vyriy/handler';
|
|
70
|
+
import { streamServer } from '@vyriy/server';
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const handler = streamApi((event, responseStream) => {
|
|
73
|
+
responseStream.setContentType?.('text/plain');
|
|
74
|
+
responseStream.write(`Path: ${event.path}\n`);
|
|
75
|
+
responseStream.end('Done');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
streamServer(handler);
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
For static apps that should fall back to `index.html`, configure the fallback explicitly:
|
|
81
|
+
Keep the AWS-specific `awslambda.streamifyResponse(handler)` wrapper in a separate Lambda entrypoint.
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
const router = createRouter().prefix(
|
|
81
|
-
'/static',
|
|
82
|
-
staticFiles('./public', {
|
|
83
|
-
fallback: 'index.html',
|
|
84
|
-
fallbackStatusCode: 200,
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
```
|
|
83
|
+
Static files are intentionally left to the Docker/web-server layer, for example Nginx, Caddy, or the platform serving assets in front of this Node process.
|
|
88
84
|
|
|
89
85
|
The server listens on `PORT` from `@vyriy/env`. The default port is `3000`.
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { server } from './server.js';
|
|
2
|
-
export { staticFiles } from './static.js';
|
|
1
|
+
export { server, streamServer } from './server.js';
|
package/listener.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const listener: (handler:
|
|
1
|
+
import type { LambdaHandler, LambdaStreamHandler, NativeRequestListener } from './types.js';
|
|
2
|
+
export declare const listener: (handler: LambdaHandler) => NativeRequestListener;
|
|
3
|
+
export declare const streamListener: (handler: LambdaStreamHandler) => NativeRequestListener;
|
package/listener.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mapParams } from './params.js';
|
|
2
2
|
import { error, result } from './result.js';
|
|
3
|
-
const isStreamHandler = (handler) => handler.length >= 3;
|
|
4
3
|
const createResponseStream = (response) => {
|
|
5
4
|
response.setContentType = (contentType) => response.setHeader?.('content-type', contentType) ?? response;
|
|
6
5
|
return response;
|
|
@@ -8,10 +7,6 @@ const createResponseStream = (response) => {
|
|
|
8
7
|
export const listener = (handler) => async (request, response) => {
|
|
9
8
|
try {
|
|
10
9
|
const { context, event } = await mapParams(request);
|
|
11
|
-
if (isStreamHandler(handler)) {
|
|
12
|
-
await handler(event, createResponseStream(response), context);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
10
|
const value = await handler(event, context);
|
|
16
11
|
await result(response, value);
|
|
17
12
|
}
|
|
@@ -19,3 +14,12 @@ export const listener = (handler) => async (request, response) => {
|
|
|
19
14
|
error(response);
|
|
20
15
|
}
|
|
21
16
|
};
|
|
17
|
+
export const streamListener = (handler) => async (request, response) => {
|
|
18
|
+
try {
|
|
19
|
+
const { context, event } = await mapParams(request);
|
|
20
|
+
await handler(event, createResponseStream(response), context);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
error(response);
|
|
24
|
+
}
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vyriy/server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Small HTTP server adapter for Lambda-style Vyriy handlers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@types/aws-lambda": "^8.10.161",
|
|
9
|
-
"@vyriy/env": "0.3.
|
|
10
|
-
"@vyriy/error": "0.3.
|
|
11
|
-
"@vyriy/handler": "0.3.
|
|
12
|
-
"@vyriy/logger": "0.3.
|
|
13
|
-
"@vyriy/router": "0.3.0"
|
|
9
|
+
"@vyriy/env": "0.3.4",
|
|
10
|
+
"@vyriy/error": "0.3.4",
|
|
11
|
+
"@vyriy/handler": "0.3.4",
|
|
12
|
+
"@vyriy/logger": "0.3.4"
|
|
14
13
|
},
|
|
15
14
|
"agents": "./AGENTS.md",
|
|
16
15
|
"license": "MIT",
|
|
@@ -115,16 +114,6 @@
|
|
|
115
114
|
"types": "./shutdown.d.ts",
|
|
116
115
|
"import": "./shutdown.js",
|
|
117
116
|
"default": "./shutdown.js"
|
|
118
|
-
},
|
|
119
|
-
"./static": {
|
|
120
|
-
"types": "./static.d.ts",
|
|
121
|
-
"import": "./static.js",
|
|
122
|
-
"default": "./static.js"
|
|
123
|
-
},
|
|
124
|
-
"./static.js": {
|
|
125
|
-
"types": "./static.d.ts",
|
|
126
|
-
"import": "./static.js",
|
|
127
|
-
"default": "./static.js"
|
|
128
117
|
}
|
|
129
118
|
}
|
|
130
119
|
}
|
package/server.d.ts
CHANGED
package/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
|
-
import { listener } from './listener.js';
|
|
2
|
+
import { listener, streamListener } from './listener.js';
|
|
3
3
|
import { listen } from './listen.js';
|
|
4
4
|
export const server = (handler) => listen(http.createServer(listener(handler)));
|
|
5
|
+
export const streamServer = (handler) => listen(http.createServer(streamListener(handler)));
|
package/types.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export type LambdaEvent = APIGatewayProxyEvent;
|
|
|
7
7
|
export type LambdaResult = APIGatewayProxyResult;
|
|
8
8
|
export type LambdaHandler = (event: LambdaEvent, context: Context) => Promise<LambdaResult>;
|
|
9
9
|
export type LambdaStreamHandler = (event: LambdaEvent, responseStream: ResponseStream, context: Context) => Promise<LambdaResult | void>;
|
|
10
|
-
export type ServerHandler = LambdaHandler | LambdaStreamHandler;
|
|
11
10
|
export type RequestMessage = {
|
|
12
11
|
headers: IncomingHttpHeaders;
|
|
13
12
|
method?: string;
|
|
@@ -42,4 +41,5 @@ export type Listen = (server: Server) => Server;
|
|
|
42
41
|
export type NormalizeHeaders = (headers: IncomingHttpHeaders) => Record<string, string | undefined>;
|
|
43
42
|
export type WriteResult<Response extends ResponseMessage = ResponseMessage> = (response: Response, result: LambdaResult | void) => Promise<void> | void;
|
|
44
43
|
export type WriteError<Response extends ResponseMessage = ResponseMessage> = (response: Response) => void;
|
|
45
|
-
export type CreateServer = (handler:
|
|
44
|
+
export type CreateServer = (handler: LambdaHandler) => Server;
|
|
45
|
+
export type CreateStreamServer = (handler: LambdaStreamHandler) => Server;
|
package/static.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
|
2
|
-
import type { Handler } from '@vyriy/router';
|
|
3
|
-
export type StaticFilesOptions = {
|
|
4
|
-
fallback?: false | string;
|
|
5
|
-
fallbackStatusCode?: number;
|
|
6
|
-
};
|
|
7
|
-
export type StaticFilesHandler = Handler & ((event: APIGatewayProxyEvent) => Promise<APIGatewayProxyResult>);
|
|
8
|
-
export declare const staticFiles: (directory: string, options?: StaticFilesOptions) => StaticFilesHandler;
|
package/static.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { STATUS_CODES } from 'node:http';
|
|
2
|
-
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import { extname, resolve, sep } from 'node:path';
|
|
4
|
-
const CONTENT_TYPES = {
|
|
5
|
-
'.css': 'text/css; charset=utf-8',
|
|
6
|
-
'.gif': 'image/gif',
|
|
7
|
-
'.html': 'text/html; charset=utf-8',
|
|
8
|
-
'.ico': 'image/x-icon',
|
|
9
|
-
'.jpeg': 'image/jpeg',
|
|
10
|
-
'.jpg': 'image/jpeg',
|
|
11
|
-
'.js': 'text/javascript; charset=utf-8',
|
|
12
|
-
'.json': 'application/json; charset=utf-8',
|
|
13
|
-
'.mjs': 'text/javascript; charset=utf-8',
|
|
14
|
-
'.png': 'image/png',
|
|
15
|
-
'.svg': 'image/svg+xml; charset=utf-8',
|
|
16
|
-
'.txt': 'text/plain; charset=utf-8',
|
|
17
|
-
'.webp': 'image/webp',
|
|
18
|
-
};
|
|
19
|
-
const getContentType = (filePath) => CONTENT_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
|
20
|
-
const notFound = () => ({
|
|
21
|
-
body: JSON.stringify({
|
|
22
|
-
message: STATUS_CODES[404],
|
|
23
|
-
}),
|
|
24
|
-
headers: {
|
|
25
|
-
'content-type': 'application/json',
|
|
26
|
-
},
|
|
27
|
-
statusCode: 404,
|
|
28
|
-
});
|
|
29
|
-
const methodNotAllowed = () => ({
|
|
30
|
-
body: JSON.stringify({
|
|
31
|
-
message: STATUS_CODES[405],
|
|
32
|
-
}),
|
|
33
|
-
headers: {
|
|
34
|
-
allow: 'GET, HEAD',
|
|
35
|
-
'content-type': 'application/json',
|
|
36
|
-
},
|
|
37
|
-
statusCode: 405,
|
|
38
|
-
});
|
|
39
|
-
const getFilePath = (directory, relativePath) => {
|
|
40
|
-
const rootPath = resolve(directory);
|
|
41
|
-
const filePath = resolve(rootPath, relativePath.replace(/^\/+/, '') || 'index.html');
|
|
42
|
-
if (filePath !== rootPath && !filePath.startsWith(`${rootPath}${sep}`)) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
return filePath;
|
|
46
|
-
};
|
|
47
|
-
const readFileResponse = async (filePath, method, statusCode = 200) => {
|
|
48
|
-
const file = await stat(filePath);
|
|
49
|
-
if (!file.isFile()) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
const headers = {
|
|
53
|
-
'content-length': String(file.size),
|
|
54
|
-
'content-type': getContentType(filePath),
|
|
55
|
-
};
|
|
56
|
-
if (method === 'HEAD') {
|
|
57
|
-
return {
|
|
58
|
-
body: '',
|
|
59
|
-
headers,
|
|
60
|
-
statusCode,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
body: (await readFile(filePath)).toString('base64'),
|
|
65
|
-
headers,
|
|
66
|
-
isBase64Encoded: true,
|
|
67
|
-
statusCode,
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
const readFallbackResponse = async (directory, method, options) => {
|
|
71
|
-
if (options.fallback === false) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
const fallbackPath = getFilePath(directory, options.fallback);
|
|
75
|
-
if (!fallbackPath) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
return await readFileResponse(fallbackPath, method, options.fallbackStatusCode);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const isRouterParams = (value) => 'event' in value;
|
|
86
|
-
const getRequest = (value) => {
|
|
87
|
-
if (isRouterParams(value)) {
|
|
88
|
-
return {
|
|
89
|
-
event: value.event,
|
|
90
|
-
relativePath: value.pathParameters?.proxy ?? '',
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
event: value,
|
|
95
|
-
relativePath: value.path.replace(/^\/+/, ''),
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
export const staticFiles = (directory, options = {}) => async (params) => {
|
|
99
|
-
const { event, relativePath } = getRequest(params);
|
|
100
|
-
const normalizedOptions = {
|
|
101
|
-
fallback: '404.html',
|
|
102
|
-
fallbackStatusCode: 404,
|
|
103
|
-
...options,
|
|
104
|
-
};
|
|
105
|
-
if (event.httpMethod !== 'GET' && event.httpMethod !== 'HEAD') {
|
|
106
|
-
return methodNotAllowed();
|
|
107
|
-
}
|
|
108
|
-
const filePath = getFilePath(directory, relativePath);
|
|
109
|
-
if (!filePath) {
|
|
110
|
-
return (await readFallbackResponse(directory, event.httpMethod, normalizedOptions)) ?? notFound();
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
return ((await readFileResponse(filePath, event.httpMethod)) ??
|
|
114
|
-
(await readFallbackResponse(directory, event.httpMethod, normalizedOptions)) ??
|
|
115
|
-
notFound());
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return (await readFallbackResponse(directory, event.httpMethod, normalizedOptions)) ?? notFound();
|
|
119
|
-
}
|
|
120
|
-
};
|