@gradial/aci 0.1.0 → 0.1.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 (68) hide show
  1. package/README.md +47 -2
  2. package/bin/aci +0 -0
  3. package/bin/aci.js +157 -0
  4. package/dist/assets/index.d.ts +3 -0
  5. package/dist/assets/index.js +3 -0
  6. package/dist/astro/index.d.ts +24 -2
  7. package/dist/astro/index.js +42 -4
  8. package/dist/block-ref.d.ts +34 -0
  9. package/dist/block-ref.js +34 -0
  10. package/dist/content/index.d.ts +0 -3
  11. package/dist/content/index.js +0 -3
  12. package/dist/content/provider.d.ts +32 -8
  13. package/dist/content/provider.js +26 -16
  14. package/dist/content/routes.d.ts +6 -12
  15. package/dist/content/routes.js +9 -55
  16. package/dist/content/validation.js +1 -1
  17. package/dist/define-component.d.ts +1 -0
  18. package/dist/define-component.js +1 -0
  19. package/dist/define-layout.d.ts +1 -0
  20. package/dist/define-layout.js +6 -1
  21. package/dist/dev/browser.d.ts +1 -1
  22. package/dist/dev/browser.js +1 -1
  23. package/dist/dev/index.d.ts +9 -3
  24. package/dist/dev/index.js +74 -8
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +4 -0
  27. package/dist/next/asset-route.d.ts +9 -0
  28. package/dist/next/asset-route.js +15 -0
  29. package/dist/next/config.d.ts +6 -0
  30. package/dist/next/config.js +25 -0
  31. package/dist/next/dev-refresh.js +4 -4
  32. package/dist/next/edge-config.d.ts +1 -0
  33. package/dist/next/edge-config.js +92 -0
  34. package/dist/next/index.d.ts +2 -0
  35. package/dist/next/index.js +2 -0
  36. package/dist/next/middleware.js +4 -6
  37. package/dist/next/server.d.ts +9 -24
  38. package/dist/next/server.js +100 -152
  39. package/dist/providers/file.d.ts +11 -17
  40. package/dist/providers/file.js +44 -78
  41. package/dist/providers/s3.d.ts +26 -0
  42. package/dist/providers/s3.js +174 -0
  43. package/dist/react/GradialMedia.d.ts +24 -0
  44. package/dist/react/GradialMedia.js +31 -0
  45. package/dist/react/GradialPicture.d.ts +14 -0
  46. package/dist/react/GradialPicture.js +30 -0
  47. package/dist/react/GradialVideoPlayer.d.ts +13 -0
  48. package/dist/react/GradialVideoPlayer.js +28 -0
  49. package/dist/react/index.d.ts +3 -0
  50. package/dist/react/index.js +3 -0
  51. package/dist/sveltekit/index.d.ts +18 -2
  52. package/dist/sveltekit/index.js +40 -4
  53. package/dist/testing/index.d.ts +14 -12
  54. package/dist/testing/index.js +41 -28
  55. package/dist/types/component.d.ts +24 -2
  56. package/dist/types/config.d.ts +4 -0
  57. package/dist/types/image.d.ts +51 -0
  58. package/dist/types/image.js +58 -0
  59. package/dist/types/index.d.ts +3 -0
  60. package/dist/types/index.js +3 -0
  61. package/dist/types/layout.d.ts +12 -0
  62. package/dist/types/media.d.ts +69 -0
  63. package/dist/types/media.js +86 -0
  64. package/dist/types/video.d.ts +70 -0
  65. package/dist/types/video.js +22 -0
  66. package/package.json +30 -2
  67. package/src/cli/compile-registry.mjs +303 -0
  68. package/src/cli/validate-content.mjs +489 -0
@@ -1,5 +1,5 @@
1
1
  export declare const DEFAULT_DEV_REFRESH_PORT = 24680;
