@gradial/aci 0.1.48 → 0.1.51-ssr.2

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 (109) hide show
  1. package/README.md +13 -20
  2. package/dist/astro/assets.d.ts +6 -6
  3. package/dist/astro/assets.js +6 -6
  4. package/dist/astro/index.d.ts +0 -2
  5. package/dist/astro/index.js +0 -4
  6. package/dist/cli/emit-error-pages.mjs +0 -1
  7. package/dist/cli/verify-next-cdn-routes.mjs +3 -4
  8. package/dist/collect/aci.js +1 -1
  9. package/dist/collect/aci.js.sha256 +1 -1
  10. package/dist/collect/webvitals.d.ts +0 -3
  11. package/dist/collect/webvitals.js +11 -21
  12. package/dist/content/cache.d.ts +1 -9
  13. package/dist/content/cache.js +2 -37
  14. package/dist/content/error-pages.d.ts +1 -2
  15. package/dist/content/index.d.ts +0 -1
  16. package/dist/content/index.js +0 -1
  17. package/dist/index.d.ts +0 -1
  18. package/dist/internal/domain/edge/identity.d.ts +14 -0
  19. package/dist/internal/domain/edge/identity.js +214 -0
  20. package/dist/internal/domain/edge/index.d.ts +4 -0
  21. package/dist/internal/domain/edge/index.js +6 -0
  22. package/dist/internal/domain/edge/personalization-core.d.ts +24 -0
  23. package/dist/{edge → internal/domain/edge}/personalization-core.js +165 -64
  24. package/dist/internal/domain/edge/record/codec.d.ts +71 -0
  25. package/dist/internal/domain/edge/record/codec.js +401 -0
  26. package/dist/internal/domain/edge/resolve.d.ts +2 -0
  27. package/dist/internal/domain/edge/resolve.js +76 -0
  28. package/dist/{next/personalization-host.d.ts → internal/domain/edge/types.d.ts} +110 -61
  29. package/dist/internal/domain/edge/types.js +7 -0
  30. package/dist/internal/platform/collectionstore/errors.d.ts +5 -0
  31. package/dist/internal/platform/collectionstore/errors.js +7 -0
  32. package/dist/next/config.d.ts +0 -8
  33. package/dist/next/config.js +3 -78
  34. package/dist/next/error-pages.d.ts +2 -3
  35. package/dist/next/error-pages.js +5 -5
  36. package/dist/next/error-seat.d.ts +2 -3
  37. package/dist/next/index.d.ts +2 -2
  38. package/dist/next/index.js +1 -2
  39. package/dist/next/page.d.ts +3 -13
  40. package/dist/next/page.js +39 -51
  41. package/dist/next/server.d.ts +12 -32
  42. package/dist/next/server.js +66 -295
  43. package/dist/providers/object.d.ts +24 -0
  44. package/dist/providers/object.js +105 -0
  45. package/dist/providers/vercel-object-reader.d.ts +20 -0
  46. package/dist/providers/vercel-object-reader.js +45 -0
  47. package/dist/testing/conformance/checks.d.ts +1 -1
  48. package/dist/testing/conformance/checks.js +14 -144
  49. package/dist/testing/conformance/index.d.ts +1 -1
  50. package/dist/testing/conformance/index.js +1 -1
  51. package/dist/testing/conformance/preset.js +1 -1
  52. package/dist/testing/conformance/types.d.ts +1 -1
  53. package/dist/types/config.d.ts +0 -4
  54. package/dist/types/index.d.ts +0 -2
  55. package/dist/types/index.js +0 -2
  56. package/dist/types/page.d.ts +0 -10
  57. package/package.json +11 -35
  58. package/src/cli/emit-error-pages.mjs +0 -1
  59. package/src/cli/verify-next-cdn-routes.mjs +8 -11
  60. package/src/types/config.ts +0 -5
  61. package/src/types/index.ts +0 -2
  62. package/src/types/page.ts +0 -11
  63. package/templates/astro/template/.aci.yaml.tmpl +0 -11
  64. package/templates/common/.agents/skills/aci/SKILL.md +8 -9
  65. package/templates/common/.agents/skills/authoring/SKILL.md +3 -4
  66. package/templates/common/.agents/skills/authoring/references/block-registry.md +13 -36
  67. package/templates/common/.agents/skills/authoring/references/contracts.md +10 -17
  68. package/templates/common/.agents/skills/authoring/references/repo-structure.md +2 -2
  69. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +45 -95
  70. package/templates/common/.agents/skills/migration/references/canon-migration.md +17 -37
  71. package/templates/nextjs/template/.aci.yaml.tmpl +0 -11
  72. package/templates/nextjs/template/README.md.tmpl +0 -1
  73. package/templates/nextjs/template/src/app/[[...slug]]/page.tsx +1 -1
  74. package/dist/content/assets.d.ts +0 -18
  75. package/dist/content/assets.js +0 -66
  76. package/dist/edge/personalization-core.d.ts +0 -11
  77. package/dist/internal/render-proof.d.ts +0 -1
  78. package/dist/internal/render-proof.js +0 -197
  79. package/dist/next/asset-route.d.ts +0 -10
  80. package/dist/next/asset-route.js +0 -45
  81. package/dist/next/collect-route.d.ts +0 -19
  82. package/dist/next/collect-route.js +0 -422
  83. package/dist/next/edge-config.d.ts +0 -1
  84. package/dist/next/edge-config.js +0 -92
  85. package/dist/next/envelope-digest.d.ts +0 -21
  86. package/dist/next/envelope-digest.js +0 -58
  87. package/dist/next/middleware.d.ts +0 -40
  88. package/dist/next/middleware.js +0 -462
  89. package/dist/next/personalization-host.js +0 -257
  90. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  91. package/dist/next/vercel-collect-sdk-route.js +0 -181
  92. package/dist/next/vercel-edge-projection-v1.d.ts +0 -24
  93. package/dist/next/vercel-edge-projection-v1.js +0 -880
  94. package/dist/next/vercel-personalization.d.ts +0 -4
  95. package/dist/next/vercel-personalization.js +0 -4
  96. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  97. package/dist/next/vercel-s3-personalization.js +0 -570
  98. package/dist/providers/s3.d.ts +0 -47
  99. package/dist/providers/s3.js +0 -434
  100. package/dist/types/render-mode.d.ts +0 -2
  101. package/dist/types/render-mode.js +0 -1
  102. package/dist/types/renderer.d.ts +0 -71
  103. package/dist/types/renderer.js +0 -1
  104. package/src/types/render-mode.ts +0 -10
  105. package/src/types/renderer.ts +0 -83
  106. package/templates/nextjs/starter-content/src/middleware.ts +0 -41
  107. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +0 -1
  108. package/templates/nextjs/template/src/middleware.ts +0 -41
  109. package/templates/nextjs/template/src/middleware.ts.tmpl +0 -40
