@airdraft/next 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@airdraft/next` will be documented here.
4
+
5
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
@@ -0,0 +1,18 @@
1
+ import { type CmsConfig, type ListOptions, type Entry } from '@airdraft/core';
2
+ /**
3
+ * Creates a server-side CMS client that reads content directly via the adapter,
4
+ * bypassing HTTP. Use in Server Components, generateStaticParams, and other
5
+ * server-side Next.js patterns.
6
+ *
7
+ * ```ts
8
+ * const cms = createCmsClient(airdraft)
9
+ * const posts = await cms.listEntries('posts', { status: 'published' })
10
+ * ```
11
+ */
12
+ export declare function createCmsClient(config: CmsConfig): {
13
+ listEntries: (collection: string, options?: ListOptions) => Promise<Entry[]>;
14
+ getEntry: (collection: string, slug: string) => Promise<Entry | null>;
15
+ getSchema: () => import("@airdraft/core").CmsSchema;
16
+ getCollection: (name: string) => import("@airdraft/core").CollectionConfig | null;
17
+ };
18
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAExF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS;8BAInB,MAAM,YAAY,WAAW,KAAG,OAAO,CAAC,KAAK,EAAE,CAAC;2BAGnD,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;;0BAK7C,MAAM;EAE/B"}
package/dist/client.js ADDED
@@ -0,0 +1,21 @@
1
+ import { CmsEngine } from '@airdraft/core';
2
+ /**
3
+ * Creates a server-side CMS client that reads content directly via the adapter,
4
+ * bypassing HTTP. Use in Server Components, generateStaticParams, and other
5
+ * server-side Next.js patterns.
6
+ *
7
+ * ```ts
8
+ * const cms = createCmsClient(airdraft)
9
+ * const posts = await cms.listEntries('posts', { status: 'published' })
10
+ * ```
11
+ */
12
+ export function createCmsClient(config) {
13
+ const engine = new CmsEngine(config);
14
+ return {
15
+ listEntries: (collection, options) => engine.listEntries(collection, options),
16
+ getEntry: (collection, slug) => engine.getEntry(collection, slug),
17
+ getSchema: () => engine.getSchema(),
18
+ getCollection: (name) => engine.getCollection(name),
19
+ };
20
+ }
21
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgD,MAAM,gBAAgB,CAAA;AAExF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAA;IAEpC,OAAO;QACL,WAAW,EAAE,CAAC,UAAkB,EAAE,OAAqB,EAAoB,EAAE,CAC3E,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC;QAEzC,QAAQ,EAAE,CAAC,UAAkB,EAAE,IAAY,EAAyB,EAAE,CACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;QAEnC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;QAEnC,aAAa,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;KAC5D,CAAA;AACH,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Cloud forwarder — re-exports all HTTP method handlers that forward requests to
3
+ * the Airdraft Cloud CMS runtime.
4
+ *
5
+ * Mount in `app/api/cms/[...route]/route.ts` of a Next.js project:
6
+ *
7
+ * ```ts
8
+ * export { GET, POST, PUT, PATCH, DELETE } from '@airdraft/next/forwarder'
9
+ * ```
10
+ *
11
+ * Reads `AIRDRAFT_CMS_URL` from environment variables to locate the cloud
12
+ * CMS runtime (e.g. `https://cms.airdraft.space/my-project`).
13
+ *
14
+ * The proxy transparently forwards headers (Authorization, Content-Type, etc.)
15
+ * and the request body, then streams the response back to the client.
16
+ */
17
+ type NextRequest = Request & {
18
+ nextUrl?: URL;
19
+ };
20
+ interface RouteContext {
21
+ params: Promise<{
22
+ route?: string[];
23
+ }>;
24
+ }
25
+ declare function proxyRequest(req: NextRequest, ctx: RouteContext): Promise<Response>;
26
+ export declare const GET: typeof proxyRequest;
27
+ export declare const POST: typeof proxyRequest;
28
+ export declare const PUT: typeof proxyRequest;
29
+ export declare const PATCH: typeof proxyRequest;
30
+ export declare const DELETE: typeof proxyRequest;
31
+ export {};
32
+ //# sourceMappingURL=forwarder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.d.ts","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,KAAK,WAAW,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AAC9C,UAAU,YAAY;IACpB,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACtC;AAED,iBAAe,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BlF;AAED,eAAO,MAAM,GAAG,qBAAe,CAAA;AAC/B,eAAO,MAAM,IAAI,qBAAe,CAAA;AAChC,eAAO,MAAM,GAAG,qBAAe,CAAA;AAC/B,eAAO,MAAM,KAAK,qBAAe,CAAA;AACjC,eAAO,MAAM,MAAM,qBAAe,CAAA"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Cloud forwarder — re-exports all HTTP method handlers that forward requests to
3
+ * the Airdraft Cloud CMS runtime.
4
+ *
5
+ * Mount in `app/api/cms/[...route]/route.ts` of a Next.js project:
6
+ *
7
+ * ```ts
8
+ * export { GET, POST, PUT, PATCH, DELETE } from '@airdraft/next/forwarder'
9
+ * ```
10
+ *
11
+ * Reads `AIRDRAFT_CMS_URL` from environment variables to locate the cloud
12
+ * CMS runtime (e.g. `https://cms.airdraft.space/my-project`).
13
+ *
14
+ * The proxy transparently forwards headers (Authorization, Content-Type, etc.)
15
+ * and the request body, then streams the response back to the client.
16
+ */
17
+ async function proxyRequest(req, ctx) {
18
+ const cmsBase = process.env.AIRDRAFT_CMS_URL;
19
+ if (!cmsBase) {
20
+ return new Response(JSON.stringify({ error: { code: 'MISSING_CMS_URL', message: 'AIRDRAFT_CMS_URL is not set' } }), { status: 500, headers: { 'Content-Type': 'application/json' } });
21
+ }
22
+ const { route } = await ctx.params;
23
+ const routePath = route ? `/${route.join('/')}` : '';
24
+ const url = new URL(req.nextUrl ?? req.url);
25
+ const targetUrl = `${cmsBase.replace(/\/$/, '')}/api/cms${routePath}${url.search}`;
26
+ // Forward request with original headers + body
27
+ const init = {
28
+ method: req.method,
29
+ headers: req.headers,
30
+ // Body only for non-GET/HEAD
31
+ ...(req.method !== 'GET' && req.method !== 'HEAD' ? { body: req.body, duplex: 'half' } : {}),
32
+ };
33
+ const upstream = await fetch(targetUrl, init);
34
+ return new Response(upstream.body, {
35
+ status: upstream.status,
36
+ headers: upstream.headers,
37
+ });
38
+ }
39
+ export const GET = proxyRequest;
40
+ export const POST = proxyRequest;
41
+ export const PUT = proxyRequest;
42
+ export const PATCH = proxyRequest;
43
+ export const DELETE = proxyRequest;
44
+ //# sourceMappingURL=forwarder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.js","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,KAAK,UAAU,YAAY,CAAC,GAAgB,EAAE,GAAiB;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,6BAA6B,EAAE,EAAE,CAAC,EAC9F,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAA;IACH,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,CAAC,MAAM,CAAA;IAClC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;IAElF,+CAA+C;IAC/C,MAAM,IAAI,GAAgB;QACxB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,6BAA6B;QAC7B,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7F,CAAA;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,IAAmB,CAAC,CAAA;IAC5D,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAA;AAC/B,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAA;AAChC,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAA;AAC/B,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAA;AACjC,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { type CmsConfig } from '@airdraft/core';
2
+ type NextRequest = Request & {
3
+ nextUrl?: URL;
4
+ };
5
+ interface RouteContext {
6
+ params: Promise<{
7
+ route?: string[];
8
+ }>;
9
+ }
10
+ interface HandlerExports {
11
+ GET: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
12
+ POST: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
13
+ PATCH: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
14
+ PUT: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
15
+ DELETE: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
16
+ }
17
+ /**
18
+ * Produces App Router route handler exports from a CmsConfig.
19
+ *
20
+ * Place in `app/api/cms/[...route]/route.ts`:
21
+ *
22
+ * ```ts
23
+ * export const { GET, POST, PUT, DELETE } = createCmsHandler(airdraft)
24
+ * ```
25
+ */
26
+ export declare function createCmsHandler(config: CmsConfig): HandlerExports;
27
+ export {};
28
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAQf,MAAM,gBAAgB,CAAA;AAMvB,KAAK,WAAW,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AAE9C,UAAU,YAAY;IACpB,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACtC;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/D,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAChE,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjE,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/D,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACnE;AAmGD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,cAAc,CAuZlE"}
@@ -0,0 +1,468 @@
1
+ import { CmsEngine, AirdraftError, ValidationError, EntryNotFoundError, } from '@airdraft/core';
2
+ // ---------------------------------------------------------------------------
3
+ // JSON helpers
4
+ // ---------------------------------------------------------------------------
5
+ function json(body, status = 200) {
6
+ return new Response(JSON.stringify(body), {
7
+ status,
8
+ headers: { 'Content-Type': 'application/json' },
9
+ });
10
+ }
11
+ function errorResponse(err) {
12
+ if (err instanceof AirdraftError) {
13
+ const body = {
14
+ error: {
15
+ code: err.code,
16
+ message: err.message,
17
+ details: err instanceof ValidationError ? err.details : {},
18
+ },
19
+ };
20
+ return json(body, err.statusHint);
21
+ }
22
+ // Unhandled — log server-side, return opaque 500
23
+ console.error('[airdraft]', err);
24
+ return json({ error: { code: 'INTERNAL_ERROR', message: 'Internal server error', details: {} } }, 500);
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // Route parsing
28
+ // ---------------------------------------------------------------------------
29
+ function parseRoute(segments) {
30
+ if (!segments || segments.length === 0)
31
+ return null;
32
+ const [collection, slug] = segments;
33
+ return { collection, slug };
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // List query string parsing
37
+ // ---------------------------------------------------------------------------
38
+ function parseListOptions(url) {
39
+ const u = typeof url === 'string' ? new URL(url) : url;
40
+ const opts = {};
41
+ const limit = u.searchParams.get('limit');
42
+ if (limit)
43
+ opts.limit = Math.min(parseInt(limit, 10), 100);
44
+ const offset = u.searchParams.get('offset');
45
+ if (offset)
46
+ opts.offset = parseInt(offset, 10);
47
+ const sort = u.searchParams.get('sort');
48
+ const order = u.searchParams.get('order') ?? 'desc';
49
+ if (sort)
50
+ opts.sort = { field: sort, order };
51
+ const status = u.searchParams.get('status');
52
+ if (status)
53
+ opts.status = status;
54
+ const filter = {};
55
+ for (const [key, value] of u.searchParams.entries()) {
56
+ const match = key.match(/^filter\[(.+)\]$/);
57
+ if (match)
58
+ filter[match[1]] = value;
59
+ }
60
+ if (Object.keys(filter).length > 0)
61
+ opts.filter = filter;
62
+ const expandParam = u.searchParams.get('expand');
63
+ if (expandParam)
64
+ opts.expand = expandParam.split(',').map((s) => s.trim()).filter(Boolean);
65
+ return opts;
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Production warning for missing auth
69
+ // ---------------------------------------------------------------------------
70
+ let authWarned = false;
71
+ function warnIfNoAuth(config) {
72
+ if (authWarned)
73
+ return;
74
+ if (process.env.NODE_ENV === 'production') {
75
+ const hasAuth = config.plugins.some((p) => p.name === 'auth');
76
+ if (!hasAuth) {
77
+ console.warn('[airdraft] WARNING: No auth plugin detected. The CMS API is publicly accessible. ' +
78
+ 'Install @airdraft/plugin-auth to protect it.');
79
+ authWarned = true;
80
+ }
81
+ }
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // createCmsHandler
85
+ // ---------------------------------------------------------------------------
86
+ /**
87
+ * Produces App Router route handler exports from a CmsConfig.
88
+ *
89
+ * Place in `app/api/cms/[...route]/route.ts`:
90
+ *
91
+ * ```ts
92
+ * export const { GET, POST, PUT, DELETE } = createCmsHandler(airdraft)
93
+ * ```
94
+ */
95
+ export function createCmsHandler(config) {
96
+ const engine = new CmsEngine(config);
97
+ warnIfNoAuth(config);
98
+ async function runMiddleware(req) {
99
+ try {
100
+ for (const plugin of config.plugins) {
101
+ await plugin.middleware?.(req);
102
+ }
103
+ return null;
104
+ }
105
+ catch (err) {
106
+ return errorResponse(err);
107
+ }
108
+ }
109
+ /** Dispatch a request to a plugin-defined route. Returns null if no match. */
110
+ async function dispatchPluginRoute(req, route) {
111
+ const routeKey = '/' + route.join('/');
112
+ // Build a sorted list of all plugin routes (most literal segments first so
113
+ // `/media/:key/url` is tried before `/media/:key`).
114
+ const candidates = [];
115
+ for (const plugin of config.plugins) {
116
+ if (!plugin.routes)
117
+ continue;
118
+ for (const pattern of Object.keys(plugin.routes)) {
119
+ candidates.push({ plugin, pattern });
120
+ }
121
+ }
122
+ candidates.sort((a, b) => {
123
+ const literalCount = (p) => p.split('/').filter((s) => !s.startsWith(':')).length;
124
+ return literalCount(b.pattern) - literalCount(a.pattern);
125
+ });
126
+ for (const { plugin, pattern } of candidates) {
127
+ // Exact match
128
+ if (pattern === routeKey) {
129
+ return plugin.routes[pattern](req, { adapter: config.adapter, config });
130
+ }
131
+ // Pattern match: :param segments match one or more path characters (greedy).
132
+ // This allows keys that contain slashes, e.g. /media/:key matching
133
+ // /media/uploads/2026/05/hero.jpg
134
+ if (pattern.includes(':')) {
135
+ const regexStr = pattern
136
+ .split('/')
137
+ .map((seg) => (seg.startsWith(':') ? '(.+?)' : seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
138
+ .join('\\/');
139
+ const match = new RegExp(`^${regexStr}$`).test(routeKey);
140
+ if (match) {
141
+ return plugin.routes[pattern](req, { adapter: config.adapter, config });
142
+ }
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ /** Run all plugin transformEntry hooks in order on a single entry. */
148
+ async function runTransformEntry(entry, collection) {
149
+ const collectionConfig = config.collections[collection];
150
+ if (!collectionConfig)
151
+ return entry;
152
+ let result = entry;
153
+ for (const plugin of config.plugins) {
154
+ if (plugin.hooks?.transformEntry) {
155
+ result = await plugin.hooks.transformEntry(result, collectionConfig);
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ // -------------------------------------------------------------------------
161
+ // GET
162
+ // -------------------------------------------------------------------------
163
+ async function GET(req, ctx) {
164
+ const authErr = await runMiddleware(req);
165
+ if (authErr)
166
+ return authErr;
167
+ try {
168
+ const { route = [] } = await ctx.params;
169
+ // Plugin-defined GET routes
170
+ const pluginRes = await dispatchPluginRoute(req, route);
171
+ if (pluginRes)
172
+ return pluginRes;
173
+ // GET /schema
174
+ if (route[0] === 'schema' && route.length === 1) {
175
+ return json({ data: engine.getSchema() });
176
+ }
177
+ // GET /docs → redirect to Scalar (placeholder until openapi is wired)
178
+ if (route[0] === 'docs' && route.length === 1) {
179
+ return json({ message: 'OpenAPI docs coming in Phase 3' });
180
+ }
181
+ // GET /openapi.json
182
+ if (route[0] === 'openapi.json' && route.length === 1) {
183
+ return json({ message: 'OpenAPI spec coming in Phase 3' });
184
+ }
185
+ const parsed = parseRoute(route);
186
+ if (!parsed)
187
+ return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
188
+ const { collection, slug } = parsed;
189
+ if (slug) {
190
+ // GET /{collection}/{slug}
191
+ const url = req.nextUrl ?? new URL(req.url);
192
+ const expandParam = url.searchParams.get('expand');
193
+ const getOptions = expandParam
194
+ ? { expand: expandParam.split(',').map((s) => s.trim()).filter(Boolean) }
195
+ : {};
196
+ const raw = await engine.getEntry(collection, slug, getOptions);
197
+ if (!raw)
198
+ throw new EntryNotFoundError(collection, slug);
199
+ const entry = await runTransformEntry(raw, collection);
200
+ return json({ data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) }, meta: { sha: entry.meta.sha } });
201
+ }
202
+ else {
203
+ // GET /{collection}
204
+ const url = req.nextUrl ?? new URL(req.url);
205
+ const opts = parseListOptions(url);
206
+ const rawEntries = await engine.listEntries(collection, opts);
207
+ const entries = await Promise.all(rawEntries.map((e) => runTransformEntry(e, collection)));
208
+ const data = entries.map((e) => ({
209
+ slug: e.slug,
210
+ ...e.data,
211
+ ...(e.published !== undefined ? { published: e.published, publishedAt: e.publishedAt } : {}),
212
+ _sha: e.meta.sha,
213
+ }));
214
+ return json({
215
+ data,
216
+ meta: { total: data.length, offset: opts.offset ?? 0, limit: opts.limit ?? 20 },
217
+ });
218
+ }
219
+ }
220
+ catch (err) {
221
+ return errorResponse(err);
222
+ }
223
+ }
224
+ // -------------------------------------------------------------------------
225
+ // POST
226
+ // -------------------------------------------------------------------------
227
+ async function POST(req, ctx) {
228
+ const authErr = await runMiddleware(req);
229
+ if (authErr)
230
+ return authErr;
231
+ try {
232
+ const { route = [] } = await ctx.params;
233
+ // Plugin-defined POST routes (e.g. /media/upload)
234
+ const pluginRes = await dispatchPluginRoute(req, route);
235
+ if (pluginRes)
236
+ return pluginRes;
237
+ const parsed = parseRoute(route);
238
+ if (!parsed || parsed.slug) {
239
+ return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
240
+ }
241
+ const body = await req.json();
242
+ const payload = body.data ?? {};
243
+ const slug = body.slug ?? payload['slug'];
244
+ const entry = await engine.createEntry(parsed.collection, slug, payload);
245
+ return json({
246
+ data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) },
247
+ meta: { sha: entry.meta.sha },
248
+ }, 201);
249
+ }
250
+ catch (err) {
251
+ return errorResponse(err);
252
+ }
253
+ }
254
+ // -------------------------------------------------------------------------
255
+ // PATCH — plugin-defined routes only (e.g. PATCH /media/:key)
256
+ // -------------------------------------------------------------------------
257
+ async function PATCH(req, ctx) {
258
+ const authErr = await runMiddleware(req);
259
+ if (authErr)
260
+ return authErr;
261
+ try {
262
+ const { route = [] } = await ctx.params;
263
+ const pluginRes = await dispatchPluginRoute(req, route);
264
+ if (pluginRes)
265
+ return pluginRes;
266
+ return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
267
+ }
268
+ catch (err) {
269
+ return errorResponse(err);
270
+ }
271
+ }
272
+ // -------------------------------------------------------------------------
273
+ // PUT
274
+ // -------------------------------------------------------------------------
275
+ async function PUT(req, ctx) {
276
+ const authErr = await runMiddleware(req);
277
+ if (authErr)
278
+ return authErr;
279
+ try {
280
+ const { route = [] } = await ctx.params;
281
+ // Plugin-defined PUT routes (e.g. PUT /schema/collections/:name)
282
+ const pluginRes = await dispatchPluginRoute(req, route);
283
+ if (pluginRes)
284
+ return pluginRes;
285
+ const parsed = parseRoute(route);
286
+ if (!parsed || !parsed.slug) {
287
+ return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
288
+ }
289
+ const body = await req.json();
290
+ const sha = body.meta?.sha;
291
+ if (!sha) {
292
+ return json({ error: { code: 'CONFLICT', message: 'meta.sha is required for update operations', details: {} } }, 409);
293
+ }
294
+ const payload = { ...(body.data ?? {}), 'meta.sha': sha };
295
+ const entry = await engine.updateEntry(parsed.collection, parsed.slug, payload);
296
+ return json({
297
+ data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) },
298
+ meta: { sha: entry.meta.sha },
299
+ });
300
+ }
301
+ catch (err) {
302
+ return errorResponse(err);
303
+ }
304
+ }
305
+ // -------------------------------------------------------------------------
306
+ // DELETE
307
+ // -------------------------------------------------------------------------
308
+ async function DELETE(req, ctx) {
309
+ const authErr = await runMiddleware(req);
310
+ if (authErr)
311
+ return authErr;
312
+ try {
313
+ const { route = [] } = await ctx.params;
314
+ // Plugin-defined DELETE routes (e.g. DELETE /schema/collections/:name)
315
+ const pluginRes = await dispatchPluginRoute(req, route);
316
+ if (pluginRes)
317
+ return pluginRes;
318
+ const parsed = parseRoute(route);
319
+ if (!parsed || !parsed.slug) {
320
+ return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
321
+ }
322
+ const body = await req.json().catch(() => ({}));
323
+ const sha = body.meta?.sha;
324
+ if (!sha) {
325
+ return json({ error: { code: 'CONFLICT', message: 'meta.sha is required for delete operations', details: {} } }, 409);
326
+ }
327
+ await engine.deleteEntry(parsed.collection, parsed.slug, sha);
328
+ return new Response(null, { status: 204 });
329
+ }
330
+ catch (err) {
331
+ return errorResponse(err);
332
+ }
333
+ }
334
+ // -------------------------------------------------------------------------
335
+ // Audit helpers
336
+ // -------------------------------------------------------------------------
337
+ /** Test a route key against a parameterised pattern like /media/:key/url. */
338
+ function matchesPattern(pattern, routeKey) {
339
+ const regexStr = pattern
340
+ .split('/')
341
+ .map((seg) => seg.startsWith(':') ? '(.+?)' : seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
342
+ .join('\\/');
343
+ return new RegExp(`^${regexStr}$`).test(routeKey);
344
+ }
345
+ /** Classify method + route segments into a human-readable action string. */
346
+ function classifyAction(method, route) {
347
+ const routeKey = '/' + route.join('/');
348
+ // Build sorted plugin route candidates (same logic as dispatchPluginRoute)
349
+ const candidates = [];
350
+ for (const plugin of config.plugins) {
351
+ if (!plugin.routes)
352
+ continue;
353
+ for (const pattern of Object.keys(plugin.routes))
354
+ candidates.push(pattern);
355
+ }
356
+ candidates.sort((a, b) => {
357
+ const literalCount = (p) => p.split('/').filter((s) => !s.startsWith(':')).length;
358
+ return literalCount(b) - literalCount(a);
359
+ });
360
+ for (const pattern of candidates) {
361
+ if (pattern === routeKey || (pattern.includes(':') && matchesPattern(pattern, routeKey))) {
362
+ // e.g. /media/:key/url → plugin.media.*.url
363
+ const action = 'plugin' + pattern.replace(/:[^/]+/g, '*');
364
+ return { action };
365
+ }
366
+ }
367
+ if (route.length === 0)
368
+ return { action: 'unknown' };
369
+ if (route[0] === 'schema')
370
+ return { action: 'schema.get' };
371
+ if (route[0] === 'docs')
372
+ return { action: 'docs.get' };
373
+ if (route[0] === 'openapi.json')
374
+ return { action: 'openapi.get' };
375
+ const collection = route[0];
376
+ const slug = route[1];
377
+ const hasSingle = !!slug;
378
+ const actionMap = {
379
+ GET: ['collection.list', 'collection.get'],
380
+ POST: ['collection.create', 'unknown'],
381
+ PATCH: ['unknown', 'unknown'],
382
+ PUT: ['unknown', 'collection.update'],
383
+ DELETE: ['unknown', 'collection.delete'],
384
+ };
385
+ const pair = actionMap[method] ?? ['unknown', 'unknown'];
386
+ return { action: pair[hasSingle ? 1 : 0], collection, slug };
387
+ }
388
+ /** Emit an audit event to all plugins that registered the onAuditEvent hook. */
389
+ async function emitAuditEvent(event) {
390
+ for (const plugin of config.plugins) {
391
+ if (plugin.hooks?.onAuditEvent) {
392
+ try {
393
+ await plugin.hooks.onAuditEvent(event);
394
+ }
395
+ catch (e) {
396
+ console.error('[airdraft] audit event emit error:', e);
397
+ }
398
+ }
399
+ }
400
+ }
401
+ /** Wrap a handler function so it automatically emits a wide audit event on completion. */
402
+ function auditWrap(method, fn) {
403
+ return async (req, ctx) => {
404
+ const startMs = Date.now();
405
+ const url = new URL(req.url);
406
+ const { route = [] } = await ctx.params;
407
+ const { action, collection, slug } = classifyAction(method, route);
408
+ const actor = req.headers.get('x-api-key') ??
409
+ req.headers.get('authorization')?.replace(/^Bearer\s+/i, '') ??
410
+ undefined;
411
+ const query = Object.fromEntries(url.searchParams);
412
+ const event = {
413
+ requestId: crypto.randomUUID(),
414
+ timestamp: new Date(startMs).toISOString(),
415
+ method,
416
+ path: url.pathname,
417
+ action,
418
+ ...(collection !== undefined && { collection }),
419
+ ...(slug !== undefined && { slug }),
420
+ statusCode: 0,
421
+ durationMs: 0,
422
+ ...(actor !== undefined && { actor }),
423
+ ...(Object.keys(query).length > 0 && { request: { query } }),
424
+ };
425
+ try {
426
+ const response = await fn(req, ctx);
427
+ event.statusCode = response.status;
428
+ // Extract error details from 4xx/5xx responses
429
+ if (response.status >= 400) {
430
+ try {
431
+ const body = (await response.clone().json());
432
+ if (body?.error) {
433
+ event.error = {
434
+ message: body.error.message ?? 'Unknown error',
435
+ ...(body.error.code ? { code: body.error.code } : {}),
436
+ };
437
+ }
438
+ }
439
+ catch {
440
+ /* body parse failed — not JSON */
441
+ }
442
+ }
443
+ return response;
444
+ }
445
+ catch (err) {
446
+ event.statusCode = 500;
447
+ event.error = { message: err instanceof Error ? err.message : String(err) };
448
+ throw err;
449
+ }
450
+ finally {
451
+ event.durationMs = Date.now() - startMs;
452
+ await emitAuditEvent(event);
453
+ }
454
+ };
455
+ }
456
+ // Only wrap when at least one plugin listens for audit events (zero overhead otherwise)
457
+ const hasAuditPlugins = config.plugins.some((p) => p.hooks?.onAuditEvent);
458
+ if (!hasAuditPlugins)
459
+ return { GET, POST, PATCH, PUT, DELETE };
460
+ return {
461
+ GET: auditWrap('GET', GET),
462
+ POST: auditWrap('POST', POST),
463
+ PATCH: auditWrap('PATCH', PATCH),
464
+ PUT: auditWrap('PUT', PUT),
465
+ DELETE: auditWrap('DELETE', DELETE),
466
+ };
467
+ }
468
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from './handler.js';
2
+ export * from './client.js';
3
+ export * from './revalidation.js';
4
+ export * from './middleware.js';
5
+ export * from './tokenProvider.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @airdraft/next — public surface
2
+ export * from './handler.js';
3
+ export * from './client.js';
4
+ export * from './revalidation.js';
5
+ export * from './middleware.js';
6
+ export * from './tokenProvider.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
@@ -0,0 +1,39 @@
1
+ export interface CmsProxyOptions {
2
+ /**
3
+ * Base path of the CMS API. Requests to paths under this prefix will be
4
+ * gated by the middleware. Default: `/api/cms`.
5
+ */
6
+ basePath?: string;
7
+ /**
8
+ * CORS origins to allow. Defaults to same-origin only (no CORS header added).
9
+ * Pass `'*'` to allow any origin (not recommended for write routes).
10
+ */
11
+ corsOrigins?: string | string[];
12
+ /**
13
+ * When `true`, the middleware checks for the presence of any recognized
14
+ * credential (Bearer, X-API-Key, X-Embed-Token, cookie, or ?embedToken)
15
+ * and returns 401 if none are found. Full verification still happens in
16
+ * the route handler — this is a first-pass edge gate only.
17
+ * Default: `false`.
18
+ */
19
+ requireCredentials?: boolean;
20
+ }
21
+ type NextMiddlewareRequest = Request & {
22
+ nextUrl?: URL;
23
+ };
24
+ type NextMiddlewareResponse = Response;
25
+ /**
26
+ * Returns a Next.js-compatible Proxy function that applies CORS headers
27
+ * and an optional credential presence check to all CMS routes.
28
+ *
29
+ * Place in your project's `proxy.ts` (Next.js 16+) or `middleware.ts`:
30
+ *
31
+ * ```ts
32
+ * import { withCmsProxy } from '@airdraft/next'
33
+ * export default withCmsProxy({ basePath: '/api/cms', requireCredentials: true })
34
+ * export const config = { matcher: ['/api/cms/:path*'] }
35
+ * ```
36
+ */
37
+ export declare function withCmsProxy(options?: CmsProxyOptions): (request: NextMiddlewareRequest) => NextMiddlewareResponse;
38
+ export {};
39
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAE/B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,KAAK,qBAAqB,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AACxD,KAAK,sBAAsB,GAAG,QAAQ,CAAA;AAEtC;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,eAAoB,IAI/B,SAAS,qBAAqB,KAAG,sBAAsB,CAiDjF"}
@@ -0,0 +1,89 @@
1
+ // ---------------------------------------------------------------------------
2
+ // withCmsProxy — Next.js Proxy (edge) handler for CMS route protection
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Returns a Next.js-compatible Proxy function that applies CORS headers
6
+ * and an optional credential presence check to all CMS routes.
7
+ *
8
+ * Place in your project's `proxy.ts` (Next.js 16+) or `middleware.ts`:
9
+ *
10
+ * ```ts
11
+ * import { withCmsProxy } from '@airdraft/next'
12
+ * export default withCmsProxy({ basePath: '/api/cms', requireCredentials: true })
13
+ * export const config = { matcher: ['/api/cms/:path*'] }
14
+ * ```
15
+ */
16
+ export function withCmsProxy(options = {}) {
17
+ const basePath = options.basePath ?? '/api/cms';
18
+ const { corsOrigins, requireCredentials = false } = options;
19
+ return function cmsProxy(request) {
20
+ const url = request.nextUrl ?? new URL(request.url);
21
+ const pathname = url.pathname;
22
+ // Only act on CMS routes
23
+ if (!pathname.startsWith(basePath)) {
24
+ return new Response(null, { status: 200 });
25
+ }
26
+ // OPTIONS preflight
27
+ if (request.method === 'OPTIONS') {
28
+ return buildCorsResponse(request, corsOrigins);
29
+ }
30
+ // Credential presence check (edge gate — not full verification)
31
+ if (requireCredentials) {
32
+ const hasCredential = request.headers.has('authorization') ||
33
+ request.headers.has('x-api-key') ||
34
+ request.headers.has('x-embed-token') ||
35
+ hasCmsSessionCookie(request.headers.get('cookie')) ||
36
+ url.searchParams.has('embedToken');
37
+ if (!hasCredential) {
38
+ return new Response(JSON.stringify({
39
+ error: { code: 'UNAUTHORIZED', message: 'Authentication required', details: {} },
40
+ }), {
41
+ status: 401,
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ ...corsHeaders(request, corsOrigins),
45
+ },
46
+ });
47
+ }
48
+ }
49
+ // Pass through — add CORS headers to response
50
+ const response = new Response(null, { status: 200 });
51
+ if (corsOrigins) {
52
+ const h = corsHeaders(request, corsOrigins);
53
+ for (const [k, v] of Object.entries(h)) {
54
+ response.headers.set(k, v);
55
+ }
56
+ }
57
+ return response;
58
+ };
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Helpers
62
+ // ---------------------------------------------------------------------------
63
+ function hasCmsSessionCookie(cookieHeader) {
64
+ if (!cookieHeader)
65
+ return false;
66
+ return cookieHeader.split(';').some((c) => c.trim().startsWith('airdraft_session='));
67
+ }
68
+ function corsHeaders(request, origins) {
69
+ if (!origins)
70
+ return {};
71
+ const requestOrigin = request.headers.get('origin') ?? '';
72
+ const allowed = origins === '*' ||
73
+ (Array.isArray(origins) ? origins.includes(requestOrigin) : origins === requestOrigin);
74
+ if (!allowed)
75
+ return {};
76
+ return {
77
+ 'Access-Control-Allow-Origin': requestOrigin || '*',
78
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
79
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key, X-Embed-Token',
80
+ 'Access-Control-Max-Age': '86400',
81
+ };
82
+ }
83
+ function buildCorsResponse(request, origins) {
84
+ return new Response(null, {
85
+ status: 204,
86
+ headers: corsHeaders(request, origins),
87
+ });
88
+ }
89
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export interface RevalidationWebhookOptions {
2
+ secret: string;
3
+ onRevalidate?: (paths: string[], payload: WebhookPayload) => Promise<void>;
4
+ }
5
+ export interface WebhookPayload {
6
+ action: 'entry.created' | 'entry.updated' | 'entry.deleted';
7
+ collection: string;
8
+ slug: string;
9
+ paths: string[];
10
+ timestamp: string;
11
+ }
12
+ /**
13
+ * Creates a Next.js App Router POST handler for the Airdraft revalidation webhook.
14
+ *
15
+ * Verifies `X-Webhook-Signature: sha256=<hex>` using HMAC-SHA256 over the raw body,
16
+ * then calls `revalidatePath` (or `onRevalidate`) for each path in the payload.
17
+ *
18
+ * Usage in `app/api/cms/revalidate/route.ts`:
19
+ *
20
+ * ```ts
21
+ * export const { POST } = createRevalidationHandler({
22
+ * secret: process.env.AIRDRAFT_REVALIDATE_SECRET!,
23
+ * })
24
+ * ```
25
+ */
26
+ export declare function createRevalidationHandler(opts: RevalidationWebhookOptions): {
27
+ POST: (req: Request) => Promise<Response>;
28
+ };
29
+ //# sourceMappingURL=revalidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidation.d.ts","sourceRoot":"","sources":["../src/revalidation.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,eAAe,CAAA;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B;gBAC/C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAoDrD"}
@@ -0,0 +1,69 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * Creates a Next.js App Router POST handler for the Airdraft revalidation webhook.
4
+ *
5
+ * Verifies `X-Webhook-Signature: sha256=<hex>` using HMAC-SHA256 over the raw body,
6
+ * then calls `revalidatePath` (or `onRevalidate`) for each path in the payload.
7
+ *
8
+ * Usage in `app/api/cms/revalidate/route.ts`:
9
+ *
10
+ * ```ts
11
+ * export const { POST } = createRevalidationHandler({
12
+ * secret: process.env.AIRDRAFT_REVALIDATE_SECRET!,
13
+ * })
14
+ * ```
15
+ */
16
+ export function createRevalidationHandler(opts) {
17
+ async function POST(req) {
18
+ const signature = req.headers.get('x-webhook-signature');
19
+ if (!signature) {
20
+ return errorJson(401, 'UNAUTHORIZED', 'Missing X-Webhook-Signature header');
21
+ }
22
+ const rawBody = await req.text();
23
+ // Verify HMAC-SHA256 signature
24
+ const expected = `sha256=${createHmac('sha256', opts.secret).update(rawBody).digest('hex')}`;
25
+ const expectedBuf = Buffer.from(expected);
26
+ const actualBuf = Buffer.from(signature);
27
+ if (expectedBuf.length !== actualBuf.length ||
28
+ !timingSafeEqual(expectedBuf, actualBuf)) {
29
+ return errorJson(401, 'UNAUTHORIZED', 'Invalid webhook signature');
30
+ }
31
+ let payload;
32
+ try {
33
+ payload = JSON.parse(rawBody);
34
+ }
35
+ catch {
36
+ return errorJson(400, 'VALIDATION_ERROR', 'Invalid JSON payload');
37
+ }
38
+ if (!Array.isArray(payload.paths)) {
39
+ return errorJson(400, 'VALIDATION_ERROR', '"paths" must be an array');
40
+ }
41
+ if (opts.onRevalidate) {
42
+ await opts.onRevalidate(payload.paths, payload);
43
+ }
44
+ else {
45
+ // Default: attempt Next.js revalidatePath if available (edge-safe dynamic import)
46
+ try {
47
+ const { revalidatePath } = await import('next/cache');
48
+ for (const p of payload.paths) {
49
+ revalidatePath(p);
50
+ }
51
+ }
52
+ catch {
53
+ // Not running in a Next.js context — caller should use onRevalidate instead
54
+ }
55
+ }
56
+ return new Response(JSON.stringify({ ok: true, revalidated: payload.paths }), {
57
+ status: 200,
58
+ headers: { 'Content-Type': 'application/json' },
59
+ });
60
+ }
61
+ return { POST };
62
+ }
63
+ function errorJson(status, code, message) {
64
+ return new Response(JSON.stringify({ error: { code, message, details: {} } }), {
65
+ status,
66
+ headers: { 'Content-Type': 'application/json' },
67
+ });
68
+ }
69
+ //# sourceMappingURL=revalidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidation.js","sourceRoot":"","sources":["../src/revalidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAezD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAgC;IACxE,KAAK,UAAU,IAAI,CAAC,GAAY;QAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;QACxD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,oCAAoC,CAAC,CAAA;QAC7E,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAEhC,+BAA+B;QAC/B,MAAM,QAAQ,GAAG,UAAU,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;QAC5F,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACzC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAExC,IACE,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;YACvC,CAAC,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,EACxC,CAAC;YACD,OAAO,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,2BAA2B,CAAC,CAAA;QACpE,CAAC;QAED,IAAI,OAAuB,CAAA;QAC3B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAA;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,sBAAsB,CAAC,CAAA;QACnE,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,0BAA0B,CAAC,CAAA;QACvE,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QACjD,CAAC;aAAM,CAAC;YACN,kFAAkF;YAClF,IAAI,CAAC;gBACH,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;gBACrD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;oBAC9B,cAAc,CAAC,CAAC,CAAC,CAAA;gBACnB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4EAA4E;YAC9E,CAAC;QACH,CAAC;QAED,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE;YAC5E,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAA;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,MAAc,EAAE,IAAY,EAAE,OAAe;IAC9D,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;QAC7E,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface AirdraftTokenProviderOptions {
2
+ /**
3
+ * Airdraft project API key (`ntk_*`). Typically from
4
+ * `process.env.AIRDRAFT_PROJECT_KEY`.
5
+ */
6
+ projectKey: string;
7
+ /**
8
+ * Override the relay base URL. Reads `AIRDRAFT_API_URL` env var when omitted.
9
+ * Default: `https://api.airdraft.space`.
10
+ */
11
+ relayUrl?: string;
12
+ }
13
+ /**
14
+ * Returns a `tokenProvider` function for use with `GitHubAdapter` in mode 2
15
+ * (self-hosted + Airdraft shared GitHub App).
16
+ *
17
+ * Exchanges the project API key for a GitHub installation access token via the
18
+ * Airdraft token relay API. Tokens are cached in-memory for the lifetime of
19
+ * the process and refreshed 5 minutes before expiry.
20
+ *
21
+ * ```ts
22
+ * import { createAirdraftTokenProvider } from '@airdraft/next'
23
+ *
24
+ * new GitHubAdapter({
25
+ * tokenProvider: createAirdraftTokenProvider({
26
+ * projectKey: process.env.AIRDRAFT_PROJECT_KEY!,
27
+ * }),
28
+ * repo: process.env.GITCMS_REPO!,
29
+ * })
30
+ * ```
31
+ */
32
+ export declare function createAirdraftTokenProvider(options: AirdraftTokenProviderOptions): () => Promise<string>;
33
+ //# sourceMappingURL=tokenProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenProvider.d.ts","sourceRoot":"","sources":["../src/tokenProvider.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,MAAM,OAAO,CAAC,MAAM,CAAC,CAkDvB"}
@@ -0,0 +1,68 @@
1
+ import { AdapterError } from '@airdraft/core';
2
+ // ---------------------------------------------------------------------------
3
+ // createAirdraftTokenProvider
4
+ // ---------------------------------------------------------------------------
5
+ const RELAY_PATH = '/v1/github/token';
6
+ const EXPIRY_BUFFER_MS = 5 * 60 * 1000; // refresh 5 minutes before expiry
7
+ /**
8
+ * Returns a `tokenProvider` function for use with `GitHubAdapter` in mode 2
9
+ * (self-hosted + Airdraft shared GitHub App).
10
+ *
11
+ * Exchanges the project API key for a GitHub installation access token via the
12
+ * Airdraft token relay API. Tokens are cached in-memory for the lifetime of
13
+ * the process and refreshed 5 minutes before expiry.
14
+ *
15
+ * ```ts
16
+ * import { createAirdraftTokenProvider } from '@airdraft/next'
17
+ *
18
+ * new GitHubAdapter({
19
+ * tokenProvider: createAirdraftTokenProvider({
20
+ * projectKey: process.env.AIRDRAFT_PROJECT_KEY!,
21
+ * }),
22
+ * repo: process.env.GITCMS_REPO!,
23
+ * })
24
+ * ```
25
+ */
26
+ export function createAirdraftTokenProvider(options) {
27
+ const { projectKey } = options;
28
+ const baseUrl = options.relayUrl ??
29
+ (typeof process !== 'undefined' ? process.env['AIRDRAFT_API_URL'] : undefined) ??
30
+ 'https://api.airdraft.space';
31
+ if (!projectKey || !projectKey.startsWith('ntk_')) {
32
+ throw new AdapterError('createAirdraftTokenProvider: projectKey must be a valid ntk_ API key');
33
+ }
34
+ const relayUrl = `${baseUrl.replace(/\/$/, '')}${RELAY_PATH}`;
35
+ let cached = null;
36
+ return async function tokenProvider() {
37
+ // Return cached token if still fresh
38
+ if (cached && cached.expiresAt - Date.now() > EXPIRY_BUFFER_MS) {
39
+ return cached.token;
40
+ }
41
+ const res = await fetch(relayUrl, {
42
+ method: 'POST',
43
+ headers: {
44
+ Authorization: `Bearer ${projectKey}`,
45
+ Accept: 'application/json',
46
+ },
47
+ });
48
+ if (res.status === 401) {
49
+ throw new AdapterError('Airdraft token relay: invalid or revoked project API key (INVALID_KEY)');
50
+ }
51
+ if (res.status === 404) {
52
+ throw new AdapterError('Airdraft token relay: project has no GitHub connection (GITHUB_NOT_CONNECTED)');
53
+ }
54
+ if (res.status === 503) {
55
+ throw new AdapterError('Airdraft token relay: GitHub token API unavailable (GITHUB_UNAVAILABLE)');
56
+ }
57
+ if (!res.ok) {
58
+ throw new AdapterError(`Airdraft token relay: unexpected status ${res.status}`);
59
+ }
60
+ const body = await res.json();
61
+ cached = {
62
+ token: body.token,
63
+ expiresAt: new Date(body.expiresAt).getTime(),
64
+ };
65
+ return cached.token;
66
+ };
67
+ }
68
+ //# sourceMappingURL=tokenProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenProvider.js","sourceRoot":"","sources":["../src/tokenProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAyB7C,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E,MAAM,UAAU,GAAG,kBAAkB,CAAA;AACrC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,kCAAkC;AAEzE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAAqC;IAErC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAA;IAC9B,MAAM,OAAO,GACX,OAAO,CAAC,QAAQ;QAChB,CAAC,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,4BAA4B,CAAA;IAE9B,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,YAAY,CACpB,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,CAAA;IAC7D,IAAI,MAAM,GAAuB,IAAI,CAAA;IAErC,OAAO,KAAK,UAAU,aAAa;QACjC,qCAAqC;QACrC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,EAAE,CAAC;YAC/D,OAAO,MAAM,CAAC,KAAK,CAAA;QACrB,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,UAAU,EAAE;gBACrC,MAAM,EAAE,kBAAkB;aAC3B;SACF,CAAC,CAAA;QAEF,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,wEAAwE,CAAC,CAAA;QAClG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,+EAA+E,CAAC,CAAA;QACzG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,yEAAyE,CAAC,CAAA;QACnG,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CAAC,2CAA2C,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QACjF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA0C,CAAA;QACrE,MAAM,GAAG;YACP,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;SAC9C,CAAA;QACD,OAAO,MAAM,CAAC,KAAK,CAAA;IACrB,CAAC,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@airdraft/next",
3
+ "version": "0.1.0",
4
+ "description": "Airdraft Next.js App Router integration — route handlers, OpenAPI, revalidation",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./forwarder": {
14
+ "import": "./dist/forwarder.js",
15
+ "types": "./dist/forwarder.d.ts"
16
+ }
17
+ },
18
+ "files": ["dist", "README.md", "CHANGELOG.md"],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "clean": "rm -rf dist",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run build",
27
+ "release": "standard-version",
28
+ "release:patch": "standard-version --release-as patch",
29
+ "release:minor": "standard-version --release-as minor",
30
+ "release:major": "standard-version --release-as major"
31
+ },
32
+ "publishConfig": { "access": "public" },
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "next": ">=14.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.0.0",
39
+ "next": "^14.0.0",
40
+ "standard-version": "^9.5.0",
41
+ "tsx": "^4.7.0",
42
+ "typescript": "^5.4.0",
43
+ "vitest": "^1.5.0"
44
+ },
45
+ "dependencies": {
46
+ "@airdraft/core": "*",
47
+ "zod": "^3.23.0"
48
+ },
49
+ "engines": { "node": ">=18.0.0" }
50
+ }