veryfront 0.1.764 → 0.1.765

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 (59) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/cache/keys/builders/render.d.ts +8 -1
  3. package/esm/src/cache/keys/builders/render.d.ts.map +1 -1
  4. package/esm/src/cache/keys/builders/render.js +10 -2
  5. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  6. package/esm/src/html/html-shell-generator.js +24 -4
  7. package/esm/src/html/schemas/html.schema.d.ts +2 -0
  8. package/esm/src/html/schemas/html.schema.d.ts.map +1 -1
  9. package/esm/src/html/schemas/html.schema.js +1 -0
  10. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
  11. package/esm/src/platform/adapters/fs/veryfront/adapter.js +31 -0
  12. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +31 -0
  13. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
  14. package/esm/src/platform/adapters/veryfront-api-client/client.js +21 -0
  15. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +6 -1
  16. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
  17. package/esm/src/platform/adapters/veryfront-api-client/operations.js +59 -1
  18. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +22 -0
  19. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
  20. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +39 -0
  21. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts +1 -1
  22. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts.map +1 -1
  23. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.js +1 -1
  24. package/esm/src/proxy/asset-handler.d.ts +30 -0
  25. package/esm/src/proxy/asset-handler.d.ts.map +1 -0
  26. package/esm/src/proxy/asset-handler.js +108 -0
  27. package/esm/src/proxy/main.js +4 -0
  28. package/esm/src/release-assets/build-executor.d.ts +89 -0
  29. package/esm/src/release-assets/build-executor.d.ts.map +1 -0
  30. package/esm/src/release-assets/build-executor.js +405 -0
  31. package/esm/src/release-assets/constants.d.ts +35 -0
  32. package/esm/src/release-assets/constants.d.ts.map +1 -0
  33. package/esm/src/release-assets/constants.js +50 -0
  34. package/esm/src/release-assets/hash.d.ts +14 -0
  35. package/esm/src/release-assets/hash.d.ts.map +1 -0
  36. package/esm/src/release-assets/hash.js +22 -0
  37. package/esm/src/release-assets/html-consumption.d.ts +31 -0
  38. package/esm/src/release-assets/html-consumption.d.ts.map +1 -0
  39. package/esm/src/release-assets/html-consumption.js +64 -0
  40. package/esm/src/release-assets/manifest-cache.d.ts +70 -0
  41. package/esm/src/release-assets/manifest-cache.d.ts.map +1 -0
  42. package/esm/src/release-assets/manifest-cache.js +178 -0
  43. package/esm/src/release-assets/manifest-schema.d.ts +70 -0
  44. package/esm/src/release-assets/manifest-schema.d.ts.map +1 -0
  45. package/esm/src/release-assets/manifest-schema.js +126 -0
  46. package/esm/src/rendering/context/render-context.d.ts.map +1 -1
  47. package/esm/src/rendering/context/render-context.js +3 -1
  48. package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
  49. package/esm/src/rendering/orchestrator/html.js +16 -0
  50. package/esm/src/rendering/orchestrator/types.d.ts +2 -0
  51. package/esm/src/rendering/orchestrator/types.d.ts.map +1 -1
  52. package/esm/src/server/context/enriched-context.d.ts.map +1 -1
  53. package/esm/src/server/context/enriched-context.js +2 -1
  54. package/esm/src/server/handlers/request/project-run-execute.handler.d.ts +5 -0
  55. package/esm/src/server/handlers/request/project-run-execute.handler.d.ts.map +1 -1
  56. package/esm/src/server/handlers/request/project-run-execute.handler.js +86 -3
  57. package/esm/src/utils/version-constant.d.ts +1 -1
  58. package/esm/src/utils/version-constant.js +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Release Asset Manifest — shared constants.