@@ -0,0 +1,5 @@
1
+ export declare class IdentityErasureFencedError extends Error {
2
+ readonly name = "IdentityErasureFencedError";
3
+ readonly code = "IDENTITY_ERASURE_FENCED";
4
+ constructor();
5
+ }
@@ -0,0 +1,7 @@
1
+ export class IdentityErasureFencedError extends Error {
2
+ name = 'IdentityErasureFencedError';
3
+ code = 'IDENTITY_ERASURE_FENCED';
4
+ constructor() {
5
+ super('identity collection is fenced by erasure');
6
+ }
7
+ }
@@ -8,13 +8,5 @@ export interface WithAciOptions {
8
8
  assetOrigin?: string;
9
9
  /** Asset rewrite prefix. Defaults to '/.gradial-dam' */
10
10
  assetPrefix?: string;
11
- /**
12
- * Customer-owned routes that ACI middleware should leave to Next.
13
- *
14
- * Use exact paths such as '/healthz' or simple Next-style catch-alls such as
15
- * '/checkout/:path*'. CMS document routes such as '/robots.txt' and
16
- * '/sitemap.xml' should be omitted so they stay release-scoped.
17
- */
18
- passthroughRoutes?: string[];
19
11
  }
20
12
  export declare function withAci(nextConfig?: NextConfig, options?: WithAciOptions): NextConfig;
@@ -1,5 +1,3 @@
1
- import { readdirSync, existsSync } from 'fs';
2
- import { join, relative } from 'path';
3
1
  import { createRequire } from 'module';
4
2
  import { createContentWatchWebpackPlugin } from './content-watch.js';
5
3
  import { buildErrorSeatPayload } from './error-seat-payload.js';
