@h-ai/serv 0.1.0-alpha.76 → 1.0.0-beta.10

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/dist/index.js CHANGED
@@ -1,17 +1,16 @@
1
- import { validateInputOrFail, resolveRequestLocale, localizeZodError } from './chunk-65XNI7TH.js';
2
- import { servM, WILDCARD_ROLE, WILDCARD_PERMISSION, implement, buildHaiErrorBody } from './chunk-JAQIEQJS.js';
1
+ import { validateInputOrFail, resolveRequestLocale, localizeZodError } from './chunk-IQZQTAEW.js';
2
+ import { servM, WILDCARD_ROLE, WILDCARD_PERMISSION, implement, buildHaiErrorBody } from './chunk-FEG22LCX.js';
3
3
  import process from 'process';
4
4
  import { upgradeWebSocket, serve } from '@hono/node-server';
5
+ import { posix } from 'path';
5
6
  import { apiContract, AUDIO_WS_PATH, AudioWsClientMessageSchema } from '@h-ai/api-contract';
6
7
  import { audit } from '@h-ai/audit';
7
- import { core, HaiCommonError, err, ok } from '@h-ai/core';
8
+ import { core, RequestAccessConfigSchema, TrustedProxiesSchema, HaiCommonError, err, ok } from '@h-ai/core';
8
9
  import { TRANSPORT_PLAINTEXT_RANGE, createTransportBodyPolicy, TRANSPORT_PROTOCOL, authenticateTransportRequest, TransportTimeError, createTransportErrorResponse, TransportBodyTooLargeError, readTransportBody, TRANSPORT_BODY_LIMITS, isValidSM2PublicKey } from '@h-ai/crypto';
9
10
  import { OpenAPIHandler } from '@orpc/openapi/fetch';
10
- import { RPCHandler } from '@orpc/server/fetch';
11
11
  import { Hono } from 'hono';
12
12
  import { WebSocketServer } from 'ws';
13
13
  import { Buffer } from 'buffer';
14
- import { BlockList, isIP } from 'net';
15
14
  import { z } from 'zod';
16
15
  import { getCookie } from 'hono/cookie';
17
16
  import { readFile } from 'fs/promises';
@@ -549,134 +548,6 @@ function toArrayBuffer(data) {
549
548
  new Uint8Array(buffer).set(data);
550
549
  return buffer;
551
550
  }
