@gradial/aci 0.1.52-rc.28 → 0.1.52-rc.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.52-rc.28` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.52-rc.29` is a pre-1.0 public-surface reset for the ACI
4
4
  client SDK. Breaking changes from the old `0.1.x` accidental surface are
5
5
  intentional: the root import is framework-neutral, framework runtime behavior is
6
6
  behind explicit subpaths, and the first required customer path is React/Next.
@@ -8,7 +8,7 @@ behind explicit subpaths, and the first required customer path is React/Next.
8
8
  ## Install
9
9
 
10
10
  ```bash
11
- npm install @gradial/aci@0.1.52-rc.28 zod@^4
11
+ npm install @gradial/aci@0.1.52-rc.29 zod@^4
12
12
  ```
13
13
 
14
14
  ## Root Import
@@ -137,13 +137,13 @@ host bundles select and serve immutable release artifacts.
137
137
  The first RC scaffold path is Next:
138
138
 
139
139
  ```bash
140
- npx @gradial/aci@0.1.52-rc.28 init my-site --template next-starter
140
+ npx @gradial/aci@0.1.52-rc.29 init my-site --template next-starter
141
141
  ```
142
142
 
143
143
  List available templates:
144
144
 
145
145
  ```bash
146
- npx @gradial/aci@0.1.52-rc.28 init --list
146
+ npx @gradial/aci@0.1.52-rc.29 init --list
147
147
  ```
148
148
 
149
149
  ## Personalization: The One-Tag Install