@@ -7,11 +5,10 @@ const require = createRequire(import.meta.url);
7
5
  export function withAci(nextConfig = {}, options = {}) {
8
6
  if (process.env.TURBOPACK) {
9
7
  throw new Error('withAci does not support Turbopack yet: ACI dev-mode content ' +
10
- 'compilation and the middleware route constants are wired through ' +
11
- 'the webpack() callback, which Turbopack never invokes. Run ' +
8
+ 'compilation and build-time error-page payload are wired through the ' +
9
+ 'webpack() callback, which Turbopack never invokes. Run ' +
12
10
  '`next dev` / `next build` without --turbopack.');
13
11
  }
14
- const publicPaths = collectPublicPaths(join(process.cwd(), 'public'));
15
12
  // Authorable error pages (D-9): the Next error seat is client-only, so the
16
13
  // bound catchAll page's content is serialized here — while next.config is
17
14
  // evaluated — and injected into the bundle via DefinePlugin below. The D-5
@@ -27,32 +24,15 @@ export function withAci(nextConfig = {}, options = {}) {
27
24
  for (const warning of errorSeat.warnings) {
28
25
  console.warn(`\n[aci error-pages] WARNING: ${warning}\n`);
29
26
  }
30
- const passthroughRoutes = normalizePassthroughRoutes(options.passthroughRoutes ?? []);
31
27
  const assetPrefix = options.assetPrefix || '/.gradial-dam';
32
28
  const assetOrigin = options.assetOrigin || '';
33
29
  const userRewrites = nextConfig.rewrites;
34
- const userHeaders = nextConfig.headers;
35
30
  const userWebpack = nextConfig.webpack;
36
31
  return {
37
32
  ...nextConfig,
38
33
  output: 'standalone',
39
34
  outputFileTracingRoot: nextConfig.outputFileTracingRoot ?? process.cwd(),
40
- headers: async () => {
41
- const gradialHeaders = [
42
- {
43
- source: '/__r/:releaseId/:path*',
44
- headers: [
45
- { key: 'Cache-Control', value: 'public, max-age=0, must-revalidate' },
46
- { key: 'CDN-Cache-Control', value: 'max-age=31536000' },
47
- { key: 'Vercel-CDN-Cache-Control', value: 'max-age=31536000' },
48
- ],
49
- },
50
- ];
51
- if (!userHeaders)
52
- return gradialHeaders;
53
- const userResult = await (typeof userHeaders === 'function' ? userHeaders() : userHeaders);
54
- return [...gradialHeaders, ...userResult];
55
- },
35
+ headers: nextConfig.headers,
56
36
  rewrites: async () => {
57
37
  const rewrites = [];
58
38
  if (assetOrigin) {
@@ -61,21 +41,6 @@ export function withAci(nextConfig = {}, options = {}) {
61
41
  destination: `${assetOrigin}${assetPrefix}/:path*`,
62
42
  });
63
43
  }
64
- else {
65
- // Without an external asset origin (the deliberate CDN override),
66
- // DAM requests must be served by the app itself: on Vercel nothing
67
- // else maps `/.gradial-dam/*` to the hosting bucket. Route them to
68
- // the asset handler, which serves DAM keys from the bucket root.
69
- rewrites.push({
70
- source: `${assetPrefix}/:path*`,
71
- destination: `/gradial/assets/${assetPrefix.replace(/^\/+/, '')}/:path*`,
72
- });
73
- }
74
- // CMS document rewrites: robots.txt and sitemap.xml are release-scoped
75
- // artifacts served via the asset route handler (#40). The middleware
76
- // passes them through (CMS_DOCUMENT_PATHS) so these rewrites fire and
77
- // route to the asset handler which resolves the active release.
78
- rewrites.push({ source: '/robots.txt', destination: '/gradial/assets/robots.txt' }, { source: '/sitemap.xml', destination: '/gradial/assets/sitemap.xml' });
79
44
  if (!userRewrites)
80
45
  return rewrites;
81
46
  const userResult = await (typeof userRewrites === 'function' ? userRewrites() : userRewrites);
@@ -95,9 +60,6 @@ export function withAci(nextConfig = {}, options = {}) {
95
60
  }
96
61
  const webpack = require('webpack');
97
62
  config.plugins.push(new webpack.DefinePlugin({
98
- '__ACI_PUBLIC_PATHS__': JSON.stringify(publicPaths),
99
- '__ACI_PASSTHROUGH_ROUTES__': JSON.stringify(passthroughRoutes),
100
- '__ACI_ASSET_PREFIX__': JSON.stringify(assetPrefix),
101
63
  '__ACI_ERROR_PAGE__': JSON.stringify(errorSeat.payload),
102
64
  }));
103
65
  return typeof userWebpack === 'function'
@@ -106,40 +68,3 @@ export function withAci(nextConfig = {}, options = {}) {
106
68
  },
107
69
  };
108
70
  }
109
- function normalizePassthroughRoutes(routes) {
110
- const out = [];
111
- const seen = new Set();
112
- for (const route of routes) {
113
- const clean = normalizeRoutePattern(route);
114
- if (!clean || seen.has(clean))
115
- continue;
116
- seen.add(clean);
117
- out.push(clean);
118
- }
119
- return out;
120
- }
121
- function normalizeRoutePattern(route) {
122
- const trimmed = route.trim();
123
- if (!trimmed)
124
- return '';
125
- const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
126
- return withSlash.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
127
- }
128
- function collectPublicPaths(publicDir) {
129
- if (!existsSync(publicDir))
130
- return [];
131
- const paths = [];
132
- const walk = (dir) => {
133
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
134
- const full = join(dir, entry.name);
135
- if (entry.isDirectory()) {
136
- walk(full);
137
- }
138
- else {
139
- paths.push('/' + relative(publicDir, full).split('\\').join('/'));
140
- }
141
- }
142
- };
143
- walk(publicDir);
144
- return paths;
145
- }
@@ -1,5 +1,4 @@
1
1
  import type { ReactElement } from 'react';