3
+ *
4
+ * @module release-assets/constants
5
+ */
6
+ /** Current manifest body schema version. */
7
+ export declare const RELEASE_ASSET_MANIFEST_SCHEMA_VERSION: 1;
8
+ /** Public asset base path served on the project's own domain (proxy-owned). */
9
+ export declare const RELEASE_ASSET_BASE_PATH: "/_vf/assets";
10
+ /** Content types permitted for release assets. */
11
+ export declare const RELEASE_ASSET_CONTENT_TYPES: {
12
+ readonly js: "text/javascript";
13
+ readonly css: "text/css";
14
+ };
15
+ export type ReleaseAssetExtension = keyof typeof RELEASE_ASSET_CONTENT_TYPES;
16
+ export type ReleaseAssetContentType = (typeof RELEASE_ASSET_CONTENT_TYPES)[ReleaseAssetExtension];
17
+ /** Allowlist of accepted content types (upstream + upload validation). */
18
+ export declare const RELEASE_ASSET_CONTENT_TYPE_ALLOWLIST: readonly ReleaseAssetContentType[];
19
+ /** Maximum size (bytes) for a single uploaded asset (10 MB). */
20
+ export declare const RELEASE_ASSET_MAX_SIZE_BYTES: number;
21
+ /** Immutable cache max-age in seconds (1 year). */
22
+ export declare const RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS = 31536000;
23
+ /** Bounded upload concurrency when posting assets during a build. */
24
+ export declare const RELEASE_ASSET_UPLOAD_CONCURRENCY = 8;
25
+ /** Env flag that enables HTML manifest consumption in production (default OFF). */
26
+ export declare const RELEASE_ASSET_MANIFEST_ENV_FLAG = "VERYFRONT_RELEASE_ASSET_MANIFEST";
27
+ /** Map a 64-hex content hash + extension to its public asset URL. */
28
+ export declare function releaseAssetUrl(contentHash: string, extension: ReleaseAssetExtension): string;
29
+ /** Resolve the content type for an extension, or null if not allowed. */
30
+ export declare function contentTypeForExtension(extension: string): ReleaseAssetContentType | null;
31
+ /** True when the value is a valid allowlisted release asset content type. */
32
+ export declare function isAllowedReleaseAssetContentType(value: string | null | undefined): value is ReleaseAssetContentType;
33
+ /** Validate a content hash is exactly 64 lowercase hex characters. */
34
+ export declare function isValidContentHash(hash: string): boolean;
35
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,qCAAqC,EAAG,CAAU,CAAC;AAEhE,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,EAAG,aAAsB,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,2BAA2B;;;CAG9B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,MAAM,OAAO,2BAA2B,CAAC;AAC7E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,qBAAqB,CAAC,CAAC;AAElG,0EAA0E;AAC1E,eAAO,MAAM,oCAAoC,EAAE,SAAS,uBAAuB,EAGlF,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAE7D,mDAAmD;AACnD,eAAO,MAAM,uCAAuC,WAAa,CAAC;AAElE,qEAAqE;AACrE,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,mFAAmF;AACnF,eAAO,MAAM,+BAA+B,qCAAqC,CAAC;AAElF,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,qBAAqB,GAAG,MAAM,CAE7F;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB,uBAAuB,GAAG,IAAI,CAIhC;AAED,6EAA6E;AAC7E,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,KAAK,IAAI,uBAAuB,CAIlC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Release Asset Manifest — shared constants.
3
+ *
4
+ * @module release-assets/constants
5
+ */
6
+ /** Current manifest body schema version. */
7
+ export const RELEASE_ASSET_MANIFEST_SCHEMA_VERSION = 1;
8
+ /** Public asset base path served on the project's own domain (proxy-owned). */
9
+ export const RELEASE_ASSET_BASE_PATH = "/_vf/assets";
10
+ /** Content types permitted for release assets. */
11
+ export const RELEASE_ASSET_CONTENT_TYPES = {
12
+ js: "text/javascript",
13
+ css: "text/css",
14
+ };
15
+ /** Allowlist of accepted content types (upstream + upload validation). */
16
+ export const RELEASE_ASSET_CONTENT_TYPE_ALLOWLIST = [
17
+ RELEASE_ASSET_CONTENT_TYPES.js,
18
+ RELEASE_ASSET_CONTENT_TYPES.css,
19
+ ];
20
+ /** Maximum size (bytes) for a single uploaded asset (10 MB). */
21
+ export const RELEASE_ASSET_MAX_SIZE_BYTES = 10 * 1024 * 1024;
22
+ /** Immutable cache max-age in seconds (1 year). */
23
+ export const RELEASE_ASSET_IMMUTABLE_MAX_AGE_SECONDS = 31_536_000;
24
+ /** Bounded upload concurrency when posting assets during a build. */
25
+ export const RELEASE_ASSET_UPLOAD_CONCURRENCY = 8;
26
+ /** Env flag that enables HTML manifest consumption in production (default OFF). */
27
+ export const RELEASE_ASSET_MANIFEST_ENV_FLAG = "VERYFRONT_RELEASE_ASSET_MANIFEST";
28
+ /** Map a 64-hex content hash + extension to its public asset URL. */
29
+ export function releaseAssetUrl(contentHash, extension) {
30
+ return `${RELEASE_ASSET_BASE_PATH}/${contentHash}.${extension}`;
31
+ }
32
+ /** Resolve the content type for an extension, or null if not allowed. */
33
+ export function contentTypeForExtension(extension) {
34
+ if (extension === "js")
35
+ return RELEASE_ASSET_CONTENT_TYPES.js;
36
+ if (extension === "css")
37
+ return RELEASE_ASSET_CONTENT_TYPES.css;
38
+ return null;
39
+ }
40
+ /** True when the value is a valid allowlisted release asset content type. */
41
+ export function isAllowedReleaseAssetContentType(value) {
42
+ if (!value)
43
+ return false;
44
+ const base = value.split(";")[0]?.trim();
45
+ return RELEASE_ASSET_CONTENT_TYPE_ALLOWLIST.some((allowed) => allowed === base);
46
+ }
47
+ /** Validate a content hash is exactly 64 lowercase hex characters. */
48
+ export function isValidContentHash(hash) {
49
+ return /^[0-9a-f]{64}$/.test(hash);
50
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Release Asset Manifest — SHA-256 helper.
3
+ *
4
+ * Produces lowercase hex SHA-256 digests of raw asset bytes using the Web
5
+ * Crypto API (`crypto.subtle`), matching the content-addressing contract.
6
+ *
7
+ * @module release-assets/hash
8
+ */
9
+ import * as dntShim from "../../_dnt.shims.js";
10
+ /** Compute the lowercase hex SHA-256 digest of raw bytes. */
11
+ export declare function sha256HexBytes(bytes: dntShim.BufferSource): Promise<string>;
12
+ /** Compute the lowercase hex SHA-256 digest of a UTF-8 string. */
13
+ export declare function sha256Hex(input: string): Promise<string>;
14
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAO/C,6DAA6D;AAC7D,wBAAsB,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAGjF;AAED,kEAAkE;AAClE,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG9D"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Release Asset Manifest — SHA-256 helper.
3
+ *
4
+ * Produces lowercase hex SHA-256 digests of raw asset bytes using the Web
5
+ * Crypto API (`crypto.subtle`), matching the content-addressing contract.
6
+ *
7
+ * @module release-assets/hash
8
+ */
9
+ import * as dntShim from "../../_dnt.shims.js";
10
+ function toHex(buffer) {
11
+ return Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, "0")).join("");
12
+ }
13
+ /** Compute the lowercase hex SHA-256 digest of raw bytes. */
14
+ export async function sha256HexBytes(bytes) {
15
+ const digest = await dntShim.crypto.subtle.digest("SHA-256", bytes);
16
+ return toHex(digest);
17
+ }
18
+ /** Compute the lowercase hex SHA-256 digest of a UTF-8 string. */
19
+ export async function sha256Hex(input) {
20
+ const digest = await dntShim.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
21
+ return toHex(digest);
22
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Release Asset Manifest — HTML consumption helpers.
3
+ *
4
+ * Pure helpers used by the HTML shell generator to rewrite module URLs and
5
+ * preload hints to content-addressed `/_vf/assets/{hash}.js` URLs when a ready
6
+ * manifest covers the entry. Misses fall back to the existing URL (per-entry)
7
+ * and are counted via structured debug logs.
8
+ *
9
+ * @module release-assets/html-consumption
10
+ */
11
+ import type { ReleaseAssetManifest } from "./manifest-schema.js";
12
+ /**
13
+ * Normalize a logical module path to the manifest's key convention.
14
+ *
15
+ * The HTML shell works with relative source paths like `pages/index.tsx` and
16
+ * `/_vf_modules/pages/index.js` URLs. Manifest module keys use the logical
17
+ * source path (e.g. `pages/index.tsx`). This strips a leading `/_vf_modules/`
18
+ * and any `.js` URL extension so both forms resolve.
19
+ */
20
+ export declare function normalizeManifestModuleKey(path: string): string;
21
+ /**
22
+ * Resolve a module URL through the manifest.
23
+ *
24
+ * Returns the content-addressed asset URL on a hit, or null on a miss (caller
25
+ * keeps the existing URL). The manifest is consulted by both the logical key
26
+ * and its `.js`-stripped form to tolerate either input shape.
27
+ */
28
+ export declare function resolveManifestModuleUrl(manifest: ReleaseAssetManifest, logicalPath: string): string | null;
29
+ /** Resolve the route closure module URLs for preload hints from the manifest. */
30
+ export declare function resolveManifestRoutePreloadUrls(manifest: ReleaseAssetManifest, route: string): string[];
31
+ //# sourceMappingURL=html-consumption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-consumption.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/html-consumption.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAIjE;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,oBAAoB,EAC9B,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAcf;AAED,iFAAiF;AACjF,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,oBAAoB,EAC9B,KAAK,EAAE,MAAM,GACZ,MAAM,EAAE,CAcV"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Release Asset Manifest — HTML consumption helpers.
3
+ *
4
+ * Pure helpers used by the HTML shell generator to rewrite module URLs and
5
+ * preload hints to content-addressed `/_vf/assets/{hash}.js` URLs when a ready
6
+ * manifest covers the entry. Misses fall back to the existing URL (per-entry)
7
+ * and are counted via structured debug logs.
8
+ *
9
+ * @module release-assets/html-consumption
10
+ */
11
+ import { serverLogger } from "../utils/index.js";
12
+ import { releaseAssetUrl } from "./constants.js";
13
+ const logger = serverLogger.component("release-asset-consume");
14
+ /**
15
+ * Normalize a logical module path to the manifest's key convention.
16
+ *
17
+ * The HTML shell works with relative source paths like `pages/index.tsx` and
18
+ * `/_vf_modules/pages/index.js` URLs. Manifest module keys use the logical
19
+ * source path (e.g. `pages/index.tsx`). This strips a leading `/_vf_modules/`
20
+ * and any `.js` URL extension so both forms resolve.
21
+ */
22
+ export function normalizeManifestModuleKey(path) {
23
+ let key = path.replace(/^\/?_vf_modules\//, "");
24
+ key = key.replace(/^\/+/, "");
25
+ return key;
26
+ }
27
+ /**
28
+ * Resolve a module URL through the manifest.
29
+ *
30
+ * Returns the content-addressed asset URL on a hit, or null on a miss (caller
31
+ * keeps the existing URL). The manifest is consulted by both the logical key
32
+ * and its `.js`-stripped form to tolerate either input shape.
33
+ */
34
+ export function resolveManifestModuleUrl(manifest, logicalPath) {
35
+ const key = normalizeManifestModuleKey(logicalPath);
36
+ const direct = manifest.modules[key];
37
+ if (direct)
38
+ return releaseAssetUrl(direct.contentHash, "js");
39
+ // Tolerate keys that differ only by extension (e.g. ".js" vs source ext).
40
+ const withoutExt = key.replace(/\.(tsx|ts|jsx|mdx|js)$/, "");
41
+ for (const candidateExt of [".tsx", ".ts", ".jsx", ".mdx", ".js"]) {
42
+ const candidate = manifest.modules[withoutExt + candidateExt];
43
+ if (candidate)
44
+ return releaseAssetUrl(candidate.contentHash, "js");
45
+ }
46
+ logger.debug("manifest module miss", { key });
47
+ return null;
48
+ }
49
+ /** Resolve the route closure module URLs for preload hints from the manifest. */
50
+ export function resolveManifestRoutePreloadUrls(manifest, route) {
51
+ const entry = manifest.routes[route] ?? manifest.routes[`/${route}`] ??
52
+ manifest.routes[route.replace(/^\//, "")];
53
+ if (!entry) {
54
+ logger.debug("manifest route miss", { route });
55
+ return [];
56
+ }
57
+ const urls = [];
58
+ for (const modulePath of entry.modules) {
59
+ const asset = manifest.modules[modulePath];
60
+ if (asset)
61
+ urls.push(releaseAssetUrl(asset.contentHash, "js"));
62
+ }
63
+ return urls;
64
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Release Asset Manifest — in-process consumption cache (production HTML).
3
+ *
4
+ * Fetches manifests from the project-scoped GET endpoint once per release and
5
+ * caches them keyed by `${releaseId}:${manifestVersion}`. Ready manifests are
6
+ * cached for a bounded TTL (15 min) so superseded manifests are eventually
7
+ * replaced; non-ready / missing results are cached for a short TTL so the
8
+ * common "no manifest" case stays cheap.
9
+ *
10
+ * Consumption is gated by `VERYFRONT_RELEASE_ASSET_MANIFEST=1` (default OFF).
11
+ * When the flag is off, `getReadyManifestForRender` always returns null so the
12
+ * HTML output is byte-identical to today.
13
+ *
14
+ * The HTML shell is synchronous, so reads are non-blocking: a cache miss kicks
15
+ * off a background fetch and returns null for the current render. Subsequent
16
+ * renders for the same release pick up the cached result.
17
+ *
18
+ * Multi-tenancy: each releaseId is served by the fetcher registered for that
19
+ * specific releaseId (the adapter that owns it). There is no cross-project
20
+ * token reuse. If no per-releaseId fetcher is registered, the call returns null
21
+ * (byte-identical JIT fallback).
22
+ *
23
+ * @module release-assets/manifest-cache
24
+ */
25
+ import { type ReleaseAssetManifest } from "./manifest-schema.js";
26
+ /**
27
+ * Fetcher used to retrieve a manifest for a release. Registered per-releaseId
28
+ * by the runtime adapter that owns that release, so the correct project-scoped
29
+ * token is always used. Returns null when the manifest is unavailable.
30
+ */
31
+ export interface ReleaseAssetManifestFetcher {
32
+ (releaseId: string): Promise<{
33
+ state: string;
34
+ manifest: ReleaseAssetManifest | null;
35
+ } | null>;
36
+ }
37
+ /**
38
+ * Register a project-scoped manifest fetcher for the given releaseId.
39
+ *
40
+ * Called by the FS adapter when its content context is set to a release.
41
+ * Overwrites any previous registration for the same releaseId (safe — the
42
+ * latest adapter for a release is the authoritative owner).
43
+ */
44
+ export declare function registerManifestFetcherForRelease(releaseId: string, fetcher: ReleaseAssetManifestFetcher): void;
45
+ /**
46
+ * Remove the manifest fetcher for the given releaseId.
47
+ *
48
+ * Called when an adapter transitions away from a release context.
49
+ */
50
+ export declare function unregisterManifestFetcherForRelease(releaseId: string): void;
51
+ /**
52
+ * Register a single global fetcher (for tests / simple single-project setups).
53
+ *
54
+ * In production multi-tenant mode prefer `registerManifestFetcherForRelease`.
55
+ * Passing `undefined` clears the global fallback.
56
+ */
57
+ export declare function configureReleaseAssetManifestFetcher(fetcher: ReleaseAssetManifestFetcher | undefined): void;
58
+ /** True when production manifest consumption is enabled via env flag. */
59
+ export declare function isReleaseAssetManifestEnabled(): boolean;
60
+ /**
61
+ * Return a ready manifest for `releaseId` if one is cached, else null.
62
+ *
63
+ * Non-blocking: on a cache miss (or expired entry) it schedules a background
64
+ * fetch and returns null for the current render. Returns null immediately when
65
+ * the flag is off or no fetcher is registered for this releaseId.
66
+ */
67
+ export declare function getReadyManifestForRender(releaseId: string | null | undefined): ReleaseAssetManifest | null;
68
+ /** Clear the cache (deployment / memory pressure / tests). */
69
+ export declare function clearReleaseAssetManifestCache(): void;
70
+ //# sourceMappingURL=manifest-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/manifest-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,OAAO,EAA6B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAuB5F;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC1B;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAChE,CAAC;CACH;AAKD;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,2BAA2B,GAAG,SAAS,GAC/C,IAAI,CAMN;AASD,yEAAyE;AACzE,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD;AAOD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,oBAAoB,GAAG,IAAI,CA+B7B;AA+CD,8DAA8D;AAC9D,wBAAgB,8BAA8B,IAAI,IAAI,CAIrD"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Release Asset Manifest — in-process consumption cache (production HTML).
3
+ *
4
+ * Fetches manifests from the project-scoped GET endpoint once per release and
5
+ * caches them keyed by `${releaseId}:${manifestVersion}`. Ready manifests are
6
+ * cached for a bounded TTL (15 min) so superseded manifests are eventually
7
+ * replaced; non-ready / missing results are cached for a short TTL so the
8
+ * common "no manifest" case stays cheap.
9
+ *
10
+ * Consumption is gated by `VERYFRONT_RELEASE_ASSET_MANIFEST=1` (default OFF).
11
+ * When the flag is off, `getReadyManifestForRender` always returns null so the
12
+ * HTML output is byte-identical to today.
13
+ *
14
+ * The HTML shell is synchronous, so reads are non-blocking: a cache miss kicks
15
+ * off a background fetch and returns null for the current render. Subsequent
16
+ * renders for the same release pick up the cached result.
17
+ *
18
+ * Multi-tenancy: each releaseId is served by the fetcher registered for that
19
+ * specific releaseId (the adapter that owns it). There is no cross-project
20
+ * token reuse. If no per-releaseId fetcher is registered, the call returns null
21
+ * (byte-identical JIT fallback).
22
+ *
23
+ * @module release-assets/manifest-cache
24
+ */
25
+ import { serverLogger } from "../utils/index.js";
26
+ import { LRUCache } from "../utils/lru-wrapper.js";
27
+ import { registerLRUCache } from "../cache/index.js";
28
+ import { getEnv } from "../platform/compat/process.js";
29
+ import { RELEASE_ASSET_MANIFEST_ENV_FLAG } from "./constants.js";
30
+ import { parseReleaseAssetManifest } from "./manifest-schema.js";
31
+ const logger = serverLogger.component("release-asset-manifest");
32
+ /** Bound on cached manifests (per releaseId:manifestVersion). */
33
+ const MAX_CACHED_MANIFESTS = 500;
34
+ /** Short TTL for non-ready / missing results (ms). */
35
+ const NON_READY_TTL_MS = 30_000;
36
+ /** TTL for ready manifests — long but finite so superseded entries are picked up (15 min). */
37
+ const READY_TTL_MS = 15 * 60 * 1000;
38
+ const manifestCache = new LRUCache({ maxEntries: MAX_CACHED_MANIFESTS });
39
+ registerLRUCache("release-asset-manifest-cache", manifestCache);
40
+ /** In-flight fetches, deduped per releaseId. */
41
+ const inFlight = new Map();
42
+ /** Per-releaseId fetcher registry (keyed by releaseId). */
43
+ const fetcherRegistry = new Map();
44
+ /**
45
+ * Register a project-scoped manifest fetcher for the given releaseId.
46
+ *
47
+ * Called by the FS adapter when its content context is set to a release.
48
+ * Overwrites any previous registration for the same releaseId (safe — the
49
+ * latest adapter for a release is the authoritative owner).
50
+ */
51
+ export function registerManifestFetcherForRelease(releaseId, fetcher) {
52
+ fetcherRegistry.set(releaseId, fetcher);
53
+ }
54
+ /**
55
+ * Remove the manifest fetcher for the given releaseId.
56
+ *
57
+ * Called when an adapter transitions away from a release context.
58
+ */
59
+ export function unregisterManifestFetcherForRelease(releaseId) {
60
+ fetcherRegistry.delete(releaseId);
61
+ }
62
+ /**
63
+ * Register a single global fetcher (for tests / simple single-project setups).
64
+ *
65
+ * In production multi-tenant mode prefer `registerManifestFetcherForRelease`.
66
+ * Passing `undefined` clears the global fallback.
67
+ */
68
+ export function configureReleaseAssetManifestFetcher(fetcher) {
69
+ if (fetcher) {
70
+ fetcherRegistry.set("*", fetcher);
71
+ }
72
+ else {
73
+ fetcherRegistry.delete("*");
74
+ }
75
+ }
76
+ /** Resolve the fetcher for a releaseId: prefer per-releaseId, then global fallback. */
77
+ function resolveFetcher(releaseId) {
78
+ return fetcherRegistry.get(releaseId) ?? fetcherRegistry.get("*");
79
+ }
80
+ /** True when production manifest consumption is enabled via env flag. */
81
+ export function isReleaseAssetManifestEnabled() {
82
+ return getEnv(RELEASE_ASSET_MANIFEST_ENV_FLAG) === "1";
83
+ }
84
+ /** Build the cache key from releaseId + the latest known manifestVersion. */
85
+ function cacheKey(releaseId, manifestVersion) {
86
+ return manifestVersion !== undefined ? `${releaseId}:${manifestVersion}` : releaseId;
87
+ }
88
+ /**
89
+ * Return a ready manifest for `releaseId` if one is cached, else null.
90
+ *
91
+ * Non-blocking: on a cache miss (or expired entry) it schedules a background
92
+ * fetch and returns null for the current render. Returns null immediately when
93
+ * the flag is off or no fetcher is registered for this releaseId.
94
+ */
95
+ export function getReadyManifestForRender(releaseId) {
96
+ if (!releaseId)
97
+ return null;
98
+ if (!isReleaseAssetManifestEnabled())
99
+ return null;
100
+ if (!resolveFetcher(releaseId))
101
+ return null;
102
+ // Look up the most recent cached entry for this release. We do a prefix scan
103
+ // of the LRU to find any `releaseId:*` entry; if we find a live ready one we
104
+ // return it without scheduling another fetch.
105
+ let best = null;
106
+ for (const [k, v] of manifestCache.entries()) {
107
+ if (k !== releaseId && !k.startsWith(`${releaseId}:`))
108
+ continue;
109
+ if (v.expiresAt > Date.now() && v.manifest) {
110
+ // Prefer the entry with the highest manifest version.
111
+ const existingVersion = best?.manifest?.manifestVersion ?? -1;
112
+ if ((v.manifest.manifestVersion ?? 0) > existingVersion) {
113
+ best = v;
114
+ }
115
+ }
116
+ }
117
+ if (best)
118
+ return best.manifest;
119
+ // Also check the plain releaseId slot (non-ready / null entry).
120
+ const plain = manifestCache.get(releaseId);
121
+ if (plain && plain.expiresAt > Date.now()) {
122
+ // Non-ready entry still warm — return null without scheduling another fetch.
123
+ return null;
124
+ }
125
+ scheduleFetch(releaseId);
126
+ return null;
127
+ }
128
+ function scheduleFetch(releaseId) {
129
+ if (inFlight.has(releaseId))
130
+ return;
131
+ const active = resolveFetcher(releaseId);
132
+ if (!active)
133
+ return;
134
+ const promise = (async () => {
135
+ try {
136
+ const result = await active(releaseId);
137
+ if (!result) {
138
+ manifestCache.set(releaseId, { manifest: null, expiresAt: Date.now() + NON_READY_TTL_MS });
139
+ return;
140
+ }
141
+ const manifest = result.state === "ready" && result.manifest
142
+ ? parseReleaseAssetManifest(result.manifest)
143
+ : null;
144
+ if (manifest) {
145
+ const key = cacheKey(releaseId, manifest.manifestVersion);
146
+ manifestCache.set(key, { manifest, expiresAt: Date.now() + READY_TTL_MS });
147
+ logger.debug("Cached ready manifest", {
148
+ releaseId,
149
+ manifestVersion: manifest.manifestVersion,
150
+ ttlMs: READY_TTL_MS,
151
+ });
152
+ }
153
+ else {
154
+ manifestCache.set(releaseId, {
155
+ manifest: null,
156
+ expiresAt: Date.now() + NON_READY_TTL_MS,
157
+ });
158
+ }
159
+ }
160
+ catch (error) {
161
+ logger.debug("Manifest fetch failed", {
162
+ releaseId,
163
+ error: error instanceof Error ? error.message : String(error),
164
+ });
165
+ manifestCache.set(releaseId, { manifest: null, expiresAt: Date.now() + NON_READY_TTL_MS });
166
+ }
167
+ finally {
168
+ inFlight.delete(releaseId);
169
+ }
170
+ })();
171
+ inFlight.set(releaseId, promise);
172
+ }
173
+ /** Clear the cache (deployment / memory pressure / tests). */
174
+ export function clearReleaseAssetManifestCache() {
175
+ manifestCache.clear();
176
+ inFlight.clear();
177
+ fetcherRegistry.clear();
178
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Release Asset Manifest — v1 body schema, types, and validator.
3
+ *
4
+ * The manifest body is content-addressed metadata describing the transformed
5
+ * browser modules and compiled CSS for a release, plus the per-route closure
6
+ * used to drive preload hints and asset URL rewriting.
7
+ *
8
+ * Validation follows the repo's `defineSchema` convention (zod via the
9
+ * `SchemaValidator` extension contract). The schema is materialized lazily so
10
+ * core modules can import these types without pulling in the validator.
11
+ *
12
+ * @module release-assets/manifest-schema
13
+ */
14
+ import type { InferSchema } from "../extensions/schema/index.js";
15
+ export declare const getReleaseAssetManifestSchema: () => import("../internal-agents/schema.js").Schema<import("../extensions/schema/schema-validator.js").InferShape<{
16
+ schemaVersion: import("../internal-agents/schema.js").Schema<1>;
17
+ projectId: import("../internal-agents/schema.js").Schema<string>;
18
+ releaseId: import("../internal-agents/schema.js").Schema<string>;
19
+ releaseVersion: import("../internal-agents/schema.js").Schema<number>;
20
+ manifestVersion: import("../internal-agents/schema.js").Schema<number>;
21
+ builderVersion: import("../internal-agents/schema.js").Schema<string>;
22
+ sourceContentHash: import("../internal-agents/schema.js").Schema<string>;
23
+ createdAt: import("../internal-agents/schema.js").Schema<string>;
24
+ assetBasePath: import("../internal-agents/schema.js").Schema<string>;
25
+ modules: import("../internal-agents/schema.js").Schema<Record<string, import("../extensions/schema/schema-validator.js").InferShape<{
26
+ contentHash: import("../internal-agents/schema.js").Schema<string>;
27
+ size: import("../internal-agents/schema.js").Schema<number>;
28
+ contentType: import("../internal-agents/schema.js").Schema<string>;
29
+ }>>>;
30
+ css: import("../internal-agents/schema.js").Schema<import("../extensions/schema/schema-validator.js").InferShape<{
31
+ contentHash: import("../internal-agents/schema.js").Schema<string>;
32
+ size: import("../internal-agents/schema.js").Schema<number>;
33
+ contentType: import("../internal-agents/schema.js").Schema<string>;
34
+ styleProfileHash: import("../internal-agents/schema.js").Schema<string | null>;
35
+ }>[]>;
36
+ routes: import("../internal-agents/schema.js").Schema<Record<string, import("../extensions/schema/schema-validator.js").InferShape<{
37
+ modules: import("../internal-agents/schema.js").Schema<string[]>;
38
+ css: import("../internal-agents/schema.js").Schema<string[]>;
39
+ }>>>;
40
+ dependencies: import("../internal-agents/schema.js").Schema<Record<string, import("../extensions/schema/schema-validator.js").InferShape<{
41
+ contentHash: import("../internal-agents/schema.js").Schema<string>;
42
+ size: import("../internal-agents/schema.js").Schema<number>;
43
+ contentType: import("../internal-agents/schema.js").Schema<string>;
44
+ }>>>;
45
+ fallback: import("../internal-agents/schema.js").Schema<import("../extensions/schema/schema-validator.js").InferShape<{
46
+ mode: import("../internal-agents/schema.js").Schema<"jit">;
47
+ gaps: import("../internal-agents/schema.js").Schema<string[]>;
48
+ }>>;
49
+ }>>;
50
+ export type ReleaseAssetManifest = InferSchema<ReturnType<typeof getReleaseAssetManifestSchema>>;
51
+ export type ReleaseAssetEntry = ReleaseAssetManifest["modules"][string];
52
+ export type ReleaseAssetCssEntry = ReleaseAssetManifest["css"][number];
53
+ export type ReleaseAssetRouteEntry = ReleaseAssetManifest["routes"][string];
54
+ /** Manifest lifecycle states (DB-owned; mirrored here for runtime checks). */
55
+ export type ReleaseAssetManifestState = "queued" | "building" | "partial" | "ready" | "failed" | "superseded";
56
+ /** Response shape for the GET asset-manifest endpoint. */
57
+ export interface ReleaseAssetManifestResponse {
58
+ state: ReleaseAssetManifestState;
59
+ manifest_version: number;
60
+ manifest: ReleaseAssetManifest | null;
61
+ }
62
+ /**
63
+ * Hand-rolled structural validator.
64
+ *
65
+ * Used on consumption paths (HTML/proxy) where the `SchemaValidator` extension
66
+ * may not be registered. Returns the typed manifest on success, or null. Does
67
+ * not throw — consumption is always best-effort with a JIT fallback.
68
+ */
69
+ export declare function parseReleaseAssetManifest(value: unknown): ReleaseAssetManifest | null;
70
+ //# sourceMappingURL=manifest-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-schema.d.ts","sourceRoot":"","sources":["../../../src/src/release-assets/manifest-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,+BAA+B,CAAC;AAkClF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmBzC,CAAC;AAMF,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAC5C,UAAU,CAAC,OAAO,6BAA6B,CAAC,CACjD,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACxE,MAAM,MAAM,oBAAoB,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AACvE,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,8EAA8E;AAC9E,MAAM,MAAM,yBAAyB,GACjC,QAAQ,GACR,UAAU,GACV,SAAS,GACT,OAAO,GACP,QAAQ,GACR,YAAY,CAAC;AAEjB,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,yBAAyB,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,GAAG,IAAI,CAuBrF"}