@@ -50,6 +50,7 @@ export async function getErrorPageData(astro, kind, options = {}) {
50
50
  if (resolved.route) {
51
51
  const slug = resolved.route.replace(/^\/+|\/+$/g, '');
52
52
  const data = await getPageData({
53
+ isPrerendered: astro.isPrerendered,
53
54
  params: { slug },
54
55
  request: astro.request,
55
56
  url: astro.url,
@@ -37,6 +37,7 @@ import { type LayoutEntry } from '../content/provider.js';
37
37
  * This avoids depending on Astro's own types at the SDK level.
38
38
  */
39
39
  export interface AstroGlobal {
40
+ isPrerendered?: boolean;
40
41
  params: {
41
42
  slug?: string;
42
43
  };
@@ -51,6 +52,8 @@ export interface AstroGlobal {
51
52
  export interface PageData {
52
53
  /** Route string (normalized, e.g. "/" or "/about"). */
53
54
  route: string;
55
+ /** Selected render tuple, including the explicit "base" identity. */
56
+ variantKey: string;
54
57
  /** Site configuration. */
55
58
  siteConfig: KernelSiteConfig;
56
59
  /** Page content. */
@@ -91,4 +94,4 @@ export interface GetPageDataOptions {
91
94
  * directly from CloudFront in production (the CDN points at the release
92
95
  * artifacts in S3, no runtime content fetching).
93
96
  */
94
- export declare function getPageData(astro: AstroGlobal, _options?: GetPageDataOptions): Promise<PageData>;
97
+ export declare function getPageData(astro: AstroGlobal, options?: GetPageDataOptions): Promise<PageData>;
@@ -35,7 +35,7 @@ import { normalizeRoute } from '../content/routes.js';
35
35
  import { routeMetadataForContent } from '../content/provider.js';
36
36
  import { resolveSlots } from '../content/resolve-slots.js';
37
37
  import { assignmentsScriptTagHTML, collectScriptTagHTML } from '../content/personalization.js';
38
- import { getPendingRenderInputFromHeaders } from '../runtime/page.js';
38
+ import { GRADIAL_RENDER_INPUT_HEADER, getPendingRenderInputFromHeaders } from '../runtime/page.js';
39
39
  // ---------------------------------------------------------------------------
40
40
  // Implementation
41
41
  // ---------------------------------------------------------------------------
@@ -53,23 +53,25 @@ import { getPendingRenderInputFromHeaders } from '../runtime/page.js';
53
53
  * directly from CloudFront in production (the CDN points at the release
54
54
  * artifacts in S3, no runtime content fetching).
55
55
  */
56
- export async function getPageData(astro, _options) {
56
+ export async function getPageData(astro, options) {
57
57
  const route = normalizeRoute(astro.params.slug ? `/${astro.params.slug}` : '/');
58
- // Check for preview render input first (same as Next.js tier 1)
59
- // Skip during prerender/static generation — request.headers is not available
58
+ // Static Astro generation is explicit; unexpected header access errors and
59
+ // expired render-input IDs must not turn a selected variant into local base.
60
60
  let runtimeInput = null;
61
- try {
62
- runtimeInput = getPendingRenderInputFromHeaders(astro.request.headers);
63
- }
64
- catch {
65
- // Astro throws when accessing request.headers during prerender — this is
66
- // expected and harmless; preview mode is a request-time concern.
61
+ if (!astro.isPrerendered) {
62
+ const headers = astro.request.headers;
63
+ runtimeInput = getPendingRenderInputFromHeaders(headers);
64
+ if (headers.has(GRADIAL_RENDER_INPUT_HEADER) && !runtimeInput) {
65
+ throw new Error(`ACI Astro render input is unavailable for route ${route}; refusing to render local base content`);
66
+ }
67
67
  }
68
68
  let siteConfig;
69
69
  let page;
70
70
  let providerModule = null;
71
71
  let provider = null;
72
+ let variantKey;
72
73
  if (runtimeInput) {
74
+ variantKey = requireVariantKey(runtimeInput.variantKey, route);
73
75
  siteConfig = runtimeInput.siteConfig;
74
76
  page = runtimeInput.page;
75
77
  }
@@ -77,15 +79,17 @@ export async function getPageData(astro, _options) {
77
79
  // Lazy-import FileContentProvider to avoid pulling it in when
78
80
  // preview render input is available
79
81
  providerModule = await import('../providers/file.js');
80
- provider = new providerModule.FileContentProvider();
82
+ provider = new providerModule.FileContentProvider(options?.compiledRoot);
81
83
  [siteConfig, page] = await Promise.all([
82
84
  provider.getSiteConfig(),
83
85
  provider.getPage(route),
84
86
  ]);
87
+ const entry = (await provider.listRoutes()).find((entry) => normalizeRoute(entry.path) === route);
88
+ variantKey = requireVariantKey(entry?.variantKey, route);
85
89
  }
86
90
  const meta = routeMetadataForContent(siteConfig, page);
87
91
  // Resolve layout: prefer render input, fall back to provider
88
- const layout = await resolveLayout(runtimeInput, provider, providerModule, page.layout);
92
+ const layout = await resolveLayout(runtimeInput, provider, page.layout, route);
89
93
  // Resolve slots: page regions filled, then layout fragment defaults
90
94
  const slots = await resolveSlots(layout, page?.regions || {}, async (id) => {
91
95
  if (runtimeInput) {
@@ -100,22 +104,27 @@ export async function getPageData(astro, _options) {
100
104
  const isPreview = astro.url.searchParams.has('preview_token');
101
105
  const collectTag = isPreview ? '' : collectScriptTagHTML(siteConfig);
102
106
  const assignmentsTag = isPreview ? '' : assignmentsScriptTagHTML(page);
103
- return { route, siteConfig, page, meta, layout, slots, collectTag, assignmentsTag };
107
+ return { route, variantKey, siteConfig, page, meta, layout, slots, collectTag, assignmentsTag };
108
+ }
109
+ function requireVariantKey(value, route) {
110
+ if (typeof value !== 'string' || !value.trim() || value !== value.trim()) {
111
+ throw new Error(`ACI Astro render variant identity is missing or invalid for route ${route}; supply an explicit variantKey ("base" for an unpersonalized render)`);
112
+ }
113
+ return value;
104
114
  }
105
115
  // ---------------------------------------------------------------------------
106
116
  // Internal helpers
107
117
  // ---------------------------------------------------------------------------
108
- async function resolveLayout(runtimeInput, provider, providerModule, layoutName) {
109
- // Check render input first
110
- if (runtimeInput?.layouts?.[layoutName]) {
111
- return runtimeInput.layouts[layoutName];
118
+ async function resolveLayout(runtimeInput, provider, layoutName, route) {
119
+ if (runtimeInput) {
120
+ const layout = runtimeInput.layouts?.[layoutName];
121
+ if (!layout)
122
+ throw new Error(`ACI Astro render input for route ${route} is missing its selected layout ${JSON.stringify(layoutName)}`);
123
+ return layout;
112
124
  }
113
125
  // Fall back to provider
114
126
  if (provider) {
115
127
  return await provider.getLayout(layoutName);
116
128
  }
117
- // Last resort: create a new provider
118
- const mod = providerModule ?? await import('../providers/file.js');
119
- const p = new mod.FileContentProvider();
120
- return await p.getLayout(layoutName);
129
+ throw new Error(`ACI Astro content provider is unavailable for route ${route}`);
121
130
  }
@@ -2,6 +2,7 @@ import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } f
2
2
  export interface RouteEntry {
3
3
  slug: string;
4
4
  path: string;
5
+ variantKey: string;
5
6
  payloadRef: string;
6
7
  digest: string;
7
8
  fragmentRefs?: string[];
@@ -71,8 +72,9 @@ export declare class LayoutNotFoundError extends Error {
71
72
  constructor(name: string, cause?: unknown);
72
73
  }
73
74
  export interface RenderInputOptions {
75
+ variantKey: string;
74
76
  domain?: string;
75
77
  locale?: string;
76
78
  }
77
- export declare function loadRenderInput(provider: ContentProvider, route?: string, options?: RenderInputOptions): Promise<RenderInput>;
79
+ export declare function loadRenderInput(provider: ContentProvider, route: string | undefined, options: RenderInputOptions): Promise<RenderInput>;
78
80
  export declare function routeMetadataForContent(siteConfig: KernelSiteConfig, page: KernelPage | null): KernelRouteMetadata;
@@ -24,8 +24,11 @@ export class LayoutNotFoundError extends Error {
24
24
  }
25
25
  }
26
26
  import { getCachedSiteConfig } from './cache.js';
27
- export async function loadRenderInput(provider, route = '/', options = {}) {
27
+ export async function loadRenderInput(provider, route = '/', options) {
28
28
  const normalized = normalizeRoute(route);
29
+ if (typeof options?.variantKey !== 'string' || !options.variantKey.trim() || options.variantKey !== options.variantKey.trim()) {
30
+ throw new Error(`ACI render variant identity is missing or invalid for route ${normalized}; supply an explicit variantKey`);
31
+ }
29
32
  const [siteConfig, page] = await Promise.all([
30
33
  getCachedSiteConfig(provider),
31
34
  provider.getPage(normalized).catch((error) => {
@@ -36,6 +39,7 @@ export async function loadRenderInput(provider, route = '/', options = {}) {
36
39
  ]);
37
40
  return {
38
41
  route: normalized,
42
+ variantKey: options.variantKey,
39
43
  domain: options.domain || siteConfig.domain,
40
44
  locale: (options.locale || siteConfig.defaultLocale || 'en-us').toLowerCase(),
41
45
  siteConfig,
@@ -112,6 +112,8 @@ export interface KernelRouteMetadata {
112
112
  }
113
113
  export interface RenderInput<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> {
114
114
  route: string;
115
+ /** Selected render tuple. Unpersonalized renders explicitly use "base". */
116
+ variantKey: string;
115
117
  domain: string;
116
118
  locale: string;
117
119
  siteConfig: TSiteConfig;
@@ -17,7 +17,7 @@ export declare class FixtureContentProvider implements ContentProvider {
17
17
  getFragment<T = unknown>(id: string): Promise<T>;
18
18
  getLayout<T = unknown>(name: string): Promise<T>;
19
19
  listRoutes(): Promise<RouteEntry[]>;
20
- loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput>;
20
+ loadRenderInput(route: string | undefined, options: RenderInputOptions): Promise<RenderInput>;
21
21
  resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
22
22
  }
23
23
  export declare function createFixtureContentProvider(options?: FixtureContentProviderOptions): FixtureContentProvider;
@@ -55,11 +55,12 @@ export class FixtureContentProvider {
55
55
  .map(([routePath, page]) => ({
56
56
  slug: routePath === '/' ? '' : routePath.replace(/^\//, ''),
57
57
  path: routePath,
58
+ variantKey: 'base',
58
59
  payloadRef: routePath === '/' ? 'routes/index.json' : `routes/${routePath.replace(/^\//, '')}.json`,
59
60
  digest: '',
60
61
  }));
61
62
  }
62
- async loadRenderInput(route = '/', options = {}) {
63
+ async loadRenderInput(route = '/', options) {
63
64
  return loadRenderInput(this, route, options);
64
65
  }
65
66
  async resolveRouteMetadata(route = '/') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.52-rc.28",
3
+ "version": "0.1.52-rc.29",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/gradial/bare-metal-go.git",
@@ -159,11 +159,11 @@
159
159
  "zod": "^4.0.0"
160
160
  },
161
161
  "optionalDependencies": {
162
- "@gradial/aci-cli-darwin-arm64": "0.1.52-rc.28",
163
- "@gradial/aci-cli-darwin-x64": "0.1.52-rc.28",
164
- "@gradial/aci-cli-linux-arm64": "0.1.52-rc.28",
165
- "@gradial/aci-cli-linux-x64": "0.1.52-rc.28",
166
- "@gradial/aci-cli-win32-arm64": "0.1.52-rc.28",
167
- "@gradial/aci-cli-win32-x64": "0.1.52-rc.28"
162
+ "@gradial/aci-cli-darwin-arm64": "0.1.52-rc.29",
163
+ "@gradial/aci-cli-darwin-x64": "0.1.52-rc.29",
164
+ "@gradial/aci-cli-linux-arm64": "0.1.52-rc.29",
165
+ "@gradial/aci-cli-linux-x64": "0.1.52-rc.29",
166
+ "@gradial/aci-cli-win32-arm64": "0.1.52-rc.29",
167
+ "@gradial/aci-cli-win32-x64": "0.1.52-rc.29"
168
168
  }
169
169
  }