@gradial/aci 0.1.25 → 0.1.27
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 +22 -5
- package/bin/aci-darwin-arm64 +0 -0
- package/dist/next/config.d.ts +8 -0
- package/dist/next/config.js +23 -0
- package/dist/next/middleware.js +44 -5
- package/package.json +1 -1
- package/templates/nextjs/template/src/middleware.ts +4 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agentic Content Infrastructure SDK
|
|
2
2
|
|
|
3
|
-
`@gradial/aci` `0.1.
|
|
3
|
+
`@gradial/aci` `0.1.27` 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.
|
|
11
|
+
npm install @gradial/aci@0.1.27 zod@^4
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Root Import
|
|
@@ -44,10 +44,27 @@ Next integration helpers live under explicit Next subpaths:
|
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
46
|
import { createPage, withAci } from '@gradial/aci/next';
|
|
47
|
-
import {
|
|
47
|
+
import { createGradialMiddleware } from '@gradial/aci/next/middleware';
|
|
48
48
|
import { DevRefresh } from '@gradial/aci/next/dev-refresh';
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Use `ACI_SITE_ID` for the deployed site identity. Passing `siteId` to
|
|
52
|
+
`createGradialMiddleware()` is still supported, but the default template relies
|
|
53
|
+
on the environment so server and middleware resolution behave the same way.
|
|
54
|
+
|
|
55
|
+
Middleware release-scopes document routes by default, including files such as
|
|
56
|
+
`/robots.txt` and `/sitemap.xml`. Next internals, API routes, `favicon.ico`,
|
|
57
|
+
public files discovered from `public/`, and `/gradial/assets/*` are left to
|
|
58
|
+
Next. App-owned routes can opt out through `next.config`:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import { withAci } from '@gradial/aci/next/config';
|
|
62
|
+
|
|
63
|
+
export default withAci(nextConfig, {
|
|
64
|
+
passthroughRoutes: ['/healthz', '/checkout/:path*'],
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
51
68
|
Astro public subpath is `@gradial/aci/astro`.
|
|
52
69
|
|
|
53
70
|
## Server-Side Content Providers
|
|
@@ -68,13 +85,13 @@ go through an app-owned HTTP endpoint backed server-side by a `ContentProvider`.
|
|
|
68
85
|
The first RC scaffold path is Next:
|
|
69
86
|
|
|
70
87
|
```bash
|
|
71
|
-
npx @gradial/aci@0.1.
|
|
88
|
+
npx @gradial/aci@0.1.27 init my-site --template next-starter
|
|
72
89
|
```
|
|
73
90
|
|
|
74
91
|
List available templates:
|
|
75
92
|
|
|
76
93
|
```bash
|
|
77
|
-
npx @gradial/aci@0.1.
|
|
94
|
+
npx @gradial/aci@0.1.27 init --list
|
|
78
95
|
```
|
|
79
96
|
|
|
80
97
|
## Verify Locally
|
package/bin/aci-darwin-arm64
CHANGED
|
Binary file
|
package/dist/next/config.d.ts
CHANGED
|
@@ -2,5 +2,13 @@ import type { NextConfig } from 'next';
|
|
|
2
2
|
export interface WithAciOptions {
|
|
3
3
|
/** Asset rewrite prefix. Defaults to '/.gradial-dam' */
|
|
4
4
|
assetPrefix?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Customer-owned routes that ACI middleware should leave to Next.
|
|
7
|
+
*
|
|
8
|
+
* Use exact paths such as '/healthz' or simple Next-style catch-alls such as
|
|
9
|
+
* '/checkout/:path*'. CMS document routes such as '/robots.txt' and
|
|
10
|
+
* '/sitemap.xml' should be omitted so they stay release-scoped.
|
|
11
|
+
*/
|
|
12
|
+
passthroughRoutes?: string[];
|
|
5
13
|
}
|
|
6
14
|
export declare function withAci(nextConfig?: NextConfig, options?: WithAciOptions): NextConfig;
|
package/dist/next/config.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { readdirSync, existsSync } from 'fs';
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
3
4
|
import { createContentWatchWebpackPlugin } from './content-watch.js';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
4
6
|
export function withAci(nextConfig = {}, options = {}) {
|
|
5
7
|
const publicPaths = collectPublicPaths(join(process.cwd(), 'public'));
|
|
8
|
+
const passthroughRoutes = normalizePassthroughRoutes(options.passthroughRoutes ?? []);
|
|
6
9
|
const assetPrefix = options.assetPrefix || '/.gradial-dam';
|
|
7
10
|
const userRewrites = nextConfig.rewrites;
|
|
8
11
|
const userHeaders = nextConfig.headers;
|
|
@@ -60,6 +63,7 @@ export function withAci(nextConfig = {}, options = {}) {
|
|
|
60
63
|
const webpack = require('webpack');
|
|
61
64
|
config.plugins.push(new webpack.DefinePlugin({
|
|
62
65
|
'__ACI_PUBLIC_PATHS__': JSON.stringify(publicPaths),
|
|
66
|
+
'__ACI_PASSTHROUGH_ROUTES__': JSON.stringify(passthroughRoutes),
|
|
63
67
|
}));
|
|
64
68
|
return typeof userWebpack === 'function'
|
|
65
69
|
? userWebpack(config, context)
|
|
@@ -67,6 +71,25 @@ export function withAci(nextConfig = {}, options = {}) {
|
|
|
67
71
|
},
|
|
68
72
|
};
|
|
69
73
|
}
|
|
74
|
+
function normalizePassthroughRoutes(routes) {
|
|
75
|
+
const out = [];
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
for (const route of routes) {
|
|
78
|
+
const clean = normalizeRoutePattern(route);
|
|
79
|
+
if (!clean || seen.has(clean))
|
|
80
|
+
continue;
|
|
81
|
+
seen.add(clean);
|
|
82
|
+
out.push(clean);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function normalizeRoutePattern(route) {
|
|
87
|
+
const trimmed = route.trim();
|
|
88
|
+
if (!trimmed)
|
|
89
|
+
return '';
|
|
90
|
+
const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
91
|
+
return withSlash.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
92
|
+
}
|
|
70
93
|
function collectPublicPaths(publicDir) {
|
|
71
94
|
if (!existsSync(publicDir))
|
|
72
95
|
return [];
|
package/dist/next/middleware.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getUncachedEdgeConfigValue } from './edge-config.js';
|
|
|
3
3
|
import { PREVIEW_TOKEN_COOKIE, PREVIEW_RELEASE_COOKIE, extractPreviewToken, resolvePreviewContext, createPreviewCookies, previewSignKey, } from '../preview/core.js';
|
|
4
4
|
const RELEASE_SCOPED_PREFIX = '/__r';
|
|
5
5
|
const PUBLIC_PATHS = new Set(typeof __ACI_PUBLIC_PATHS__ !== 'undefined' ? __ACI_PUBLIC_PATHS__ : []);
|
|
6
|
+
const PASSTHROUGH_ROUTES = (typeof __ACI_PASSTHROUGH_ROUTES__ !== 'undefined' ? __ACI_PASSTHROUGH_ROUTES__ : []);
|
|
6
7
|
export function createGradialMiddleware(config = {}) {
|
|
7
8
|
return async function middleware(request) {
|
|
8
9
|
const url = request.nextUrl;
|
|
@@ -17,6 +18,9 @@ export function createGradialMiddleware(config = {}) {
|
|
|
17
18
|
if (PUBLIC_PATHS.has(url.pathname)) {
|
|
18
19
|
return NextResponse.next();
|
|
19
20
|
}
|
|
21
|
+
if (isPassthroughRoute(url.pathname)) {
|
|
22
|
+
return NextResponse.next();
|
|
23
|
+
}
|
|
20
24
|
// Block direct access to /__r/... — only middleware rewrites may use it.
|
|
21
25
|
if (isReleaseScopedPath(url.pathname, RELEASE_SCOPED_PREFIX)) {
|
|
22
26
|
return new Response('not found', {
|
|
@@ -32,6 +36,12 @@ export function createGradialMiddleware(config = {}) {
|
|
|
32
36
|
if (process.env.ACI_CONTENT_PROVIDER === 'file') {
|
|
33
37
|
return NextResponse.next();
|
|
34
38
|
}
|
|
39
|
+
if (!siteIdForConfig(config)) {
|
|
40
|
+
return new Response('ACI site id is not configured', {
|
|
41
|
+
status: 500,
|
|
42
|
+
headers: { 'Cache-Control': 'private, no-store' },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
35
45
|
return new Response('no active release', {
|
|
36
46
|
status: 503,
|
|
37
47
|
headers: { 'Cache-Control': 'private, no-store' },
|
|
@@ -123,7 +133,7 @@ async function resolvePreviewFromRequest(request, config) {
|
|
|
123
133
|
console.warn('[aci/mw] GRADIAL_PREVIEW_SIGN_KEY not set — preview tokens will be ignored');
|
|
124
134
|
return null;
|
|
125
135
|
}
|
|
126
|
-
return await resolvePreviewContext({ token, siteId: config
|
|
136
|
+
return await resolvePreviewContext({ token, siteId: siteIdForConfig(config), signKey });
|
|
127
137
|
}
|
|
128
138
|
function cookieMap(cookies) {
|
|
129
139
|
const map = new Map();
|
|
@@ -134,19 +144,23 @@ function cookieMap(cookies) {
|
|
|
134
144
|
}
|
|
135
145
|
async function activeReleaseFromEdgeConfig(config) {
|
|
136
146
|
const edgeConfig = config.edgeConfig || process.env.EDGE_CONFIG || '';
|
|
137
|
-
|
|
147
|
+
const siteId = siteIdForConfig(config);
|
|
148
|
+
if (!edgeConfig || !siteId)
|
|
138
149
|
return '';
|
|
139
|
-
const active = await activeReleasePointer(edgeConfig,
|
|
150
|
+
const active = await activeReleasePointer(edgeConfig, siteId);
|
|
140
151
|
if (!active.releaseId)
|
|
141
152
|
return '';
|
|
142
153
|
const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
|
|
143
154
|
if (!deploymentId)
|
|
144
155
|
return active.releaseId;
|
|
145
|
-
const compatibility = await getEdgeConfigJSON(edgeConfig, deploymentCompatibilityKey(
|
|
156
|
+
const compatibility = await getEdgeConfigJSON(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
|
|
146
157
|
if (!compatibility)
|
|
147
158
|
return active.releaseId;
|
|
148
159
|
return clampRelease(active.releaseId, active.sequence || 0, compatibility);
|
|
149
160
|
}
|
|
161
|
+
function siteIdForConfig(config) {
|
|
162
|
+
return config.siteId || process.env.ACI_SITE_ID || '';
|
|
163
|
+
}
|
|
150
164
|
async function getEdgeConfigJSON(edgeConfig, key) {
|
|
151
165
|
const value = await getEdgeConfigValue(edgeConfig, key);
|
|
152
166
|
if (!value)
|
|
@@ -202,6 +216,31 @@ function clampRelease(activeReleaseId, activeSequence, compatibility) {
|
|
|
202
216
|
function edgeKeySegment(value) {
|
|
203
217
|
return value.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
204
218
|
}
|
|
219
|
+
function isPassthroughRoute(pathname) {
|
|
220
|
+
return PASSTHROUGH_ROUTES.some((route) => routeMatches(pathname, route));
|
|
221
|
+
}
|
|
222
|
+
function routeMatches(pathname, route) {
|
|
223
|
+
const cleanPath = normalizeRoutePath(pathname);
|
|
224
|
+
const cleanRoute = normalizeRoutePath(route);
|
|
225
|
+
if (!cleanRoute)
|
|
226
|
+
return false;
|
|
227
|
+
if (cleanRoute.endsWith('/:path*')) {
|
|
228
|
+
const prefix = cleanRoute.slice(0, -'/:path*'.length) || '/';
|
|
229
|
+
return cleanPath === prefix || cleanPath.startsWith(`${prefix}/`);
|
|
230
|
+
}
|
|
231
|
+
if (cleanRoute.endsWith('/*')) {
|
|
232
|
+
const prefix = cleanRoute.slice(0, -'/*'.length) || '/';
|
|
233
|
+
return cleanPath === prefix || cleanPath.startsWith(`${prefix}/`);
|
|
234
|
+
}
|
|
235
|
+
return cleanPath === cleanRoute;
|
|
236
|
+
}
|
|
237
|
+
function normalizeRoutePath(value) {
|
|
238
|
+
const trimmed = value.trim();
|
|
239
|
+
if (!trimmed)
|
|
240
|
+
return '';
|
|
241
|
+
const pathname = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
242
|
+
return pathname.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
243
|
+
}
|
|
205
244
|
export const config = {
|
|
206
|
-
matcher: ['/((?!_next/).*)'],
|
|
245
|
+
matcher: ['/((?!_next(?:/|$)|api(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)'],
|
|
207
246
|
};
|
package/package.json
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { createGradialMiddleware } from '@gradial/aci/next/middleware';
|
|
2
2
|
|
|
3
3
|
export default createGradialMiddleware();
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
export const config = {
|
|
6
|
+
matcher: ['/((?!_next(?:/|$)|api(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)'],
|
|
7
|
+
};
|