2
- export declare const DEFAULT_DEV_REFRESH_PATH = "/baremetal-dev";
2
+ export declare const DEFAULT_DEV_REFRESH_PATH = "/aci-dev";
3
3
  export interface DevRefreshOptions {
4
4
  wsPort?: number | string;
5
5
  path?: string;
@@ -1,2 +1,2 @@
1
1
  export const DEFAULT_DEV_REFRESH_PORT = 24680;
2
- export const DEFAULT_DEV_REFRESH_PATH = '/baremetal-dev';
2
+ export const DEFAULT_DEV_REFRESH_PATH = '/aci-dev';
@@ -1,15 +1,20 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
1
2
  import { type DevRefreshOptions } from './browser.js';
2
3
  export { DEFAULT_DEV_REFRESH_PATH, DEFAULT_DEV_REFRESH_PORT, type DevRefreshOptions } from './browser.js';
3
4
  declare global {
4
5
  interface Window {
5
- __bareMetalDevRefresh?: boolean;
6
+ __gradialDevRefresh?: boolean;
6
7
  }
7
8
  }
8
- export interface BareMetalContentWatchOptions extends DevRefreshOptions {
9
+ export interface GradialContentWatchOptions extends DevRefreshOptions {
9
10
  contentRoot?: string;
11
+ compiledRoot?: string;
10
12
  enabled?: boolean;
11
13
  }
12
14
  interface ViteDevServerLike {
15
+ middlewares?: {
16
+ use(path: string, handler: (req: IncomingMessage, res: ServerResponse, next: () => void) => void): void;
17
+ };
13
18
  watcher: {
14
19
  add(path: string): void;
15
20
  on(event: string, callback: (eventName: string, filePath: string) => void): void;
@@ -27,4 +32,5 @@ export interface VitePluginLike {
27
32
  export declare function devRefreshPort(options?: DevRefreshOptions): number;
28
33
  export declare function devRefreshScript(options?: DevRefreshOptions): string;
29
34
  export declare function devRefreshScriptTag(options?: DevRefreshOptions): string;
30
- export declare function baremetalContentWatchPlugin(options?: BareMetalContentWatchOptions): VitePluginLike;
35
+ export declare function gradialContentWatchPlugin(options?: GradialContentWatchOptions): VitePluginLike;
36
+ export declare function gradialDamAssetPlugin(options?: GradialContentWatchOptions): VitePluginLike;
package/dist/dev/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import fs from 'node:fs/promises';
1
2
  import path from 'node:path';
2
- import { contentRoot } from '../content/routes.js';
3
+ import { compiledContentRoot } from '../content/routes.js';
3
4
  import { DEFAULT_DEV_REFRESH_PATH, DEFAULT_DEV_REFRESH_PORT, } from './browser.js';
4
5
  export { DEFAULT_DEV_REFRESH_PATH, DEFAULT_DEV_REFRESH_PORT } from './browser.js';
5
6
  function numberOption(value, fallback) {
@@ -10,7 +11,7 @@ function numberOption(value, fallback) {
10
11
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
11
12
  }
12
13
  export function devRefreshPort(options = {}) {
13
- const value = options.wsPort ?? process.env.BARE_METAL_DEV_WS_PORT;
14
+ const value = options.wsPort ?? process.env.ACI_DEV_WS_PORT;
14
15
  if (value === 0 || value === '0') {
15
16
  return 0;
16
17
  }
@@ -24,8 +25,8 @@ export function devRefreshScript(options = {}) {
24
25
  const wsPath = options.path || DEFAULT_DEV_REFRESH_PATH;
25
26
  const reconnectMs = numberOption(options.reconnectMs, 1000);
26
27
  return `(() => {
27
- if (typeof window === 'undefined' || window.__bareMetalDevRefresh) return;
28
- window.__bareMetalDevRefresh = true;
28
+ if (typeof window === 'undefined' || window.__gradialDevRefresh) return;
29
+ window.__gradialDevRefresh = true;
29
30
  const port = ${JSON.stringify(wsPort)};
30
31
  const path = ${JSON.stringify(wsPath)};
31
32
  const reconnectMs = ${JSON.stringify(reconnectMs)};
@@ -47,16 +48,16 @@ export function devRefreshScript(options = {}) {
47
48
  export function devRefreshScriptTag(options = {}) {
48
49
  return `<script>${devRefreshScript(options)}</script>`;
49
50
  }
50
- export function baremetalContentWatchPlugin(options = {}) {
51
- const enabled = options.enabled ?? process.env.BARE_METAL_DISABLE_CONTENT_WATCH !== '1';
51
+ export function gradialContentWatchPlugin(options = {}) {
52
+ const enabled = options.enabled ?? process.env.ACI_DISABLE_CONTENT_WATCH !== '1';
52
53
  return {
53
- name: 'baremetal-content-watch',
54
+ name: 'gradial-content-watch',
54
55
  apply: 'serve',
55
56
  configureServer(server) {
56
57
  if (!enabled) {
57
58
  return;
58
59
  }
59
- const root = path.resolve(options.contentRoot || contentRoot());
60
+ const root = path.resolve(options.contentRoot || compiledContentRoot());
60
61
  server.watcher.add(root);
61
62
  server.watcher.on('all', (_eventName, filePath) => {
62
63
  const changed = path.resolve(filePath);
@@ -68,3 +69,68 @@ export function baremetalContentWatchPlugin(options = {}) {
68
69
  },
69
70
  };
70
71
  }
72
+ export function gradialDamAssetPlugin(options = {}) {
73
+ return {
74
+ name: 'gradial-dam-assets',
75
+ apply: 'serve',
76
+ configureServer(server) {
77
+ if (!server.middlewares) {
78
+ return;
79
+ }
80
+ server.middlewares.use('/.gradial-dam', async (req, res, next) => {
81
+ const root = path.resolve(options.compiledRoot || compiledContentRoot());
82
+ const requestPath = safeRequestPath(req.url || '');
83
+ if (!requestPath) {
84
+ res.statusCode = 404;
85
+ res.end('asset not found');
86
+ return;
87
+ }
88
+ const filePath = path.resolve(root, '.gradial-dam', requestPath.replace(/^\/+/, ''));
89
+ if (!isInside(root, filePath)) {
90
+ res.statusCode = 404;
91
+ res.end('asset not found');
92
+ return;
93
+ }
94
+ try {
95
+ const body = await fs.readFile(filePath);
96
+ res.statusCode = 200;
97
+ res.setHeader('Content-Type', contentType(filePath));
98
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
99
+ res.end(body);
100
+ }
101
+ catch {
102
+ next();
103
+ }
104
+ });
105
+ },
106
+ };
107
+ }
108
+ function safeRequestPath(url) {
109
+ const rawPath = url.split('?')[0] || '';
110
+ try {
111
+ const decoded = decodeURIComponent(rawPath);
112
+ return decoded.startsWith('/.gradial-dam/') ? decoded.slice('/.gradial-dam/'.length) : decoded;
113
+ }
114
+ catch {
115
+ return '';
116
+ }
117
+ }
118
+ function isInside(root, filePath) {
119
+ const rel = path.relative(root, filePath);
120
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
121
+ }
122
+ function contentType(filePath) {
123
+ switch (path.extname(filePath).toLowerCase()) {
124
+ case '.webp':
125
+ return 'image/webp';
126
+ case '.svg':
127
+ return 'image/svg+xml';
128
+ case '.jpg':
129
+ case '.jpeg':
130
+ return 'image/jpeg';
131
+ case '.png':
132
+ return 'image/png';
133
+ default:
134
+ return 'application/octet-stream';
135
+ }
136
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './block-ref.js';
1
2
  export * from './define-component.js';
2
3
  export * from './define-layout.js';
3
4
  export * from './types/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ export * from './block-ref.js';
1
2
  export * from './define-component.js';
2
3
  export * from './define-layout.js';
3
4
  export * from './types/index.js';
5
+ // Note: content/ is NOT re-exported here because it imports node:path,
6
+ // making it incompatible with client-side bundlers. Import server-only
7
+ // content utilities from '@gradial/aci/content' explicitly.
@@ -0,0 +1,9 @@
1
+ type AssetRouteContext = {
2
+ params: Promise<AssetRouteParams>;
3
+ };
4
+ type AssetRouteParams = {
5
+ releaseId?: string;
6
+ path?: string[];
7
+ };
8
+ export declare function GET(_request: Request, { params }: AssetRouteContext): Promise<Response>;
9
+ export {};
@@ -0,0 +1,15 @@
1
+ import { getReleaseAssetResponse } from './server.js';
2
+ export async function GET(_request, { params }) {
3
+ const resolvedParams = await params;
4
+ const releaseId = resolvedParams.releaseId || '';
5
+ const assetPath = (resolvedParams.path || []).join('/');
6
+ if (!releaseId || !assetPath) {
7
+ return new Response('asset not found', { status: 404 });
8
+ }
9
+ try {
10
+ return await getReleaseAssetResponse(releaseId, assetPath);
11
+ }
12
+ catch {
13
+ return new Response('asset not found', { status: 404 });
14
+ }
15
+ }
@@ -0,0 +1,6 @@
1
+ import type { NextConfig } from 'next';
2
+ export interface WithGradialOptions {
3
+ /** Asset rewrite prefix. Defaults to '/.gradial-dam' */
4
+ assetPrefix?: string;
5
+ }
6
+ export declare function withGradial(nextConfig?: NextConfig, options?: WithGradialOptions): NextConfig;
@@ -0,0 +1,25 @@
1
+ export function withGradial(nextConfig = {}, options = {}) {
2
+ const assetPrefix = options.assetPrefix || '/.gradial-dam';
3
+ const userRewrites = nextConfig.rewrites;
4
+ return {
5
+ ...nextConfig,
6
+ rewrites: async () => {
7
+ const gradialRewrites = [
8
+ {
9
+ source: `${assetPrefix}/:path*`,
10
+ destination: `/api/aci-assets${assetPrefix}/:path*`,
11
+ },
12
+ ];
13
+ if (!userRewrites)
14
+ return gradialRewrites;
15
+ const userResult = await (typeof userRewrites === 'function' ? userRewrites() : userRewrites);
16
+ if (Array.isArray(userResult)) {
17
+ return [...gradialRewrites, ...userResult];
18
+ }
19
+ return {
20
+ ...userResult,
21
+ beforeFiles: [...gradialRewrites, ...(userResult.beforeFiles ?? [])],
22
+ };
23
+ },
24
+ };
25
+ }
@@ -3,10 +3,10 @@ import { useEffect } from 'react';
3
3
  import { DEFAULT_DEV_REFRESH_PATH, DEFAULT_DEV_REFRESH_PORT, } from '../dev/browser.js';
4
4
  export function DevRefresh({ enabled = process.env.NODE_ENV !== 'production', wsPort = DEFAULT_DEV_REFRESH_PORT, path = DEFAULT_DEV_REFRESH_PATH, reconnectMs = 1000, }) {
5
5
  useEffect(() => {
6
- if (!enabled || Number(wsPort) <= 0 || typeof window === 'undefined' || window.__bareMetalDevRefresh) {
6
+ if (!enabled || Number(wsPort) <= 0 || typeof window === 'undefined' || window.__gradialDevRefresh) {
7
7
  return undefined;
8
8
  }
9
- window.__bareMetalDevRefresh = true;
9
+ window.__gradialDevRefresh = true;
10
10
  let reconnectTimer;
11
11
  let socket;
12
12
  let active = true;
@@ -21,7 +21,7 @@ export function DevRefresh({ enabled = process.env.NODE_ENV !== 'production', ws
21
21
  }
22
22
  }
23
23
  catch {
24
- // Ignore non-Bare Metal dev server frames.
24
+ // Ignore non-Gradial dev server frames.
25
25
  }
26
26
  });
27
27
  socket.addEventListener('close', () => {
@@ -33,7 +33,7 @@ export function DevRefresh({ enabled = process.env.NODE_ENV !== 'production', ws
33
33
  connect();
34
34
  return () => {
35
35
  active = false;
36
- window.__bareMetalDevRefresh = false;
36
+ window.__gradialDevRefresh = false;
37
37
  if (reconnectTimer) {
38
38
  clearTimeout(reconnectTimer);
39
39
  }
@@ -0,0 +1 @@
1
+ export declare function getUncachedEdgeConfigValue(edgeConfig: string, key: string): Promise<unknown>;
@@ -0,0 +1,92 @@
1
+ export async function getUncachedEdgeConfigValue(edgeConfig, key) {
2
+ const connection = parseEdgeConfigConnection(edgeConfig);
3
+ if (!connection) {
4
+ throw new Error('Invalid Edge Config connection string');
5
+ }
6
+ const headers = new Headers({
7
+ authorization: `Bearer ${connection.token}`,
8
+ 'x-edge-config-min-updated-at': `${Number.MAX_SAFE_INTEGER}`,
9
+ });
10
+ if (process.env.VERCEL_ENV) {
11
+ headers.set('x-edge-config-vercel-env', process.env.VERCEL_ENV);
12
+ }
13
+ const url = `${connection.baseUrl}/item/${encodeURIComponent(key)}?version=${encodeURIComponent(connection.version)}`;
14
+ const response = await fetch(url, {
15
+ headers,
16
+ cache: 'no-store',
17
+ });
18
+ if (response.ok) {
19
+ return response.json();
20
+ }
21
+ if (response.status === 404 && response.headers.has('x-edge-config-digest')) {
22
+ return undefined;
23
+ }
24
+ const body = await response.text().catch(() => '');
25
+ throw new Error(`Edge Config read ${key} returned ${response.status}: ${body.slice(0, 500)}`);
26
+ }
27
+ function parseEdgeConfigConnection(value) {
28
+ const text = value.trim();
29
+ if (!text)
30
+ return null;
31
+ return parseEdgeConfigQueryConnection(text)
32
+ || parseVercelEdgeConfigURL(text)
33
+ || parseExternalEdgeConfigURL(text);
34
+ }
35
+ function parseEdgeConfigQueryConnection(value) {
36
+ if (!value.startsWith('edge-config:'))
37
+ return null;
38
+ const params = new URLSearchParams(value.slice('edge-config:'.length));
39
+ const id = params.get('id') || '';
40
+ const token = params.get('token') || '';
41
+ if (!id || !token)
42
+ return null;
43
+ return {
44
+ baseUrl: `https://edge-config.vercel.com/${id}`,
45
+ token,
46
+ version: params.get('version') || '1',
47
+ };
48
+ }
49
+ function parseVercelEdgeConfigURL(value) {
50
+ let url;
51
+ try {
52
+ url = new URL(value);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ if (url.protocol !== 'https:' || url.host !== 'edge-config.vercel.com')
58
+ return null;
59
+ const id = url.pathname.split('/').filter(Boolean)[0] || '';
60
+ const token = url.searchParams.get('token') || '';
61
+ if (!id || !id.startsWith('ecfg') || !token)
62
+ return null;
63
+ return {
64
+ baseUrl: `https://edge-config.vercel.com/${id}`,
65
+ token,
66
+ version: url.searchParams.get('version') || '1',
67
+ };
68
+ }
69
+ function parseExternalEdgeConfigURL(value) {
70
+ let url;
71
+ try {
72
+ url = new URL(value);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ let id = url.searchParams.get('id') || '';
78
+ const token = url.searchParams.get('token') || '';
79
+ const version = url.searchParams.get('version') || '1';
80
+ if (!id || url.pathname.startsWith('/ecfg_')) {
81
+ id = url.pathname.split('/').filter(Boolean)[0] || '';
82
+ }
83
+ if (!id || !token)
84
+ return null;
85
+ url.search = '';
86
+ const base = url.toString().replace(/\/+$/, '');
87
+ return {
88
+ baseUrl: base,
89
+ token,
90
+ version,
91
+ };
92
+ }
@@ -0,0 +1,2 @@
1
+ export { DevRefresh, type DevRefreshProps } from './dev-refresh.js';
2
+ export { withGradial, type WithGradialOptions } from './config.js';
@@ -0,0 +1,2 @@
1
+ export { DevRefresh } from './dev-refresh.js';
2
+ export { withGradial } from './config.js';
@@ -1,5 +1,5 @@
1
- import { createClient, get as getEdgeConfigItem } from '@vercel/edge-config';
2
1
  import { NextResponse } from 'next/server';
2
+ import { getUncachedEdgeConfigValue } from './edge-config.js';
3
3
  import { validatePreviewToken } from './preview.js';
4
4
  const RELEASE_HEADER = 'x-gradial-release-id';
5
5
  const ROUTE_HEADER = 'x-gradial-route';
@@ -62,9 +62,7 @@ async function getEdgeConfigJSON(edgeConfig, key) {
62
62
  return value;
63
63
  }
64
64
  async function getEdgeConfigValue(edgeConfig, key) {
65
- return edgeConfig === process.env.EDGE_CONFIG
66
- ? await getEdgeConfigItem(key)
67
- : await createClient(edgeConfig).get(key);
65
+ return getUncachedEdgeConfigValue(edgeConfig, key);
68
66
  }
69
67
  function activeReleaseKey(siteId) {
70
68
  return `activeRelease_${edgeKeySegment(siteId)}`;
@@ -108,7 +106,7 @@ async function releaseFromPreviewToken(request, config) {
108
106
  const token = previewTokenFromRequest(request);
109
107
  if (!token)
110
108
  return { releaseId: '', invalid: false };
111
- const signKey = config.previewSignKey || process.env.GRADIAL_PREVIEW_SIGN_KEY || process.env.HOSTING_PREVIEW_SIGN_KEY || '';
109
+ const signKey = config.previewSignKey || process.env.ACI_PREVIEW_SIGN_KEY || '';
112
110
  if (!signKey)
113
111
  return { releaseId: '', invalid: true };
114
112
  const claims = await validatePreviewToken(token, signKey);
@@ -124,7 +122,7 @@ function previewTokenFromRequest(request) {
124
122
  const queryToken = request.nextUrl.searchParams.get('preview_token');
125
123
  if (queryToken)
126
124
  return queryToken;
127
- const cookieToken = request.cookies.get('bm_preview')?.value || '';
125
+ const cookieToken = request.cookies.get('aci_preview')?.value || '';
128
126
  if (cookieToken)
129
127
  return cookieToken;
130
128
  const auth = request.headers.get('authorization') || '';
@@ -1,9 +1,5 @@
1
- export declare class PageNotFoundError extends Error {
2
- constructor(route: string, cause?: unknown);
3
- }
4
- export declare class FragmentNotFoundError extends Error {
5
- constructor(name: string, cause?: unknown);
6
- }
1
+ import { FragmentNotFoundError, PageNotFoundError } from '../content/provider.js';
2
+ export { FragmentNotFoundError, PageNotFoundError };
7
3
  export interface GradialFetchConfig {
8
4
  siteId?: string;
9
5
  bucket?: string;
@@ -17,24 +13,6 @@ export interface GradialFetchConfig {
17
13
  edgeConfig?: string;
18
14
  deploymentId?: string;
19
15
  }
20
- export interface RouteManifest {
21
- manifestVersion: string;
22
- releaseId: string;
23
- siteId: string;
24
- routes: Record<string, RouteEntry>;
25
- }
26
- export interface RouteEntry {
27
- path: string;
28
- mode: string;
29
- bodyArtifact?: string;
30
- fragments?: Array<{
31
- id: string;
32
- artifactRef?: string;
33
- }>;
34
- cachePolicy?: {
35
- cacheControl?: string;
36
- };
37
- }
38
16
  export interface GradialRenderInput<TPage, TSite extends GradialSiteConfig = GradialSiteConfig> {
39
17
  route: string;
40
18
  domain: string;
@@ -55,4 +33,11 @@ export declare function routeFromNextParams(params?: Promise<{
55
33
  }> | {
56
34
  slug?: string[];
57
35
  }): Promise<string>;
36
+ export declare function generateGradialStaticParams(): Promise<Array<{
37
+ slug?: string[];
38
+ }>>;
39
+ export declare class ReleaseAssetNotFoundError extends Error {
40
+ constructor(releaseId: string, assetPath: string, cause?: unknown);
41
+ }
42
+ export declare function getReleaseAssetResponse(releaseId: string, assetPath: string, config?: GradialFetchConfig): Promise<Response>;
58
43
  export declare function resolveReleaseId(config?: GradialFetchConfig): Promise<string>;