552
- function readRemoteAddress(metadata) {
553
- if (!metadata || typeof metadata !== "object" || !("remoteAddress" in metadata) || typeof metadata.remoteAddress !== "string")
554
- return void 0;
555
- return normalizeClientIp(metadata.remoteAddress);
556
- }
557
- function normalizeClientIp(value) {
558
- if (!value)
559
- return void 0;
560
- const address = value.trim();
561
- if (!isIP(address))
562
- return void 0;
563
- const mapped = address.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
564
- return mapped?.[1] ?? address;
565
- }
566
- function addProxy(blockList, entry) {
567
- const [address, prefix, extra] = entry.split("/");
568
- const family = address ? isIP(address) : 0;
569
- if (!family || extra !== void 0)
570
- throw new Error("trustedProxies entries must be IP addresses or CIDRs");
571
- const type = family === 4 ? "ipv4" : "ipv6";
572
- if (prefix === void 0) {
573
- blockList.addAddress(address, type);
574
- return;
575
- }
576
- const bits = Number(prefix);
577
- if (!/^\d+$/.test(prefix) || bits < 0 || bits > (family === 4 ? 32 : 128))
578
- throw new Error("Invalid trustedProxies CIDR prefix");
579
- blockList.addSubnet(address, bits, type);
580
- }
581
- function isTrustedProxyEntry(entry) {
582
- try {
583
- addProxy(new BlockList(), entry);
584
- return true;
585
- } catch {
586
- return false;
587
- }
588
- }
589
- function createClientIpResolver(trustedProxies) {
590
- const proxies = new BlockList();
591
- for (const entry of trustedProxies)
592
- addProxy(proxies, entry);
593
- const trusted = (address) => proxies.check(address, isIP(address) === 4 ? "ipv4" : "ipv6");
594
- return (request, remoteAddress) => {
595
- const peer = normalizeClientIp(remoteAddress);
596
- if (!peer || !trusted(peer))
597
- return peer;
598
- const forwarded = request.headers.get("x-forwarded-for");
599
- if (forwarded === null) {
600
- const realIp = request.headers.get("x-real-ip");
601
- return realIp === null ? peer : normalizeClientIp(realIp);
602
- }
603
- const chain = forwarded.split(",");
604
- if (chain.length > 32)
605
- return void 0;
606
- let current = peer;
607
- for (let index = chain.length - 1; index >= 0 && trusted(current); index--) {
608
- const address = normalizeClientIp(chain[index]);
609
- if (!address)
610
- return void 0;
611
- current = address;
612
- }
613
- return current;
614
- };
615
- }
616
-
617
- // src/pipelines/serv-pipeline-require-internal-rpc.ts
618
- var pipelineLogger = core.logger.child({ module: "serv", scope: "pipeline" });
619
- var cachedGetConnInfo;
620
- async function resolveGetConnInfo() {
621
- if (cachedGetConnInfo !== void 0)
622
- return cachedGetConnInfo;
623
- try {
624
- const mod = await import('@hono/node-server/conninfo');
625
- cachedGetConnInfo = mod.getConnInfo;
626
- } catch (cause) {
627
- pipelineLogger.warn("getConnInfo unavailable in this runtime; loopback/private-network access modes will fail closed", { error: cause });
628
- cachedGetConnInfo = null;
629
- }
630
- return cachedGetConnInfo;
631
- }
632
- var LOOPBACK_IPS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1", "localhost"]);
633
- function requireInternalRPC(config) {
634
- return async (c, next) => {
635
- const locale = resolveRequestLocale(c.req.raw.headers);
636
- if (config.access === "gateway-only") {
637
- const headerName = config.gatewayHeader ?? "x-hai-internal-rpc";
638
- const received = c.req.header(headerName);
639
- if (!config.gatewaySecret || !received || !core.string.constantTimeEqual(received, config.gatewaySecret))
640
- return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM("serv_errorForbidden", { locale })), 403);
641
- return next();
642
- }
643
- const ip = await getRequestRemoteAddress(c) ?? "";
644
- if (config.access === "loopback" && !isLoopbackIP(ip))
645
- return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM("serv_errorForbidden", { locale })), 403);
646
- if (config.access === "private-network" && !isPrivateNetworkIP(ip))
647
- return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM("serv_errorForbidden", { locale })), 403);
648
- return next();
649
- };
650
- }
651
- async function getRequestRemoteAddress(c) {
652
- const remoteAddress = readRemoteAddress(c.env);
653
- if (remoteAddress)
654
- return remoteAddress;
655
- const getConnInfo = await resolveGetConnInfo();
656
- if (!getConnInfo)
657
- return void 0;
658
- try {
659
- return getConnInfo(c).remote.address;
660
- } catch {
661
- return void 0;
662
- }
663
- }
664
- function normalizeIpAddress(ip) {
665
- return ip.startsWith("::ffff:") ? ip.slice("::ffff:".length) : ip;
666
- }
667
- function isLoopbackIP(ip) {
668
- const normalized = normalizeIpAddress(ip);
669
- return LOOPBACK_IPS.has(ip) || LOOPBACK_IPS.has(normalized);
670
- }
671
- function isPrivateNetworkIP(ip) {
672
- const normalized = normalizeIpAddress(ip);
673
- if (isLoopbackIP(ip))
674
- return true;
675
- if (normalized.startsWith("10.") || normalized.startsWith("192.168."))
676
- return true;
677
- const second = Number(normalized.split(".")[1]);
678
- return normalized.startsWith("172.") && Number.isInteger(second) && second >= 16 && second <= 31;
679
- }
680
551
 
681
552
  // src/pipelines/serv-pipeline-security-headers.ts
682
553
  function securityHeaders() {
@@ -687,9 +558,14 @@ function securityHeaders() {
687
558
  await next();
688
559
  };
689
560
  }
561
+ function readRemoteAddress(metadata) {
562
+ if (!metadata || typeof metadata !== "object" || !("remoteAddress" in metadata) || typeof metadata.remoteAddress !== "string")
563
+ return void 0;
564
+ return core.access.normalizeClientIp(metadata.remoteAddress);
565
+ }
690
566
  var ApiPrefixSchema = z.custom(
691
- (value) => typeof value === "string" && value.startsWith("/api/"),
692
- "apiPrefix must start with /api/"
567
+ (value) => typeof value === "string" && /^\/[\w.-]+(?:\/[\w.-]+)*$/.test(value) && !value.split("/").some((segment) => segment === "." || segment === ".."),
568
+ { error: () => servM("serv_invalidApiPrefix") }
693
569
  );
