gateway: serve the SPA bundle same-origin (#1444)

New spa/serve-spa.ts: @fastify/static with wildcard:false plus a GET /*
catch-all that returns index.html for non-backend paths and a JSON 404
for unknown /api, /mcp, /socket.io paths. WEB_DIST_DIR unset disables
serving (dev uses the Vite dev server, which proxies to the gateway);
set but invalid fails loud at boot.

A wildcard route, not setNotFoundHandler: Nest installs its own
not-found handler at init and Fastify allows only one. find-my-way
matches most-specific-first, so declared routes win over the catch-all.
Cache semantics stay the library default (max-age=0 + ETag
revalidation); immutable hashed-asset caching is deferred to P6.

gateway.Dockerfile builds the web bundle and ships it at /app/web-dist
with WEB_DIST_DIR set.
This commit is contained in:
fred
2026-08-27 07:32:27 -05:00
parent 70c7311a46
commit 385b2b6886
5 changed files with 194 additions and 62 deletions
+109 -60
View File
@@ -66,6 +66,9 @@ importers:
'@fastify/helmet':
specifier: ^13.0.2
version: 13.0.2
'@fastify/static':
specifier: ^8.3.0
version: 8.3.0
'@mariozechner/pi-ai':
specifier: ^0.65.0
version: 0.65.0(@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])
@@ -122,7 +125,7 @@ importers:
version: 11.1.16(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])([email protected])
'@nestjs/platform-fastify':
specifier: ^11.0.0
version: 11.1.16(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])
version: 11.1.16(@fastify/[email protected])(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])
'@nestjs/platform-socket.io':
specifier: ^11.0.0
version: 11.1.16(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])
@@ -262,9 +265,6 @@ importers:
clsx:
specifier: ^2.1.0
version: 2.1.1
next:
specifier: ^16.0.0
version: 16.1.6(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
react:
specifier: ^19.0.0
version: 19.2.4
@@ -789,10 +789,10 @@ importers:
dependencies:
'@mariozechner/pi-agent-core':
specifier: ^0.63.1
version: 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])(zod@3.25.76)
version: 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])(zod@4.3.6)
'@mariozechner/pi-ai':
specifier: ^0.63.1
version: 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])(zod@3.25.76)
version: 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])(zod@4.3.6)
'@sinclair/typebox':
specifier: ^0.34.41
version: 0.34.48
@@ -1862,6 +1862,9 @@ packages:
'@noble/hashes':
optional: true
'@fastify/[email protected]':
resolution: {integrity: sha512-F3EVbzWt+xcnVaOHmWyIlpuFtbxOln7HDZQsh09MtMmMm/CipMayNt8hnIL8VQi54u2ZociDbf+iluGYkf7B1A==}
'@fastify/[email protected]':
resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
@@ -1889,6 +1892,12 @@ packages:
'@fastify/[email protected]':
resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
'@fastify/[email protected]':
resolution: {integrity: sha512-BYo+EiaKwlxH+WetGk6hAs1d39iP0y1gqB8lGF/qwkJ9ZZ/cBY1vx5NvExb9Sc3yRMFjD5X4Eyh4e4+TzRkzdw==}
'@fastify/[email protected]':
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
'@google/[email protected]':
resolution: {integrity: sha512-+sNRWhKiRibVgc4OKi7aBJJ0A7RcoVD8tGG+eFkqxAWRjASDW+ktS9lLwTDnAxZICzCVoeAdu8dYLJVTX60N9w==}
engines: {node: '>=20.0.0'}
@@ -2080,6 +2089,10 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@isaacs/[email protected]':
resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
engines: {node: '>=18'}
'@isaacs/[email protected]':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
@@ -2115,6 +2128,10 @@ packages:
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'}
'@lukeed/[email protected]':
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
engines: {node: '>=8'}
'@lydell/[email protected]':
resolution: {integrity: sha512-owcv+e1/OSu3bf9ZBdUQqJsQF888KyuSIiPYFNn0fLhgkhm9F3Pvha76Kj5mCPnodf7hh3suDe7upw7GPRXftQ==}
cpu: [arm64]
@@ -4601,6 +4618,10 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
[email protected]:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
[email protected]:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
@@ -5320,6 +5341,12 @@ packages:
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
hasBin: true
[email protected]:
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
engines: {node: 20 || >=22}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]
hasBin: true
[email protected]:
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
engines: {node: 18 || 20 || >=22}
@@ -5615,6 +5642,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
[email protected]:
resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
engines: {node: 20 || >=22}
[email protected]:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
@@ -6113,6 +6144,11 @@ packages:
engines: {node: '>=4.0.0'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -7777,12 +7813,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
'@anthropic-ai/[email protected]([email protected])':
dependencies:
json-schema-to-ts: 3.1.1
optionalDependencies:
zod: 3.25.76
'@anthropic-ai/[email protected]([email protected])':
dependencies:
json-schema-to-ts: 3.1.1
@@ -8786,6 +8816,8 @@ snapshots:
optionalDependencies:
'@noble/hashes': 2.0.1
'@fastify/[email protected]': {}
'@fastify/[email protected]':
dependencies:
ajv: 8.18.0
@@ -8824,6 +8856,23 @@ snapshots:
'@fastify/forwarded': 3.0.1
ipaddr.js: 2.3.0
'@fastify/[email protected]':
dependencies:
'@lukeed/ms': 2.0.2
escape-html: 1.0.3
fast-decode-uri-component: 1.0.1
http-errors: 2.0.1
mime: 3.0.0
'@fastify/[email protected]':
dependencies:
'@fastify/accept-negotiator': 2.1.0
'@fastify/send': 4.1.1
content-disposition: 0.5.4
fastify-plugin: 5.1.0
fastq: 1.20.1
glob: 11.1.0
'@google/[email protected](@modelcontextprotocol/[email protected]([email protected]))':
dependencies:
google-auth-library: 10.6.1
@@ -8999,6 +9048,8 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: [email protected]
'@isaacs/[email protected]': {}
'@isaacs/[email protected]':
dependencies:
minipass: 7.1.3
@@ -9036,6 +9087,8 @@ snapshots:
'@lukeed/[email protected]': {}
'@lukeed/[email protected]': {}
'@lydell/[email protected]':
optional: true
@@ -9124,18 +9177,6 @@ snapshots:
- ws
- zod
'@mariozechner/[email protected](@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@mariozechner/pi-ai': 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- aws-crt
- bufferutil
- supports-color
- utf-8-validate
- ws
- zod
'@mariozechner/[email protected](@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@mariozechner/pi-ai': 0.63.2(@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])
@@ -9184,30 +9225,6 @@ snapshots:
- ws
- zod
'@mariozechner/[email protected](@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@anthropic-ai/sdk': 0.73.0([email protected])
'@aws-sdk/client-bedrock-runtime': 3.1008.0
'@google/genai': 1.45.0(@modelcontextprotocol/[email protected]([email protected]))
'@mistralai/mistralai': 1.14.1
'@sinclair/typebox': 0.34.48
ajv: 8.18.0
ajv-formats: 3.0.1([email protected])
chalk: 5.6.2
openai: 6.26.0([email protected])([email protected])
partial-json: 0.1.7
proxy-agent: 6.5.0
undici: 7.24.6
zod-to-json-schema: 3.25.1([email protected])
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- aws-crt
- bufferutil
- supports-color
- utf-8-validate
- ws
- zod
'@mariozechner/[email protected](@modelcontextprotocol/[email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@anthropic-ai/sdk': 0.73.0([email protected])
@@ -9505,7 +9522,7 @@ snapshots:
optionalDependencies:
'@nestjs/websockets': 11.1.16(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])(@nestjs/[email protected])([email protected])([email protected])
'@nestjs/[email protected](@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])':
'@nestjs/[email protected](@fastify/[email protected])(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])':
dependencies:
'@fastify/cors': 11.2.0
'@fastify/formbody': 8.0.2
@@ -9519,6 +9536,8 @@ snapshots:
path-to-regexp: 8.3.0
reusify: 1.1.0
tslib: 2.8.1
optionalDependencies:
'@fastify/static': 8.3.0
'@nestjs/[email protected](@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])':
dependencies:
@@ -9556,7 +9575,8 @@ snapshots:
optionalDependencies:
'@nestjs/platform-socket.io': 11.1.16(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])
'@next/[email protected]': {}
'@next/[email protected]':
optional: true
'@next/[email protected]':
optional: true
@@ -11136,6 +11156,7 @@ snapshots:
'@swc/[email protected]':
dependencies:
tslib: 2.8.1
optional: true
'@swc/[email protected]':
dependencies:
@@ -11522,6 +11543,14 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/[email protected]([email protected](@types/[email protected])([email protected]))':
dependencies:
'@vitest/spy': 2.1.9
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 5.4.21(@types/[email protected])([email protected])
'@vitest/[email protected]([email protected](@types/[email protected])([email protected]))':
dependencies:
'@vitest/spy': 2.1.9
@@ -11703,7 +11732,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
optional: true
[email protected]: {}
@@ -11892,7 +11922,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
optional: true
[email protected]: {}
@@ -11966,7 +11997,8 @@ snapshots:
slice-ansi: 5.0.0
string-width: 7.2.0
[email protected]: {}
[email protected]:
optional: true
[email protected]:
dependencies:
@@ -12012,6 +12044,10 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
safe-buffer: 5.2.1
[email protected]: {}
[email protected]: {}
@@ -12863,6 +12899,15 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
[email protected]:
dependencies:
foreground-child: 3.3.1
jackspeak: 4.2.3
minimatch: 10.2.4
minipass: 7.1.3
package-json-from-dist: 1.0.1
path-scurry: 2.0.2
[email protected]:
dependencies:
minimatch: 10.2.4
@@ -13192,6 +13237,10 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
[email protected]:
dependencies:
'@isaacs/cliui': 9.0.0
[email protected]: {}
[email protected]: {}
@@ -13795,6 +13844,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -13911,6 +13962,7 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
optional: true
[email protected]:
dependencies:
@@ -13994,11 +14046,6 @@ snapshots:
dependencies:
mimic-function: 5.0.1
[email protected]([email protected])([email protected]):
optionalDependencies:
ws: 8.20.0
zod: 3.25.76
[email protected]([email protected])([email protected]):
optionalDependencies:
ws: 8.20.0
@@ -14252,9 +14299,10 @@ snapshots:
[email protected]:
dependencies:
nanoid: 3.3.11
nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
optional: true
[email protected]:
dependencies:
@@ -14935,6 +14983,7 @@ snapshots:
dependencies:
client-only: 0.0.1
react: 19.2.4
optional: true
[email protected]:
dependencies:
@@ -15360,7 +15409,7 @@ snapshots:
[email protected](@types/[email protected])([email protected](@noble/[email protected]))([email protected]):
dependencies:
'@vitest/expect': 2.1.9
'@vitest/mocker': 2.1.9([email protected](@types/node@24.12.0)([email protected]))
'@vitest/mocker': 2.1.9([email protected](@types/node@22.19.15)([email protected]))
'@vitest/pretty-format': 2.1.9
'@vitest/runner': 2.1.9
'@vitest/snapshot': 2.1.9