@gradial/aci 0.1.2 → 0.1.4
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 +19 -0
- package/bin/aci.js +1 -1
- package/dist/astro/index.d.ts +2 -0
- package/dist/astro/index.js +4 -0
- package/dist/next/server.d.ts +3 -0
- package/dist/next/server.js +35 -4
- package/dist/runtime/page.d.ts +8 -0
- package/dist/runtime/page.js +21 -0
- package/dist/sveltekit/index.d.ts +4 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/types/config.d.ts +1 -0
- package/package.json +8 -3
- package/src/types/component.ts +59 -0
- package/src/types/config.ts +37 -0
- package/src/types/image.ts +100 -0
- package/src/types/index.ts +9 -0
- package/src/types/layout.ts +29 -0
- package/src/types/media.ts +125 -0
- package/src/types/page.ts +48 -0
- package/src/types/render-mode.ts +18 -0
- package/src/types/renderer.ts +83 -0
- package/src/types/video.ts +66 -0
- package/bin/aci +0 -0
package/README.md
CHANGED
|
@@ -106,3 +106,22 @@ npm publish --access public
|
|
|
106
106
|
|
|
107
107
|
The package includes a `prepack` script that builds `dist` before packing or
|
|
108
108
|
publishing.
|
|
109
|
+
|
|
110
|
+
## Verify the Published Shape Locally
|
|
111
|
+
|
|
112
|
+
Before publishing, inspect the exact tarball npm will upload. This catches
|
|
113
|
+
package `files` mistakes where local source imports work from the repo but fail
|
|
114
|
+
after npm filters the packed files.
|
|
115
|
+
|
|
116
|
+
From `packages/sdk`:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
tmpdir="$(mktemp -d /tmp/gradial-aci-pack.XXXXXX)"
|
|
120
|
+
filename="$(npm pack --pack-destination "$tmpdir" | tail -n 1)"
|
|
121
|
+
tar -tzf "$tmpdir/$filename" | sort | rg '^(package/src/cli/|package/src/types/|package/dist/types/)'
|
|
122
|
+
echo "tarball=$tmpdir/$filename"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
If a shipped source CLI imports another source file, make sure both paths appear
|
|
126
|
+
in the tarball. For example, `src/cli/compile-registry.mjs` imports
|
|
127
|
+
`src/types/image.ts`, so both files must be included by `package.json#files`.
|
package/bin/aci.js
CHANGED
|
@@ -46,7 +46,7 @@ function resolvePackagedBinary() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function resolveWorkspaceBinary() {
|
|
49
|
-
const binaryPath = resolve(
|
|
49
|
+
const binaryPath = resolve(repoRoot, 'packages', `cli-${platformKey}`, 'bin', binName);
|
|
50
50
|
|
|
51
51
|
return existsSync(binaryPath) ? binaryPath : null;
|
|
52
52
|
}
|
package/dist/astro/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RenderInput } from '../content/types.js';
|
|
1
2
|
import { type GradialContentWatchOptions } from '../dev/index.js';
|
|
2
3
|
interface AstroConfigSetupParams {
|
|
3
4
|
injectScript(stage: 'head-inline' | 'page' | 'page-ssr', content: string): void;
|
|
@@ -31,6 +32,7 @@ export declare function getGradialStaticPaths(): Promise<Array<{
|
|
|
31
32
|
slug?: string;
|
|
32
33
|
};
|
|
33
34
|
}>>;
|
|
35
|
+
export declare function getPageRuntimeRenderInput(request: Request): RenderInput | null;
|
|
34
36
|
export interface GradialAstroOptions extends GradialContentWatchOptions {
|
|
35
37
|
/** Path to compiled content root. Defaults to ACI_CONTENT_ROOT env or '.aci/compiled' */
|
|
36
38
|
compiledRoot?: string;
|
package/dist/astro/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { getPendingRenderInputFromHeaders } from '../runtime/page.js';
|
|
3
4
|
import { gradialContentWatchPlugin, gradialDamAssetPlugin, devRefreshPort, devRefreshScript, } from '../dev/index.js';
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// Static paths helper — for Astro SSG
|
|
@@ -27,6 +28,9 @@ export async function getGradialStaticPaths() {
|
|
|
27
28
|
};
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
export function getPageRuntimeRenderInput(request) {
|
|
32
|
+
return getPendingRenderInputFromHeaders(request.headers);
|
|
33
|
+
}
|
|
30
34
|
export function gradialAstro(options = {}) {
|
|
31
35
|
return {
|
|
32
36
|
name: 'gradial-astro',
|
package/dist/next/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
|
|
2
|
+
import type { RenderInput } from '../content/types.js';
|
|
2
3
|
export { FragmentNotFoundError, PageNotFoundError };
|
|
3
4
|
export interface GradialFetchConfig {
|
|
4
5
|
siteId?: string;
|
|
@@ -25,9 +26,11 @@ export interface GradialSiteConfig {
|
|
|
25
26
|
defaultLocale?: string;
|
|
26
27
|
}
|
|
27
28
|
export declare function getPage<T = unknown>(route?: string, config?: GradialFetchConfig): Promise<T>;
|
|
29
|
+
export declare function getSiteConfig<T extends GradialSiteConfig = GradialSiteConfig>(config?: GradialFetchConfig): Promise<T>;
|
|
28
30
|
export declare function getFragment<T = unknown>(name: string, config?: GradialFetchConfig): Promise<T>;
|
|
29
31
|
export declare function getRoutes(config?: GradialFetchConfig): Promise<string[]>;
|
|
30
32
|
export declare function getRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig>(route?: string, config?: GradialFetchConfig): Promise<GradialRenderInput<TPage, TSite>>;
|
|
33
|
+
export declare function getPageRuntimeRenderInput(): Promise<RenderInput | null>;
|
|
31
34
|
export declare function routeFromNextParams(params?: Promise<{
|
|
32
35
|
slug?: string[];
|
|
33
36
|
}> | {
|
package/dist/next/server.js
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
import { headers } from 'next/headers';
|
|
2
2
|
import { FragmentNotFoundError, PageNotFoundError, } from '../content/provider.js';
|
|
3
3
|
import { S3ContentProvider } from '../providers/s3.js';
|
|
4
|
+
import { GRADIAL_RENDER_INPUT_HEADER, getPendingRenderInput } from '../runtime/page.js';
|
|
4
5
|
import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
5
6
|
const RELEASE_HEADER = 'x-gradial-release-id';
|
|
6
7
|
export { FragmentNotFoundError, PageNotFoundError };
|
|
7
8
|
export async function getPage(route = '', config = {}) {
|
|
8
|
-
const
|
|
9
|
+
const runtimeInput = await getPageRuntimeRenderInput();
|
|
10
|
+
if (runtimeInput)
|
|
11
|
+
return runtimeInput.page;
|
|
12
|
+
const provider = await resolveProviderWithFallback(config);
|
|
9
13
|
return await provider.getPage(route);
|
|
10
14
|
}
|
|
15
|
+
export async function getSiteConfig(config = {}) {
|
|
16
|
+
const runtimeInput = await getPageRuntimeRenderInput();
|
|
17
|
+
if (runtimeInput)
|
|
18
|
+
return runtimeInput.siteConfig;
|
|
19
|
+
const provider = await resolveProviderWithFallback(config);
|
|
20
|
+
return await provider.getSiteConfig();
|
|
21
|
+
}
|
|
11
22
|
export async function getFragment(name, config = {}) {
|
|
12
|
-
const provider = await
|
|
23
|
+
const provider = await resolveProviderWithFallback(config);
|
|
13
24
|
return await provider.getFragment(name);
|
|
14
25
|
}
|
|
15
26
|
export async function getRoutes(config = {}) {
|
|
16
|
-
const provider = await
|
|
27
|
+
const provider = await resolveProviderWithFallback(config);
|
|
17
28
|
const routes = await provider.listRoutes();
|
|
18
29
|
return routes.map((route) => route.path).sort();
|
|
19
30
|
}
|
|
20
31
|
export async function getRenderInput(route = '/', config = {}) {
|
|
21
|
-
const
|
|
32
|
+
const runtimeInput = await getPageRuntimeRenderInput();
|
|
33
|
+
if (runtimeInput) {
|
|
34
|
+
return runtimeInput;
|
|
35
|
+
}
|
|
36
|
+
const provider = await resolveProviderWithFallback(config);
|
|
22
37
|
const [page, siteConfig] = await Promise.all([
|
|
23
38
|
provider.getPage(route),
|
|
24
39
|
provider.getSiteConfig(),
|
|
@@ -31,6 +46,15 @@ export async function getRenderInput(route = '/', config = {}) {
|
|
|
31
46
|
page,
|
|
32
47
|
};
|
|
33
48
|
}
|
|
49
|
+
export async function getPageRuntimeRenderInput() {
|
|
50
|
+
try {
|
|
51
|
+
const current = await headers();
|
|
52
|
+
return getPendingRenderInput(current.get(GRADIAL_RENDER_INPUT_HEADER));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
34
58
|
export async function routeFromNextParams(params) {
|
|
35
59
|
if (!params)
|
|
36
60
|
return '/';
|
|
@@ -125,6 +149,13 @@ async function resolveProvider(config) {
|
|
|
125
149
|
endpoint: config.endpoint,
|
|
126
150
|
});
|
|
127
151
|
}
|
|
152
|
+
async function resolveProviderWithFallback(config) {
|
|
153
|
+
if (process.env.ACI_CONTENT_PROVIDER === 'file') {
|
|
154
|
+
const { FileContentProvider } = await import('../providers/file.js');
|
|
155
|
+
return new FileContentProvider();
|
|
156
|
+
}
|
|
157
|
+
return await resolveProvider(config);
|
|
158
|
+
}
|
|
128
159
|
async function releaseIdFromHeaders() {
|
|
129
160
|
try {
|
|
130
161
|
const current = await headers();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RenderInput } from '../content/types.js';
|
|
2
|
+
export declare const GRADIAL_RENDER_INPUT_HEADER = "x-gradial-render-input-id";
|
|
3
|
+
export declare const GRADIAL_PENDING_RENDER_INPUTS_KEY = "__GRADIAL_PENDING_RENDER_INPUTS__";
|
|
4
|
+
export declare function pendingRenderInputs(): Map<string, RenderInput>;
|
|
5
|
+
export declare function setPendingRenderInput(id: string, input: RenderInput): void;
|
|
6
|
+
export declare function deletePendingRenderInput(id: string): void;
|
|
7
|
+
export declare function getPendingRenderInput(id: string | null | undefined): RenderInput | null;
|
|
8
|
+
export declare function getPendingRenderInputFromHeaders(headers: Headers): RenderInput | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const GRADIAL_RENDER_INPUT_HEADER = 'x-gradial-render-input-id';
|
|
2
|
+
export const GRADIAL_PENDING_RENDER_INPUTS_KEY = '__GRADIAL_PENDING_RENDER_INPUTS__';
|
|
3
|
+
export function pendingRenderInputs() {
|
|
4
|
+
const global = globalThis;
|
|
5
|
+
global[GRADIAL_PENDING_RENDER_INPUTS_KEY] ??= new Map();
|
|
6
|
+
return global[GRADIAL_PENDING_RENDER_INPUTS_KEY];
|
|
7
|
+
}
|
|
8
|
+
export function setPendingRenderInput(id, input) {
|
|
9
|
+
pendingRenderInputs().set(id, input);
|
|
10
|
+
}
|
|
11
|
+
export function deletePendingRenderInput(id) {
|
|
12
|
+
pendingRenderInputs().delete(id);
|
|
13
|
+
}
|
|
14
|
+
export function getPendingRenderInput(id) {
|
|
15
|
+
if (!id)
|
|
16
|
+
return null;
|
|
17
|
+
return pendingRenderInputs().get(id) ?? null;
|
|
18
|
+
}
|
|
19
|
+
export function getPendingRenderInputFromHeaders(headers) {
|
|
20
|
+
return getPendingRenderInput(headers.get(GRADIAL_RENDER_INPUT_HEADER));
|
|
21
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type GradialContentWatchOptions, type VitePluginLike } from '../dev/index.js';
|
|
2
|
+
import type { RenderInput } from '../content/types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Generates prerender entries from the compiled content manifest for SvelteKit.
|
|
4
5
|
*
|
|
@@ -15,4 +16,7 @@ import { type GradialContentWatchOptions, type VitePluginLike } from '../dev/ind
|
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
export declare function gradialEntries(): Promise<string[]>;
|
|
19
|
+
export declare function getPageRuntimeRenderInput(event: {
|
|
20
|
+
request: Request;
|
|
21
|
+
}): RenderInput | null;
|
|
18
22
|
export declare function gradialSvelteKit(options?: GradialContentWatchOptions): VitePluginLike;
|
package/dist/sveltekit/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { gradialContentWatchPlugin, gradialDamAssetPlugin, devRefreshPort, devRefreshScriptTag, } from '../dev/index.js';
|
|
2
|
+
import { getPendingRenderInputFromHeaders } from '../runtime/page.js';
|
|
2
3
|
// ---------------------------------------------------------------------------
|
|
3
4
|
// Entries helper — for SvelteKit SSG prerendering
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
@@ -27,6 +28,9 @@ export async function gradialEntries() {
|
|
|
27
28
|
return normalized === '/' ? '/' : `/${normalized.replace(/^\/|\/$/g, '')}/`;
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
export function getPageRuntimeRenderInput(event) {
|
|
32
|
+
return getPendingRenderInputFromHeaders(event.request.headers);
|
|
33
|
+
}
|
|
30
34
|
// ---------------------------------------------------------------------------
|
|
31
35
|
// SvelteKit Vite plugin
|
|
32
36
|
// ---------------------------------------------------------------------------
|
package/dist/types/config.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"bin/aci.js",
|
|
10
10
|
"bin/aci",
|
|
11
11
|
"src/cli/*.mjs",
|
|
12
|
+
"src/types/*.ts",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"bin": {
|
|
@@ -58,6 +59,10 @@
|
|
|
58
59
|
"types": "./dist/assets/index.d.ts",
|
|
59
60
|
"import": "./dist/assets/index.js"
|
|
60
61
|
},
|
|
62
|
+
"./runtime": {
|
|
63
|
+
"types": "./dist/runtime/page.d.ts",
|
|
64
|
+
"import": "./dist/runtime/page.js"
|
|
65
|
+
},
|
|
61
66
|
"./react": {
|
|
62
67
|
"types": "./dist/react/index.d.ts",
|
|
63
68
|
"import": "./dist/react/index.js"
|
|
@@ -128,13 +133,13 @@
|
|
|
128
133
|
}
|
|
129
134
|
},
|
|
130
135
|
"devDependencies": {
|
|
131
|
-
"@vercel/edge-config": "^1.4.3",
|
|
132
136
|
"@types/node": "^24.10.1",
|
|
133
137
|
"@types/react": "^19.0.0",
|
|
138
|
+
"@vercel/edge-config": "^1.4.3",
|
|
134
139
|
"next": "^15.5.6",
|
|
135
140
|
"react": "^19.0.0",
|
|
136
141
|
"tsx": "^4.20.0",
|
|
137
|
-
"typescript": "^5.9.
|
|
142
|
+
"typescript": "^5.9.3",
|
|
138
143
|
"zod": "^4.0.0"
|
|
139
144
|
}
|
|
140
145
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IslandMode, VaryDimension } from './render-mode.js';
|
|
2
|
+
import type { ImageSlotContract } from './image.js';
|
|
3
|
+
import type { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
export interface ComponentRenderModes {
|
|
6
|
+
canStatic: boolean;
|
|
7
|
+
canSSR: boolean;
|
|
8
|
+
canClientIsland: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A CMS-registered component — sync or async (server components).
|
|
13
|
+
* Accepts content props derived from the Zod schema.
|
|
14
|
+
*/
|
|
15
|
+
type CmsComponentFn<TProps = any> =
|
|
16
|
+
| ((props: TProps) => import('react').ReactElement | null)
|
|
17
|
+
| ((props: TProps) => Promise<import('react').ReactElement | null>);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Infers the props type from a schema.
|
|
21
|
+
* If TSchema is a Zod type, returns z.infer<TSchema>.
|
|
22
|
+
* Otherwise returns Record<string, unknown>.
|
|
23
|
+
*/
|
|
24
|
+
type InferContentProps<TSchema> = TSchema extends z.ZodType<infer T> ? T : Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
export interface ComponentDefinition<TSchema = unknown> {
|
|
27
|
+
name: string;
|
|
28
|
+
/**
|
|
29
|
+
* The React component that renders this CMS block.
|
|
30
|
+
* Accepts at least the schema-derived props, but may accept additional
|
|
31
|
+
* composition params (className, etc.) passed by parent sections.
|
|
32
|
+
*/
|
|
33
|
+
component?: CmsComponentFn<any>;
|
|
34
|
+
schema: TSchema;
|
|
35
|
+
renderModes?: ComponentRenderModes;
|
|
36
|
+
render?: ComponentRenderModes;
|
|
37
|
+
defaultIslandMode?: IslandMode;
|
|
38
|
+
varyDimensions?: VaryDimension[];
|
|
39
|
+
vary?: VaryDimension[];
|
|
40
|
+
imageSlots?: Record<string, ImageSlotContract>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ComponentContract<TSchema = unknown> {
|
|
44
|
+
name: string;
|
|
45
|
+
component?: CmsComponentFn<any>;
|
|
46
|
+
schema: TSchema;
|
|
47
|
+
renderModes: ComponentRenderModes;
|
|
48
|
+
render: ComponentRenderModes;
|
|
49
|
+
defaultIslandMode?: IslandMode;
|
|
50
|
+
varyDimensions?: VaryDimension[];
|
|
51
|
+
vary?: VaryDimension[];
|
|
52
|
+
imageSlots?: Record<string, ImageSlotContract>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type InferComponentProps<TContract> = TContract extends ComponentContract<infer TSchema>
|
|
56
|
+
? InferContentProps<TSchema>
|
|
57
|
+
: Record<string, unknown>;
|
|
58
|
+
|
|
59
|
+
export type { CmsComponentFn, InferContentProps };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RenderCapabilities } from './renderer.js';
|
|
2
|
+
|
|
3
|
+
export interface BareMetalConfig {
|
|
4
|
+
version: '1';
|
|
5
|
+
siteId: string;
|
|
6
|
+
framework: 'astro' | 'next' | 'sveltekit' | 'custom';
|
|
7
|
+
source: {
|
|
8
|
+
root: string;
|
|
9
|
+
outDir?: string;
|
|
10
|
+
publicDir?: string;
|
|
11
|
+
};
|
|
12
|
+
componentRegistry: string;
|
|
13
|
+
layoutRegistry: string;
|
|
14
|
+
rendererEntry: string;
|
|
15
|
+
capabilities: RenderCapabilities;
|
|
16
|
+
routes: RouteConfig;
|
|
17
|
+
dam?: DAMConfig;
|
|
18
|
+
externalDependencies?: ExternalDependency[];
|
|
19
|
+
rendererProtocol: 'http' | 'stdio-json';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RouteConfig {
|
|
23
|
+
cmsManaged: string;
|
|
24
|
+
frameworkOwned?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExternalDependency {
|
|
28
|
+
name: string;
|
|
29
|
+
host: string;
|
|
30
|
+
kind?: 'api' | 'client-script';
|
|
31
|
+
description?: string;
|
|
32
|
+
cacheTTL?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DAMConfig {
|
|
36
|
+
autoApproveUploads?: boolean;
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export interface ImageSource {
|
|
4
|
+
src: string;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
type: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PictureSource {
|
|
11
|
+
media?: string;
|
|
12
|
+
type: string;
|
|
13
|
+
srcset: string;
|
|
14
|
+
sizes?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GradialImage {
|
|
18
|
+
$type: 'gradial.image';
|
|
19
|
+
assetId: string;
|
|
20
|
+
versionId: string;
|
|
21
|
+
alt: string;
|
|
22
|
+
fallback: ImageSource;
|
|
23
|
+
sources: PictureSource[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ImageSlotContract {
|
|
27
|
+
outputs: SlotOutput[];
|
|
28
|
+
formats: string[];
|
|
29
|
+
sizes: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SlotOutput {
|
|
33
|
+
aspectRatio: string;
|
|
34
|
+
widths: number[];
|
|
35
|
+
media?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const GradialImageSchema = z.object({
|
|
39
|
+
$type: z.literal('gradial.image'),
|
|
40
|
+
assetId: z.string().min(1),
|
|
41
|
+
versionId: z.string().min(1),
|
|
42
|
+
alt: z.string(),
|
|
43
|
+
fallback: z.object({
|
|
44
|
+
src: z.string().min(1),
|
|
45
|
+
width: z.number().int().nonnegative(),
|
|
46
|
+
height: z.number().int().nonnegative(),
|
|
47
|
+
type: z.string().min(1),
|
|
48
|
+
}),
|
|
49
|
+
sources: z.array(z.object({
|
|
50
|
+
media: z.string().optional(),
|
|
51
|
+
type: z.string().min(1),
|
|
52
|
+
srcset: z.string().min(1),
|
|
53
|
+
sizes: z.string().optional(),
|
|
54
|
+
})).nullable().default([]),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export type ImageHTMLAttributes = Record<string, string | number | boolean | undefined | null>;
|
|
58
|
+
|
|
59
|
+
export function renderImageHTML(image: GradialImage, attrs: ImageHTMLAttributes = {}): string {
|
|
60
|
+
const imgAttrs = renderAttrs({
|
|
61
|
+
...attrs,
|
|
62
|
+
src: image.fallback.src,
|
|
63
|
+
alt: image.alt,
|
|
64
|
+
width: image.fallback.width > 0 ? image.fallback.width : undefined,
|
|
65
|
+
height: image.fallback.height > 0 ? image.fallback.height : undefined,
|
|
66
|
+
});
|
|
67
|
+
const img = `<img${imgAttrs}>`;
|
|
68
|
+
if (!image.sources.length) {
|
|
69
|
+
return img;
|
|
70
|
+
}
|
|
71
|
+
const sources = image.sources.map((source) => `<source${renderAttrs({
|
|
72
|
+
media: source.media,
|
|
73
|
+
type: source.type,
|
|
74
|
+
srcset: source.srcset,
|
|
75
|
+
sizes: source.sizes,
|
|
76
|
+
})}>`).join('');
|
|
77
|
+
return `<picture>${sources}${img}</picture>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderAttrs(attrs: ImageHTMLAttributes): string {
|
|
81
|
+
const rendered = Object.entries(attrs)
|
|
82
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== false)
|
|
83
|
+
.map(([name, value]) => value === true ? escapeName(name) : `${escapeName(name)}="${escapeAttr(String(value))}"`);
|
|
84
|
+
if (!rendered.length) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
return ' ' + rendered.join(' ');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function escapeName(name: string): string {
|
|
91
|
+
return name.replace(/[^A-Za-z0-9_:-]/g, '');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function escapeAttr(value: string): string {
|
|
95
|
+
return value
|
|
96
|
+
.replace(/&/g, '&')
|
|
97
|
+
.replace(/"/g, '"')
|
|
98
|
+
.replace(/</g, '<')
|
|
99
|
+
.replace(/>/g, '>');
|
|
100
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './config.js';
|
|
2
|
+
export * from './component.js';
|
|
3
|
+
export * from './layout.js';
|
|
4
|
+
export * from './page.js';
|
|
5
|
+
export * from './renderer.js';
|
|
6
|
+
export * from './render-mode.js';
|
|
7
|
+
export * from './image.js';
|
|
8
|
+
export * from './video.js';
|
|
9
|
+
export * from './media.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FragmentRefNode } from './page.js';
|
|
2
|
+
|
|
3
|
+
export interface LayoutSlot {
|
|
4
|
+
name: string;
|
|
5
|
+
required: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Default content for non-required layout slots, keyed by slot name. */
|
|
9
|
+
export type LayoutDefaults = Record<string, FragmentRefNode[]>;
|
|
10
|
+
|
|
11
|
+
export interface LayoutDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
slots: LayoutSlot[];
|
|
14
|
+
/** Default fragment references for slots not provided by the page. */
|
|
15
|
+
defaults?: LayoutDefaults;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LayoutContract {
|
|
19
|
+
name: string;
|
|
20
|
+
slots: LayoutSlot[];
|
|
21
|
+
defaults?: LayoutDefaults;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A compiled layout file as stored on disk. */
|
|
25
|
+
export interface CompiledLayout {
|
|
26
|
+
name: string;
|
|
27
|
+
slots: LayoutSlot[];
|
|
28
|
+
defaults: LayoutDefaults;
|
|
29
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GradialImageSchema, renderImageHTML, type GradialImage } from './image.js';
|
|
3
|
+
import { GradialVideoSchema, type GradialVideo } from './video.js';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// GradialAsset — union of all DAM-backed asset types
|
|
7
|
+
//
|
|
8
|
+
// Content authors write: { "$type": "dam.assetRef", "assetId": "..." }
|
|
9
|
+
// The compiler resolves this to a GradialImage or GradialVideo based on
|
|
10
|
+
// the asset's media type in the DAM.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/** Discriminated union of all compiler-resolved asset types. */
|
|
14
|
+
export type GradialAsset = GradialImage | GradialVideo;
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Zod schema — discriminated on $type
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export const GradialAssetSchema = z.discriminatedUnion('$type', [
|
|
21
|
+
GradialImageSchema,
|
|
22
|
+
GradialVideoSchema,
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Type guards
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export function isGradialImage(value: unknown): value is GradialImage {
|
|
30
|
+
return (
|
|
31
|
+
typeof value === 'object' &&
|
|
32
|
+
value !== null &&
|
|
33
|
+
(value as Record<string, unknown>).$type === 'gradial.image'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isGradialVideo(value: unknown): value is GradialVideo {
|
|
38
|
+
return (
|
|
39
|
+
typeof value === 'object' &&
|
|
40
|
+
value !== null &&
|
|
41
|
+
(value as Record<string, unknown>).$type === 'gradial.video'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isGradialAsset(value: unknown): value is GradialAsset {
|
|
46
|
+
return isGradialImage(value) || isGradialVideo(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// HTML string renderers (framework-agnostic)
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Renders a GradialVideo to an HTML string.
|
|
55
|
+
* Uses poster.fallback.src for the poster attribute.
|
|
56
|
+
* Includes <source> elements for format variants.
|
|
57
|
+
*/
|
|
58
|
+
export function renderVideoHTML(
|
|
59
|
+
video: GradialVideo,
|
|
60
|
+
attrs: Record<string, string | number | boolean | undefined | null> = {},
|
|
61
|
+
): string {
|
|
62
|
+
const posterSrc = video.poster?.fallback.src;
|
|
63
|
+
const attrPairs: Array<[string, string | number | boolean | undefined | null]> = [
|
|
64
|
+
...Object.entries(attrs),
|
|
65
|
+
['src', video.sources?.length ? undefined : video.src],
|
|
66
|
+
['poster', posterSrc],
|
|
67
|
+
['width', video.width > 0 ? video.width : undefined],
|
|
68
|
+
['height', video.height > 0 ? video.height : undefined],
|
|
69
|
+
];
|
|
70
|
+
const attrString = renderAttrs(attrPairs);
|
|
71
|
+
|
|
72
|
+
const sources = (video.sources ?? [])
|
|
73
|
+
.map((s) => `<source${renderAttrs([['src', s.src], ['type', s.type]])}>`)
|
|
74
|
+
.join('');
|
|
75
|
+
|
|
76
|
+
// If no format variants, src is on the <video> element itself
|
|
77
|
+
if (!sources) {
|
|
78
|
+
return `<video${attrString}></video>`;
|
|
79
|
+
}
|
|
80
|
+
return `<video${attrString}>${sources}</video>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Renders any GradialAsset to an HTML string.
|
|
85
|
+
* Delegates to renderImageHTML or renderVideoHTML based on $type.
|
|
86
|
+
*/
|
|
87
|
+
export function renderAssetHTML(
|
|
88
|
+
asset: GradialAsset,
|
|
89
|
+
attrs: Record<string, string | number | boolean | undefined | null> = {},
|
|
90
|
+
): string {
|
|
91
|
+
if (asset.$type === 'gradial.image') {
|
|
92
|
+
return renderImageHTML(asset, attrs);
|
|
93
|
+
}
|
|
94
|
+
return renderVideoHTML(asset, attrs);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Internal helpers
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
function renderAttrs(
|
|
102
|
+
pairs: Array<[string, string | number | boolean | undefined | null]>,
|
|
103
|
+
): string {
|
|
104
|
+
const rendered = pairs
|
|
105
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== false)
|
|
106
|
+
.map(([name, value]) =>
|
|
107
|
+
value === true
|
|
108
|
+
? escapeName(name)
|
|
109
|
+
: `${escapeName(name)}="${escapeAttr(String(value))}"`,
|
|
110
|
+
);
|
|
111
|
+
if (!rendered.length) return '';
|
|
112
|
+
return ' ' + rendered.join(' ');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function escapeName(name: string): string {
|
|
116
|
+
return name.replace(/[^A-Za-z0-9_:-]/g, '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function escapeAttr(value: string): string {
|
|
120
|
+
return value
|
|
121
|
+
.replace(/&/g, '&')
|
|
122
|
+
.replace(/"/g, '"')
|
|
123
|
+
.replace(/</g, '<')
|
|
124
|
+
.replace(/>/g, '>');
|
|
125
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RenderMode, VaryDimension } from './render-mode.js';
|
|
2
|
+
|
|
3
|
+
export interface PageDocument {
|
|
4
|
+
path: string;
|
|
5
|
+
layout: string;
|
|
6
|
+
locale: string;
|
|
7
|
+
metadata: PageMetadata;
|
|
8
|
+
regions: Record<string, RegionNode>;
|
|
9
|
+
renderMode: RenderMode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PageMetadata {
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
openGraph?: Record<string, string>;
|
|
16
|
+
canonicalUrl?: string;
|
|
17
|
+
alternateLocales?: { locale: string; path: string }[];
|
|
18
|
+
customHead?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RegionNode {
|
|
22
|
+
kind: 'region';
|
|
23
|
+
name: string;
|
|
24
|
+
children: Array<BlockNode | FragmentRefNode>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BlockNode {
|
|
28
|
+
kind: 'block';
|
|
29
|
+
id: string;
|
|
30
|
+
component: string;
|
|
31
|
+
props: Record<string, unknown>;
|
|
32
|
+
renderHints: BlockRenderHints;
|
|
33
|
+
contentRef?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BlockRenderHints {
|
|
37
|
+
canStatic: boolean;
|
|
38
|
+
needsSSR: boolean;
|
|
39
|
+
isIsland: boolean;
|
|
40
|
+
islandMode?: 'ssr' | 'client';
|
|
41
|
+
varyDimensions?: VaryDimension[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface FragmentRefNode {
|
|
45
|
+
kind: 'fragment-ref';
|
|
46
|
+
fragmentId: string;
|
|
47
|
+
inline: boolean;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RenderMode =
|
|
2
|
+
| 'static'
|
|
3
|
+
| 'static-with-fragments'
|
|
4
|
+
| 'prerender-on-demand'
|
|
5
|
+
| 'ssr-page'
|
|
6
|
+
| 'ssr-island'
|
|
7
|
+
| 'client-island';
|
|
8
|
+
|
|
9
|
+
export type IslandMode = 'ssr' | 'client';
|
|
10
|
+
|
|
11
|
+
export type VaryDimension =
|
|
12
|
+
| 'geo:country'
|
|
13
|
+
| 'geo:region'
|
|
14
|
+
| 'geo:city'
|
|
15
|
+
| `cookie:${string}`
|
|
16
|
+
| `header:${string}`
|
|
17
|
+
| 'locale'
|
|
18
|
+
| `query:${string}`;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { PageDocument } from './page.js';
|
|
2
|
+
import type { VaryDimension } from './render-mode.js';
|
|
3
|
+
|
|
4
|
+
export interface RenderCapabilities {
|
|
5
|
+
staticRender: boolean;
|
|
6
|
+
ssr: boolean;
|
|
7
|
+
ssrIslands: boolean;
|
|
8
|
+
clientIslands: boolean;
|
|
9
|
+
fragmentRender: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GradialRenderer {
|
|
13
|
+
renderPage(request: RenderPageRequest): Promise<RenderPageResult>;
|
|
14
|
+
renderFragment?(request: RenderFragmentRequest): Promise<RenderResult>;
|
|
15
|
+
renderIsland?(request: RenderIslandRequest): Promise<RenderResult>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RenderPageRequest {
|
|
19
|
+
page: PageDocument;
|
|
20
|
+
requestContext?: RequestContext;
|
|
21
|
+
release: ReleaseContext;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RequestContext {
|
|
25
|
+
url: string;
|
|
26
|
+
method: string;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
cookies: Record<string, string>;
|
|
29
|
+
geo?: GeoContext;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GeoContext {
|
|
33
|
+
country?: string;
|
|
34
|
+
region?: string;
|
|
35
|
+
city?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ReleaseContext {
|
|
39
|
+
releaseId: string;
|
|
40
|
+
codeDigest: string;
|
|
41
|
+
contentSnapshotId: string;
|
|
42
|
+
assetUrl(logicalPath: string): string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RenderFragmentRequest {
|
|
46
|
+
fragmentId: string;
|
|
47
|
+
content: unknown;
|
|
48
|
+
release: ReleaseContext;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RenderIslandRequest {
|
|
52
|
+
islandId: string;
|
|
53
|
+
component: string;
|
|
54
|
+
props: Record<string, unknown>;
|
|
55
|
+
requestContext: RequestContext;
|
|
56
|
+
release: ReleaseContext;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RenderResult {
|
|
60
|
+
html: string;
|
|
61
|
+
status?: number;
|
|
62
|
+
headers?: Record<string, string>;
|
|
63
|
+
cachePolicy?: CachePolicy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RenderPageResult extends RenderResult {
|
|
67
|
+
islands?: IslandDescriptor[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface IslandDescriptor {
|
|
71
|
+
islandId: string;
|
|
72
|
+
component: string;
|
|
73
|
+
props: Record<string, unknown>;
|
|
74
|
+
mode: 'ssr' | 'client';
|
|
75
|
+
varyDimensions?: VaryDimension[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CachePolicy {
|
|
79
|
+
scope: 'public' | 'private' | 'no-store';
|
|
80
|
+
ttl?: number;
|
|
81
|
+
staleWhileRevalidate?: number;
|
|
82
|
+
tags?: string[];
|
|
83
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GradialImageSchema, type GradialImage } from './image.js';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Video source variant (format alternatives)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export interface VideoSource {
|
|
9
|
+
src: string;
|
|
10
|
+
type: string; // MIME type: video/mp4, video/webm
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// GradialVideo — DAM-backed video asset with compiler-resolved derivatives
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface GradialVideo {
|
|
18
|
+
/** Discriminator — always 'gradial.video'. */
|
|
19
|
+
$type: 'gradial.video';
|
|
20
|
+
/** DAM asset identifier. */
|
|
21
|
+
assetId: string;
|
|
22
|
+
/** DAM version identifier. */
|
|
23
|
+
versionId: string;
|
|
24
|
+
/** Accessible description of the video content. */
|
|
25
|
+
alt: string;
|
|
26
|
+
/** Primary video URL (compiler-resolved). */
|
|
27
|
+
src: string;
|
|
28
|
+
/** Primary video MIME type (e.g. 'video/mp4'). */
|
|
29
|
+
type: string;
|
|
30
|
+
/** Intrinsic width in pixels. */
|
|
31
|
+
width: number;
|
|
32
|
+
/** Intrinsic height in pixels. */
|
|
33
|
+
height: number;
|
|
34
|
+
/**
|
|
35
|
+
* Poster frame — a full GradialImage with responsive srcset.
|
|
36
|
+
* The compiler extracts or generates this from the video asset.
|
|
37
|
+
*/
|
|
38
|
+
poster?: GradialImage;
|
|
39
|
+
/** Format variants (e.g. mp4 + webm). Maps to <video><source> elements. */
|
|
40
|
+
sources?: VideoSource[];
|
|
41
|
+
/** Duration in seconds (if known). */
|
|
42
|
+
duration?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Zod schema
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export const VideoSourceSchema = z.object({
|
|
50
|
+
src: z.string().min(1),
|
|
51
|
+
type: z.string().min(1),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const GradialVideoSchema = z.object({
|
|
55
|
+
$type: z.literal('gradial.video'),
|
|
56
|
+
assetId: z.string().min(1),
|
|
57
|
+
versionId: z.string().min(1),
|
|
58
|
+
alt: z.string(),
|
|
59
|
+
src: z.string().min(1),
|
|
60
|
+
type: z.string().min(1),
|
|
61
|
+
width: z.number().int().nonnegative(),
|
|
62
|
+
height: z.number().int().nonnegative(),
|
|
63
|
+
poster: GradialImageSchema.optional(),
|
|
64
|
+
sources: z.array(VideoSourceSchema).optional(),
|
|
65
|
+
duration: z.number().nonnegative().optional(),
|
|
66
|
+
});
|
package/bin/aci
DELETED
|
Binary file
|