2
- import { type FetchConfig } from './server.js';
3
2
  import { type ErrorPageKind, type ErrorPagePush, type ResolvedErrorPage } from '../content/error-pages.js';
4
3
  import type { KernelSiteConfig } from '../content/types.js';
5
4
  import type { Registry } from '../registry.js';
@@ -18,7 +17,7 @@ export interface NextResolvedErrorPage extends ResolvedErrorPage {
18
17
  * the shipped default — an error surface must not re-enter the failing
19
18
  * system (ANTI-2).
20
19
  */
21
- export declare function resolveErrorPage(kind: ErrorPageKind, config?: FetchConfig): Promise<NextResolvedErrorPage>;
20
+ export declare function resolveErrorPage(kind: ErrorPageKind): Promise<NextResolvedErrorPage>;
22
21
  /**
23
22
  * Renders the content page bound to `route` through the normal page
24
23
  * pipeline (createPage: layout slots, fragments, registry blocks, collect/
@@ -47,6 +46,6 @@ export declare function DefaultErrorPageContent(): import("react").JSX.Element;
47
46
  * `pushErrorPageView` (D-15): the tenant-owned policy module's push,
48
47
  * forwarded to ErrorPageInstrumentation; omitted → the SDK default.
49
48
  */
50
- export declare function createNotFound(registry: Registry, config?: FetchConfig & {
49
+ export declare function createNotFound(registry: Registry, options?: {
51
50
  pushErrorPageView?: ErrorPagePush;
52
51
  }): () => Promise<ReactElement>;
@@ -12,11 +12,11 @@ import { ErrorPageMarker } from './error-page-marker.js';
12
12
  * the shipped default — an error surface must not re-enter the failing
13
13
  * system (ANTI-2).
14
14
  */
15
- export async function resolveErrorPage(kind, config = {}) {
15
+ export async function resolveErrorPage(kind) {
16
16
  let siteConfig = null;
17
17
  const warnings = [];
18
18
  try {
19
- siteConfig = await getSiteConfig(config);
19
+ siteConfig = await getSiteConfig();
20
20
  }
21
21
  catch (error) {
22
22
  warnings.push(`site config unavailable at the error seat: ${String(error)}`);
@@ -58,7 +58,7 @@ export function DefaultErrorPageContent() {
58
58
  * `pushErrorPageView` (D-15): the tenant-owned policy module's push,
59
59
  * forwarded to ErrorPageInstrumentation; omitted → the SDK default.
60
60
  */
61
- export function createNotFound(registry, config = {}) {
61
+ export function createNotFound(registry, options = {}) {
62
62
  return async function NotFound() {
63
63
  // Force dynamic rendering: Next prerenders /_not-found at build time,
64
64
  // where no content provider is reachable. The DYNAMIC_SERVER_USAGE
@@ -73,7 +73,7 @@ export function createNotFound(registry, config = {}) {
73
73
  if (typeof digest === 'string' && digest.startsWith('DYNAMIC_SERVER_USAGE'))
74
74
  throw error;
75
75
  }
76
- const resolved = await resolveErrorPage('notFound', config);
76
+ const resolved = await resolveErrorPage('notFound');
77
77
  for (const warning of resolved.warnings) {
78
78
  console.warn(`[aci error-pages] ${warning}`);
79
79
  }
@@ -89,6 +89,6 @@ export function createNotFound(registry, config = {}) {
89
89
  content = null;
90
90
  }
91
91
  }
92
- return (_jsxs(_Fragment, { children: [content ?? _jsx(DefaultErrorPageContent, {}), _jsx(ErrorPageMarker, { kind: "notFound" }), _jsx(ErrorPageInstrumentation, { kind: "notFound", push: config.pushErrorPageView })] }));
92
+ return (_jsxs(_Fragment, { children: [content ?? _jsx(DefaultErrorPageContent, {}), _jsx(ErrorPageMarker, { kind: "notFound" }), _jsx(ErrorPageInstrumentation, { kind: "notFound", push: options.pushErrorPageView })] }));
93
93
  };
94
94
  }
@@ -6,9 +6,8 @@ import type { Registry } from '../registry.js';
6
6
  * D-9): Next's error seat is client-only and cannot reach the server content
7
7
  * provider, so withAci() resolves `site.json#errorPages.catchAll` while
8
8
  * next.config is evaluated and injects the page's resolved slots into the
9
- * bundle as the `__ACI_ERROR_PAGE__` webpack define (same mechanism as the
10
- * middleware's `__ACI_PUBLIC_PATHS__`). Content edits to the bound page
11
- * therefore require a code build — accepted by D-4.
9
+ * bundle as the `__ACI_ERROR_PAGE__` webpack define. Content edits to the
10
+ * bound page therefore require a code build — accepted by D-4.
12
11
  */
13
12
  export interface ErrorSeatPayload {
14
13
  /** True when no binding is authored: render the shipped default (REQ-4). */
@@ -2,10 +2,10 @@ export { DevRefresh, type DevRefreshProps } from './dev-refresh.js';
2
2
  export { withAci, type WithAciOptions } from './config.js';
3
3
  export { isPreviewMode } from './preview-mode.js';
4
4
  export { createPage, type CreatePageOptions, type SlotPropsFn } from './page.js';
5
- export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
5
+ export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams, setReleaseObjectReader } from './server.js';
6
+ export type { HostedContentContext } from './server.js';
6
7
  export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
7
8
  export { CollectScript, type CollectScriptProps } from './collect-script.js';
8
- export { aciMiddlewareMatcher } from './middleware.js';
9
9
  export { AssignmentsScript, type AssignmentsScriptProps } from './assignments-script.js';
10
10
  export { createNotFound, renderPageForRoute, resolveErrorPage, type NextResolvedErrorPage, } from './error-pages.js';
11
11
  export { ErrorPageInstrumentation } from './error-page-instrumentation.js';
@@ -2,10 +2,9 @@ export { DevRefresh } from './dev-refresh.js';
2
2
  export { withAci } from './config.js';
3
3
  export { isPreviewMode } from './preview-mode.js';
4
4
  export { createPage } from './page.js';
5
- export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
5
+ export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams, setReleaseObjectReader } from './server.js';
6
6
  export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
7
7
  export { CollectScript } from './collect-script.js';
8
- export { aciMiddlewareMatcher } from './middleware.js';
9
8
  export { AssignmentsScript } from './assignments-script.js';
10
9
  export { createNotFound, renderPageForRoute, resolveErrorPage, } from './error-pages.js';
11
10
  export { ErrorPageInstrumentation } from './error-page-instrumentation.js';
@@ -30,19 +30,9 @@ export interface CreatePageOptions {
30
30
  * When the site config enables personalization, the page tree also carries
31
31
  * the collect script tag exactly once per page (design/04 contract tag).
32
32
  *
33
- * Release-scoped routes (production): the middleware rewrites a public URL
34
- * to `/__r/{releaseId}/routes/{path}`. The optional catch-all `[[...slug]]`
35
- * receives `['__r', releaseId, 'routes', ...pathSegments]`. createPage
36
- * extracts the releaseId and passes it to the content loader, giving
37
- * each release a unique CDN cache key without a separate route folder.
38
- * The `routes` namespace segment leaves room for future siblings under
39
- * the release prefix (e.g. `/__r/{releaseId}/assets/*`).
40
- *
41
- * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
42
- * requests through unrewritten, so `/` arrives with an empty/undefined
43
- * slug and `/about` arrives as `['about']`. The optional catch-all
44
- * `[[...slug]]` handles both cases. No releaseId is present; the file
45
- * provider serves content directly from disk.
33
+ * In SSR delivery the provider edge rewrites to an immutable release/page
34
+ * path. The SDK derives the exact JSON ref from that path and renders it with
35
+ * the same registry used by static publishing.
46
36
  *
47
37
  * Layout slots: createPage loads the page's layout, iterates all slots, and
48
38
  * fills each from the page's regions. If a slot is empty, it falls back to
package/dist/next/page.js CHANGED
@@ -6,7 +6,6 @@ import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
6
6
  import { renderBlocks } from './render.js';
7
7
  import { AssignmentsScript } from './assignments-script.js';
8
8
  import { CollectScript } from './collect-script.js';
9
- import { renderProofContentFor } from '../internal/render-proof.js';
10
9
  /**
11
10
  * Creates the catch-all route handler for a Next.js ACI site.
12
11
  *
@@ -27,19 +26,9 @@ import { renderProofContentFor } from '../internal/render-proof.js';
27
26
  * When the site config enables personalization, the page tree also carries
28
27
  * the collect script tag exactly once per page (design/04 contract tag).
29
28
  *
30
- * Release-scoped routes (production): the middleware rewrites a public URL
31
- * to `/__r/{releaseId}/routes/{path}`. The optional catch-all `[[...slug]]`
32
- * receives `['__r', releaseId, 'routes', ...pathSegments]`. createPage
33
- * extracts the releaseId and passes it to the content loader, giving
34
- * each release a unique CDN cache key without a separate route folder.
35
- * The `routes` namespace segment leaves room for future siblings under
36
- * the release prefix (e.g. `/__r/{releaseId}/assets/*`).
37
- *
38
- * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
39
- * requests through unrewritten, so `/` arrives with an empty/undefined
40
- * slug and `/about` arrives as `['about']`. The optional catch-all
41
- * `[[...slug]]` handles both cases. No releaseId is present; the file
42
- * provider serves content directly from disk.
29
+ * In SSR delivery the provider edge rewrites to an immutable release/page
30
+ * path. The SDK derives the exact JSON ref from that path and renders it with
31
+ * the same registry used by static publishing.
43
32
  *
44
33
  * Layout slots: createPage loads the page's layout, iterates all slots, and
45
34
  * fills each from the page's regions. If a slot is empty, it falls back to
@@ -47,10 +36,14 @@ import { renderProofContentFor } from '../internal/render-proof.js';
47
36
  * be CMS-managed fragments instead of hardcoded component imports.
48
37
  */
49
38
  export function createPage(registry, options) {
39
+ // The compiler invokes this route repeatedly against isolated content
40
+ // snapshots while producing immutable HTML. It must not let Next cache the
41
+ // first snapshot. Deployed CMS traffic is intercepted by the provider
42
+ // gateway and never reaches this framework route.
50
43
  const dynamic = 'force-dynamic';
51
44
  async function generateMetadata({ params }) {
52
- const { route, releaseId } = await resolveRouteAndRelease(params);
53
- const input = await getRenderInput(route, { releaseId });
45
+ const resolved = await resolveRoute(params);
46
+ const input = await getRenderInput(resolved.route, resolved.context);
54
47
  const meta = routeMetadataForContent(input.siteConfig, input.page);
55
48
  return {
56
49
  title: meta.title,
@@ -64,15 +57,14 @@ export function createPage(registry, options) {
64
57
  };
65
58
  }
66
59
  async function CatchAllPage({ params }) {
67
- const { route, releaseId } = await resolveRouteAndRelease(params);
68
- const fetchConfig = { releaseId };
60
+ const resolved = await resolveRoute(params);
61
+ const context = resolved.context;
69
62
  try {
70
- const input = await getRenderInput(route, fetchConfig);
63
+ const input = await getRenderInput(resolved.route, context);
71
64
  if (!input.page)
72
65
  notFound();
73
- const renderProofContent = renderProofContentFor(input);
74
- const layout = await getLayout(input.page.layout, fetchConfig);
75
- const resolvedSlots = await resolveSlots(layout, input.page.regions || {}, (id) => getFragment(id, fetchConfig));
66
+ const layout = await getLayout(input.page.layout, context);
67
+ const resolvedSlots = await resolveSlots(layout, input.page.regions || {}, (id) => getFragment(id, context));
76
68
  const renderedSlots = await Promise.all(layout.slots.map(async (slot) => {
77
69
  const blocks = resolvedSlots[slot.name];
78
70
  if (!blocks?.length)
@@ -84,9 +76,7 @@ export function createPage(registry, options) {
84
76
  }
85
77
  return (_jsx(Fragment, { children: renderedBlocks }, slot.name));
86
78
  }));
87
- return (_jsxs(_Fragment, { children: [renderProofContent
88
- ? _jsx("meta", { name: "aci-render-proof", content: renderProofContent })
89
- : null, renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig })] }));
79
+ return (_jsxs(_Fragment, { children: [renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig })] }));
90
80
  }
91
81
  catch (error) {
92
82
  if (error instanceof PageNotFoundError)
@@ -101,43 +91,41 @@ export function createPage(registry, options) {
101
91
  };
102
92
  }
103
93
  /**
104
- * Resolves the logical route and releaseId from Next.js catch-all params.
105
- *
106
- * Two shapes are supported:
107
- *
108
- * 1. **Rewritten (production):** middleware rewrites a public URL to
109
- * `/__r/{releaseId}/routes/{path}`, so slug is
110
- * `['__r', releaseId, 'routes', ...pathSegments]`. The releaseId is
111
- * extracted; the remaining segments form the content route. The `routes`
112
- * segment is the namespace for page routes (siblings like `assets/` live
113
- * under the same release prefix).
114
- *
115
- * 2. **Dev pass-through (ACI_CONTENT_PROVIDER=file):** middleware passes
116
- * requests through unrewritten, so the slug is the raw public path
117
- * segments (possibly empty/undefined for `/`). No releaseId is present;
118
- * the file provider serves content from disk.
94
+ * Resolves the logical source route used while building static output.
119
95
  */
120
- async function resolveRouteAndRelease(params) {
96
+ async function resolveRoute(params) {
121
97
  const resolved = await params;
122
98
  const slug = resolved.slug ?? [];
123
99
  if (slug.length >= 2 && slug[0] === '__r') {
124
- const releaseId = decodeURIComponent(slug[1]);
125
- if (!releaseId || releaseId.includes('/') || releaseId === '.' || releaseId === '..') {
100
+ const releaseId = slug[1];
101
+ const selected = slug.slice(2);
102
+ if (!portableReleaseId(releaseId) || selected.some((part) => !portablePathPart(part))) {
126
103
  return { route: '/' };
127
104
  }
128
- // Skip the 'routes' namespace segment when present.
129
- const rest = slug.length >= 3 && slug[2] === 'routes' ? slug.slice(3) : slug.slice(2);
130
- if (rest.length === 0)
131
- return { route: '/', releaseId };
132
- if (rest.some((s) => s === '' || s === '.' || s === '..'))
105
+ const variantAt = selected.indexOf('__variant');
106
+ if (variantAt >= 0 && (variantAt !== selected.length - 2 || !selected[variantAt + 1])) {
133
107
  return { route: '/' };
134
- return { route: `/${rest.join('/')}/`, releaseId };
108
+ }
109
+ const routeParts = variantAt < 0 ? selected : selected.slice(0, variantAt);
110
+ const route = routeFromSlug(routeParts);
111
+ const pageRef = [
112
+ 'routes',
113
+ ...routeParts,
114
+ ...(variantAt < 0 ? [] : ['__variant', selected[variantAt + 1]]),
115
+ 'index.json',
116
+ ].join('/');
117
+ return { route, context: { releaseId, pageRef } };
135
118
  }
136
- const route = routeFromSlug(slug);
137
- return { route };
119
+ return { route: routeFromSlug(slug) };
138
120
  }
139
121
  function routeFromSlug(slug) {
140
122
  if (slug.length === 0)
141
123
  return '/';
142
124
  return `/${slug.join('/')}/`;
143
125
  }
126
+ function portableReleaseId(value) {
127
+ return value.length <= 128 && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value);
128
+ }
129
+ function portablePathPart(value) {
130
+ return value.length > 0 && value !== '.' && value !== '..' && !value.includes('/');
131
+ }
@@ -1,20 +1,6 @@
1
1
  import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError } from '../content/provider.js';
2
- import type { RenderInput } from '../content/types.js';
2
+ import { type ReleaseObjectReader } from '../providers/object.js';
3
3
  export { FragmentNotFoundError, PageNotFoundError, LayoutNotFoundError };
4
- export interface FetchConfig {
5
- siteId?: string;
6
- bucket?: string;
7
- keyPrefix?: string;
8
- region?: string;
9
- accessKeyId?: string;
10
- secretAccessKey?: string;
11
- sessionToken?: string;
12
- roleArn?: string;
13
- endpoint?: string;
14
- releaseId?: string;
15
- edgeConfig?: string;
16
- deploymentId?: string;
17
- }
18
4
  export interface ServerRenderInput<TPage, TSite extends SiteConfig = SiteConfig> {
19
5
  route: string;
20
6
  domain: string;
@@ -27,13 +13,17 @@ export interface SiteConfig {
27
13
  domain?: string;
28
14
  defaultLocale?: string;
29
15
  }
30
- export declare function getPage<T = unknown>(route?: string, config?: FetchConfig): Promise<T>;
31
- export declare function getSiteConfig<T extends SiteConfig = SiteConfig>(config?: FetchConfig): Promise<T>;
32
- export declare function getFragment<T = unknown>(name: string, config?: FetchConfig): Promise<T>;
33
- export declare function getLayout<T = unknown>(name: string, config?: FetchConfig): Promise<T>;
34
- export declare function getRoutes(config?: FetchConfig): Promise<string[]>;
35
- export declare function getRenderInput<TPage, TSite extends SiteConfig = SiteConfig>(route?: string, config?: FetchConfig): Promise<ServerRenderInput<TPage, TSite>>;
36
- export declare function getPageRuntimeRenderInput(): Promise<RenderInput | null>;
16
+ export interface HostedContentContext {
17
+ releaseId?: string;
18
+ pageRef?: string;
19
+ }
20
+ export declare function setReleaseObjectReader(reader: ReleaseObjectReader | undefined): void;
21
+ export declare function getPage<T = unknown>(route?: string, context?: HostedContentContext): Promise<T>;
22
+ export declare function getSiteConfig<T extends SiteConfig = SiteConfig>(context?: HostedContentContext): Promise<T>;
23
+ export declare function getFragment<T = unknown>(name: string, context?: HostedContentContext): Promise<T>;
24
+ export declare function getLayout<T = unknown>(name: string, context?: HostedContentContext): Promise<T>;
25
+ export declare function getRoutes(context?: HostedContentContext): Promise<string[]>;
26
+ export declare function getRenderInput<TPage, TSite extends SiteConfig = SiteConfig>(route?: string, context?: HostedContentContext): Promise<ServerRenderInput<TPage, TSite>>;
37
27
  export { isPreviewMode } from './preview-mode.js';
38
28
  export declare function routeFromNextParams(params: Promise<{
39
29
  slug?: string[];
@@ -43,13 +33,3 @@ export declare function routeFromNextParams(params: Promise<{
43
33
  export declare function generateStaticParams(): Promise<Array<{
44
34
  slug?: string[];
45
35
  }>>;
46
- export declare class ReleaseAssetNotFoundError extends Error {
47
- constructor(releaseId: string, assetPath: string, cause?: unknown);
48
- }
49
- export declare function getReleaseAssetResponse(releaseId: string, assetPath: string, config?: FetchConfig): Promise<Response>;
50
- export declare function isDamAssetPath(assetPath: string): boolean;
51
- export declare class DamAssetNotFoundError extends Error {
52
- constructor(assetPath: string, cause?: unknown);
53
- }
54
- export declare function getDamAssetResponse(assetPath: string, config?: FetchConfig): Promise<Response>;
55
- export declare function resolveReleaseId(config?: FetchConfig): Promise<string>;