veryfront 0.1.810 → 0.1.812

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 (114) hide show
  1. package/esm/deno.d.ts +3 -0
  2. package/esm/deno.js +5 -2
  3. package/esm/src/agent/conversation/durable-contracts.d.ts +204 -0
  4. package/esm/src/agent/conversation/durable-contracts.d.ts.map +1 -0
  5. package/esm/src/agent/conversation/durable-contracts.js +168 -0
  6. package/esm/src/agent/conversation/durable.d.ts +3 -198
  7. package/esm/src/agent/conversation/durable.d.ts.map +1 -1
  8. package/esm/src/agent/conversation/durable.js +2 -167
  9. package/esm/src/agent/factory.d.ts.map +1 -1
  10. package/esm/src/agent/factory.js +18 -6
  11. package/esm/src/agent/hosted/veryfront-cloud-agent-service.d.ts.map +1 -1
  12. package/esm/src/agent/hosted/veryfront-cloud-agent-service.js +1 -0
  13. package/esm/src/agent/types.d.ts +8 -0
  14. package/esm/src/agent/types.d.ts.map +1 -1
  15. package/esm/src/config/schemas/config.schema.d.ts +12 -0
  16. package/esm/src/config/schemas/config.schema.d.ts.map +1 -1
  17. package/esm/src/config/schemas/config.schema.js +12 -0
  18. package/esm/src/discovery/discovery-engine.d.ts.map +1 -1
  19. package/esm/src/discovery/discovery-engine.js +6 -1
  20. package/esm/src/discovery/handlers/index.d.ts +1 -0
  21. package/esm/src/discovery/handlers/index.d.ts.map +1 -1
  22. package/esm/src/discovery/handlers/index.js +1 -0
  23. package/esm/src/discovery/handlers/work-handler.d.ts +7 -0
  24. package/esm/src/discovery/handlers/work-handler.d.ts.map +1 -0
  25. package/esm/src/discovery/handlers/work-handler.js +19 -0
  26. package/esm/src/discovery/import-rewriter.d.ts +1 -1
  27. package/esm/src/discovery/import-rewriter.d.ts.map +1 -1
  28. package/esm/src/discovery/import-rewriter.js +1 -0
  29. package/esm/src/discovery/project-discovery-config.d.ts +2 -0
  30. package/esm/src/discovery/project-discovery-config.d.ts.map +1 -1
  31. package/esm/src/discovery/project-discovery-config.js +2 -0
  32. package/esm/src/discovery/transpiler.d.ts.map +1 -1
  33. package/esm/src/discovery/transpiler.js +2 -0
  34. package/esm/src/discovery/types.d.ts +3 -0
  35. package/esm/src/discovery/types.d.ts.map +1 -1
  36. package/esm/src/modules/import-map/loader.d.ts.map +1 -1
  37. package/esm/src/modules/import-map/loader.js +2 -1
  38. package/esm/src/modules/manifest/route-module-manifest.d.ts.map +1 -1
  39. package/esm/src/modules/manifest/route-module-manifest.js +2 -0
  40. package/esm/src/modules/server/module-server.d.ts.map +1 -1
  41. package/esm/src/modules/server/module-server.js +27 -9
  42. package/esm/src/observability/request-profiler.d.ts +1 -0
  43. package/esm/src/observability/request-profiler.d.ts.map +1 -1
  44. package/esm/src/observability/request-profiler.js +6 -0
  45. package/esm/src/observability/simple-metrics/index.d.ts +6 -3
  46. package/esm/src/observability/simple-metrics/index.d.ts.map +1 -1
  47. package/esm/src/observability/simple-metrics/index.js +5 -2
  48. package/esm/src/observability/simple-metrics/metrics-recorder.d.ts +4 -0
  49. package/esm/src/observability/simple-metrics/metrics-recorder.d.ts.map +1 -1
  50. package/esm/src/observability/simple-metrics/metrics-recorder.js +34 -0
  51. package/esm/src/observability/simple-metrics/metrics-state.d.ts.map +1 -1
  52. package/esm/src/observability/simple-metrics/metrics-state.js +24 -0
  53. package/esm/src/observability/simple-metrics/otel-instruments.d.ts.map +1 -1
  54. package/esm/src/observability/simple-metrics/otel-instruments.js +13 -0
  55. package/esm/src/observability/simple-metrics/types.d.ts +12 -0
  56. package/esm/src/observability/simple-metrics/types.d.ts.map +1 -1
  57. package/esm/src/platform/adapters/runtime/shared/shared-watcher.d.ts.map +1 -1
  58. package/esm/src/platform/adapters/runtime/shared/shared-watcher.js +1 -1
  59. package/esm/src/platform/compat/http/native-response.d.ts +10 -1
  60. package/esm/src/platform/compat/http/native-response.d.ts.map +1 -1
  61. package/esm/src/platform/compat/http/native-response.js +20 -14
  62. package/esm/src/proxy/handler.d.ts +1 -7
  63. package/esm/src/proxy/handler.d.ts.map +1 -1
  64. package/esm/src/proxy/handler.js +24 -160
  65. package/esm/src/proxy/proxy-access-control.d.ts +41 -0
  66. package/esm/src/proxy/proxy-access-control.d.ts.map +1 -0
  67. package/esm/src/proxy/proxy-access-control.js +151 -0
  68. package/esm/src/react/components/chat/chat/composition/chat-composer.d.ts.map +1 -1
  69. package/esm/src/react/components/chat/chat/composition/chat-composer.js +3 -1
  70. package/esm/src/rendering/cache/cache-coordinator.d.ts +3 -0
  71. package/esm/src/rendering/cache/cache-coordinator.d.ts.map +1 -1
  72. package/esm/src/rendering/cache/cache-coordinator.js +27 -2
  73. package/esm/src/rendering/cache/stores/memory-store.d.ts +2 -0
  74. package/esm/src/rendering/cache/stores/memory-store.d.ts.map +1 -1
  75. package/esm/src/rendering/cache/stores/memory-store.js +2 -1
  76. package/esm/src/rendering/orchestrator/lifecycle.d.ts.map +1 -1
  77. package/esm/src/rendering/orchestrator/lifecycle.js +1 -0
  78. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  79. package/esm/src/rendering/orchestrator/pipeline.js +13 -25
  80. package/esm/src/rendering/orchestrator/render-result-assembly.d.ts +27 -0
  81. package/esm/src/rendering/orchestrator/render-result-assembly.d.ts.map +1 -0
  82. package/esm/src/rendering/orchestrator/render-result-assembly.js +28 -0
  83. package/esm/src/rendering/renderer.d.ts.map +1 -1
  84. package/esm/src/rendering/renderer.js +3 -1
  85. package/esm/src/rendering/shared/context-aware-cache.d.ts +3 -0
  86. package/esm/src/rendering/shared/context-aware-cache.d.ts.map +1 -1
  87. package/esm/src/rendering/shared/context-aware-cache.js +25 -2
  88. package/esm/src/server/dev-server/server.d.ts.map +1 -1
  89. package/esm/src/server/dev-server/server.js +4 -1
  90. package/esm/src/server/handlers/dev/dashboard/api.d.ts +3 -0
  91. package/esm/src/server/handlers/dev/dashboard/api.d.ts.map +1 -1
  92. package/esm/src/server/handlers/dev/dashboard/api.js +41 -57
  93. package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
  94. package/esm/src/server/handlers/dev/framework-candidates.generated.js +11 -1
  95. package/esm/src/transforms/esm/specifier-resolver.d.ts.map +1 -1
  96. package/esm/src/transforms/esm/specifier-resolver.js +14 -0
  97. package/esm/src/utils/version-constant.d.ts +1 -1
  98. package/esm/src/utils/version-constant.js +1 -1
  99. package/esm/src/work/factory.d.ts +4 -0
  100. package/esm/src/work/factory.d.ts.map +1 -0
  101. package/esm/src/work/factory.js +51 -0
  102. package/esm/src/work/index.d.ts +27 -0
  103. package/esm/src/work/index.d.ts.map +1 -0
  104. package/esm/src/work/index.js +25 -0
  105. package/esm/src/work/prompt-augmentation.d.ts +6 -0
  106. package/esm/src/work/prompt-augmentation.d.ts.map +1 -0
  107. package/esm/src/work/prompt-augmentation.js +30 -0
  108. package/esm/src/work/registry.d.ts +10 -0
  109. package/esm/src/work/registry.d.ts.map +1 -0
  110. package/esm/src/work/registry.js +23 -0
  111. package/esm/src/work/types.d.ts +30 -0
  112. package/esm/src/work/types.d.ts.map +1 -0
  113. package/esm/src/work/types.js +1 -0
  114. package/package.json +5 -1