694
570
  var AbsolutePathSchema = z.custom(
695
571
  (value) => typeof value === "string" && value.startsWith("/"),
@@ -713,15 +589,6 @@ var HealthHttpConfigInputSchema = z.union([
713
589
  readyPath: AbsolutePathSchema.optional().default("/ready")
714
590
  })
715
591
  ]);
716
- var RpcHttpConfigInputSchema = z.union([
717
- z.literal(false),
718
- z.object({
719
- prefix: AbsolutePathSchema.default("/rpc"),
720
- access: z.enum(["loopback", "private-network", "gateway-only"]).default("loopback"),
721
- gatewayHeader: z.string().optional(),
722
- gatewaySecret: z.string().optional()
723
- })
724
- ]);
725
592
  var TransportRuntimeConfigInputSchema = z.union([
726
593
  z.literal(false),
727
594
  z.object({
@@ -743,12 +610,12 @@ var CorsRuntimeConfigInputSchema = z.object({
743
610
  credentials: z.boolean().default(false)
744
611
  });
745
612
  var ServHttpConfigInputSchema = z.object({
746
- trustedProxies: z.array(z.string().trim().refine(isTrustedProxyEntry, "Expected an IP address or CIDR")).default([]),
613
+ trustedProxies: TrustedProxiesSchema.default("auto"),
614
+ access: RequestAccessConfigSchema.default(false),
747
615
  apiPrefix: ApiPrefixSchema.default("/api/v1"),
748
616
  openapi: OpenAPIHttpConfigInputSchema.default(false),
749
617
  docs: DocsHttpConfigInputSchema.default(false),
750
- health: HealthHttpConfigInputSchema.default({ path: "/health", readyPath: "/ready" }),
751
- rpc: RpcHttpConfigInputSchema.default(false)
618
+ health: HealthHttpConfigInputSchema.default({ path: "/health", readyPath: "/ready" })
752
619
  });
753
620
  var ServConfigSchema = z.object({
754
621
  server: ServerConfigInputSchema.optional(),
@@ -803,7 +670,7 @@ function extractBearerToken(value) {
803
670
  function parseRequestContext(input) {
804
671
  const headers = input.request.headers;
805
672
  const requestId = core.context.get().requestId ?? resolveRequestId(headers.get(HEADER_REQUEST_ID));
806
- const ip = normalizeClientIp(Object.hasOwn(input, "ip") ? input.ip : input.remoteAddress);
673
+ const ip = core.access.normalizeClientIp(Object.hasOwn(input, "ip") ? input.ip : input.remoteAddress);
807
674
  const locale = resolveRequestLocale(headers);
808
675
  const accessToken = extractBearerToken(headers.get(HEADER_AUTHORIZATION));
809
676
  const terminal = headers.get("x-hai-terminal");
@@ -999,6 +866,96 @@ async function readRefreshTokenFromBody(request) {
999
866
  const body = await request.json().catch(() => void 0);
1000
867
  return typeof body?.refreshToken === "string" && body.refreshToken.length > 0 ? body.refreshToken : void 0;
1001
868
  }
869
+
870
+ // src/serv-cors.ts
871
+ var DEFAULT_CORS_METHODS = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"];
872
+ var DEFAULT_CORS_ALLOWED_HEADERS = ["Authorization", "Content-Type", "X-Requested-With", "X-Hai-Reauthentication", "X-Hai-Terminal"];
873
+ var DEFAULT_CORS_MAX_AGE = 86400;
874
+ var corsMiddlewares = /* @__PURE__ */ new WeakSet();
875
+ function isCorsMiddleware(middleware) {
876
+ return corsMiddlewares.has(middleware);
877
+ }
878
+ function cors(config = {}) {
879
+ const methods = [...config.methods ?? DEFAULT_CORS_METHODS];
880
+ const allowedHeaders = [...config.allowedHeaders ?? DEFAULT_CORS_ALLOWED_HEADERS];
881
+ const exposedHeaders = [.../* @__PURE__ */ new Set(["X-Request-Id", "X-Trace-Id", ...config.exposedHeaders ?? []])];
882
+ const credentials = config.credentials ?? false;
883
+ const maxAge = config.maxAge ?? DEFAULT_CORS_MAX_AGE;
884
+ const originConfig = config.origin ?? "*";
885
+ const middleware = async (c, next) => {
886
+ const allowedOrigin = resolveCorsOrigin(c.req.header("Origin"), originConfig);
887
+ if (c.req.method === "OPTIONS") {
888
+ return new Response(null, {
889
+ status: 204,
890
+ headers: buildCorsHeaders({
891
+ allowedOrigin,
892
+ allowedHeaders,
893
+ credentials,
894
+ exposedHeaders,
895
+ maxAge,
896
+ methods,
897
+ request: c.req.raw
898
+ })
899
+ });
900
+ }
901
+ await next();
902
+ const corsHeaders = buildCorsHeaders({
903
+ allowedOrigin,
904
+ allowedHeaders,
905
+ credentials,
906
+ exposedHeaders,
907
+ maxAge,
908
+ methods,
909
+ request: c.req.raw
910
+ });
911
+ for (const [key, value] of corsHeaders) {
912
+ if (key.toLowerCase() === "vary") {
913
+ c.res.headers.append(key, value);
914
+ continue;
915
+ }
916
+ c.res.headers.set(key, value);
917
+ }
918
+ return void 0;
919
+ };
920
+ corsMiddlewares.add(middleware);
921
+ return middleware;
922
+ }
923
+ function resolveCorsOrigin(origin, configOrigin) {
924
+ if (!origin)
925
+ return null;
926
+ if (configOrigin === "*")
927
+ return "*";
928
+ if (typeof configOrigin === "string")
929
+ return origin === configOrigin ? origin : null;
930
+ if (Array.isArray(configOrigin))
931
+ return configOrigin.includes(origin) ? origin : null;
932
+ if (typeof configOrigin === "function")
933
+ return configOrigin(origin) ? origin : null;
934
+ return null;
935
+ }
936
+ function buildCorsHeaders(options) {
937
+ const headers = new Headers();
938
+ if (!options.allowedOrigin)
939
+ return headers;
940
+ const requestedHeaders = options.request.headers.get("Access-Control-Request-Headers");
941
+ headers.set("Access-Control-Allow-Origin", options.allowedOrigin);
942
+ if (options.credentials)
943
+ headers.set("Access-Control-Allow-Credentials", "true");
944
+ if (options.allowedOrigin !== "*") {
945
+ headers.set(
946
+ "Vary",
947
+ requestedHeaders ? "Origin, Access-Control-Request-Headers" : "Origin"
948
+ );
949
+ }
950
+ if (options.exposedHeaders.length > 0)
951
+ headers.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
952
+ if (options.request.method === "OPTIONS") {
953
+ headers.set("Access-Control-Allow-Methods", options.methods.join(", "));
954
+ headers.set("Access-Control-Allow-Headers", requestedHeaders ?? options.allowedHeaders.join(", "));
955
+ headers.set("Access-Control-Max-Age", String(options.maxAge));
956
+ }
957
+ return headers;
958
+ }
1002
959
  var SCALAR_ROUTE = "/_hai/scalar.js";
1003
960
  var SCALAR_BROWSER_ENTRY_FALLBACK = "./dist/browser/standalone.js";
1004
961
  var scalarScriptCache;
@@ -1063,12 +1020,20 @@ function readScalarBrowserEntry(packageInfo) {
1063
1020
  }
1064
1021
  var logger2 = core.logger.child({ module: "serv", scope: "transport" });
1065
1022
  var ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ["application/json"];
1066
- function createTransportMiddleware(manager, keyExchangePath, excludePaths = [], policy = createTransportBodyPolicy(manager.bodyLimits.plaintextBytes)) {
1023
+ function createTransportMiddleware(manager, keyExchangePath, excludePaths = [], policy = createTransportBodyPolicy(manager.bodyLimits.plaintextBytes), encryption) {
1067
1024
  return async (c, next) => {
1068
1025
  const pathname = new URL(c.req.url).pathname;
1026
+ if (c.req.method === "OPTIONS")
1027
+ return next();
1069
1028
  if (pathname === keyExchangePath && c.req.method === "POST")
1070
1029
  return handleKeyExchange(c.req.raw, manager);
1071
- if (shouldExclude(pathname, excludePaths, keyExchangePath))
1030
+ const mode = encryption?.(pathname, c.req.method);
1031
+ if (mode === "disabled") {
1032
+ if (c.req.raw.headers.has(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) || c.req.raw.headers.has(TRANSPORT_PROTOCOL.ENVELOPE_HEADER))
1033
+ return Response.json(err({ ...HaiCommonError.VALIDATION_ERROR, message: servM("serv_transportPlaintextRequired"), traceId: core.context.get().traceId ?? core.context.get().requestId }), { status: 400 });
1034
+ return next();
1035
+ }
1036
+ if (mode !== "required" && shouldExclude(pathname, excludePaths, keyExchangePath))
1072
1037
  return next();
1073
1038
  const clientId = c.req.header(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER);
1074
1039
  if (!clientId)
@@ -1222,7 +1187,10 @@ var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
1222
1187
  var audioWsServers = /* @__PURE__ */ new WeakMap();
1223
1188
  function createApp(options) {
1224
1189
  const http = resolveServHttpConfig(options.http);
1225
- const resolveIp = createClientIpResolver(http.trustedProxies);
1190
+ const resolveIp = core.access.createClientIpResolver(http.trustedProxies);
1191
+ const checkAccess = core.access.createRequestCheck(http.access);
1192
+ if (checkAccess.requiresEncryption && !options.transport)
1193
+ throw new Error("Request access rules require transport encryption, but Serv transport is not configured");
1226
1194
  const app = new Hono();
1227
1195
  const requestAuditContexts = /* @__PURE__ */ new WeakMap();
1228
1196
  app.onError((error, c) => {
@@ -1280,8 +1248,45 @@ function createApp(options) {
1280
1248
  return cachedSpec;
1281
1249
  };
1282
1250
  app.use("*", securityHeaders());
1283
- if (options.middlewares)
1284
- mountCustomMiddlewares(app, options.middlewares);
1251
+ const accessDenied = (c) => {
1252
+ const ipAddress = resolveIp(c.req.raw, readRemoteAddress(c.env));
1253
+ const decision = checkAccess(c.req.path, c.req.method, ipAddress);
1254
+ if (decision.allowed) {
1255
+ if (!options.transport && c.req.method !== "OPTIONS" && (c.req.raw.headers.has(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) || c.req.raw.headers.has(TRANSPORT_PROTOCOL.ENVELOPE_HEADER)) && checkAccess.encryption(c.req.path, c.req.method) === "disabled") {
1256
+ return c.json(buildHaiErrorBody(HaiCommonError.VALIDATION_ERROR, servM("serv_transportPlaintextRequired")), 400);
1257
+ }
1258
+ return void 0;
1259
+ }
1260
+ core.logger.info("Request access policy denied", { ruleId: decision.ruleId, reason: decision.reason, method: c.req.method, ipAddress });
1261
+ return c.json(buildHaiErrorBody(HaiCommonError.REQUEST_ACCESS_DENIED, core.i18n.coreM("core_requestAccessDenied")), 403);
1262
+ };
1263
+ const cors2 = options.middlewares?.filter((entry) => isCorsMiddleware(entry.middleware)) ?? [];
1264
+ const custom = options.middlewares?.filter((entry) => !isCorsMiddleware(entry.middleware)) ?? [];
1265
+ for (const entry of cors2) {
1266
+ if (http.access === false) {
1267
+ app.use(entry.path ?? "*", entry.middleware);
1268
+ continue;
1269
+ }
1270
+ app.use(entry.path ?? "*", async (c, next) => {
1271
+ if (c.req.method !== "OPTIONS")
1272
+ return entry.middleware(c, next);
1273
+ const rejected = accessDenied(c);
1274
+ const preflight = await entry.middleware(c, async () => {
1275
+ });
1276
+ if (!rejected)
1277
+ return preflight;
1278
+ if (preflight instanceof Response) {
1279
+ for (const [key, value] of preflight.headers)
1280
+ rejected.headers.set(key, value);
1281
+ rejected.headers.set("content-type", "application/json; charset=utf-8");
1282
+ }
1283
+ return rejected;
1284
+ });
1285
+ }
1286
+ const keyExchangePath = options.transport ? `${http.apiPrefix}${options.transport.keyExchangePath ?? "/_hai/key-exchange"}` : void 0;
1287
+ if (http.access !== false && keyExchangePath) {
1288
+ app.use("*", async (c, next) => c.req.path === keyExchangePath && c.req.method === "POST" ? accessDenied(c) ?? next() : next());
1289
+ }
1285
1290
  if (options.transport) {
1286
1291
  const policy = createTransportBodyPolicy(options.transport.maxPlaintextBytes, options.transport.routePlaintextBytes);
1287
1292
  const mgr = options.transport.crypto.transport.createServer({
@@ -1296,9 +1301,11 @@ function createApp(options) {
1296
1301
  mgr.error
1297
1302
  );
1298
1303
  }
1299
- const keyExchangePath = `${http.apiPrefix}${options.transport.keyExchangePath ?? "/_hai/key-exchange"}`;
1300
- app.use("*", createTransportMiddleware(mgr.data, keyExchangePath, options.transport.excludePaths, policy));
1304
+ app.use("*", createTransportMiddleware(mgr.data, keyExchangePath, options.transport.excludePaths, policy, checkAccess.encryption));
1301
1305
  }
1306
+ if (http.access !== false)
1307
+ app.use("*", async (c, next) => accessDenied(c) ?? next());
1308
+ mountCustomMiddlewares(app, custom);
1302
1309
  if (http.health !== false)
1303
1310
  mountHealthRoutes(app, http.health);
1304
1311
  if (options.refreshCookie)
@@ -1321,14 +1328,13 @@ function createApp(options) {
1321
1328
  audioWsServers.set(app, new WebSocketServer({ noServer: true }));
1322
1329
  }
1323
1330
  mountOpenAPIRoutes(app, options.procedures, http.apiPrefix, createContext);
1324
- if (http.rpc !== false)
1325
- mountRPCRoutes(app, options.procedures, http.rpc, createContext);
1326
1331
  if (http.openapi !== false) {
1327
1332
  app.get(http.openapi.path, async (c) => c.json(await getSpec()));
1328
1333
  }
1329
1334
  if (http.docs !== false) {
1330
1335
  const docs = http.docs;
1331
- app.get(SCALAR_ROUTE, async (c) => {
1336
+ const docsDirectory = docs.path.slice(0, docs.path.lastIndexOf("/") + 1);
1337
+ app.get(`${docsDirectory}${SCALAR_ROUTE.slice(1)}`, async (c) => {
1332
1338
  const content = await getScalarScript();
1333
1339
  if (!content)
1334
1340
  return c.notFound();
@@ -1342,8 +1348,8 @@ function createApp(options) {
1342
1348
  if (!context.session)
1343
1349
  return c.json(buildHaiErrorBody(HaiCommonError.UNAUTHORIZED, servM("serv_errorUnauthorized", { locale: resolveRequestLocale(c.req.raw.headers) })), 401);
1344
1350
  }
1345
- const specUrl = http.openapi === false ? void 0 : http.openapi.path;
1346
- return c.html(createDocsPage(await getSpec(), { specUrl }));
1351
+ const specUrl = http.openapi === false ? void 0 : posix.relative(docsDirectory, http.openapi.path) || ".";
1352
+ return c.html(createDocsPage(await getSpec(), { specUrl, scriptUrl: SCALAR_ROUTE.slice(1) }));
1347
1353
  });
1348
1354
  }
1349
1355
  return app;
@@ -1361,12 +1367,6 @@ function mountOpenAPIRoutes(app, procedures, apiPrefix, createContext) {
1361
1367
  const handler = new OpenAPIHandler(procedures);
1362
1368
  app.on([...API_METHODS], `${apiPrefix}/*`, (c, next) => handleORPC(c, next, apiPrefix, handler, createContext));
1363
1369
  }
1364
- function mountRPCRoutes(app, procedures, rpcConfig, createContext) {
1365
- const handler = new RPCHandler(procedures);
1366
- const prefix = rpcConfig.prefix;
1367
- app.use(`${prefix}/*`, requireInternalRPC(rpcConfig));
1368
- app.on([...API_METHODS], `${prefix}/*`, (c, next) => handleORPC(c, next, prefix, handler, createContext));
1369
- }
1370
1370
  async function handleORPC(c, next, prefix, handler, createContext) {
1371
1371
  const context = await createContext({ request: c.req.raw, remoteAddress: readRemoteAddress(c.env) });
1372
1372
  const { matched, response } = await core.context.run({ requestId: context.requestId, userId: context.session?.userId, scopeId: context.scopeId }, () => handler.handle(c.req.raw, { prefix, context }));
@@ -1478,90 +1478,6 @@ function listen(app, options = {}) {
1478
1478
  return handle;
1479
1479
  }
1480
1480
 
1481
- // src/serv-cors.ts
1482
- var DEFAULT_CORS_METHODS = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"];
1483
- var DEFAULT_CORS_ALLOWED_HEADERS = ["Authorization", "Content-Type", "X-Requested-With", "X-Hai-Reauthentication", "X-Hai-Terminal"];
1484
- var DEFAULT_CORS_MAX_AGE = 86400;
1485
- function cors(config = {}) {
1486
- const methods = [...config.methods ?? DEFAULT_CORS_METHODS];
1487
- const allowedHeaders = [...config.allowedHeaders ?? DEFAULT_CORS_ALLOWED_HEADERS];
1488
- const exposedHeaders = [.../* @__PURE__ */ new Set(["X-Request-Id", "X-Trace-Id", ...config.exposedHeaders ?? []])];
1489
- const credentials = config.credentials ?? false;
1490
- const maxAge = config.maxAge ?? DEFAULT_CORS_MAX_AGE;
1491
- const originConfig = config.origin ?? "*";
1492
- return async (c, next) => {
1493
- const allowedOrigin = resolveCorsOrigin(c.req.header("Origin"), originConfig);
1494
- if (c.req.method === "OPTIONS") {
1495
- return new Response(null, {
1496
- status: 204,
1497
- headers: buildCorsHeaders({
1498
- allowedOrigin,
1499
- allowedHeaders,
1500
- credentials,
1501
- exposedHeaders,
1502
- maxAge,
1503
- methods,
1504
- request: c.req.raw
1505
- })
1506
- });
1507
- }
1508
- await next();
1509
- const corsHeaders = buildCorsHeaders({
1510
- allowedOrigin,
1511
- allowedHeaders,
1512
- credentials,
1513
- exposedHeaders,
1514
- maxAge,
1515
- methods,
1516
- request: c.req.raw
1517
- });
1518
- for (const [key, value] of corsHeaders) {
1519
- if (key.toLowerCase() === "vary") {
1520
- c.res.headers.append(key, value);
1521
- continue;
1522
- }
1523
- c.res.headers.set(key, value);
1524
- }
1525
- return void 0;
1526
- };
1527
- }
1528
- function resolveCorsOrigin(origin, configOrigin) {
1529
- if (!origin)
1530
- return null;
1531
- if (configOrigin === "*")
1532
- return "*";
1533
- if (typeof configOrigin === "string")
1534
- return origin === configOrigin ? origin : null;
1535
- if (Array.isArray(configOrigin))
1536
- return configOrigin.includes(origin) ? origin : null;
1537
- if (typeof configOrigin === "function")
1538
- return configOrigin(origin) ? origin : null;
1539
- return null;
1540
- }
1541
- function buildCorsHeaders(options) {
1542
- const headers = new Headers();
1543
- if (!options.allowedOrigin)
1544
- return headers;
1545
- const requestedHeaders = options.request.headers.get("Access-Control-Request-Headers");
1546
- headers.set("Access-Control-Allow-Origin", options.allowedOrigin);
1547
- if (options.credentials)
1548
- headers.set("Access-Control-Allow-Credentials", "true");
1549
- if (options.allowedOrigin !== "*") {
1550
- headers.set(
1551
- "Vary",
1552
- requestedHeaders ? "Origin, Access-Control-Request-Headers" : "Origin"
1553
- );
1554
- }
1555
- if (options.exposedHeaders.length > 0)
1556
- headers.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
1557
- if (options.request.method === "OPTIONS") {
1558
- headers.set("Access-Control-Allow-Methods", options.methods.join(", "));
1559
- headers.set("Access-Control-Allow-Headers", requestedHeaders ?? options.allowedHeaders.join(", "));
1560
- headers.set("Access-Control-Max-Age", String(options.maxAge));
1561
- }
1562
- return headers;
1563
- }
1564
-
1565
1481
  // src/serv-runtime-security.ts
1566
1482
  function createRuntimeSecurityPolicy(input) {
1567
1483
  const production = input.environment?.trim().toLowerCase() === "production";