@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.
@@ -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 };