@@ -1,46 +1,11 @@
1
1
  import { TokenManager } from "./token-manager.js";
2
2
  import { parseProjectDomain } from "../server/utils/domain-parser.js";
3
- import { getEnv } from "../platform/compat/process.js";
4
3
  import { injectContext, ProxySpanNames, withSpan } from "./tracing.js";
5
4
  import { computeContentSourceId } from "../cache/keys.js";
6
- import { resolve as resolveContract } from "../extensions/contracts.js";
5
+ import { checkProtectedProxyAccess } from "./proxy-access-control.js";
7
6
  import { createLocalProjectResolver } from "./local-project-resolver.js";
8
7
  import { isMissingCustomDomainProjectError, resolveProxyRequestToken, } from "./proxy-token-resolution.js";
9
- /**
10
- * Cache the resolved AuthProvider at module scope so the proxy does not pay
11
- * the registry lookup on every request. The cache is cleared implicitly when
12
- * `ExtensionLoader.teardownAll()` clears the registry — the next call
13
- * re-resolves (or surfaces the "install ext-auth-jwt" hint if the extension was
14
- * removed).
15
- */
16
- let cachedAuthProvider;
17
- function getAuthProvider() {
18
- if (cachedAuthProvider)
19
- return cachedAuthProvider;
20
- try {
21
- cachedAuthProvider = resolveContract("AuthProvider");
22
- return cachedAuthProvider;
23
- }
24
- catch (err) {
25
- // resolve() already throws with a helpful "Recommended: @veryfront/ext-auth-jwt"
26
- // message, but the proxy is a load-bearing code path — append a concrete
27
- // remediation hint that names the project-root extension directory so
28
- // the user knows exactly what's missing.
29
- const base = err instanceof Error ? err.message : String(err);
30
- throw new Error(`${base}\nTo enable JWT verification in the proxy, install ext-auth-jwt ` +
31
- `(scaffold with \`deno task cli extension init ext-auth-jwt\` or add the ` +
32
- `npm package @veryfront/ext-auth-jwt).`, { cause: err });
33
- }
34
- }
35
- /**
36
- * Reset the cached AuthProvider. Intended for tests that `register()` a mock
37
- * after the handler module has been imported.
38
- *
39
- * @internal
40
- */
41
- export function __resetCachedAuthProviderForTests() {
42
- cachedAuthProvider = undefined;
43
- }
8
+ export { __resetCachedAuthProviderForTests } from "./proxy-access-control.js";
44
9
  export const INTERNAL_PROXY_HEADERS = [
45
10
  "x-token",
46
11
  "x-project-slug",
@@ -75,18 +40,6 @@ function isSignedInternalControlPlaneRequest(req, url) {
75
40
  }
76
41
  return !!req.headers.get("x-token");
77
42
  }
