@agntcms/next 0.2.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/dist/assets-Cyt9upqW.d.cts +290 -0
- package/dist/assets-P8OCigDG.d.ts +290 -0
- package/dist/client.cjs +13244 -0
- package/dist/client.d.cts +806 -0
- package/dist/client.d.ts +806 -0
- package/dist/client.mjs +13234 -0
- package/dist/config.cjs +240 -0
- package/dist/config.d.cts +112 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.mjs +194 -0
- package/dist/defineForm-Bp9vzW56.d.ts +71 -0
- package/dist/defineForm-CJ8KZC93.d.cts +71 -0
- package/dist/defineSection-9qQ5ulAH.d.cts +243 -0
- package/dist/defineSection-Kr0pWqMY.d.ts +243 -0
- package/dist/form-BqY0H1V5.d.cts +753 -0
- package/dist/form-BqY0H1V5.d.ts +753 -0
- package/dist/global-CV23g5Bn.d.cts +15 -0
- package/dist/global-CV23g5Bn.d.ts +15 -0
- package/dist/handlers.cjs +2525 -0
- package/dist/handlers.d.cts +330 -0
- package/dist/handlers.d.ts +330 -0
- package/dist/handlers.mjs +2473 -0
- package/dist/index.cjs +372 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.mjs +319 -0
- package/dist/rateLimit-CXptRM_K.d.ts +391 -0
- package/dist/rateLimit-CiROGTLE.d.cts +391 -0
- package/dist/registry-CraTTwT7.d.cts +29 -0
- package/dist/registry-DMujGqt0.d.ts +29 -0
- package/dist/server.cjs +1970 -0
- package/dist/server.d.cts +153 -0
- package/dist/server.d.ts +153 -0
- package/dist/server.mjs +1889 -0
- package/package.json +62 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { P as Page, w as PageSummary } from './form-BqY0H1V5.cjs';
|
|
2
|
+
import { G as Global } from './global-CV23g5Bn.cjs';
|
|
3
|
+
|
|
4
|
+
/** A page's publication mode. Used to select which storage bucket `readPage` reads from. */
|
|
5
|
+
type PageMode = 'published' | 'draft';
|
|
6
|
+
/**
|
|
7
|
+
* Lightweight summary of a history snapshot, returned by `listHistory`.
|
|
8
|
+
*
|
|
9
|
+
* `timestamp` is the filename stem (e.g. "2026-04-12T16-00-00.000Z"),
|
|
10
|
+
* not a parsed Date, because it is also used as the key for
|
|
11
|
+
* `readHistorySnapshot`. Keeping it as a string avoids lossy Date
|
|
12
|
+
* round-trips and keeps the API surface small.
|
|
13
|
+
*/
|
|
14
|
+
interface HistoryEntry {
|
|
15
|
+
readonly slug: string;
|
|
16
|
+
/** The filename stem, e.g. "2026-04-12T16-00-00.000Z". */
|
|
17
|
+
readonly timestamp: string;
|
|
18
|
+
/** File size in bytes. */
|
|
19
|
+
readonly size: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Lightweight summary of a draft page, returned by `listDrafts`.
|
|
23
|
+
*
|
|
24
|
+
* Kept intentionally small: the UI that lists drafts only needs "which
|
|
25
|
+
* pages have pending edits, and when were they last touched". Anything
|
|
26
|
+
* beyond this (author, diff stats, etc.) is out of scope for v1.
|
|
27
|
+
*/
|
|
28
|
+
interface DraftSummary {
|
|
29
|
+
readonly slug: string;
|
|
30
|
+
readonly updatedAt: Date;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Lightweight list-entry for a published page, returned by `listPages`.
|
|
34
|
+
*
|
|
35
|
+
* BREAKING (v0.1.x → v0.1.y): renamed from `PageSummary` to
|
|
36
|
+
* `PublishedPageEntry` so the canonical `PageSummary` name can be claimed
|
|
37
|
+
* by the domain-layer metadata projection (`Omit<Page, 'sections'>`)
|
|
38
|
+
* exposed for blog-index-style listings (ARCHITECTURE.md §4). Templates
|
|
39
|
+
* that imported `PageSummary` from `@agntcms/next/server` to type the
|
|
40
|
+
* existing `listPages()` adapter return must rename their import — the
|
|
41
|
+
* shape (slug + updatedAt) is unchanged.
|
|
42
|
+
*
|
|
43
|
+
* Same shape as `DraftSummary` — kept as a separate type because the
|
|
44
|
+
* two collections have different semantic meaning and may diverge.
|
|
45
|
+
*/
|
|
46
|
+
interface PublishedPageEntry {
|
|
47
|
+
readonly slug: string;
|
|
48
|
+
readonly updatedAt: Date;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Page-metadata-with-mtime tuple, returned by `listPageSummaries`.
|
|
52
|
+
*
|
|
53
|
+
* The adapter widens domain `PageSummary` (Omit<Page, 'sections'>) with
|
|
54
|
+
* an `updatedAt: Date` carrying the storage layer's "last modified"
|
|
55
|
+
* timestamp. The runtime uses this as the sort key when a page has no
|
|
56
|
+
* explicit `publishedAt` (ARCHITECTURE.md §4: mtime fallback). Adapters
|
|
57
|
+
* that don't track mtime (e.g. a remote API) MAY return the unix epoch
|
|
58
|
+
* to mean "unknown" — sorting will then put such pages at the end of a
|
|
59
|
+
* `'newest'` query and at the start of an `'oldest'` query, but never
|
|
60
|
+
* crash.
|
|
61
|
+
*/
|
|
62
|
+
interface PageSummaryEntry extends PageSummary {
|
|
63
|
+
readonly updatedAt: Date;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lightweight summary of a global content block, returned by `listGlobals`.
|
|
67
|
+
*/
|
|
68
|
+
interface GlobalSummary {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly type: string;
|
|
71
|
+
readonly updatedAt: Date;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Lightweight summary of a global history snapshot, returned by
|
|
75
|
+
* `listGlobalHistory`. Mirrors `HistoryEntry` for pages but keyed by
|
|
76
|
+
* global `name` instead of `slug`; the shape is kept symmetric so UI
|
|
77
|
+
* code that handles history listings can stay shape-agnostic.
|
|
78
|
+
*/
|
|
79
|
+
interface GlobalHistoryEntry {
|
|
80
|
+
readonly name: string;
|
|
81
|
+
/** The filename stem, e.g. "2026-04-12T16-00-00.000Z". */
|
|
82
|
+
readonly timestamp: string;
|
|
83
|
+
/** File size in bytes. */
|
|
84
|
+
readonly size: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Content storage adapter — the seam between the runtime and whatever
|
|
88
|
+
* persists pages and drafts. See the file header for the rationale
|
|
89
|
+
* behind this exact method set.
|
|
90
|
+
*
|
|
91
|
+
* All methods are async because a real implementation (FS, remote) is
|
|
92
|
+
* always IO-bound. Implementations MUST NOT throw for "not found"; they
|
|
93
|
+
* MUST return `null` from `readPage`.
|
|
94
|
+
*/
|
|
95
|
+
interface ContentStorageAdapter {
|
|
96
|
+
/**
|
|
97
|
+
* Read a page in the requested publication mode.
|
|
98
|
+
*
|
|
99
|
+
* Returns `null` when no file exists for `(slug, mode)`. Does NOT fall
|
|
100
|
+
* back across modes; composition is the runtime's responsibility.
|
|
101
|
+
*/
|
|
102
|
+
readPage(slug: string, mode: PageMode): Promise<Page | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Persist a draft of `page`. Overwrites any existing draft for the
|
|
105
|
+
* same slug. The draft storage bucket is distinct from the published
|
|
106
|
+
* bucket — `saveDraft` MUST NOT touch the published page.
|
|
107
|
+
*/
|
|
108
|
+
saveDraft(page: Page): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* List all drafts currently on disk. Ordering is not specified by the
|
|
111
|
+
* contract; callers that need a particular order sort the result
|
|
112
|
+
* themselves.
|
|
113
|
+
*/
|
|
114
|
+
listDrafts(): Promise<ReadonlyArray<DraftSummary>>;
|
|
115
|
+
/**
|
|
116
|
+
* List all published pages with minimal info (slug + mtime). Used by
|
|
117
|
+
* the admin pages list. Ordering is not specified by the contract;
|
|
118
|
+
* callers that need a particular order sort the result themselves.
|
|
119
|
+
*/
|
|
120
|
+
listPages(): Promise<ReadonlyArray<PublishedPageEntry>>;
|
|
121
|
+
/**
|
|
122
|
+
* List published pages with metadata (slug + seo + tags + excerpt +
|
|
123
|
+
* coverImage + publishedAt + mtime). Used by the runtime's `listPages`
|
|
124
|
+
* (ARCHITECTURE.md §4) for blog-index-style queries.
|
|
125
|
+
*
|
|
126
|
+
* Distinct from the lighter `listPages()` because reading every page
|
|
127
|
+
* just to drop sections is wasteful for large sites; a dedicated
|
|
128
|
+
* summary read lets implementations parse only the metadata block.
|
|
129
|
+
* The default FS adapter still parses the whole file (small saving),
|
|
130
|
+
* but a remote adapter could expose a real summary endpoint.
|
|
131
|
+
*
|
|
132
|
+
* Ordering is not specified — the runtime sorts after the read.
|
|
133
|
+
*/
|
|
134
|
+
listPageSummaries(): Promise<ReadonlyArray<PageSummaryEntry>>;
|
|
135
|
+
/**
|
|
136
|
+
* Promote the draft for `slug` to published, and write a new full
|
|
137
|
+
* snapshot into history (ARCHITECTURE.md §4: versioning is full
|
|
138
|
+
* snapshots, not patches). Returns the `Page` that was published so
|
|
139
|
+
* the runtime can thread it into its git-commit helper without an
|
|
140
|
+
* extra round-trip.
|
|
141
|
+
*
|
|
142
|
+
* Implementations MUST reject (throw) when no draft exists for the
|
|
143
|
+
* given slug — publishing nothing is meaningless and silent no-ops
|
|
144
|
+
* hide bugs in the edit flow.
|
|
145
|
+
*/
|
|
146
|
+
publishDraft(slug: string): Promise<Page>;
|
|
147
|
+
/**
|
|
148
|
+
* Delete ONLY the draft for `slug`. Used by the "discard draft" flow.
|
|
149
|
+
*
|
|
150
|
+
* The published page and history are untouched — this operation is
|
|
151
|
+
* reversible in the sense that the live page remains, and the user can
|
|
152
|
+
* always reconstruct the discarded draft by editing again. Throws if no
|
|
153
|
+
* draft exists for the given slug (silent success would mask a caller
|
|
154
|
+
* bug, same discipline as `publishDraft`).
|
|
155
|
+
*/
|
|
156
|
+
deleteDraft(slug: string): Promise<void>;
|
|
157
|
+
/** Delete a published page. Also removes its draft if one exists. History is preserved. */
|
|
158
|
+
readonly deletePage: (slug: string) => Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Unpublish a page: remove it from the live site while preserving
|
|
161
|
+
* editable content and version history. Behaviour depends on the
|
|
162
|
+
* current state of `slug`:
|
|
163
|
+
*
|
|
164
|
+
* - No published page exists → throws. There is nothing to unpublish,
|
|
165
|
+
* and silently succeeding would mask a caller bug.
|
|
166
|
+
* - Published exists, no draft → the published page is converted
|
|
167
|
+
* into a draft (so the editor still has something to work with),
|
|
168
|
+
* and the published file is removed. Net: live URL 404s, draft
|
|
169
|
+
* retained with the last-published content.
|
|
170
|
+
* - Published exists, draft exists → only the published file is
|
|
171
|
+
* removed. The draft is already the newer version (publishDraft
|
|
172
|
+
* deletes the draft on publish, so any coexisting draft is by
|
|
173
|
+
* definition post-publish) and is left untouched.
|
|
174
|
+
*
|
|
175
|
+
* History is NEVER touched by this method — the last publish already
|
|
176
|
+
* wrote a snapshot, and restore-from-history remains the recovery
|
|
177
|
+
* path if the user changes their mind.
|
|
178
|
+
*/
|
|
179
|
+
unpublishPage(slug: string): Promise<void>;
|
|
180
|
+
/** List history snapshots for a page, newest first. Returns [] if no history exists. */
|
|
181
|
+
listHistory(slug: string): Promise<ReadonlyArray<HistoryEntry>>;
|
|
182
|
+
/** Read a specific history snapshot by slug and timestamp. Returns null if not found. */
|
|
183
|
+
readHistorySnapshot(slug: string, timestamp: string): Promise<Page | null>;
|
|
184
|
+
/**
|
|
185
|
+
* Atomically rename a page slug. Renames the published page, draft
|
|
186
|
+
* (if any), and history directory (if any). Throws if the source
|
|
187
|
+
* slug does not exist or the target slug already exists.
|
|
188
|
+
*/
|
|
189
|
+
renamePage(fromSlug: string, toSlug: string): Promise<void>;
|
|
190
|
+
/** Read a global by name. Returns null if not found. */
|
|
191
|
+
readGlobal(name: string): Promise<Global | null>;
|
|
192
|
+
/**
|
|
193
|
+
* Save (create or overwrite) a global. No draft/publish cycle.
|
|
194
|
+
*
|
|
195
|
+
* Implementations MUST write a full-snapshot history entry on every
|
|
196
|
+
* save (same "full snapshot" rule as pages, ARCHITECTURE.md §4), with
|
|
197
|
+
* the same semantic de-dup: a save whose content deep-equals the most
|
|
198
|
+
* recent history snapshot does NOT create a new history entry.
|
|
199
|
+
*/
|
|
200
|
+
saveGlobal(global: Global): Promise<void>;
|
|
201
|
+
/** List all globals. Ordering is not specified by the contract. */
|
|
202
|
+
listGlobals(): Promise<ReadonlyArray<GlobalSummary>>;
|
|
203
|
+
/** Delete a global by name. Throws if not found. */
|
|
204
|
+
deleteGlobal(name: string): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* List history snapshots for a global, newest first. Returns [] if
|
|
207
|
+
* no history exists for `name`. Mirrors `listHistory` for pages.
|
|
208
|
+
*/
|
|
209
|
+
listGlobalHistory(name: string): Promise<ReadonlyArray<GlobalHistoryEntry>>;
|
|
210
|
+
/**
|
|
211
|
+
* Read a specific history snapshot by global name and timestamp.
|
|
212
|
+
* Returns null if not found. Mirrors `readHistorySnapshot` for pages.
|
|
213
|
+
*/
|
|
214
|
+
readGlobalHistorySnapshot(name: string, timestamp: string): Promise<Global | null>;
|
|
215
|
+
/**
|
|
216
|
+
* Roll a global back to a specific history snapshot. Reads the
|
|
217
|
+
* snapshot and re-saves it via `saveGlobal`, which will decide
|
|
218
|
+
* whether a new history entry is written based on de-dup.
|
|
219
|
+
*
|
|
220
|
+
* Throws if the snapshot does not exist — there is nothing to roll
|
|
221
|
+
* back to in that case, and silent success would mask caller bugs
|
|
222
|
+
* (same discipline as `publishDraft` for pages).
|
|
223
|
+
*/
|
|
224
|
+
rollbackGlobal(name: string, timestamp: string): Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Input for an asset upload. `bytes` is the raw file body, `filename`
|
|
229
|
+
* is a hint for naming and extension, `contentType` is the MIME type
|
|
230
|
+
* the adapter should associate with the uploaded object.
|
|
231
|
+
*/
|
|
232
|
+
interface AssetUploadInput {
|
|
233
|
+
readonly bytes: Uint8Array;
|
|
234
|
+
readonly filename: string;
|
|
235
|
+
readonly contentType: string;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Result of an asset upload. `url` is the public URL the uploaded bytes
|
|
239
|
+
* are served under; `filename` is the stored name the adapter chose
|
|
240
|
+
* (used by `EditableImage` to reference the asset independently of the
|
|
241
|
+
* URL base). The picker modal uses `filename` when inserting a freshly
|
|
242
|
+
* uploaded asset into the section payload.
|
|
243
|
+
*/
|
|
244
|
+
interface AssetUploadResult {
|
|
245
|
+
readonly url: string;
|
|
246
|
+
readonly filename: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* A single entry in the result of `AssetStorageAdapter.list()`. Carries
|
|
250
|
+
* everything the image picker UI needs to render a card: the stored
|
|
251
|
+
* filename (for display + section-payload value), the public URL (for
|
|
252
|
+
* the `<img src>`), the content type when known, and the modification
|
|
253
|
+
* timestamp so the adapter can sort newest-first.
|
|
254
|
+
*/
|
|
255
|
+
interface AssetListEntry {
|
|
256
|
+
readonly filename: string;
|
|
257
|
+
readonly url: string;
|
|
258
|
+
readonly contentType: string | undefined;
|
|
259
|
+
readonly modifiedAt: Date;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Asset storage adapter — the seam between the runtime and whatever
|
|
263
|
+
* persists user-uploaded binary assets (primarily images via the
|
|
264
|
+
* EditableImage flow, per ARCHITECTURE.md §6).
|
|
265
|
+
*
|
|
266
|
+
* Two methods: `upload` ingests new bytes; `list` enumerates existing
|
|
267
|
+
* assets for the image picker. See the file header for why there is no
|
|
268
|
+
* `resolve` method.
|
|
269
|
+
*/
|
|
270
|
+
interface AssetStorageAdapter {
|
|
271
|
+
/**
|
|
272
|
+
* Persist the given bytes as an asset and return a public URL that
|
|
273
|
+
* serves them, alongside the stored filename. Implementations MUST
|
|
274
|
+
* NOT mutate `input.bytes`.
|
|
275
|
+
*
|
|
276
|
+
* The URL format is adapter-defined. For the default FS adapter
|
|
277
|
+
* (T-006), it is a path under `/assets/...` served by Next.js static
|
|
278
|
+
* file handling. For a remote adapter, it could be an absolute URL.
|
|
279
|
+
* Callers treat the returned string as opaque.
|
|
280
|
+
*/
|
|
281
|
+
upload(input: AssetUploadInput): Promise<AssetUploadResult>;
|
|
282
|
+
/**
|
|
283
|
+
* Enumerate every asset in the store, newest first. Used by the image
|
|
284
|
+
* picker modal to let the user browse and re-insert existing assets
|
|
285
|
+
* without re-uploading.
|
|
286
|
+
*/
|
|
287
|
+
list(): Promise<readonly AssetListEntry[]>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type { AssetStorageAdapter as A, ContentStorageAdapter as C, DraftSummary as D, GlobalSummary as G, HistoryEntry as H, PageMode as P, AssetUploadInput as a, AssetUploadResult as b, PageSummaryEntry as c, PublishedPageEntry as d };
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { P as Page, w as PageSummary } from './form-BqY0H1V5.js';
|
|
2
|
+
import { G as Global } from './global-CV23g5Bn.js';
|
|
3
|
+
|
|
4
|
+
/** A page's publication mode. Used to select which storage bucket `readPage` reads from. */
|
|
5
|
+
type PageMode = 'published' | 'draft';
|
|
6
|
+
/**
|
|
7
|
+
* Lightweight summary of a history snapshot, returned by `listHistory`.
|
|
8
|
+
*
|
|
9
|
+
* `timestamp` is the filename stem (e.g. "2026-04-12T16-00-00.000Z"),
|
|
10
|
+
* not a parsed Date, because it is also used as the key for
|
|
11
|
+
* `readHistorySnapshot`. Keeping it as a string avoids lossy Date
|
|
12
|
+
* round-trips and keeps the API surface small.
|
|
13
|
+
*/
|
|
14
|
+
interface HistoryEntry {
|
|
15
|
+
readonly slug: string;
|
|
16
|
+
/** The filename stem, e.g. "2026-04-12T16-00-00.000Z". */
|
|
17
|
+
readonly timestamp: string;
|
|
18
|
+
/** File size in bytes. */
|
|
19
|
+
readonly size: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Lightweight summary of a draft page, returned by `listDrafts`.
|
|
23
|
+
*
|
|
24
|
+
* Kept intentionally small: the UI that lists drafts only needs "which
|
|
25
|
+
* pages have pending edits, and when were they last touched". Anything
|
|
26
|
+
* beyond this (author, diff stats, etc.) is out of scope for v1.
|
|
27
|
+
*/
|
|
28
|
+
interface DraftSummary {
|
|
29
|
+
readonly slug: string;
|
|
30
|
+
readonly updatedAt: Date;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Lightweight list-entry for a published page, returned by `listPages`.
|
|
34
|
+
*
|
|
35
|
+
* BREAKING (v0.1.x → v0.1.y): renamed from `PageSummary` to
|
|
36
|
+
* `PublishedPageEntry` so the canonical `PageSummary` name can be claimed
|
|
37
|
+
* by the domain-layer metadata projection (`Omit<Page, 'sections'>`)
|
|
38
|
+
* exposed for blog-index-style listings (ARCHITECTURE.md §4). Templates
|
|
39
|
+
* that imported `PageSummary` from `@agntcms/next/server` to type the
|
|
40
|
+
* existing `listPages()` adapter return must rename their import — the
|
|
41
|
+
* shape (slug + updatedAt) is unchanged.
|
|
42
|
+
*
|
|
43
|
+
* Same shape as `DraftSummary` — kept as a separate type because the
|
|
44
|
+
* two collections have different semantic meaning and may diverge.
|
|
45
|
+
*/
|
|
46
|
+
interface PublishedPageEntry {
|
|
47
|
+
readonly slug: string;
|
|
48
|
+
readonly updatedAt: Date;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Page-metadata-with-mtime tuple, returned by `listPageSummaries`.
|
|
52
|
+
*
|
|
53
|
+
* The adapter widens domain `PageSummary` (Omit<Page, 'sections'>) with
|
|
54
|
+
* an `updatedAt: Date` carrying the storage layer's "last modified"
|
|
55
|
+
* timestamp. The runtime uses this as the sort key when a page has no
|
|
56
|
+
* explicit `publishedAt` (ARCHITECTURE.md §4: mtime fallback). Adapters
|
|
57
|
+
* that don't track mtime (e.g. a remote API) MAY return the unix epoch
|
|
58
|
+
* to mean "unknown" — sorting will then put such pages at the end of a
|
|
59
|
+
* `'newest'` query and at the start of an `'oldest'` query, but never
|
|
60
|
+
* crash.
|
|
61
|
+
*/
|
|
62
|
+
interface PageSummaryEntry extends PageSummary {
|
|
63
|
+
readonly updatedAt: Date;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lightweight summary of a global content block, returned by `listGlobals`.
|
|
67
|
+
*/
|
|
68
|
+
interface GlobalSummary {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly type: string;
|
|
71
|
+
readonly updatedAt: Date;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Lightweight summary of a global history snapshot, returned by
|
|
75
|
+
* `listGlobalHistory`. Mirrors `HistoryEntry` for pages but keyed by
|
|
76
|
+
* global `name` instead of `slug`; the shape is kept symmetric so UI
|
|
77
|
+
* code that handles history listings can stay shape-agnostic.
|
|
78
|
+
*/
|
|
79
|
+
interface GlobalHistoryEntry {
|
|
80
|
+
readonly name: string;
|
|
81
|
+
/** The filename stem, e.g. "2026-04-12T16-00-00.000Z". */
|
|
82
|
+
readonly timestamp: string;
|
|
83
|
+
/** File size in bytes. */
|
|
84
|
+
readonly size: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Content storage adapter — the seam between the runtime and whatever
|
|
88
|
+
* persists pages and drafts. See the file header for the rationale
|
|
89
|
+
* behind this exact method set.
|
|
90
|
+
*
|
|
91
|
+
* All methods are async because a real implementation (FS, remote) is
|
|
92
|
+
* always IO-bound. Implementations MUST NOT throw for "not found"; they
|
|
93
|
+
* MUST return `null` from `readPage`.
|
|
94
|
+
*/
|
|
95
|
+
interface ContentStorageAdapter {
|
|
96
|
+
/**
|
|
97
|
+
* Read a page in the requested publication mode.
|
|
98
|
+
*
|
|
99
|
+
* Returns `null` when no file exists for `(slug, mode)`. Does NOT fall
|
|
100
|
+
* back across modes; composition is the runtime's responsibility.
|
|
101
|
+
*/
|
|
102
|
+
readPage(slug: string, mode: PageMode): Promise<Page | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Persist a draft of `page`. Overwrites any existing draft for the
|
|
105
|
+
* same slug. The draft storage bucket is distinct from the published
|
|
106
|
+
* bucket — `saveDraft` MUST NOT touch the published page.
|
|
107
|
+
*/
|
|
108
|
+
saveDraft(page: Page): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* List all drafts currently on disk. Ordering is not specified by the
|
|
111
|
+
* contract; callers that need a particular order sort the result
|
|
112
|
+
* themselves.
|
|
113
|
+
*/
|
|
114
|
+
listDrafts(): Promise<ReadonlyArray<DraftSummary>>;
|
|
115
|
+
/**
|
|
116
|
+
* List all published pages with minimal info (slug + mtime). Used by
|
|
117
|
+
* the admin pages list. Ordering is not specified by the contract;
|
|
118
|
+
* callers that need a particular order sort the result themselves.
|
|
119
|
+
*/
|
|
120
|
+
listPages(): Promise<ReadonlyArray<PublishedPageEntry>>;
|
|
121
|
+
/**
|
|
122
|
+
* List published pages with metadata (slug + seo + tags + excerpt +
|
|
123
|
+
* coverImage + publishedAt + mtime). Used by the runtime's `listPages`
|
|
124
|
+
* (ARCHITECTURE.md §4) for blog-index-style queries.
|
|
125
|
+
*
|
|
126
|
+
* Distinct from the lighter `listPages()` because reading every page
|
|
127
|
+
* just to drop sections is wasteful for large sites; a dedicated
|
|
128
|
+
* summary read lets implementations parse only the metadata block.
|
|
129
|
+
* The default FS adapter still parses the whole file (small saving),
|
|
130
|
+
* but a remote adapter could expose a real summary endpoint.
|
|
131
|
+
*
|
|
132
|
+
* Ordering is not specified — the runtime sorts after the read.
|
|
133
|
+
*/
|
|
134
|
+
listPageSummaries(): Promise<ReadonlyArray<PageSummaryEntry>>;
|
|
135
|
+
/**
|
|
136
|
+
* Promote the draft for `slug` to published, and write a new full
|
|
137
|
+
* snapshot into history (ARCHITECTURE.md §4: versioning is full
|
|
138
|
+
* snapshots, not patches). Returns the `Page` that was published so
|
|
139
|
+
* the runtime can thread it into its git-commit helper without an
|
|
140
|
+
* extra round-trip.
|
|
141
|
+
*
|
|
142
|
+
* Implementations MUST reject (throw) when no draft exists for the
|
|
143
|
+
* given slug — publishing nothing is meaningless and silent no-ops
|
|
144
|
+
* hide bugs in the edit flow.
|
|
145
|
+
*/
|
|
146
|
+
publishDraft(slug: string): Promise<Page>;
|
|
147
|
+
/**
|
|
148
|
+
* Delete ONLY the draft for `slug`. Used by the "discard draft" flow.
|
|
149
|
+
*
|
|
150
|
+
* The published page and history are untouched — this operation is
|
|
151
|
+
* reversible in the sense that the live page remains, and the user can
|
|
152
|
+
* always reconstruct the discarded draft by editing again. Throws if no
|
|
153
|
+
* draft exists for the given slug (silent success would mask a caller
|
|
154
|
+
* bug, same discipline as `publishDraft`).
|
|
155
|
+
*/
|
|
156
|
+
deleteDraft(slug: string): Promise<void>;
|
|
157
|
+
/** Delete a published page. Also removes its draft if one exists. History is preserved. */
|
|
158
|
+
readonly deletePage: (slug: string) => Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Unpublish a page: remove it from the live site while preserving
|
|
161
|
+
* editable content and version history. Behaviour depends on the
|
|
162
|
+
* current state of `slug`:
|
|
163
|
+
*
|
|
164
|
+
* - No published page exists → throws. There is nothing to unpublish,
|
|
165
|
+
* and silently succeeding would mask a caller bug.
|
|
166
|
+
* - Published exists, no draft → the published page is converted
|
|
167
|
+
* into a draft (so the editor still has something to work with),
|
|
168
|
+
* and the published file is removed. Net: live URL 404s, draft
|
|
169
|
+
* retained with the last-published content.
|
|
170
|
+
* - Published exists, draft exists → only the published file is
|
|
171
|
+
* removed. The draft is already the newer version (publishDraft
|
|
172
|
+
* deletes the draft on publish, so any coexisting draft is by
|
|
173
|
+
* definition post-publish) and is left untouched.
|
|
174
|
+
*
|
|
175
|
+
* History is NEVER touched by this method — the last publish already
|
|
176
|
+
* wrote a snapshot, and restore-from-history remains the recovery
|
|
177
|
+
* path if the user changes their mind.
|
|
178
|
+
*/
|
|
179
|
+
unpublishPage(slug: string): Promise<void>;
|
|
180
|
+
/** List history snapshots for a page, newest first. Returns [] if no history exists. */
|
|
181
|
+
listHistory(slug: string): Promise<ReadonlyArray<HistoryEntry>>;
|
|
182
|
+
/** Read a specific history snapshot by slug and timestamp. Returns null if not found. */
|
|
183
|
+
readHistorySnapshot(slug: string, timestamp: string): Promise<Page | null>;
|
|
184
|
+
/**
|
|
185
|
+
* Atomically rename a page slug. Renames the published page, draft
|
|
186
|
+
* (if any), and history directory (if any). Throws if the source
|
|
187
|
+
* slug does not exist or the target slug already exists.
|
|
188
|
+
*/
|
|
189
|
+
renamePage(fromSlug: string, toSlug: string): Promise<void>;
|
|
190
|
+
/** Read a global by name. Returns null if not found. */
|
|
191
|
+
readGlobal(name: string): Promise<Global | null>;
|
|
192
|
+
/**
|
|
193
|
+
* Save (create or overwrite) a global. No draft/publish cycle.
|
|
194
|
+
*
|
|
195
|
+
* Implementations MUST write a full-snapshot history entry on every
|
|
196
|
+
* save (same "full snapshot" rule as pages, ARCHITECTURE.md §4), with
|
|
197
|
+
* the same semantic de-dup: a save whose content deep-equals the most
|
|
198
|
+
* recent history snapshot does NOT create a new history entry.
|
|
199
|
+
*/
|
|
200
|
+
saveGlobal(global: Global): Promise<void>;
|
|
201
|
+
/** List all globals. Ordering is not specified by the contract. */
|
|
202
|
+
listGlobals(): Promise<ReadonlyArray<GlobalSummary>>;
|
|
203
|
+
/** Delete a global by name. Throws if not found. */
|
|
204
|
+
deleteGlobal(name: string): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* List history snapshots for a global, newest first. Returns [] if
|
|
207
|
+
* no history exists for `name`. Mirrors `listHistory` for pages.
|
|
208
|
+
*/
|
|
209
|
+
listGlobalHistory(name: string): Promise<ReadonlyArray<GlobalHistoryEntry>>;
|
|
210
|
+
/**
|
|
211
|
+
* Read a specific history snapshot by global name and timestamp.
|
|
212
|
+
* Returns null if not found. Mirrors `readHistorySnapshot` for pages.
|
|
213
|
+
*/
|
|
214
|
+
readGlobalHistorySnapshot(name: string, timestamp: string): Promise<Global | null>;
|
|
215
|
+
/**
|
|
216
|
+
* Roll a global back to a specific history snapshot. Reads the
|
|
217
|
+
* snapshot and re-saves it via `saveGlobal`, which will decide
|
|
218
|
+
* whether a new history entry is written based on de-dup.
|
|
219
|
+
*
|
|
220
|
+
* Throws if the snapshot does not exist — there is nothing to roll
|
|
221
|
+
* back to in that case, and silent success would mask caller bugs
|
|
222
|
+
* (same discipline as `publishDraft` for pages).
|
|
223
|
+
*/
|
|
224
|
+
rollbackGlobal(name: string, timestamp: string): Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Input for an asset upload. `bytes` is the raw file body, `filename`
|
|
229
|
+
* is a hint for naming and extension, `contentType` is the MIME type
|
|
230
|
+
* the adapter should associate with the uploaded object.
|
|
231
|
+
*/
|
|
232
|
+
interface AssetUploadInput {
|
|
233
|
+
readonly bytes: Uint8Array;
|
|
234
|
+
readonly filename: string;
|
|
235
|
+
readonly contentType: string;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Result of an asset upload. `url` is the public URL the uploaded bytes
|
|
239
|
+
* are served under; `filename` is the stored name the adapter chose
|
|
240
|
+
* (used by `EditableImage` to reference the asset independently of the
|
|
241
|
+
* URL base). The picker modal uses `filename` when inserting a freshly
|
|
242
|
+
* uploaded asset into the section payload.
|
|
243
|
+
*/
|
|
244
|
+
interface AssetUploadResult {
|
|
245
|
+
readonly url: string;
|
|
246
|
+
readonly filename: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* A single entry in the result of `AssetStorageAdapter.list()`. Carries
|
|
250
|
+
* everything the image picker UI needs to render a card: the stored
|
|
251
|
+
* filename (for display + section-payload value), the public URL (for
|
|
252
|
+
* the `<img src>`), the content type when known, and the modification
|
|
253
|
+
* timestamp so the adapter can sort newest-first.
|
|
254
|
+
*/
|
|
255
|
+
interface AssetListEntry {
|
|
256
|
+
readonly filename: string;
|
|
257
|
+
readonly url: string;
|
|
258
|
+
readonly contentType: string | undefined;
|
|
259
|
+
readonly modifiedAt: Date;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Asset storage adapter — the seam between the runtime and whatever
|
|
263
|
+
* persists user-uploaded binary assets (primarily images via the
|
|
264
|
+
* EditableImage flow, per ARCHITECTURE.md §6).
|
|
265
|
+
*
|
|
266
|
+
* Two methods: `upload` ingests new bytes; `list` enumerates existing
|
|
267
|
+
* assets for the image picker. See the file header for why there is no
|
|
268
|
+
* `resolve` method.
|
|
269
|
+
*/
|
|
270
|
+
interface AssetStorageAdapter {
|
|
271
|
+
/**
|
|
272
|
+
* Persist the given bytes as an asset and return a public URL that
|
|
273
|
+
* serves them, alongside the stored filename. Implementations MUST
|
|
274
|
+
* NOT mutate `input.bytes`.
|
|
275
|
+
*
|
|
276
|
+
* The URL format is adapter-defined. For the default FS adapter
|
|
277
|
+
* (T-006), it is a path under `/assets/...` served by Next.js static
|
|
278
|
+
* file handling. For a remote adapter, it could be an absolute URL.
|
|
279
|
+
* Callers treat the returned string as opaque.
|
|
280
|
+
*/
|
|
281
|
+
upload(input: AssetUploadInput): Promise<AssetUploadResult>;
|
|
282
|
+
/**
|
|
283
|
+
* Enumerate every asset in the store, newest first. Used by the image
|
|
284
|
+
* picker modal to let the user browse and re-insert existing assets
|
|
285
|
+
* without re-uploading.
|
|
286
|
+
*/
|
|
287
|
+
list(): Promise<readonly AssetListEntry[]>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type { AssetStorageAdapter as A, ContentStorageAdapter as C, DraftSummary as D, GlobalSummary as G, HistoryEntry as H, PageMode as P, AssetUploadInput as a, AssetUploadResult as b, PageSummaryEntry as c, PublishedPageEntry as d };
|