@ampless/runtime 0.2.0-alpha.9 → 1.0.0-alpha.14

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/dist/index.js CHANGED
@@ -20,39 +20,25 @@ function createStorage(outputs) {
20
20
  // src/posts.ts
21
21
  import { cookies } from "next/headers";
22
22
  import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
23
- function decodeBody(value) {
24
- if (typeof value !== "string") return value;
25
- try {
26
- return JSON.parse(value);
27
- } catch {
28
- return value;
29
- }
30
- }
23
+ import { decodeAwsJson } from "ampless";
31
24
  function decodeMetadata(value) {
32
25
  if (value === null || value === void 0) return void 0;
33
- const parsed = typeof value === "string" ? safeJsonParse(value) : value;
26
+ const parsed = decodeAwsJson(value);
34
27
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
35
28
  }
36
- function safeJsonParse(value) {
37
- try {
38
- return JSON.parse(value);
39
- } catch {
40
- return value;
41
- }
42
- }
43
29
  function toCorePost(p) {
44
30
  return {
45
31
  postId: p.postId,
46
- siteId: p.siteId,
47
32
  slug: p.slug,
48
33
  title: p.title,
49
34
  excerpt: p.excerpt ?? void 0,
50
35
  format: p.format ?? "markdown",
51
- body: decodeBody(p.body),
36
+ body: decodeAwsJson(p.body),
52
37
  status: p.status ?? "draft",
53
38
  publishedAt: p.publishedAt ?? void 0,
54
39
  tags: (p.tags ?? []).filter((t) => typeof t === "string"),
55
- metadata: decodeMetadata(p.metadata)
40
+ metadata: decodeMetadata(p.metadata),
41
+ updatedAt: p.updatedAt ?? void 0
56
42
  };
57
43
  }
58
44
  function createPostsApi(outputs) {
@@ -66,7 +52,6 @@ function createPostsApi(outputs) {
66
52
  return {
67
53
  async listPublishedPosts(opts = {}) {
68
54
  const { data, errors } = await client.queries.listPublishedPosts({
69
- siteId: opts.siteId ?? "default",
70
55
  from: opts.from,
71
56
  to: opts.to,
72
57
  limit: opts.limit ?? 20,
@@ -76,9 +61,8 @@ function createPostsApi(outputs) {
76
61
  const items = (data?.items ?? []).filter((p) => p !== null).map(toCorePost);
77
62
  return { items, nextToken: data?.nextToken ?? null };
78
63
  },
79
- async getPublishedPost(slug, opts = {}) {
64
+ async getPublishedPost(slug) {
80
65
  const { data, errors } = await client.queries.getPublishedPost({
81
- siteId: opts.siteId ?? "default",
82
66
  slug
83
67
  });
84
68
  if (errors) throw new Error(errors[0]?.message ?? "Failed to get post");
@@ -86,7 +70,6 @@ function createPostsApi(outputs) {
86
70
  },
87
71
  async listPostsByTag(tag, opts = {}) {
88
72
  const { data, errors } = await client.queries.listPostsByTag({
89
- siteId: opts.siteId ?? "default",
90
73
  tag,
91
74
  limit: opts.limit ?? 20,
92
75
  nextToken: opts.nextToken
@@ -99,25 +82,27 @@ function createPostsApi(outputs) {
99
82
  }
100
83
 
101
84
  // src/site-settings.ts
102
- import { DEFAULT_SITE_ID, siteFor, unflattenSettings } from "ampless";
85
+ import { unflattenSettings } from "ampless";
103
86
  function createSiteSettings(cmsConfig, storage) {
104
- async function fetchRemote(siteId) {
87
+ async function fetchRemote() {
105
88
  if (!storage.isStorageConfigured()) return null;
106
89
  let url;
107
90
  try {
108
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
91
+ url = storage.publicAssetUrl("public/site-settings.json");
109
92
  } catch {
110
93
  return null;
111
94
  }
112
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
95
+ const res = await fetch(url, {
96
+ next: { revalidate: 60, tags: ["site-settings"] }
97
+ });
113
98
  if (!res.ok) return null;
114
99
  const flat = await res.json();
115
100
  return unflattenSettings(flat);
116
101
  }
117
102
  return {
118
- async loadSiteSettings(siteId = DEFAULT_SITE_ID) {
119
- const remote = await fetchRemote(siteId).catch(() => null);
120
- const baseSite = siteFor(siteId, cmsConfig);
103
+ async loadSiteSettings() {
104
+ const remote = await fetchRemote().catch(() => null);
105
+ const baseSite = cmsConfig.site;
121
106
  return {
122
107
  site: {
123
108
  name: remote?.site?.name ?? baseSite.name,
@@ -142,15 +127,14 @@ function createSiteSettings(cmsConfig, storage) {
142
127
  }
143
128
 
144
129
  // src/seo.ts
145
- import { DEFAULT_SITE_ID as DEFAULT_SITE_ID2 } from "ampless";
146
130
  function isPlugin(p) {
147
131
  return typeof p === "object" && p !== null && "apiVersion" in p;
148
132
  }
149
133
  function createSeo(cmsConfig, settingsApi) {
150
134
  const plugins = (cmsConfig.plugins ?? []).filter(isPlugin);
151
135
  return {
152
- async postMetadata(post, siteId = DEFAULT_SITE_ID2) {
153
- const settings = await settingsApi.loadSiteSettings(siteId);
136
+ async postMetadata(post) {
137
+ const settings = await settingsApi.loadSiteSettings();
154
138
  const accum = {};
155
139
  for (const plugin of plugins) {
156
140
  if (!plugin.metadata) continue;
@@ -167,8 +151,8 @@ function createSeo(cmsConfig, settingsApi) {
167
151
  }
168
152
  return accum;
169
153
  },
170
- async siteMetadata(siteId = DEFAULT_SITE_ID2) {
171
- const settings = await settingsApi.loadSiteSettings(siteId);
154
+ async siteMetadata() {
155
+ const settings = await settingsApi.loadSiteSettings();
172
156
  const accum = {
173
157
  title: settings.site.name,
174
158
  description: settings.site.description
@@ -193,24 +177,25 @@ function createSeo(cmsConfig, settingsApi) {
193
177
 
194
178
  // src/theme-active.ts
195
179
  import { headers } from "next/headers";
196
- import { DEFAULT_SITE_ID as DEFAULT_SITE_ID3 } from "ampless";
197
180
  function createThemeActive(registry, storage) {
198
- async function fetchActiveFromCache(siteId) {
181
+ async function fetchActiveFromCache() {
199
182
  if (!storage.isStorageConfigured()) return null;
200
183
  let url;
201
184
  try {
202
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
185
+ url = storage.publicAssetUrl("public/site-settings.json");
203
186
  } catch {
204
187
  return null;
205
188
  }
206
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
189
+ const res = await fetch(url, {
190
+ next: { revalidate: 60, tags: ["site-settings"] }
191
+ });
207
192
  if (!res.ok) return null;
208
193
  const flat = await res.json();
209
194
  const v = flat["theme.active"];
210
195
  return typeof v === "string" ? v : null;
211
196
  }
212
197
  return {
213
- async resolveActiveTheme(siteId = DEFAULT_SITE_ID3) {
198
+ async resolveActiveTheme() {
214
199
  let previewOverride = null;
215
200
  try {
216
201
  const h = await headers();
@@ -221,7 +206,7 @@ function createThemeActive(registry, storage) {
221
206
  const mod2 = registry.themes[previewOverride];
222
207
  if (mod2) return { name: previewOverride, module: mod2 };
223
208
  }
224
- const stored = await fetchActiveFromCache(siteId).catch(() => null);
209
+ const stored = await fetchActiveFromCache().catch(() => null);
225
210
  const name = stored && stored in registry.themes ? stored : registry.defaultTheme;
226
211
  const mod = registry.themes[name] ?? registry.themes[registry.defaultTheme];
227
212
  if (!mod) {
@@ -236,7 +221,6 @@ function createThemeActive(registry, storage) {
236
221
 
237
222
  // src/theme-config.ts
238
223
  import {
239
- DEFAULT_SITE_ID as DEFAULT_SITE_ID4,
240
224
  resolveThemeValues,
241
225
  themeSettingKey
242
226
  } from "ampless";
@@ -247,23 +231,23 @@ function validateColorScheme(raw) {
247
231
  return DEFAULT_COLOR_SCHEME;
248
232
  }
249
233
  function createThemeConfig(themeActive, storage) {
250
- async function fetchRemote(siteId) {
234
+ async function fetchRemote() {
251
235
  if (!storage.isStorageConfigured()) return null;
252
236
  let url;
253
237
  try {
254
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
238
+ url = storage.publicAssetUrl("public/site-settings.json");
255
239
  } catch {
256
240
  return null;
257
241
  }
258
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
242
+ const res = await fetch(url, { next: { revalidate: 60, tags: ["site-settings"] } });
259
243
  if (!res.ok) return null;
260
244
  return await res.json();
261
245
  }
262
246
  return {
263
- async loadThemeConfig(siteId = DEFAULT_SITE_ID4) {
247
+ async loadThemeConfig() {
264
248
  const [active, flat] = await Promise.all([
265
- themeActive.resolveActiveTheme(siteId),
266
- fetchRemote(siteId).catch(() => null)
249
+ themeActive.resolveActiveTheme(),
250
+ fetchRemote().catch(() => null)
267
251
  ]);
268
252
  const manifest = active.module.manifest;
269
253
  const stored = {};
@@ -299,9 +283,17 @@ ${lines.join("\n")}
299
283
  }
300
284
 
301
285
  // src/rendering.ts
286
+ import { marked } from "marked";
302
287
  function escape(s) {
303
288
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
304
289
  }
290
+ function textAlignStyle(attrs) {
291
+ const v = attrs?.textAlign;
292
+ if (v === "left" || v === "center" || v === "right" || v === "justify") {
293
+ return ` style="text-align: ${v}"`;
294
+ }
295
+ return "";
296
+ }
305
297
  function renderTiptap(node) {
306
298
  if (node.type === "text") {
307
299
  let html = escape(node.text ?? "");
@@ -310,6 +302,8 @@ function renderTiptap(node) {
310
302
  else if (mark.type === "italic") html = `<em>${html}</em>`;
311
303
  else if (mark.type === "code") html = `<code>${html}</code>`;
312
304
  else if (mark.type === "strike") html = `<s>${html}</s>`;
305
+ else if (mark.type === "underline") html = `<u>${html}</u>`;
306
+ else if (mark.type === "highlight") html = `<mark>${html}</mark>`;
313
307
  else if (mark.type === "link") {
314
308
  const href = escape(String(mark.attrs?.href ?? "#"));
315
309
  html = `<a href="${href}" target="_blank" rel="noopener">${html}</a>`;
@@ -322,10 +316,10 @@ function renderTiptap(node) {
322
316
  case "doc":
323
317
  return children;
324
318
  case "paragraph":
325
- return `<p>${children}</p>`;
319
+ return `<p${textAlignStyle(node.attrs)}>${children}</p>`;
326
320
  case "heading": {
327
321
  const level = Number(node.attrs?.level ?? 1);
328
- return `<h${level}>${children}</h${level}>`;
322
+ return `<h${level}${textAlignStyle(node.attrs)}>${children}</h${level}>`;
329
323
  }
330
324
  case "bulletList":
331
325
  return `<ul>${children}</ul>`;
@@ -350,52 +344,39 @@ function renderTiptap(node) {
350
344
  const display = node.attrs?.display ? ` data-display="${escape(String(node.attrs.display))}"` : "";
351
345
  return `<img src="${src}" alt="${alt}"${title}${display} loading="lazy" />`;
352
346
  }
347
+ case "table":
348
+ return `<table class="tiptap-table"><tbody>${children}</tbody></table>`;
349
+ case "tableRow":
350
+ return `<tr>${children}</tr>`;
351
+ case "tableHeader":
352
+ return `<th${tableCellAttrs(node.attrs)}>${children}</th>`;
353
+ case "tableCell":
354
+ return `<td${tableCellAttrs(node.attrs)}>${children}</td>`;
355
+ case "taskList":
356
+ return `<ul data-type="taskList">${children}</ul>`;
357
+ case "taskItem": {
358
+ const checked = node.attrs?.checked === true ? "true" : "false";
359
+ return `<li data-type="taskItem" data-checked="${checked}">${children}</li>`;
360
+ }
353
361
  default:
354
362
  return children;
355
363
  }
356
364
  }
357
- function renderMarkdown(md) {
358
- const lines = md.split("\n");
359
- const out = [];
360
- let i = 0;
361
- while (i < lines.length) {
362
- const line = lines[i] ?? "";
363
- if (line.startsWith("# ")) {
364
- out.push(`<h1>${escape(line.slice(2))}</h1>`);
365
- i++;
366
- } else if (line.startsWith("## ")) {
367
- out.push(`<h2>${escape(line.slice(3))}</h2>`);
368
- i++;
369
- } else if (line.startsWith("```")) {
370
- const lang = line.slice(3).trim();
371
- const code = [];
372
- i++;
373
- while (i < lines.length && !(lines[i] ?? "").startsWith("```")) {
374
- code.push(lines[i] ?? "");
375
- i++;
376
- }
377
- i++;
378
- out.push(
379
- `<pre><code${lang ? ` class="language-${escape(lang)}"` : ""}>${escape(code.join("\n"))}</code></pre>`
380
- );
381
- } else if (line.startsWith("- ")) {
382
- const items = [];
383
- while (i < lines.length && (lines[i] ?? "").startsWith("- ")) {
384
- items.push(`<li>${renderInlineMarkdown((lines[i] ?? "").slice(2))}</li>`);
385
- i++;
386
- }
387
- out.push(`<ul>${items.join("")}</ul>`);
388
- } else if (line.trim() === "") {
389
- i++;
390
- } else {
391
- out.push(`<p>${renderInlineMarkdown(line)}</p>`);
392
- i++;
393
- }
365
+ function tableCellAttrs(attrs) {
366
+ let out = "";
367
+ const colspan = Number(attrs?.colspan ?? 1);
368
+ if (colspan > 1) out += ` colspan="${colspan}"`;
369
+ const rowspan = Number(attrs?.rowspan ?? 1);
370
+ if (rowspan > 1) out += ` rowspan="${rowspan}"`;
371
+ const colwidth = attrs?.colwidth;
372
+ if (Array.isArray(colwidth) && colwidth.length > 0) {
373
+ const w = Number(colwidth[0]);
374
+ if (Number.isFinite(w) && w > 0) out += ` style="width: ${w}px"`;
394
375
  }
395
- return out.join("\n");
376
+ return out;
396
377
  }
397
- function renderInlineMarkdown(text) {
398
- return text.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
378
+ function renderMarkdown(md) {
379
+ return marked.parse(md, { gfm: true, breaks: false, async: false });
399
380
  }
400
381
  function renderBody(post) {
401
382
  if (post.format === "html") return String(post.body);
@@ -426,6 +407,8 @@ function tiptapNodeToMarkdown(node) {
426
407
  else if (mark.type === "italic") txt = `*${txt}*`;
427
408
  else if (mark.type === "code") txt = `\`${txt}\``;
428
409
  else if (mark.type === "strike") txt = `~~${txt}~~`;
410
+ else if (mark.type === "underline") txt = `<u>${txt}</u>`;
411
+ else if (mark.type === "highlight") txt = `<mark>${txt}</mark>`;
429
412
  else if (mark.type === "link") txt = `[${txt}](${String(mark.attrs?.href ?? "#")})`;
430
413
  }
431
414
  return txt;
@@ -463,12 +446,54 @@ function tiptapNodeToMarkdown(node) {
463
446
  const alt = String(node.attrs?.alt ?? "");
464
447
  return `![${alt}](${src})`;
465
448
  }
449
+ case "table":
450
+ return tiptapTableToMarkdown(node);
451
+ case "taskList":
452
+ return children + "\n";
453
+ case "taskItem": {
454
+ const checked = node.attrs?.checked === true ? "x" : " ";
455
+ const inner = children.replace(/\n+$/, "");
456
+ const [first, ...rest] = inner.split("\n");
457
+ const cont = rest.map((l) => l ? " " + l : l).join("\n");
458
+ return `- [${checked}] ${first ?? ""}${cont ? "\n" + cont : ""}
459
+ `;
460
+ }
466
461
  default:
467
462
  return children;
468
463
  }
469
464
  }
465
+ function tiptapTableToMarkdown(node) {
466
+ const rows = node.content ?? [];
467
+ if (rows.length === 0) return "";
468
+ const renderedRows = [];
469
+ let headerIdx = -1;
470
+ for (let i = 0; i < rows.length; i++) {
471
+ const row = rows[i];
472
+ const cells = row.content ?? [];
473
+ const cellTexts = cells.map((c) => {
474
+ const inner = (c.content ?? []).map(tiptapNodeToMarkdown).join("");
475
+ return inner.replace(/\n+$/, "").replace(/\n/g, "<br>").replace(/\|/g, "\\|");
476
+ });
477
+ renderedRows.push(cellTexts);
478
+ if (headerIdx === -1 && cells.some((c) => c.type === "tableHeader")) headerIdx = i;
479
+ }
480
+ if (headerIdx === -1) headerIdx = 0;
481
+ const header = renderedRows[headerIdx] ?? [];
482
+ const body = renderedRows.filter((_, i) => i !== headerIdx);
483
+ const cols = header.length;
484
+ const headerLine = "| " + header.join(" | ") + " |";
485
+ const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
486
+ const bodyLines = body.map((r) => {
487
+ const cells = Array.from({ length: cols }, (_, i) => r[i] ?? "");
488
+ return "| " + cells.join(" | ") + " |";
489
+ });
490
+ return "\n" + [headerLine, sepLine, ...bodyLines].join("\n") + "\n\n";
491
+ }
470
492
  function htmlToMarkdown(html) {
471
493
  let md = html;
494
+ md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, inner) => {
495
+ return "\n" + convertHtmlTable(String(inner)) + "\n";
496
+ });
472
497
  md = md.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, text) => {
473
498
  return "\n" + "#".repeat(Number(level)) + " " + String(text).trim() + "\n\n";
474
499
  });
@@ -481,6 +506,9 @@ function htmlToMarkdown(html) {
481
506
  md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
482
507
  return "\n" + String(content).trim().split("\n").map((l) => "> " + l).join("\n") + "\n\n";
483
508
  });
509
+ md = md.replace(/<ul[^>]*data-type="taskList"[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
510
+ return "\n" + convertHtmlTaskList(String(items)) + "\n";
511
+ });
484
512
  md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
485
513
  return "\n" + String(items).replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n") + "\n";
486
514
  });
@@ -500,11 +528,63 @@ function htmlToMarkdown(html) {
500
528
  md = md.replace(/<(em|i)>([\s\S]*?)<\/\1>/gi, "*$2*");
501
529
  md = md.replace(/<s>([\s\S]*?)<\/s>/gi, "~~$1~~");
502
530
  md = md.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`");
531
+ const PH_U_OPEN = "AMP_U_OPEN";
532
+ const PH_U_CLOSE = "AMP_U_CLOSE";
533
+ const PH_MARK_OPEN = "AMP_MARK_OPEN";
534
+ const PH_MARK_CLOSE = "AMP_MARK_CLOSE";
535
+ md = md.replace(/<u>([\s\S]*?)<\/u>/gi, `${PH_U_OPEN}$1${PH_U_CLOSE}`);
536
+ md = md.replace(/<mark>([\s\S]*?)<\/mark>/gi, `${PH_MARK_OPEN}$1${PH_MARK_CLOSE}`);
503
537
  md = md.replace(/<\/?[^>]+>/g, "");
538
+ md = md.split(PH_U_OPEN).join("<u>").split(PH_U_CLOSE).join("</u>").split(PH_MARK_OPEN).join("<mark>").split(PH_MARK_CLOSE).join("</mark>");
504
539
  md = md.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
505
540
  md = md.replace(/\n{3,}/g, "\n\n");
506
541
  return md.trim() + "\n";
507
542
  }
543
+ function convertHtmlTable(inner) {
544
+ const stripped = inner.replace(/<\/?(thead|tbody)[^>]*>/gi, "");
545
+ const rowRe = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
546
+ const rows = [];
547
+ let m;
548
+ while ((m = rowRe.exec(stripped)) !== null) {
549
+ const rowHtml = m[1] ?? "";
550
+ const cellRe = /<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi;
551
+ const cells = [];
552
+ let isHeader = false;
553
+ let cm;
554
+ while ((cm = cellRe.exec(rowHtml)) !== null) {
555
+ if ((cm[1] ?? "").toLowerCase() === "th") isHeader = true;
556
+ cells.push(normalizeTableCell(cm[2] ?? ""));
557
+ }
558
+ if (cells.length > 0) rows.push({ isHeader, cells });
559
+ }
560
+ if (rows.length === 0) return "";
561
+ let headerIdx = rows.findIndex((r) => r.isHeader);
562
+ if (headerIdx === -1) headerIdx = 0;
563
+ const header = rows[headerIdx].cells;
564
+ const body = rows.filter((_, i) => i !== headerIdx).map((r) => r.cells);
565
+ const cols = header.length;
566
+ const headerLine = "| " + header.join(" | ") + " |";
567
+ const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
568
+ const bodyLines = body.map((cells) => {
569
+ const padded = Array.from({ length: cols }, (_, i) => cells[i] ?? "");
570
+ return "| " + padded.join(" | ") + " |";
571
+ });
572
+ return [headerLine, sepLine, ...bodyLines].join("\n") + "\n";
573
+ }
574
+ function normalizeTableCell(html) {
575
+ return html.replace(/<br\s*\/?>/gi, " ").replace(/<\/?[^>]+>/g, "").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
576
+ }
577
+ function convertHtmlTaskList(items) {
578
+ const liRe = /<li[^>]*data-checked="(true|false)"[^>]*>([\s\S]*?)<\/li>/gi;
579
+ const out = [];
580
+ let m;
581
+ while ((m = liRe.exec(items)) !== null) {
582
+ const checked = m[1] === "true" ? "x" : " ";
583
+ const inner = String(m[2] ?? "").replace(/<\/?p[^>]*>/gi, "").trim();
584
+ out.push(`- [${checked}] ${inner}`);
585
+ }
586
+ return out.join("\n");
587
+ }
508
588
 
509
589
  // src/index.ts
510
590
  function createAmpless(opts) {
@@ -517,13 +597,13 @@ function createAmpless(opts) {
517
597
  const themeConfig = createThemeConfig(themeActive, storage);
518
598
  return {
519
599
  listPublishedPosts: (o) => posts.listPublishedPosts(o),
520
- getPublishedPost: (slug, o) => posts.getPublishedPost(slug, o),
600
+ getPublishedPost: (slug) => posts.getPublishedPost(slug),
521
601
  listPostsByTag: (tag, o) => posts.listPostsByTag(tag, o),
522
- loadSiteSettings: (siteId) => settings.loadSiteSettings(siteId),
523
- resolveActiveTheme: (siteId) => themeActive.resolveActiveTheme(siteId),
524
- loadThemeConfig: (siteId) => themeConfig.loadThemeConfig(siteId),
525
- postMetadata: (post, siteId) => seo.postMetadata(post, siteId),
526
- siteMetadata: (siteId) => seo.siteMetadata(siteId),
602
+ loadSiteSettings: () => settings.loadSiteSettings(),
603
+ resolveActiveTheme: () => themeActive.resolveActiveTheme(),
604
+ loadThemeConfig: () => themeConfig.loadThemeConfig(),
605
+ postMetadata: (post) => seo.postMetadata(post),
606
+ siteMetadata: () => seo.siteMetadata(),
527
607
  renderBody: (post) => renderBody(post),
528
608
  renderThemeCss: (cssVars) => renderThemeCss(cssVars),
529
609
  publicAssetUrl: (key) => storage.publicAssetUrl(key),
@@ -1,35 +1,47 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { Config } from 'ampless';
2
+ import { Config, PostMetadata } from 'ampless';
3
3
 
4
+ declare const runtime = "nodejs";
4
5
  interface CreateMiddlewareOpts {
5
6
  cmsConfig: Config;
7
+ /** AppSync GraphQL endpoint URL (from `amplify_outputs.json` `data.url`). */
8
+ appsyncUrl: string;
9
+ /** Public API key for the AppSync endpoint (from `amplify_outputs.json` `data.api_key`). */
10
+ apiKey: string;
6
11
  }
7
- type MiddlewareFn = (request: NextRequest) => NextResponse;
12
+ type MiddlewareFn = (request: NextRequest) => Promise<NextResponse>;
13
+ declare function _resetFlagCache(): void;
14
+ /**
15
+ * Compute the `Cache-Control` header for a post response. The
16
+ * strategy lives on `post.metadata.cache`; absent (or 'auto') falls
17
+ * back to cooldown-by-edit-time logic. See `CacheStrategy` /
18
+ * `CacheConfig` in ampless types for the semantic contract.
19
+ */
20
+ declare function computeCacheControl(flags: {
21
+ metadata: PostMetadata | null;
22
+ updatedAt: string;
23
+ }, cmsConfig: Config): string;
8
24
  /**
9
25
  * Build the ampless public-site middleware. Performs:
10
26
  *
11
- * - hostname siteId resolution (multi-site rewrites)
12
- * - `/path` `/site/<siteId>/path` internal rewrite
13
- * - `?previewTheme=<name>` `x-preview-theme` header forwarding
14
- * - `Cache-Control: private, no-store` in multi-site mode (Amplify
15
- * Hosting's CloudFront cache key doesn't include Host, so SSR
16
- * responses would cross-contaminate at the same path)
17
- *
18
- * No-layout / bare-HTML routing is data-driven: the theme post
19
- * dispatcher reads `post.metadata.no_layout` and redirects to
20
- * `/raw/<slug>` when set. The plain prefix-prepend below picks up
21
- * that path because the raw route lives at
22
- * `app/site/<siteId>/raw/<slug>/route.ts` — no special middleware
23
- * branch is needed.
24
- *
25
- * The factory captures the multi-site flag at construction time so the
26
- * hot path stays a pair of cheap header lookups.
27
+ * - AppSync flag fetch (`format` / `metadata` / `updatedAt`) per
28
+ * slug, with a 200-entry LRU keyed by slug, 60s TTL. Hot slugs
29
+ * cost zero queries for the cache lifetime.
30
+ * - Rewrite to `/r/<slug>(/<path>)` when the post is no_layout HTML
31
+ * (`format=html` + `metadata.no_layout=true`) or a static bundle
32
+ * (`format='static'`). Themed posts (default) get no rewrite —
33
+ * `app/[slug]/page.tsx` serves them directly.
34
+ * - `Cache-Control` header computed from `post.metadata.cache` +
35
+ * `post.updatedAt` + `cms.config.cache.*` and set on the response.
36
+ * - `?previewTheme` / `?previewColorScheme` `x-preview-theme` /
37
+ * `x-preview-color-scheme` header forwarding for the admin's
38
+ * iframe-based theme preview.
27
39
  */
28
- declare function createAmplessMiddleware({ cmsConfig }: CreateMiddlewareOpts): MiddlewareFn;
40
+ declare function createAmplessMiddleware(opts: CreateMiddlewareOpts): MiddlewareFn;
29
41
  /**
30
42
  * Reference matcher config — admin / api / login / static assets /
31
43
  * amplify_outputs.json are excluded so middleware doesn't rewrite
32
- * legitimate non-blog routes into the public site tree.
44
+ * legitimate non-blog routes.
33
45
  *
34
46
  * **You can't re-export this directly.** Next.js 16's Turbopack
35
47
  * requires `export const config` in `proxy.ts` (or `middleware.ts`)
@@ -46,4 +58,4 @@ declare const defaultMatcherConfig: {
46
58
  matcher: string[];
47
59
  };
48
60
 
49
- export { type CreateMiddlewareOpts, type MiddlewareFn, createAmplessMiddleware, defaultMatcherConfig };
61
+ export { type CreateMiddlewareOpts, type MiddlewareFn, _resetFlagCache, computeCacheControl, createAmplessMiddleware, defaultMatcherConfig, runtime };