78
- function resolveApiJwksUrl(apiBaseUrl, logger) {
79
- try {
80
- const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
81
- return new URL(".well-known/jwks.json", normalizedBaseUrl).toString();
82
- }
83
- catch (error) {
84
- logger?.error("Invalid API base URL for JWKS lookup", error, {
85
- apiBaseUrl,
86
- });
87
- return undefined;
88
- }
89
- }
90
43
  async function lookupProjectByDomain(domain, apiBaseUrl, token, logger) {
91
44
  return withSpan(ProxySpanNames.PROXY_DOMAIN_LOOKUP, async () => {
92
45
  const domainWithoutPort = domain.replace(/:\d+$/, "");
@@ -141,59 +94,6 @@ function parseStatusFromError(error) {
141
94
  const match = message.match(/failed: (\d+)/);
142
95
  return match ? Number(match[1]) : null;
143
96
  }
144
- async function extractUserIdFromToken(token, apiBaseUrl, log) {
145
- const auth = getAuthProvider();
146
- const header = auth.decode(token);
147
- if (!header) {
148
- log?.debug("Failed to decode JWT header");
149
- return undefined;
150
- }
151
- const algorithm = header.alg;
152
- if (algorithm === "RS256") {
153
- const jwksUrl = resolveApiJwksUrl(apiBaseUrl, log);
154
- if (!jwksUrl)
155
- return undefined;
156
- try {
157
- const payload = await auth.verifyWithJwks(token, jwksUrl, {
158
- algorithms: ["RS256"],
159
- });
160
- return payload.userId;
161
- }
162
- catch (error) {
163
- log?.debug("RS256 JWT verification failed", {
164
- error: error instanceof Error ? error.message : String(error),
165
- });
166
- return undefined;
167
- }
168
- }
169
- if (algorithm !== "HS256") {
170
- log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
171
- return undefined;
172
- }
173
- const jwtSecret = getEnv("JWT_SECRET");
174
- if (!jwtSecret) {
175
- log?.warn("JWT_SECRET not configured — cannot verify user token");
176
- return undefined;
177
- }
178
- try {
179
- // ext-auth-jwt reads JWT_SECRET from the environment when no `secret` was
180
- // passed to the extension factory; the explicit env check above is kept
181
- // so callers can warn once before we attempt verification.
182
- const payload = await auth.verify(token, { algorithms: ["HS256"] });
183
- return payload.userId;
184
- }
185
- catch (error) {
186
- log?.debug("JWT verification failed", {
187
- error: error instanceof Error ? error.message : String(error),
188
- });
189
- return undefined;
190
- }
191
- }
192
- function isProjectMember(users, userId) {
193
- if (!users || !userId)
194
- return false;
195
- return users.some((u) => u.id === userId);
196
- }
197
97
  export function createProxyHandler(options) {
198
98
  const { config, cache, logger } = options;
199
99
  const localProjects = config.localProjects ?? {};
@@ -227,71 +127,25 @@ export function createProxyHandler(options) {
227
127
  error: { status, message, redirectUrl, slug },
228
128
  };
229
129
  }
230
- function makeAuthRedirectUrl(url) {
231
- // Collapse leading slashes to prevent protocol-relative open redirects (e.g. "//evil.com/path")
232
- const safePath = url.pathname.replace(/^\/\/+/, "/");
233
- let returnPath = safePath + url.search;
234
- // Ensure the return path stays within the application and is not an absolute URL.
235
- // - It must start with "/".
236
- // - It must not contain a scheme delimiter ("://").
237
- // If it fails validation, fall back to the root path.
238
- if (!returnPath.startsWith("/") || returnPath.includes("://")) {
239
- returnPath = "/";
240
- }
241
- const isHostedProductionDeployment = url.hostname.endsWith(".production.veryfront.org") ||
242
- url.hostname.endsWith(".production.veryfront.com");
243
- const returnTarget = isHostedProductionDeployment ? url.toString() : returnPath;
244
- return `https://veryfront.com/sign-in?from=${encodeURIComponent(returnTarget)}`;
245
- }
246
130
  function makeProjectNotFoundContext(base, message, token) {
247
131
  return makeErrorContext(base, 404, message, token, undefined, "project-not-found");
248
132
  }
249
- async function checkProtectedAccess(req, url, matchingEnv, userToken, users, logContext) {
250
- if (!matchingEnv?.protected)
251
- return null;
252
- if (isSignedInternalControlPlaneRequest(req, url)) {
253
- logger?.debug("Allowing signed internal control-plane request through protected environment", {
254
- ...logContext,
255
- environmentName: matchingEnv.name,
256
- pathname: url.pathname,
257
- });
258
- return null;
259
- }
260
- if (!userToken) {
261
- const redirectUrl = makeAuthRedirectUrl(url);
262
- logger?.info("Protected environment requires authentication", {
263
- ...logContext,
264
- environmentName: matchingEnv.name,
265
- redirectUrl,
266
- });
267
- return { status: 302, message: "Authentication required", redirectUrl };
268
- }
269
- const userId = await extractUserIdFromToken(userToken, config.apiBaseUrl, logger);
270
- if (!userId) {
271
- const redirectUrl = makeAuthRedirectUrl(url);
272
- logger?.info("Could not extract userId from token", {
273
- ...logContext,
274
- environmentName: matchingEnv.name,
275
- redirectUrl,
276
- });
277
- return { status: 302, message: "Authentication required", redirectUrl };
278
- }
279
- if (!isProjectMember(users, userId)) {
280
- logger?.info("User is not a member of the project", {
281
- ...logContext,
282
- environmentName: matchingEnv.name,
283
- userId,
284
- });
285
- return { status: 403, message: "Access denied" };
286
- }
287
- return null;
288
- }
289
133
  async function resolveReleaseAndProtection(req, url, token, userToken, lookupKey, envMatcher, logContext) {
290
134
  const lookupResult = await lookupProjectByDomain(lookupKey, config.apiBaseUrl, token, logger);
291
135
  if (!lookupResult)
292
136
  return { projectId: undefined, releaseId: undefined };
293
137
  const matchingEnv = lookupResult.environments?.find(envMatcher);
294
- const protectionError = await checkProtectedAccess(req, url, matchingEnv, userToken, lookupResult.users, logContext);
138
+ const protectionError = await checkProtectedProxyAccess({
139
+ req,
140
+ url,
141
+ matchingEnv,
142
+ userToken,
143
+ users: lookupResult.users,
144
+ apiBaseUrl: config.apiBaseUrl,
145
+ logger,
146
+ logContext,
147
+ isSignedInternalControlPlaneRequest: isSignedInternalControlPlaneRequest(req, url),
148
+ });
295
149
  if (protectionError)
296
150
  return { error: protectionError };
297
151
  return {
@@ -398,7 +252,17 @@ export function createProxyHandler(options) {
398
252
  const matchingEnv = lookupResult.environments?.find((env) => env.domains?.some((d) => d.toLowerCase() === normalizedHost));
399
253
  releaseId = matchingEnv?.active_release_id ?? undefined;
400
254
  environmentId = matchingEnv?.id;
401
- const protectionError = await checkProtectedAccess(req, url, matchingEnv, userToken, lookupResult.users, { domain: host });
255
+ const protectionError = await checkProtectedProxyAccess({
256
+ req,
257
+ url,
258
+ matchingEnv,
259
+ userToken,
260
+ users: lookupResult.users,
261
+ apiBaseUrl: config.apiBaseUrl,
262
+ logger,
263
+ logContext: { domain: host },
264
+ isSignedInternalControlPlaneRequest: isSignedInternalControlPlaneRequest(req, url),
265
+ });
402
266
  if (protectionError) {
403
267
  return makeErrorContext(base, protectionError.status, protectionError.message, token, protectionError.redirectUrl);
404
268
  }
@@ -0,0 +1,41 @@
1
+ export interface ProxyAccessControlLogger {
2
+ debug: (msg: string, extra?: Record<string, unknown>) => void;
3
+ info: (msg: string, extra?: Record<string, unknown>) => void;
4
+ warn: (msg: string, extra?: Record<string, unknown>) => void;
5
+ error: (msg: string, error?: Error, extra?: Record<string, unknown>) => void;
6
+ }
7
+ export interface ProtectedProxyEnvironment {
8
+ name: string;
9
+ protected?: boolean;
10
+ }
11
+ export interface ProtectedProxyProjectUser {
12
+ id: string;
13
+ }
14
+ export interface ProxyAccessError {
15
+ status: number;
16
+ message: string;
17
+ redirectUrl?: string;
18
+ }
19
+ /**
20
+ * Reset the cached AuthProvider. Intended for tests that `register()` a mock
21
+ * after the handler module has been imported.
22
+ *
23
+ * @internal
24
+ */
25
+ export declare function __resetCachedAuthProviderForTests(): void;
26
+ export declare function extractUserIdFromToken(token: string, apiBaseUrl: string, log?: ProxyAccessControlLogger): Promise<string | undefined>;
27
+ export declare function buildProxyAuthRedirectUrl(url: URL): string;
28
+ export declare function isProjectMember(users: ProtectedProxyProjectUser[] | undefined, userId: string | undefined): boolean;
29
+ export declare function checkProtectedProxyAccess(input: {
30
+ req: Request;
31
+ url: URL;
32
+ matchingEnv: ProtectedProxyEnvironment | undefined;
33
+ userToken: string | undefined;
34
+ users: ProtectedProxyProjectUser[] | undefined;
35
+ apiBaseUrl: string;
36
+ logger?: ProxyAccessControlLogger;
37
+ logContext?: Record<string, unknown>;
38
+ isSignedInternalControlPlaneRequest: boolean;
39
+ extractUserIdFromToken?: (token: string, apiBaseUrl: string, log?: ProxyAccessControlLogger) => Promise<string | undefined>;
40
+ }): Promise<ProxyAccessError | null>;
41
+ //# sourceMappingURL=proxy-access-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-access-control.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/proxy-access-control.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA2BD;;;;;GAKG;AACH,wBAAgB,iCAAiC,IAAI,IAAI,CAExD;AAiBD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,wBAAwB,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoD7B;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAa1D;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,yBAAyB,EAAE,GAAG,SAAS,EAC9C,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAGT;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE;IACrD,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,WAAW,EAAE,yBAAyB,GAAG,SAAS,CAAC;IACnD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,yBAAyB,EAAE,GAAG,SAAS,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,mCAAmC,EAAE,OAAO,CAAC;IAC7C,sBAAsB,CAAC,EAAE,CACvB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,wBAAwB,KAC3B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAClC,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA4DnC"}
@@ -0,0 +1,151 @@
1
+ import { getEnv } from "../platform/compat/process.js";
2
+ import { resolve as resolveContract } from "../extensions/contracts.js";
3
+ /**
4
+ * Cache the resolved AuthProvider at module scope so the proxy does not pay
5
+ * the registry lookup on every request. The cache is cleared implicitly when
6
+ * `ExtensionLoader.teardownAll()` clears the registry. The next call re-resolves
7
+ * or surfaces the install hint if the extension was removed.
8
+ */
9
+ let cachedAuthProvider;
10
+ function getAuthProvider() {
11
+ if (cachedAuthProvider)
12
+ return cachedAuthProvider;
13
+ try {
14
+ cachedAuthProvider = resolveContract("AuthProvider");
15
+ return cachedAuthProvider;
16
+ }
17
+ catch (err) {
18
+ const base = err instanceof Error ? err.message : String(err);
19
+ throw new Error(`${base}\nTo enable JWT verification in the proxy, install ext-auth-jwt ` +
20
+ `(scaffold with \`deno task cli extension init ext-auth-jwt\` or add the ` +
21
+ `npm package @veryfront/ext-auth-jwt).`, { cause: err });
22
+ }
23
+ }
24
+ /**
25
+ * Reset the cached AuthProvider. Intended for tests that `register()` a mock
26
+ * after the handler module has been imported.
27
+ *
28
+ * @internal
29
+ */
30
+ export function __resetCachedAuthProviderForTests() {
31
+ cachedAuthProvider = undefined;
32
+ }
33
+ function resolveApiJwksUrl(apiBaseUrl, logger) {
34
+ try {
35
+ const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
36
+ return new URL(".well-known/jwks.json", normalizedBaseUrl).toString();
37
+ }
38
+ catch (error) {
39
+ logger?.error("Invalid API base URL for JWKS lookup", error, {
40
+ apiBaseUrl,
41
+ });
42
+ return undefined;
43
+ }
44
+ }
45
+ export async function extractUserIdFromToken(token, apiBaseUrl, log) {
46
+ const auth = getAuthProvider();
47
+ const header = auth.decode(token);
48
+ if (!header) {
49
+ log?.debug("Failed to decode JWT header");
50
+ return undefined;
51
+ }
52
+ const algorithm = header.alg;
53
+ if (algorithm === "RS256") {
54
+ const jwksUrl = resolveApiJwksUrl(apiBaseUrl, log);
55
+ if (!jwksUrl)
56
+ return undefined;
57
+ try {
58
+ const payload = await auth.verifyWithJwks(token, jwksUrl, {
59
+ algorithms: ["RS256"],
60
+ });
61
+ return payload.userId;
62
+ }
63
+ catch (error) {
64
+ log?.debug("RS256 JWT verification failed", {
65
+ error: error instanceof Error ? error.message : String(error),
66
+ });
67
+ return undefined;
68
+ }
69
+ }
70
+ if (algorithm !== "HS256") {
71
+ log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
72
+ return undefined;
73
+ }
74
+ const jwtSecret = getEnv("JWT_SECRET");
75
+ if (!jwtSecret) {
76
+ log?.warn("JWT_SECRET not configured - cannot verify user token");
77
+ return undefined;
78
+ }
79
+ try {
80
+ // ext-auth-jwt reads JWT_SECRET from the environment when no `secret` was
81
+ // passed to the extension factory; the explicit env check above is kept
82
+ // so callers can warn once before attempting verification.
83
+ const payload = await auth.verify(token, { algorithms: ["HS256"] });
84
+ return payload.userId;
85
+ }
86
+ catch (error) {
87
+ log?.debug("JWT verification failed", {
88
+ error: error instanceof Error ? error.message : String(error),
89
+ });
90
+ return undefined;
91
+ }
92
+ }
93
+ export function buildProxyAuthRedirectUrl(url) {
94
+ const safePath = url.pathname.replace(/^\/\/+/, "/");
95
+ let returnPath = safePath + url.search;
96
+ if (!returnPath.startsWith("/") || returnPath.includes("://")) {
97
+ returnPath = "/";
98
+ }
99
+ const isHostedProductionDeployment = url.hostname.endsWith(".production.veryfront.org") ||
100
+ url.hostname.endsWith(".production.veryfront.com");
101
+ const returnTarget = isHostedProductionDeployment ? url.toString() : returnPath;
102
+ return `https://veryfront.com/sign-in?from=${encodeURIComponent(returnTarget)}`;
103
+ }
104
+ export function isProjectMember(users, userId) {
105
+ if (!users || !userId)
106
+ return false;
107
+ return users.some((u) => u.id === userId);
108
+ }
109
+ export async function checkProtectedProxyAccess(input) {
110
+ const { apiBaseUrl, logger, matchingEnv, url, userToken, users, } = input;
111
+ const logContext = input.logContext ?? {};
112
+ if (!matchingEnv?.protected)
113
+ return null;
114
+ if (input.isSignedInternalControlPlaneRequest) {
115
+ logger?.debug("Allowing signed internal control-plane request through protected environment", {
116
+ ...logContext,
117
+ environmentName: matchingEnv.name,
118
+ pathname: url.pathname,
119
+ });
120
+ return null;
121
+ }
122
+ if (!userToken) {
123
+ const redirectUrl = buildProxyAuthRedirectUrl(url);
124
+ logger?.info("Protected environment requires authentication", {
125
+ ...logContext,
126
+ environmentName: matchingEnv.name,
127
+ redirectUrl,
128
+ });
129
+ return { status: 302, message: "Authentication required", redirectUrl };
130
+ }
131
+ const resolveUserId = input.extractUserIdFromToken ?? extractUserIdFromToken;
132
+ const userId = await resolveUserId(userToken, apiBaseUrl, logger);
133
+ if (!userId) {
134
+ const redirectUrl = buildProxyAuthRedirectUrl(url);
135
+ logger?.info("Could not extract userId from token", {
136
+ ...logContext,
137
+ environmentName: matchingEnv.name,
138
+ redirectUrl,
139
+ });
140
+ return { status: 302, message: "Authentication required", redirectUrl };
141
+ }
142
+ if (!isProjectMember(users, userId)) {
143
+ logger?.info("User is not a member of the project", {
144
+ ...logContext,
145
+ environmentName: matchingEnv.name,
146
+ userId,
147
+ });
148
+ return { status: 403, message: "Access denied" };
149
+ }
150
+ return null;
151
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"chat-composer.d.ts","sourceRoot":"","sources":["../../../../../../../src/src/react/components/chat/chat/composition/chat-composer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,kCAAkC,CAAC;AAI1D,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAEvE,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,IAAI,CAAC;IACjF,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAGlB,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAGxC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAG1C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,4BAA4B;AAC5B,eAAO,MAAM,YAAY,0FAyMxB,CAAC"}
1
+ {"version":3,"file":"chat-composer.d.ts","sourceRoot":"","sources":["../../../../../../../src/src/react/components/chat/chat/composition/chat-composer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,kCAAkC,CAAC;AAI1D,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAEvE,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,IAAI,CAAC;IACjF,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAGlB,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAGxC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAG1C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,4BAA4B;AAC5B,eAAO,MAAM,YAAY,0FA4MxB,CAAC"}
@@ -14,6 +14,8 @@ import { downloadMarkdown } from "../utils/export.js";
14
14
  export const ChatComposer = React.forwardRef(function ChatComposer({ input, onChange, onSubmit, isLoading, placeholder = "Type a message...", theme, stop, onVoice, isListening = false, transcript, models, model, onModelChange, onAttach, onSelectAttachment, attachAccept, attachments, onRemoveAttachment, showExport = false, messages, className, children, }, ref) {
15
15
  const fileInputRef = React.useRef(null);
16
16
  const [attachmentMenuOpen, setAttachmentMenuOpen] = React.useState(false);
17
+ const inputPlaceholder = isListening ? "Listening..." : placeholder;
18
+ const inputLabel = inputPlaceholder || "Message";
17
19
  return (React.createElement("div", { ref: ref, className: cn("flex-shrink-0 pb-6 pt-2", className) },
18
20
  React.createElement("div", { className: "mx-auto w-full max-w-3xl px-4" },
19
21
  children && (React.createElement("div", { className: "flex flex-wrap items-center gap-1.5 pb-3" }, children)),
@@ -57,7 +59,7 @@ export const ChatComposer = React.forwardRef(function ChatComposer({ input, onCh
57
59
  setAttachmentMenuOpen(false);
58
60
  onSelectAttachment();
59
61
  } }, "Select document")))))),
60
- React.createElement(InputBox, { value: isListening ? transcript || input : input, onChange: onChange, onSubmit: () => onSubmit?.(), placeholder: isListening ? "Listening..." : placeholder, disabled: isLoading || isListening, multiline: true, className: cn("min-h-9 min-w-0 flex-1 py-2 text-[15px] leading-normal text-[var(--foreground)] placeholder:text-[var(--input-placeholder)]", theme?.input) }),
62
+ React.createElement(InputBox, { value: isListening ? transcript || input : input, onChange: onChange, onSubmit: () => onSubmit?.(), placeholder: inputPlaceholder, "aria-label": inputLabel, disabled: isLoading || isListening, multiline: true, className: cn("min-h-9 min-w-0 flex-1 py-2 text-[15px] leading-normal text-[var(--foreground)] placeholder:text-[var(--input-placeholder)]", theme?.input) }),
61
63
  React.createElement("div", { className: "flex items-center gap-2" },
62
64
  models && models.length > 0 && onModelChange && (React.createElement(ModelSelector, { models: models, value: model, onChange: onModelChange, disabled: isLoading })),
63
65
  showExport && messages && messages.length > 0 && (React.createElement("button", { type: "button", onClick: () => downloadMarkdown(messages), className: "size-9 flex items-center justify-center rounded-full text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:bg-[var(--foreground)]/5 transition-colors shrink-0", "aria-label": "Export conversation", title: "Export as Markdown" },
@@ -23,11 +23,14 @@ export interface CacheCoordinatorOptions {
23
23
  */
24
24
  contentSourceId?: string;
25
25
  }
26
+ export type CacheLookupStatus = "hit" | "miss" | "expired";
26
27
  export interface CacheLookupResult {
27
28
  cachedResult?: RenderResult;
28
29
  depAwareSlug: string;
29
30
  moduleCacheKey: string;
30
31
  cachedModule?: RenderResult["pageModule"];
32
+ cacheStatus: CacheLookupStatus;
33
+ lookupDurationMs: number;
31
34
  }
32
35
  export declare class CacheCoordinator {
33
36
  private store;
@@ -1 +1 @@
1
- {"version":3,"file":"cache-coordinator.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/cache/cache-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAMnF,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;CAC3C;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,OAAO,GAAE,uBAA4B;IAyBjD;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4BvE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B7E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5C;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,aAAa;CAkBtB"}
1
+ {"version":3,"file":"cache-coordinator.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/cache/cache-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAQnF,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;IAC1C,WAAW,EAAE,iBAAiB,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,OAAO,GAAE,uBAA4B;IA0BjD;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0CvE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B7E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5C;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,aAAa;CAkBtB"}
@@ -1,6 +1,8 @@
1
1
  import { rendererLogger as logger } from "../../utils/index.js";
2
2
  import { MemoryCacheStore } from "./stores/index.js";
3
3
  import { withSpan } from "../../observability/tracing/otlp-setup.js";
4
+ import { markRequestProfilePhase } from "../../observability/request-profiler.js";
5
+ import { metrics } from "../../observability/simple-metrics/index.js";
4
6
  /** Default TTL for cache entries (5 minutes) */
5
7
  const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1_000;
6
8
  export class CacheCoordinator {
@@ -27,6 +29,7 @@ export class CacheCoordinator {
27
29
  new MemoryCacheStore({
28
30
  maxEntries: options.memory?.maxEntries,
29
31
  ttlMs: options.memory?.ttlMs ?? this.ttlMs,
32
+ enforceStoreTtl: false,
30
33
  });
31
34
  }
32
35
  /**
@@ -41,19 +44,33 @@ export class CacheCoordinator {
41
44
  checkCache(slug, cacheKey) {
42
45
  const key = this.buildCacheKey(slug, cacheKey);
43
46
  return withSpan("cache.checkCache", async () => {
47
+ const lookupStart = performance.now();
44
48
  const cached = await this.store.get(key);
45
49
  if (!cached) {
46
- return { depAwareSlug: slug, moduleCacheKey: key };
50
+ const lookupDurationMs = roundDurationMs(performance.now() - lookupStart);
51
+ recordCacheLookup("miss", lookupDurationMs);
52
+ return { depAwareSlug: slug, moduleCacheKey: key, cacheStatus: "miss", lookupDurationMs };
47
53
  }
48
54
  if (this.isExpired(cached)) {
49
55
  await this.store.delete(key);
50
- return { depAwareSlug: slug, moduleCacheKey: key };
56
+ const lookupDurationMs = roundDurationMs(performance.now() - lookupStart);
57
+ recordCacheLookup("expired", lookupDurationMs);
58
+ return {
59
+ depAwareSlug: slug,
60
+ moduleCacheKey: key,
61
+ cacheStatus: "expired",
62
+ lookupDurationMs,
63
+ };
51
64
  }
65
+ const lookupDurationMs = roundDurationMs(performance.now() - lookupStart);
66
+ recordCacheLookup("hit", lookupDurationMs);
52
67
  return {
53
68
  cachedResult: this.hydrateResult(cached),
54
69
  depAwareSlug: slug,
55
70
  moduleCacheKey: key,
56
71
  cachedModule: cached.result.pageModule,
72
+ cacheStatus: "hit",
73
+ lookupDurationMs,
57
74
  };
58
75
  }, { "cache.slug": slug, "cache.key": key, "cache.projectId": this.projectId ?? "unknown" });
59
76
  }
@@ -128,3 +145,11 @@ export class CacheCoordinator {
128
145
  };
129
146
  }
130
147
  }
148
+ function roundDurationMs(value) {
149
+ return Math.round(value * 100) / 100;
150
+ }
151
+ function recordCacheLookup(status, durationMs) {
152
+ markRequestProfilePhase("render.cache_lookup", durationMs);
153
+ markRequestProfilePhase(`render.cache_${status}`);
154
+ metrics.recordCacheGet(status === "hit");
155
+ }
@@ -10,6 +10,8 @@ interface CacheLike<K, V> {
10
10
  export interface MemoryCacheStoreOptions {
11
11
  maxEntries?: number;
12
12
  ttlMs?: number;
13
+ /** Disable store-level TTL when callers classify payload expiration themselves. */
14
+ enforceStoreTtl?: boolean;
13
15
  /** Optional cache implementation for testing */
14
16
  cache?: CacheLike<string, CachePayload>;
15
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/cache/stores/memory-store.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI7E,UAAU,SAAS,CAAC,CAAC,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACrB,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACzC;AAED,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,KAAK,CAAkC;gBAEnC,OAAO,GAAE,uBAA4B;IAajD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAInD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,IAAI,IAAI,MAAM;IAMd,QAAQ,IAAI,eAAe;CAG5B"}
1
+ {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/cache/stores/memory-store.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI7E,UAAU,SAAS,CAAC,CAAC,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACrB,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACzC;AAED,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,KAAK,CAAkC;gBAEnC,OAAO,GAAE,uBAA4B;IAcjD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAInD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,IAAI,IAAI,MAAM;IAMd,QAAQ,IAAI,eAAe;CAG5B"}
@@ -18,9 +18,10 @@ export class MemoryCacheStore {
18
18
  return;
19
19
  }
20
20
  const disableIntervals = isLruIntervalDisabled();
21
+ const enforceStoreTtl = options.enforceStoreTtl ?? true;
21
22
  this.cache = new LRUCache({
22
23
  maxEntries: options.maxEntries ?? DEFAULT_MAX_ENTRIES,
23
- ttlMs: disableIntervals ? undefined : options.ttlMs,
24
+ ttlMs: disableIntervals || !enforceStoreTtl ? undefined : options.ttlMs,
24
25
  });
25
26
  }
26
27
  get(key) {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AASrD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AASxD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,oBAAoB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,gBAAgB,CAAC;CACjE;AAED,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,cAAc,EAAE,mBAAmB,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,eAAe,EAAE,eAAe,CAAC;IACjC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,eAAe,CAAC,CAAgD;gBAE5D,OAAO,EAAE,gBAAgB;IAS/B,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAqJ7C,gBAAgB,CACd,UAAU,EAAE,CACV,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,SAAS,CAAC,GACtB,IAAI;IAcP,WAAW,IAAI,gBAAgB;IAazB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3C,cAAc,IAAI,IAAI;IAWtB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAS5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AASrD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AASxD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,oBAAoB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,gBAAgB,CAAC;CACjE;AAED,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,cAAc,EAAE,mBAAmB,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,eAAe,EAAE,eAAe,CAAC;IACjC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,eAAe,CAAC,CAAgD;gBAE5D,OAAO,EAAE,gBAAgB;IAS/B,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAsJ7C,gBAAgB,CACd,UAAU,EAAE,CACV,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,SAAS,CAAC,GACtB,IAAI;IAcP,WAAW,IAAI,gBAAgB;IAazB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3C,cAAc,IAAI,IAAI;IAWtB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAS5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -82,6 +82,7 @@ export class RendererLifecycle {
82
82
  maxEntries: renderCacheConfig.maxEntries ??
83
83
  (debugMode ? DEBUG_MODE_MAX_ENTRIES : PRODUCTION_MAX_ENTRIES),
84
84
  ttlMs: renderCacheConfig.ttl,
85
+ enforceStoreTtl: false,
85
86
  });
86
87
  break;
87
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAsBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA4ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AA0BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;YAIJ,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAiGjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA+P9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAqFzE,kBAAkB;YA2ClB,cAAc;IAW5B,OAAO,CAAC,uBAAuB;YAYjB,kBAAkB;IA8EhC,OAAO,CAAC,kBAAkB;YAMZ,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAsBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AA0BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;YAIJ,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAiGjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA8O9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAqFzE,kBAAkB;YA2ClB,cAAc;IAW5B,OAAO,CAAC,uBAAuB;YAYjB,kBAAkB;IA8EhC,OAAO,CAAC,kBAAkB;YAMZ,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}