dineway 0.1.3
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/LICENSE +9 -0
- package/README.md +89 -0
- package/dist/adapters-BlzWJG82.d.mts +106 -0
- package/dist/apply-CAPvMfoU.mjs +1339 -0
- package/dist/astro/index.d.mts +50 -0
- package/dist/astro/index.mjs +1326 -0
- package/dist/astro/middleware/auth.d.mts +30 -0
- package/dist/astro/middleware/auth.mjs +708 -0
- package/dist/astro/middleware/redirect.d.mts +21 -0
- package/dist/astro/middleware/redirect.mjs +62 -0
- package/dist/astro/middleware/request-context.d.mts +17 -0
- package/dist/astro/middleware/request-context.mjs +1371 -0
- package/dist/astro/middleware/setup.d.mts +19 -0
- package/dist/astro/middleware/setup.mjs +46 -0
- package/dist/astro/middleware.d.mts +12 -0
- package/dist/astro/middleware.mjs +1716 -0
- package/dist/astro/types.d.mts +269 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-F8-DUraK.mjs +58 -0
- package/dist/byline-DeWCMU_i.mjs +234 -0
- package/dist/bylines-DyqBV9EQ.mjs +137 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3987 -0
- package/dist/client/external-auth-headers.d.mts +38 -0
- package/dist/client/external-auth-headers.mjs +101 -0
- package/dist/client/index.d.mts +397 -0
- package/dist/client/index.mjs +345 -0
- package/dist/config-Cq8H0SfX.mjs +46 -0
- package/dist/connection-C9pxzuag.mjs +52 -0
- package/dist/content-zSgdNmnt.mjs +836 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/libsql.d.mts +10 -0
- package/dist/db/libsql.mjs +21 -0
- package/dist/db/postgres.d.mts +10 -0
- package/dist/db/postgres.mjs +29 -0
- package/dist/db/sqlite.d.mts +10 -0
- package/dist/db/sqlite.mjs +15 -0
- package/dist/default-WYlzADZL.mjs +80 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
- package/dist/error-DrxtnGPg.mjs +26 -0
- package/dist/index-C-jx21qs.d.mts +4771 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-C6FCD1FU.mjs +27 -0
- package/dist/loader-qKmo0wAY.mjs +446 -0
- package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
- package/dist/media/index.d.mts +25 -0
- package/dist/media/index.mjs +54 -0
- package/dist/media/local-runtime.d.mts +38 -0
- package/dist/media/local-runtime.mjs +132 -0
- package/dist/media-DMTr80Gv.mjs +199 -0
- package/dist/mode-BlyYtIFO.mjs +22 -0
- package/dist/page/index.d.mts +148 -0
- package/dist/page/index.mjs +419 -0
- package/dist/placeholder-B3knXwNc.mjs +267 -0
- package/dist/placeholder-bOx1xCTY.d.mts +283 -0
- package/dist/plugin-utils.d.mts +57 -0
- package/dist/plugin-utils.mjs +77 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
- package/dist/query-BiaPl_g2.mjs +459 -0
- package/dist/redirect-JPqLAbxa.mjs +328 -0
- package/dist/registry-DSd1GWB8.mjs +851 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.mjs +42 -0
- package/dist/runner-B5l1JfOj.d.mts +26 -0
- package/dist/runner-BGUGywgG.mjs +1529 -0
- package/dist/runtime.d.mts +25 -0
- package/dist/runtime.mjs +41 -0
- package/dist/search-BNruJHDL.mjs +11054 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +69 -0
- package/dist/seo/index.mjs +69 -0
- package/dist/storage/local.d.mts +38 -0
- package/dist/storage/local.mjs +165 -0
- package/dist/storage/s3.d.mts +31 -0
- package/dist/storage/s3.mjs +174 -0
- package/dist/tokens-4vgYuXsZ.mjs +170 -0
- package/dist/transport-C5FYnid7.mjs +417 -0
- package/dist/transport-gIL-e43D.d.mts +41 -0
- package/dist/types-BawVha09.mjs +30 -0
- package/dist/types-BgQeVaPj.d.mts +192 -0
- package/dist/types-CLLdsG3g.d.mts +103 -0
- package/dist/types-D38djUXv.d.mts +1196 -0
- package/dist/types-DShnjzb6.mjs +15 -0
- package/dist/types-DkvMXalq.d.mts +425 -0
- package/dist/types-DuNbGKjF.mjs +74 -0
- package/dist/types-ju-_ORz7.d.mts +182 -0
- package/dist/validate-CXnRKfJK.mjs +327 -0
- package/dist/validate-CqRJb_xU.mjs +96 -0
- package/dist/validate-DVKJJ-M_.d.mts +377 -0
- package/locals.d.ts +47 -0
- package/package.json +313 -0
|
@@ -0,0 +1,4771 @@
|
|
|
1
|
+
import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-bOx1xCTY.mjs";
|
|
2
|
+
import { t as Database } from "./types-DkvMXalq.mjs";
|
|
3
|
+
import { a as ContentSeoInput, c as FindManyOptions, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary, u as UpdateContentInput } from "./types-CLLdsG3g.mjs";
|
|
4
|
+
import { $ as StandardPluginDefinition, B as PluginManifest, E as PageFragmentEvent, H as PluginStorageConfig, J as ResolvedPlugin, K as RequestMeta, O as PageMetadataContribution, R as PluginDefinition, S as MediaItem$1, T as PageFragmentContribution, a as CommentAfterModerateEvent, k as PageMetadataEvent, m as EmailMessage, p as CronEvent, r as CommentAfterCreateEvent, s as CommentBeforeCreateEvent } from "./types-D38djUXv.mjs";
|
|
5
|
+
import { g as UpdateFieldInput, h as UpdateCollectionInput, i as CollectionWithFields, l as Field, o as CreateCollectionInput, s as CreateFieldInput, t as Collection } from "./types-BgQeVaPj.mjs";
|
|
6
|
+
import { C as SiteSettingKey, w as SiteSettings } from "./validate-DVKJJ-M_.mjs";
|
|
7
|
+
import { d as Storage } from "./types-ju-_ORz7.mjs";
|
|
8
|
+
import { t as DatabaseDescriptor } from "./adapters-BlzWJG82.mjs";
|
|
9
|
+
import { Kysely } from "kysely";
|
|
10
|
+
import { z } from "astro/zod";
|
|
11
|
+
import { z as z$1 } from "zod";
|
|
12
|
+
import { PortableTextBlock } from "@dineway-ai/gutenberg-to-portable-text";
|
|
13
|
+
import { Readable } from "node:stream";
|
|
14
|
+
import { MiddlewareHandler } from "astro";
|
|
15
|
+
import { LiveLoader } from "astro/loaders";
|
|
16
|
+
|
|
17
|
+
//#region src/database/connection.d.ts
|
|
18
|
+
interface DatabaseConfig {
|
|
19
|
+
url: string;
|
|
20
|
+
authToken?: string;
|
|
21
|
+
}
|
|
22
|
+
declare class DinewayDatabaseError extends Error {
|
|
23
|
+
cause?: unknown | undefined;
|
|
24
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/database/repositories/content.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Repository for content CRUD operations
|
|
30
|
+
*
|
|
31
|
+
* Content is stored in per-collection tables (ec_posts, ec_pages, etc.)
|
|
32
|
+
* Each field becomes a real column in the table.
|
|
33
|
+
*/
|
|
34
|
+
declare class ContentRepository {
|
|
35
|
+
private db;
|
|
36
|
+
constructor(db: Kysely<Database>);
|
|
37
|
+
/**
|
|
38
|
+
* Create a new content item
|
|
39
|
+
*/
|
|
40
|
+
create(input: CreateContentInput): Promise<ContentItem>;
|
|
41
|
+
/**
|
|
42
|
+
* Generate a unique slug for a content item within a collection.
|
|
43
|
+
*
|
|
44
|
+
* Checks the collection table for existing slugs that match `baseSlug`
|
|
45
|
+
* (optionally scoped to a locale) and appends a numeric suffix (`-1`,
|
|
46
|
+
* `-2`, etc.) on collision to guarantee uniqueness.
|
|
47
|
+
*
|
|
48
|
+
* Returns `null` if `baseSlug` is empty after slugification.
|
|
49
|
+
*/
|
|
50
|
+
generateUniqueSlug(type: string, text: string, locale?: string): Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Duplicate a content item
|
|
53
|
+
* Creates a new draft copy with "(Copy)" appended to the title.
|
|
54
|
+
* A slug is auto-generated from the new title by the handler layer.
|
|
55
|
+
*/
|
|
56
|
+
duplicate(type: string, id: string, authorId?: string): Promise<ContentItem>;
|
|
57
|
+
/**
|
|
58
|
+
* Find content by ID
|
|
59
|
+
*/
|
|
60
|
+
findById(type: string, id: string): Promise<ContentItem | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Find content by id, including trashed (soft-deleted) items.
|
|
63
|
+
* Used by restore endpoint for ownership checks.
|
|
64
|
+
*/
|
|
65
|
+
findByIdIncludingTrashed(type: string, id: string): Promise<ContentItem | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Find content by ID or slug. Tries ID first if it looks like a ULID,
|
|
68
|
+
* otherwise tries slug. Falls back to the other if the first lookup misses.
|
|
69
|
+
*/
|
|
70
|
+
findByIdOrSlug(type: string, identifier: string, locale?: string): Promise<ContentItem | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Find content by ID or slug, including trashed (soft-deleted) items.
|
|
73
|
+
* Used by restore/permanent-delete endpoints.
|
|
74
|
+
*/
|
|
75
|
+
findByIdOrSlugIncludingTrashed(type: string, identifier: string, locale?: string): Promise<ContentItem | null>;
|
|
76
|
+
private _findByIdOrSlug;
|
|
77
|
+
/**
|
|
78
|
+
* Find content by slug
|
|
79
|
+
*/
|
|
80
|
+
findBySlug(type: string, slug: string, locale?: string): Promise<ContentItem | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Find content by slug, including trashed (soft-deleted) items.
|
|
83
|
+
* Used by restore/permanent-delete endpoints.
|
|
84
|
+
*/
|
|
85
|
+
findBySlugIncludingTrashed(type: string, slug: string, locale?: string): Promise<ContentItem | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Find many content items with filtering and pagination
|
|
88
|
+
*/
|
|
89
|
+
findMany(type: string, options?: FindManyOptions): Promise<FindManyResult<ContentItem>>;
|
|
90
|
+
/**
|
|
91
|
+
* Update content
|
|
92
|
+
*/
|
|
93
|
+
update(type: string, id: string, input: UpdateContentInput): Promise<ContentItem>;
|
|
94
|
+
/**
|
|
95
|
+
* Delete content (soft delete - moves to trash)
|
|
96
|
+
*/
|
|
97
|
+
delete(type: string, id: string): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Restore content from trash
|
|
100
|
+
*/
|
|
101
|
+
restore(type: string, id: string): Promise<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Permanently delete content (cannot be undone)
|
|
104
|
+
*/
|
|
105
|
+
permanentDelete(type: string, id: string): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* Find trashed content items
|
|
108
|
+
*/
|
|
109
|
+
findTrashed(type: string, options?: Omit<FindManyOptions, "where">): Promise<FindManyResult<ContentItem & {
|
|
110
|
+
deletedAt: string;
|
|
111
|
+
}>>;
|
|
112
|
+
/**
|
|
113
|
+
* Count trashed content items
|
|
114
|
+
*/
|
|
115
|
+
countTrashed(type: string): Promise<number>;
|
|
116
|
+
/**
|
|
117
|
+
* Count content items
|
|
118
|
+
*/
|
|
119
|
+
count(type: string, where?: {
|
|
120
|
+
status?: string;
|
|
121
|
+
authorId?: string;
|
|
122
|
+
locale?: string;
|
|
123
|
+
}): Promise<number>;
|
|
124
|
+
getStats(type: string): Promise<{
|
|
125
|
+
total: number;
|
|
126
|
+
published: number;
|
|
127
|
+
draft: number;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Schedule content for future publishing
|
|
131
|
+
*
|
|
132
|
+
* Sets status to 'scheduled' and stores the scheduled publish time.
|
|
133
|
+
* The content will be auto-published when the scheduled time is reached.
|
|
134
|
+
*/
|
|
135
|
+
schedule(type: string, id: string, scheduledAt: string): Promise<ContentItem>;
|
|
136
|
+
/**
|
|
137
|
+
* Unschedule content
|
|
138
|
+
*
|
|
139
|
+
* Clears the scheduled time. Published posts stay published;
|
|
140
|
+
* draft/scheduled posts revert to 'draft'.
|
|
141
|
+
*/
|
|
142
|
+
unschedule(type: string, id: string): Promise<ContentItem>;
|
|
143
|
+
/**
|
|
144
|
+
* Find content that is ready to be published
|
|
145
|
+
*
|
|
146
|
+
* Returns all content where scheduled_at <= now, regardless of status.
|
|
147
|
+
* This covers both draft-scheduled posts (status='scheduled') and
|
|
148
|
+
* published posts with scheduled draft changes (status='published').
|
|
149
|
+
*/
|
|
150
|
+
findReadyToPublish(type: string): Promise<ContentItem[]>;
|
|
151
|
+
/**
|
|
152
|
+
* Find all translations in a translation group
|
|
153
|
+
*/
|
|
154
|
+
findTranslations(type: string, translationGroup: string): Promise<ContentItem[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Publish the current draft
|
|
157
|
+
*
|
|
158
|
+
* Promotes draft_revision_id to live_revision_id and clears draft pointer.
|
|
159
|
+
* Syncs the draft revision's data into the content table columns so the
|
|
160
|
+
* content table always reflects the published version.
|
|
161
|
+
* If no draft revision exists, creates one from current data and publishes it.
|
|
162
|
+
*/
|
|
163
|
+
publish(type: string, id: string): Promise<ContentItem>;
|
|
164
|
+
/**
|
|
165
|
+
* Unpublish content
|
|
166
|
+
*
|
|
167
|
+
* Removes live pointer but preserves draft. If no draft exists,
|
|
168
|
+
* creates one from the live version so the content isn't lost.
|
|
169
|
+
*/
|
|
170
|
+
unpublish(type: string, id: string): Promise<ContentItem>;
|
|
171
|
+
/**
|
|
172
|
+
* Discard pending draft changes
|
|
173
|
+
*
|
|
174
|
+
* Clears draft_revision_id. The content table columns already hold the
|
|
175
|
+
* published version, so no data sync is needed.
|
|
176
|
+
*/
|
|
177
|
+
discardDraft(type: string, id: string): Promise<ContentItem>;
|
|
178
|
+
/**
|
|
179
|
+
* Sync data columns in the content table from a data object.
|
|
180
|
+
* Used to promote revision data into the content table on publish.
|
|
181
|
+
* Keys starting with _ are revision metadata (e.g. _slug) and are skipped.
|
|
182
|
+
*/
|
|
183
|
+
private syncDataColumns;
|
|
184
|
+
/**
|
|
185
|
+
* Count content items with a pending schedule.
|
|
186
|
+
* Includes both draft-scheduled (status='scheduled') and published
|
|
187
|
+
* posts with scheduled draft changes (status='published', scheduled_at set).
|
|
188
|
+
*/
|
|
189
|
+
countScheduled(type: string): Promise<number>;
|
|
190
|
+
/**
|
|
191
|
+
* Map database row to ContentItem
|
|
192
|
+
* Extracts system columns and puts content fields in data
|
|
193
|
+
* Excludes null values from data to match input semantics
|
|
194
|
+
*/
|
|
195
|
+
private mapRow;
|
|
196
|
+
/**
|
|
197
|
+
* Map order field names to database columns.
|
|
198
|
+
* Only allows known fields to prevent column enumeration via crafted orderBy values.
|
|
199
|
+
*/
|
|
200
|
+
private mapOrderField;
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/database/repositories/media.d.ts
|
|
204
|
+
type MediaStatus = "pending" | "ready" | "failed";
|
|
205
|
+
interface MediaItem {
|
|
206
|
+
id: string;
|
|
207
|
+
filename: string;
|
|
208
|
+
mimeType: string;
|
|
209
|
+
size: number | null;
|
|
210
|
+
width: number | null;
|
|
211
|
+
height: number | null;
|
|
212
|
+
alt: string | null;
|
|
213
|
+
caption: string | null;
|
|
214
|
+
storageKey: string;
|
|
215
|
+
status: MediaStatus;
|
|
216
|
+
contentHash: string | null;
|
|
217
|
+
blurhash: string | null;
|
|
218
|
+
dominantColor: string | null;
|
|
219
|
+
createdAt: string;
|
|
220
|
+
authorId: string | null;
|
|
221
|
+
}
|
|
222
|
+
interface CreateMediaInput {
|
|
223
|
+
filename: string;
|
|
224
|
+
mimeType: string;
|
|
225
|
+
size?: number;
|
|
226
|
+
width?: number;
|
|
227
|
+
height?: number;
|
|
228
|
+
alt?: string;
|
|
229
|
+
caption?: string;
|
|
230
|
+
storageKey: string;
|
|
231
|
+
contentHash?: string;
|
|
232
|
+
blurhash?: string;
|
|
233
|
+
dominantColor?: string;
|
|
234
|
+
status?: MediaStatus;
|
|
235
|
+
authorId?: string;
|
|
236
|
+
}
|
|
237
|
+
interface FindManyMediaOptions {
|
|
238
|
+
limit?: number;
|
|
239
|
+
cursor?: string;
|
|
240
|
+
mimeType?: string;
|
|
241
|
+
status?: MediaStatus | "all";
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Media repository for database operations
|
|
245
|
+
*/
|
|
246
|
+
declare class MediaRepository {
|
|
247
|
+
private db;
|
|
248
|
+
constructor(db: Kysely<Database>);
|
|
249
|
+
/**
|
|
250
|
+
* Create a new media item
|
|
251
|
+
*/
|
|
252
|
+
create(input: CreateMediaInput): Promise<MediaItem>;
|
|
253
|
+
/**
|
|
254
|
+
* Create a pending media item (for signed URL upload flow)
|
|
255
|
+
*/
|
|
256
|
+
createPending(input: {
|
|
257
|
+
filename: string;
|
|
258
|
+
mimeType: string;
|
|
259
|
+
size?: number;
|
|
260
|
+
storageKey: string;
|
|
261
|
+
contentHash?: string;
|
|
262
|
+
authorId?: string;
|
|
263
|
+
}): Promise<MediaItem>;
|
|
264
|
+
/**
|
|
265
|
+
* Confirm upload (mark as ready)
|
|
266
|
+
*/
|
|
267
|
+
confirmUpload(id: string, metadata?: {
|
|
268
|
+
width?: number;
|
|
269
|
+
height?: number;
|
|
270
|
+
size?: number;
|
|
271
|
+
}): Promise<MediaItem | null>;
|
|
272
|
+
/**
|
|
273
|
+
* Mark upload as failed
|
|
274
|
+
*/
|
|
275
|
+
markFailed(id: string): Promise<MediaItem | null>;
|
|
276
|
+
/**
|
|
277
|
+
* Find media by ID
|
|
278
|
+
*/
|
|
279
|
+
findById(id: string): Promise<MediaItem | null>;
|
|
280
|
+
/**
|
|
281
|
+
* Find media by filename
|
|
282
|
+
* Useful for idempotent imports
|
|
283
|
+
*/
|
|
284
|
+
findByFilename(filename: string): Promise<MediaItem | null>;
|
|
285
|
+
/**
|
|
286
|
+
* Find media by content hash
|
|
287
|
+
* Used for deduplication - same content = same hash
|
|
288
|
+
*/
|
|
289
|
+
findByContentHash(contentHash: string): Promise<MediaItem | null>;
|
|
290
|
+
/**
|
|
291
|
+
* Find many media items with cursor pagination
|
|
292
|
+
*
|
|
293
|
+
* Uses keyset pagination (cursor-based) for consistent results.
|
|
294
|
+
* The cursor encodes the created_at and id of the last item.
|
|
295
|
+
*/
|
|
296
|
+
findMany(options?: FindManyMediaOptions): Promise<FindManyResult<MediaItem>>;
|
|
297
|
+
/**
|
|
298
|
+
* Update media metadata
|
|
299
|
+
*/
|
|
300
|
+
update(id: string, input: Partial<Pick<CreateMediaInput, "alt" | "caption" | "width" | "height">>): Promise<MediaItem | null>;
|
|
301
|
+
/**
|
|
302
|
+
* Delete media item
|
|
303
|
+
*/
|
|
304
|
+
delete(id: string): Promise<boolean>;
|
|
305
|
+
/**
|
|
306
|
+
* Count media items
|
|
307
|
+
*/
|
|
308
|
+
count(mimeType?: string): Promise<number>;
|
|
309
|
+
/**
|
|
310
|
+
* Delete pending uploads older than the given age.
|
|
311
|
+
* Pending uploads that were never confirmed indicate abandoned upload flows.
|
|
312
|
+
*
|
|
313
|
+
* Returns the storage keys of deleted rows so callers can remove the
|
|
314
|
+
* corresponding files from object storage.
|
|
315
|
+
*/
|
|
316
|
+
cleanupPendingUploads(maxAgeMs?: number): Promise<string[]>;
|
|
317
|
+
/**
|
|
318
|
+
* Convert database row to MediaItem
|
|
319
|
+
*/
|
|
320
|
+
private rowToItem;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/database/repositories/revision.d.ts
|
|
324
|
+
interface Revision {
|
|
325
|
+
id: string;
|
|
326
|
+
collection: string;
|
|
327
|
+
entryId: string;
|
|
328
|
+
data: Record<string, unknown>;
|
|
329
|
+
authorId: string | null;
|
|
330
|
+
createdAt: string;
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/database/repositories/comment.d.ts
|
|
334
|
+
/** Public-facing comment shape — no private fields */
|
|
335
|
+
interface PublicComment {
|
|
336
|
+
id: string;
|
|
337
|
+
parentId: string | null;
|
|
338
|
+
authorName: string;
|
|
339
|
+
isRegisteredUser: boolean;
|
|
340
|
+
body: string;
|
|
341
|
+
createdAt: string;
|
|
342
|
+
replies?: PublicComment[];
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/database/repositories/byline.d.ts
|
|
346
|
+
interface ContentBylineInput {
|
|
347
|
+
bylineId: string;
|
|
348
|
+
roleLabel?: string | null;
|
|
349
|
+
}
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/fields/types.d.ts
|
|
352
|
+
/**
|
|
353
|
+
* SQLite column types that map from field types
|
|
354
|
+
*/
|
|
355
|
+
type ColumnType = "TEXT" | "REAL" | "INTEGER" | "JSON";
|
|
356
|
+
/**
|
|
357
|
+
* Base field definition
|
|
358
|
+
*
|
|
359
|
+
* Note: schema uses z.ZodTypeAny to accommodate optional/default wrappers
|
|
360
|
+
*/
|
|
361
|
+
interface FieldDefinition<_T = unknown> {
|
|
362
|
+
type: string;
|
|
363
|
+
/**
|
|
364
|
+
* The SQLite column type to use when storing this field
|
|
365
|
+
*/
|
|
366
|
+
columnType: ColumnType;
|
|
367
|
+
schema: z.ZodTypeAny;
|
|
368
|
+
options?: unknown;
|
|
369
|
+
ui?: FieldUIHints;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* UI hints for admin rendering
|
|
373
|
+
*/
|
|
374
|
+
interface FieldUIHints {
|
|
375
|
+
widget?: string;
|
|
376
|
+
placeholder?: string;
|
|
377
|
+
helpText?: string;
|
|
378
|
+
rows?: number;
|
|
379
|
+
min?: number | string;
|
|
380
|
+
max?: number | string;
|
|
381
|
+
[key: string]: unknown;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Portable Text block structure
|
|
385
|
+
*/
|
|
386
|
+
interface PortableTextBlock$2 {
|
|
387
|
+
_type: string;
|
|
388
|
+
_key: string;
|
|
389
|
+
[key: string]: unknown;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* @deprecated Use MediaValue instead. ImageValue is an alias for backwards compatibility.
|
|
393
|
+
*/
|
|
394
|
+
type ImageValue = MediaValue;
|
|
395
|
+
/**
|
|
396
|
+
* File field value
|
|
397
|
+
*/
|
|
398
|
+
interface FileValue {
|
|
399
|
+
id: string;
|
|
400
|
+
url: string;
|
|
401
|
+
filename: string;
|
|
402
|
+
mimeType: string;
|
|
403
|
+
size: number;
|
|
404
|
+
}
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/fields/image.d.ts
|
|
407
|
+
/**
|
|
408
|
+
* Image field
|
|
409
|
+
* References media items from the media library
|
|
410
|
+
*/
|
|
411
|
+
declare function image(options?: {
|
|
412
|
+
required?: boolean;
|
|
413
|
+
maxSize?: number;
|
|
414
|
+
allowedTypes?: string[];
|
|
415
|
+
}): FieldDefinition<ImageValue | undefined>;
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/fields/reference.d.ts
|
|
418
|
+
/**
|
|
419
|
+
* Reference field
|
|
420
|
+
* References another content item by ID
|
|
421
|
+
*/
|
|
422
|
+
declare function reference(collection: string, options?: {
|
|
423
|
+
required?: boolean;
|
|
424
|
+
}): FieldDefinition<string | undefined>;
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/fields/portable-text.d.ts
|
|
427
|
+
/**
|
|
428
|
+
* Portable Text field
|
|
429
|
+
* Stores structured content in Portable Text format
|
|
430
|
+
*/
|
|
431
|
+
declare function portableText(options?: {
|
|
432
|
+
required?: boolean;
|
|
433
|
+
}): FieldDefinition<PortableTextBlock$2[] | undefined>;
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/api/types.d.ts
|
|
436
|
+
/**
|
|
437
|
+
* List response with cursor pagination
|
|
438
|
+
*/
|
|
439
|
+
interface ListResponse<T> {
|
|
440
|
+
items: T[];
|
|
441
|
+
nextCursor?: string;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Content API responses
|
|
445
|
+
*/
|
|
446
|
+
interface ContentListResponse extends ListResponse<ContentItem> {}
|
|
447
|
+
interface ContentResponse {
|
|
448
|
+
item: ContentItem;
|
|
449
|
+
/** Opaque revision token for optimistic concurrency */
|
|
450
|
+
_rev?: string;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Manifest API response
|
|
454
|
+
*/
|
|
455
|
+
interface ManifestResponse {
|
|
456
|
+
version: string;
|
|
457
|
+
hash: string;
|
|
458
|
+
collections: Record<string, {
|
|
459
|
+
label: string;
|
|
460
|
+
labelSingular: string;
|
|
461
|
+
supports: string[];
|
|
462
|
+
fields: Record<string, FieldDescriptor>;
|
|
463
|
+
}>;
|
|
464
|
+
plugins: Record<string, {
|
|
465
|
+
adminPages?: Array<{
|
|
466
|
+
path: string;
|
|
467
|
+
component: string;
|
|
468
|
+
}>;
|
|
469
|
+
widgets?: string[];
|
|
470
|
+
}>;
|
|
471
|
+
}
|
|
472
|
+
interface FieldDescriptor {
|
|
473
|
+
kind: string;
|
|
474
|
+
label?: string;
|
|
475
|
+
required?: boolean;
|
|
476
|
+
options?: Array<{
|
|
477
|
+
value: string;
|
|
478
|
+
label: string;
|
|
479
|
+
}>;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Discriminated union for handler results.
|
|
483
|
+
*
|
|
484
|
+
* Handlers return `ApiResult<T>` -- either `{ success: true, data: T }` or
|
|
485
|
+
* `{ success: false, error: { code, message } }`. The `success` literal
|
|
486
|
+
* enables TypeScript narrowing on `.data`.
|
|
487
|
+
*
|
|
488
|
+
* The generic `E` parameter defaults to `ErrorCode` but can be narrowed to
|
|
489
|
+
* `OAuthErrorCode` for OAuth token-endpoint handlers.
|
|
490
|
+
*
|
|
491
|
+
* Use `unwrapResult()` from `error.ts` to convert to an HTTP Response.
|
|
492
|
+
*/
|
|
493
|
+
type ApiResult<T, E extends string = string> = {
|
|
494
|
+
success: true;
|
|
495
|
+
data: T;
|
|
496
|
+
} | {
|
|
497
|
+
success: false;
|
|
498
|
+
error: {
|
|
499
|
+
code: E;
|
|
500
|
+
message: string;
|
|
501
|
+
details?: Record<string, unknown>;
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
* API request context
|
|
506
|
+
*/
|
|
507
|
+
interface ApiContext {
|
|
508
|
+
userId?: string;
|
|
509
|
+
userRole?: string;
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/api/handlers/content.d.ts
|
|
513
|
+
/**
|
|
514
|
+
* Trashed content item with deletion timestamp
|
|
515
|
+
*/
|
|
516
|
+
interface TrashedContentItem {
|
|
517
|
+
id: string;
|
|
518
|
+
type: string;
|
|
519
|
+
slug: string | null;
|
|
520
|
+
status: string;
|
|
521
|
+
data: Record<string, unknown>;
|
|
522
|
+
authorId: string | null;
|
|
523
|
+
createdAt: string;
|
|
524
|
+
updatedAt: string;
|
|
525
|
+
publishedAt: string | null;
|
|
526
|
+
deletedAt: string;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Create content list handler
|
|
530
|
+
*/
|
|
531
|
+
declare function handleContentList(db: Kysely<Database>, collection: string, params: {
|
|
532
|
+
cursor?: string;
|
|
533
|
+
limit?: number;
|
|
534
|
+
status?: string;
|
|
535
|
+
orderBy?: string;
|
|
536
|
+
order?: "asc" | "desc";
|
|
537
|
+
locale?: string;
|
|
538
|
+
}): Promise<ApiResult<ContentListResponse>>;
|
|
539
|
+
/**
|
|
540
|
+
* Get single content item
|
|
541
|
+
*/
|
|
542
|
+
declare function handleContentGet(db: Kysely<Database>, collection: string, id: string, locale?: string): Promise<ApiResult<ContentResponse>>;
|
|
543
|
+
/**
|
|
544
|
+
* Get a content item by id, including trashed items.
|
|
545
|
+
* Used by restore endpoint for ownership checks on soft-deleted items.
|
|
546
|
+
*/
|
|
547
|
+
declare function handleContentGetIncludingTrashed(db: Kysely<Database>, collection: string, id: string, locale?: string): Promise<ApiResult<ContentResponse>>;
|
|
548
|
+
/**
|
|
549
|
+
* Create content item.
|
|
550
|
+
*
|
|
551
|
+
* Content + SEO writes are wrapped in a transaction so either both succeed
|
|
552
|
+
* or neither does. If `body.seo` is provided for a non-SEO collection, the
|
|
553
|
+
* API returns a validation error rather than silently dropping it.
|
|
554
|
+
*/
|
|
555
|
+
declare function handleContentCreate(db: Kysely<Database>, collection: string, body: {
|
|
556
|
+
data: Record<string, unknown>;
|
|
557
|
+
slug?: string;
|
|
558
|
+
status?: string;
|
|
559
|
+
authorId?: string;
|
|
560
|
+
bylines?: ContentBylineInput[];
|
|
561
|
+
locale?: string;
|
|
562
|
+
translationOf?: string;
|
|
563
|
+
seo?: ContentSeoInput;
|
|
564
|
+
createdAt?: string | null;
|
|
565
|
+
publishedAt?: string | null;
|
|
566
|
+
}): Promise<ApiResult<ContentResponse>>;
|
|
567
|
+
/**
|
|
568
|
+
* Update content item.
|
|
569
|
+
* If `_rev` is provided, validates it against the current version before writing.
|
|
570
|
+
* No `_rev` = blind write (backwards-compatible for admin UI).
|
|
571
|
+
*
|
|
572
|
+
* Content + SEO writes are wrapped in a transaction for atomicity.
|
|
573
|
+
*/
|
|
574
|
+
declare function handleContentUpdate(db: Kysely<Database>, collection: string, id: string, body: {
|
|
575
|
+
data?: Record<string, unknown>;
|
|
576
|
+
slug?: string;
|
|
577
|
+
status?: string;
|
|
578
|
+
authorId?: string | null;
|
|
579
|
+
bylines?: ContentBylineInput[];
|
|
580
|
+
_rev?: string;
|
|
581
|
+
seo?: ContentSeoInput;
|
|
582
|
+
}): Promise<ApiResult<ContentResponse>>;
|
|
583
|
+
/**
|
|
584
|
+
* Duplicate content item.
|
|
585
|
+
*
|
|
586
|
+
* Only copies SEO data if the collection has SEO enabled.
|
|
587
|
+
* Always returns consistent `seo` shape for SEO-enabled collections.
|
|
588
|
+
*/
|
|
589
|
+
declare function handleContentDuplicate(db: Kysely<Database>, collection: string, id: string, authorId?: string): Promise<ApiResult<{
|
|
590
|
+
item: ContentItem;
|
|
591
|
+
}>>;
|
|
592
|
+
/**
|
|
593
|
+
* Delete content item (soft delete - moves to trash)
|
|
594
|
+
*/
|
|
595
|
+
declare function handleContentDelete(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<{
|
|
596
|
+
deleted: true;
|
|
597
|
+
}>>;
|
|
598
|
+
/**
|
|
599
|
+
* Restore content item from trash
|
|
600
|
+
*/
|
|
601
|
+
declare function handleContentRestore(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<{
|
|
602
|
+
restored: true;
|
|
603
|
+
}>>;
|
|
604
|
+
/**
|
|
605
|
+
* Permanently delete content item (cannot be undone).
|
|
606
|
+
* Also cleans up associated SEO data.
|
|
607
|
+
*/
|
|
608
|
+
declare function handleContentPermanentDelete(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<{
|
|
609
|
+
deleted: true;
|
|
610
|
+
}>>;
|
|
611
|
+
/**
|
|
612
|
+
* List trashed content items
|
|
613
|
+
*/
|
|
614
|
+
declare function handleContentListTrashed(db: Kysely<Database>, collection: string, options?: {
|
|
615
|
+
limit?: number;
|
|
616
|
+
cursor?: string;
|
|
617
|
+
}): Promise<ApiResult<{
|
|
618
|
+
items: TrashedContentItem[];
|
|
619
|
+
nextCursor?: string;
|
|
620
|
+
}>>;
|
|
621
|
+
/**
|
|
622
|
+
* Count trashed content items
|
|
623
|
+
*/
|
|
624
|
+
declare function handleContentCountTrashed(db: Kysely<Database>, collection: string): Promise<ApiResult<{
|
|
625
|
+
count: number;
|
|
626
|
+
}>>;
|
|
627
|
+
/**
|
|
628
|
+
* Schedule content for future publishing
|
|
629
|
+
*/
|
|
630
|
+
declare function handleContentSchedule(db: Kysely<Database>, collection: string, id: string, scheduledAt: string): Promise<ApiResult<ContentResponse>>;
|
|
631
|
+
/**
|
|
632
|
+
* Unschedule content (revert to draft)
|
|
633
|
+
*/
|
|
634
|
+
declare function handleContentUnschedule(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<ContentResponse>>;
|
|
635
|
+
/**
|
|
636
|
+
* Publish content immediately.
|
|
637
|
+
*
|
|
638
|
+
* Wrapped in a transaction because publish performs multiple writes
|
|
639
|
+
* (syncDataColumns, slug sync, status/revision update) that must
|
|
640
|
+
* be atomic to prevent FTS shadow table corruption on crash.
|
|
641
|
+
*/
|
|
642
|
+
declare function handleContentPublish(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<ContentResponse>>;
|
|
643
|
+
/**
|
|
644
|
+
* Unpublish content (revert to draft).
|
|
645
|
+
*
|
|
646
|
+
* Wrapped in a transaction — unpublish may create a draft revision
|
|
647
|
+
* from the live version then update the status, which is multi-step.
|
|
648
|
+
*/
|
|
649
|
+
declare function handleContentUnpublish(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<ContentResponse>>;
|
|
650
|
+
/**
|
|
651
|
+
* Count scheduled content items
|
|
652
|
+
*/
|
|
653
|
+
declare function handleContentCountScheduled(db: Kysely<Database>, collection: string): Promise<ApiResult<{
|
|
654
|
+
count: number;
|
|
655
|
+
}>>;
|
|
656
|
+
/**
|
|
657
|
+
* Discard draft changes (revert to live version)
|
|
658
|
+
*/
|
|
659
|
+
declare function handleContentDiscardDraft(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<ContentResponse>>;
|
|
660
|
+
/**
|
|
661
|
+
* Compare live and draft revisions
|
|
662
|
+
*/
|
|
663
|
+
declare function handleContentCompare(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<{
|
|
664
|
+
hasChanges: boolean;
|
|
665
|
+
live: Record<string, unknown> | null;
|
|
666
|
+
draft: Record<string, unknown> | null;
|
|
667
|
+
}>>;
|
|
668
|
+
/**
|
|
669
|
+
* Get all translations for a content item.
|
|
670
|
+
* Returns the item's translation group members with locale and status info.
|
|
671
|
+
*/
|
|
672
|
+
declare function handleContentTranslations(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<{
|
|
673
|
+
translationGroup: string;
|
|
674
|
+
translations: Array<{
|
|
675
|
+
id: string;
|
|
676
|
+
locale: string | null;
|
|
677
|
+
slug: string | null;
|
|
678
|
+
status: string;
|
|
679
|
+
updatedAt: string;
|
|
680
|
+
}>;
|
|
681
|
+
}>>;
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/api/handlers/manifest.d.ts
|
|
684
|
+
interface CollectionDefinition {
|
|
685
|
+
schema: {
|
|
686
|
+
_def?: {
|
|
687
|
+
shape?: () => Record<string, unknown>;
|
|
688
|
+
};
|
|
689
|
+
shape?: Record<string, unknown>;
|
|
690
|
+
};
|
|
691
|
+
admin: {
|
|
692
|
+
label: string;
|
|
693
|
+
labelSingular?: string;
|
|
694
|
+
supports?: string[];
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
type CollectionMap = Record<string, CollectionDefinition>;
|
|
698
|
+
/**
|
|
699
|
+
* Generate admin manifest from collections
|
|
700
|
+
*/
|
|
701
|
+
declare function generateManifest(collections: CollectionMap, plugins?: Record<string, {
|
|
702
|
+
adminPages?: Array<{
|
|
703
|
+
path: string;
|
|
704
|
+
component: string;
|
|
705
|
+
}>;
|
|
706
|
+
widgets?: string[];
|
|
707
|
+
}>): Promise<ManifestResponse>;
|
|
708
|
+
//#endregion
|
|
709
|
+
//#region src/api/handlers/revision.d.ts
|
|
710
|
+
interface RevisionListResponse {
|
|
711
|
+
items: Revision[];
|
|
712
|
+
total: number;
|
|
713
|
+
}
|
|
714
|
+
interface RevisionResponse {
|
|
715
|
+
item: Revision;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* List revisions for a content entry
|
|
719
|
+
*/
|
|
720
|
+
declare function handleRevisionList(db: Kysely<Database>, collection: string, entryId: string, params?: {
|
|
721
|
+
limit?: number;
|
|
722
|
+
}): Promise<ApiResult<RevisionListResponse>>;
|
|
723
|
+
/**
|
|
724
|
+
* Get a specific revision
|
|
725
|
+
*/
|
|
726
|
+
declare function handleRevisionGet(db: Kysely<Database>, revisionId: string): Promise<ApiResult<RevisionResponse>>;
|
|
727
|
+
/**
|
|
728
|
+
* Restore a revision (updates content to this revision's data and creates new revision)
|
|
729
|
+
*/
|
|
730
|
+
declare function handleRevisionRestore(db: Kysely<Database>, revisionId: string, callerUserId: string): Promise<ApiResult<ContentResponse>>;
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/api/handlers/media.d.ts
|
|
733
|
+
interface MediaListResponse {
|
|
734
|
+
items: MediaItem[];
|
|
735
|
+
nextCursor?: string;
|
|
736
|
+
}
|
|
737
|
+
interface MediaResponse {
|
|
738
|
+
item: MediaItem;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* List media items
|
|
742
|
+
*/
|
|
743
|
+
declare function handleMediaList(db: Kysely<Database>, params: {
|
|
744
|
+
cursor?: string;
|
|
745
|
+
limit?: number;
|
|
746
|
+
mimeType?: string;
|
|
747
|
+
}): Promise<ApiResult<MediaListResponse>>;
|
|
748
|
+
/**
|
|
749
|
+
* Get single media item
|
|
750
|
+
*/
|
|
751
|
+
declare function handleMediaGet(db: Kysely<Database>, id: string): Promise<ApiResult<MediaResponse>>;
|
|
752
|
+
/**
|
|
753
|
+
* Create media item (after file upload)
|
|
754
|
+
*/
|
|
755
|
+
declare function handleMediaCreate(db: Kysely<Database>, input: {
|
|
756
|
+
filename: string;
|
|
757
|
+
mimeType: string;
|
|
758
|
+
size?: number;
|
|
759
|
+
width?: number;
|
|
760
|
+
height?: number;
|
|
761
|
+
alt?: string;
|
|
762
|
+
storageKey: string;
|
|
763
|
+
contentHash?: string;
|
|
764
|
+
blurhash?: string;
|
|
765
|
+
dominantColor?: string;
|
|
766
|
+
authorId?: string;
|
|
767
|
+
}): Promise<ApiResult<MediaResponse>>;
|
|
768
|
+
/**
|
|
769
|
+
* Update media metadata
|
|
770
|
+
*/
|
|
771
|
+
declare function handleMediaUpdate(db: Kysely<Database>, id: string, input: {
|
|
772
|
+
alt?: string;
|
|
773
|
+
caption?: string;
|
|
774
|
+
width?: number;
|
|
775
|
+
height?: number;
|
|
776
|
+
}): Promise<ApiResult<MediaResponse>>;
|
|
777
|
+
/**
|
|
778
|
+
* Delete media item
|
|
779
|
+
*/
|
|
780
|
+
declare function handleMediaDelete(db: Kysely<Database>, id: string): Promise<ApiResult<{
|
|
781
|
+
deleted: true;
|
|
782
|
+
}>>;
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region src/schema/registry.d.ts
|
|
785
|
+
/**
|
|
786
|
+
* Error thrown when a schema operation fails
|
|
787
|
+
*/
|
|
788
|
+
declare class SchemaError extends Error {
|
|
789
|
+
code: string;
|
|
790
|
+
details?: Record<string, unknown> | undefined;
|
|
791
|
+
constructor(message: string, code: string, details?: Record<string, unknown> | undefined);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Schema Registry
|
|
795
|
+
*
|
|
796
|
+
* Manages collection and field definitions stored in the database.
|
|
797
|
+
* Handles runtime DDL operations (CREATE TABLE, ALTER TABLE).
|
|
798
|
+
*/
|
|
799
|
+
declare class SchemaRegistry {
|
|
800
|
+
private db;
|
|
801
|
+
constructor(db: Kysely<Database>);
|
|
802
|
+
/**
|
|
803
|
+
* List all collections
|
|
804
|
+
*/
|
|
805
|
+
listCollections(): Promise<Collection[]>;
|
|
806
|
+
/**
|
|
807
|
+
* Get a collection by slug
|
|
808
|
+
*/
|
|
809
|
+
getCollection(slug: string): Promise<Collection | null>;
|
|
810
|
+
/**
|
|
811
|
+
* Get a collection with all its fields
|
|
812
|
+
*/
|
|
813
|
+
getCollectionWithFields(slug: string): Promise<CollectionWithFields | null>;
|
|
814
|
+
/**
|
|
815
|
+
* Create a new collection
|
|
816
|
+
*/
|
|
817
|
+
createCollection(input: CreateCollectionInput): Promise<Collection>;
|
|
818
|
+
/**
|
|
819
|
+
* Update a collection
|
|
820
|
+
*/
|
|
821
|
+
updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection>;
|
|
822
|
+
/**
|
|
823
|
+
* Delete a collection
|
|
824
|
+
*/
|
|
825
|
+
deleteCollection(slug: string, options?: {
|
|
826
|
+
force?: boolean;
|
|
827
|
+
}): Promise<void>;
|
|
828
|
+
/**
|
|
829
|
+
* List fields for a collection
|
|
830
|
+
*/
|
|
831
|
+
listFields(collectionId: string): Promise<Field[]>;
|
|
832
|
+
/**
|
|
833
|
+
* Get a field by slug within a collection
|
|
834
|
+
*/
|
|
835
|
+
getField(collectionSlug: string, fieldSlug: string): Promise<Field | null>;
|
|
836
|
+
/**
|
|
837
|
+
* Create a new field
|
|
838
|
+
*/
|
|
839
|
+
createField(collectionSlug: string, input: CreateFieldInput): Promise<Field>;
|
|
840
|
+
/**
|
|
841
|
+
* Update a field
|
|
842
|
+
*/
|
|
843
|
+
updateField(collectionSlug: string, fieldSlug: string, input: UpdateFieldInput): Promise<Field>;
|
|
844
|
+
/**
|
|
845
|
+
* Rebuild the search index for a collection
|
|
846
|
+
*
|
|
847
|
+
* Called when searchable fields change. If search is enabled for the collection,
|
|
848
|
+
* this will rebuild the FTS table with the updated field list.
|
|
849
|
+
*/
|
|
850
|
+
private rebuildSearchIndex;
|
|
851
|
+
/**
|
|
852
|
+
* Delete a field
|
|
853
|
+
*/
|
|
854
|
+
deleteField(collectionSlug: string, fieldSlug: string): Promise<void>;
|
|
855
|
+
/**
|
|
856
|
+
* Reorder fields
|
|
857
|
+
*/
|
|
858
|
+
reorderFields(collectionSlug: string, fieldSlugs: string[]): Promise<void>;
|
|
859
|
+
/**
|
|
860
|
+
* Create a content table for a collection
|
|
861
|
+
*/
|
|
862
|
+
private createContentTable;
|
|
863
|
+
/**
|
|
864
|
+
* Drop a content table
|
|
865
|
+
*/
|
|
866
|
+
private dropContentTable;
|
|
867
|
+
/**
|
|
868
|
+
* Add a column to a content table
|
|
869
|
+
*/
|
|
870
|
+
private addColumn;
|
|
871
|
+
/**
|
|
872
|
+
* Drop a column from a content table
|
|
873
|
+
*/
|
|
874
|
+
private dropColumn;
|
|
875
|
+
/**
|
|
876
|
+
* Check if a collection has any content
|
|
877
|
+
*/
|
|
878
|
+
private collectionHasContent;
|
|
879
|
+
/**
|
|
880
|
+
* Get table name for a collection
|
|
881
|
+
*/
|
|
882
|
+
private getTableName;
|
|
883
|
+
/**
|
|
884
|
+
* Get column name for a field
|
|
885
|
+
*/
|
|
886
|
+
private getColumnName;
|
|
887
|
+
/**
|
|
888
|
+
* Validate a slug
|
|
889
|
+
*/
|
|
890
|
+
private validateSlug;
|
|
891
|
+
/**
|
|
892
|
+
* Format a default value for SQL.
|
|
893
|
+
*
|
|
894
|
+
* SQLite `ALTER TABLE ADD COLUMN ... DEFAULT` requires a literal constant
|
|
895
|
+
* expression — parameterized values cannot be used here. We manually escape
|
|
896
|
+
* single quotes and coerce types to ensure the output is safe.
|
|
897
|
+
*
|
|
898
|
+
* INTEGER/REAL values are coerced through `Number()` which can only produce
|
|
899
|
+
* digits, `.`, `-`, `e`, `Infinity`, or `NaN` — all safe in SQL.
|
|
900
|
+
* TEXT/JSON values have single quotes escaped via SQL standard doubling (`''`).
|
|
901
|
+
*/
|
|
902
|
+
private formatDefaultValue;
|
|
903
|
+
/**
|
|
904
|
+
* Get empty default for a field type
|
|
905
|
+
*/
|
|
906
|
+
private getEmptyDefault;
|
|
907
|
+
/**
|
|
908
|
+
* Map a collection row to a Collection object
|
|
909
|
+
*/
|
|
910
|
+
private mapCollectionRow;
|
|
911
|
+
/**
|
|
912
|
+
* Map a field row to a Field object
|
|
913
|
+
*/
|
|
914
|
+
private mapFieldRow;
|
|
915
|
+
/**
|
|
916
|
+
* Discover orphaned content tables
|
|
917
|
+
*
|
|
918
|
+
* Finds ec_* tables that exist in the database but don't have a
|
|
919
|
+
* corresponding entry in _dineway_collections.
|
|
920
|
+
*/
|
|
921
|
+
discoverOrphanedTables(): Promise<Array<{
|
|
922
|
+
slug: string;
|
|
923
|
+
tableName: string;
|
|
924
|
+
rowCount: number;
|
|
925
|
+
}>>;
|
|
926
|
+
/**
|
|
927
|
+
* Register an orphaned table as a collection
|
|
928
|
+
*
|
|
929
|
+
* Creates a _dineway_collections entry for an existing ec_* table.
|
|
930
|
+
*/
|
|
931
|
+
registerOrphanedTable(slug: string, options?: {
|
|
932
|
+
label?: string;
|
|
933
|
+
labelSingular?: string;
|
|
934
|
+
description?: string;
|
|
935
|
+
}): Promise<Collection>;
|
|
936
|
+
/**
|
|
937
|
+
* Convert slug to human-readable label
|
|
938
|
+
*/
|
|
939
|
+
private slugToLabel;
|
|
940
|
+
}
|
|
941
|
+
//#endregion
|
|
942
|
+
//#region src/schema/query.d.ts
|
|
943
|
+
/**
|
|
944
|
+
* Get collection metadata by slug.
|
|
945
|
+
*
|
|
946
|
+
* @example
|
|
947
|
+
* ```ts
|
|
948
|
+
* import { getCollectionInfo } from "dineway";
|
|
949
|
+
*
|
|
950
|
+
* const info = await getCollectionInfo("posts");
|
|
951
|
+
* if (info?.commentsEnabled) {
|
|
952
|
+
* // render comment UI
|
|
953
|
+
* }
|
|
954
|
+
* ```
|
|
955
|
+
*/
|
|
956
|
+
declare function getCollectionInfo(slug: string): Promise<Collection | null>;
|
|
957
|
+
//#endregion
|
|
958
|
+
//#region src/sections/types.d.ts
|
|
959
|
+
/**
|
|
960
|
+
* Section source types
|
|
961
|
+
*/
|
|
962
|
+
type SectionSource = "theme" | "user" | "import";
|
|
963
|
+
/**
|
|
964
|
+
* Section as returned to templates/admin
|
|
965
|
+
*/
|
|
966
|
+
interface Section {
|
|
967
|
+
id: string;
|
|
968
|
+
slug: string;
|
|
969
|
+
title: string;
|
|
970
|
+
description?: string;
|
|
971
|
+
keywords: string[];
|
|
972
|
+
content: PortableTextBlock$2[];
|
|
973
|
+
previewUrl?: string;
|
|
974
|
+
source: SectionSource;
|
|
975
|
+
themeId?: string;
|
|
976
|
+
createdAt: string;
|
|
977
|
+
updatedAt: string;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Input for creating a section
|
|
981
|
+
*/
|
|
982
|
+
interface CreateSectionInput {
|
|
983
|
+
slug: string;
|
|
984
|
+
title: string;
|
|
985
|
+
description?: string;
|
|
986
|
+
keywords?: string[];
|
|
987
|
+
content: PortableTextBlock$2[];
|
|
988
|
+
previewMediaId?: string;
|
|
989
|
+
source?: SectionSource;
|
|
990
|
+
themeId?: string;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Input for updating a section
|
|
994
|
+
*/
|
|
995
|
+
interface UpdateSectionInput {
|
|
996
|
+
slug?: string;
|
|
997
|
+
title?: string;
|
|
998
|
+
description?: string;
|
|
999
|
+
keywords?: string[];
|
|
1000
|
+
content?: PortableTextBlock$2[];
|
|
1001
|
+
previewMediaId?: string | null;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Options for querying sections
|
|
1005
|
+
*/
|
|
1006
|
+
interface GetSectionsOptions {
|
|
1007
|
+
/** Filter by source */
|
|
1008
|
+
source?: SectionSource;
|
|
1009
|
+
/** Search title, description, keywords */
|
|
1010
|
+
search?: string;
|
|
1011
|
+
/** Limit results */
|
|
1012
|
+
limit?: number;
|
|
1013
|
+
/** Cursor for pagination */
|
|
1014
|
+
cursor?: string;
|
|
1015
|
+
}
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region src/sections/index.d.ts
|
|
1018
|
+
/**
|
|
1019
|
+
* Get a section by slug
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* import { getSection } from "dineway";
|
|
1024
|
+
*
|
|
1025
|
+
* const section = await getSection("hero-centered");
|
|
1026
|
+
* if (section) {
|
|
1027
|
+
* console.log(section.content); // Portable Text array
|
|
1028
|
+
* }
|
|
1029
|
+
* ```
|
|
1030
|
+
*/
|
|
1031
|
+
declare function getSection(slug: string): Promise<Section | null>;
|
|
1032
|
+
/**
|
|
1033
|
+
* Get all sections with optional filtering
|
|
1034
|
+
*
|
|
1035
|
+
* @example
|
|
1036
|
+
* ```ts
|
|
1037
|
+
* import { getSections } from "dineway";
|
|
1038
|
+
*
|
|
1039
|
+
* // Get all theme-provided sections
|
|
1040
|
+
* const themeSections = await getSections({ source: "theme" });
|
|
1041
|
+
*
|
|
1042
|
+
* // Search sections
|
|
1043
|
+
* const results = await getSections({ search: "pricing" });
|
|
1044
|
+
* ```
|
|
1045
|
+
*/
|
|
1046
|
+
declare function getSections(options?: GetSectionsOptions): Promise<FindManyResult<Section>>;
|
|
1047
|
+
//#endregion
|
|
1048
|
+
//#region src/plugins/sandbox/types.d.ts
|
|
1049
|
+
/**
|
|
1050
|
+
* Resource limits for sandboxed plugins.
|
|
1051
|
+
* Enforced by the sandbox runtime.
|
|
1052
|
+
*/
|
|
1053
|
+
interface ResourceLimits {
|
|
1054
|
+
/** CPU time per invocation in milliseconds (default: 50ms) */
|
|
1055
|
+
cpuMs?: number;
|
|
1056
|
+
/** Memory limit in MB (default: 128MB) */
|
|
1057
|
+
memoryMb?: number;
|
|
1058
|
+
/** Maximum subrequests per invocation (default: 10) */
|
|
1059
|
+
subrequests?: number;
|
|
1060
|
+
/** Wall-clock time limit in milliseconds (default: 30000ms) */
|
|
1061
|
+
wallTimeMs?: number;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Storage interface for loading plugin code.
|
|
1065
|
+
* Could be R2, local filesystem, or any other storage backend.
|
|
1066
|
+
*/
|
|
1067
|
+
interface PluginCodeStorage {
|
|
1068
|
+
/** Get plugin bundle code by path */
|
|
1069
|
+
get(path: string): Promise<string | null>;
|
|
1070
|
+
/** Check if a bundle exists */
|
|
1071
|
+
exists(path: string): Promise<boolean>;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Serialized email message for sandbox RPC transport.
|
|
1075
|
+
* Matches the core EmailMessage type but uses only serializable fields.
|
|
1076
|
+
*/
|
|
1077
|
+
interface SandboxEmailMessage {
|
|
1078
|
+
to: string;
|
|
1079
|
+
subject: string;
|
|
1080
|
+
text: string;
|
|
1081
|
+
html?: string;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Callback for sending email from a sandboxed plugin.
|
|
1085
|
+
* The sandbox runner wires this up from the EmailPipeline.
|
|
1086
|
+
*
|
|
1087
|
+
* @param message - The email message to send
|
|
1088
|
+
* @param pluginId - The sending plugin's ID (used as source)
|
|
1089
|
+
*/
|
|
1090
|
+
type SandboxEmailSendCallback = (message: SandboxEmailMessage, pluginId: string) => Promise<void>;
|
|
1091
|
+
/**
|
|
1092
|
+
* Options for creating a sandbox runner
|
|
1093
|
+
*/
|
|
1094
|
+
interface SandboxOptions {
|
|
1095
|
+
/** Storage interface for loading plugin code */
|
|
1096
|
+
storage?: PluginCodeStorage;
|
|
1097
|
+
/** Database for bridge operations */
|
|
1098
|
+
db: Kysely<Database>;
|
|
1099
|
+
/** Default resource limits */
|
|
1100
|
+
limits?: ResourceLimits;
|
|
1101
|
+
/** Site info for plugin context (injected into wrapper at generation time) */
|
|
1102
|
+
siteInfo?: {
|
|
1103
|
+
name: string;
|
|
1104
|
+
url: string;
|
|
1105
|
+
locale: string;
|
|
1106
|
+
};
|
|
1107
|
+
/** Email send callback, wired from the EmailPipeline by the runtime */
|
|
1108
|
+
emailSend?: SandboxEmailSendCallback;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* A sandboxed plugin instance.
|
|
1112
|
+
* Provides methods to invoke hooks and routes in the isolated environment.
|
|
1113
|
+
*/
|
|
1114
|
+
interface SandboxedPlugin {
|
|
1115
|
+
/** Unique identifier: `${manifest.id}:${manifest.version}` */
|
|
1116
|
+
readonly id: string;
|
|
1117
|
+
/**
|
|
1118
|
+
* Invoke a hook in the sandboxed plugin.
|
|
1119
|
+
*
|
|
1120
|
+
* @param hookName - Name of the hook (e.g., "content:beforeSave")
|
|
1121
|
+
* @param event - Event data to pass to the hook
|
|
1122
|
+
* @returns Hook result (transformed content, void, etc.)
|
|
1123
|
+
*/
|
|
1124
|
+
invokeHook(hookName: string, event: unknown): Promise<unknown>;
|
|
1125
|
+
/**
|
|
1126
|
+
* Invoke an API route in the sandboxed plugin.
|
|
1127
|
+
*
|
|
1128
|
+
* @param routeName - Name of the route
|
|
1129
|
+
* @param input - Validated input data
|
|
1130
|
+
* @param request - Serialized request info for context
|
|
1131
|
+
* @returns Route response data
|
|
1132
|
+
*/
|
|
1133
|
+
invokeRoute(routeName: string, input: unknown, request: SerializedRequest): Promise<unknown>;
|
|
1134
|
+
/**
|
|
1135
|
+
* Terminate the sandboxed plugin.
|
|
1136
|
+
* Releases resources and prevents further invocations.
|
|
1137
|
+
*/
|
|
1138
|
+
terminate(): Promise<void>;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Serialized request for RPC transport.
|
|
1142
|
+
* Sandbox RPC layers can't pass Request objects directly.
|
|
1143
|
+
*/
|
|
1144
|
+
interface SerializedRequest {
|
|
1145
|
+
url: string;
|
|
1146
|
+
method: string;
|
|
1147
|
+
headers: Record<string, string>;
|
|
1148
|
+
/** Normalized request metadata extracted before RPC serialization */
|
|
1149
|
+
meta: RequestMeta;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Sandbox runner interface.
|
|
1153
|
+
* Platform adapters implement this to provide plugin isolation.
|
|
1154
|
+
*/
|
|
1155
|
+
interface SandboxRunner {
|
|
1156
|
+
/**
|
|
1157
|
+
* Check if sandboxing is available on this platform.
|
|
1158
|
+
* Returns false for platforms that don't support isolation.
|
|
1159
|
+
*/
|
|
1160
|
+
isAvailable(): boolean;
|
|
1161
|
+
/**
|
|
1162
|
+
* Load a sandboxed plugin from code.
|
|
1163
|
+
*
|
|
1164
|
+
* @param manifest - Plugin manifest with metadata and capabilities
|
|
1165
|
+
* @param code - The bundled plugin JavaScript code
|
|
1166
|
+
* @returns A sandboxed plugin instance
|
|
1167
|
+
* @throws If sandboxing is not available or plugin can't be loaded
|
|
1168
|
+
*/
|
|
1169
|
+
load(manifest: PluginManifest, code: string): Promise<SandboxedPlugin>;
|
|
1170
|
+
/**
|
|
1171
|
+
* Set the email send callback for sandboxed plugins.
|
|
1172
|
+
* Called after the EmailPipeline is created, since the pipeline
|
|
1173
|
+
* doesn't exist when the sandbox runner is constructed.
|
|
1174
|
+
*/
|
|
1175
|
+
setEmailSend(callback: SandboxEmailSendCallback | null): void;
|
|
1176
|
+
/**
|
|
1177
|
+
* Terminate all loaded sandboxed plugins.
|
|
1178
|
+
* Called during shutdown or when reconfiguring.
|
|
1179
|
+
*/
|
|
1180
|
+
terminateAll(): Promise<void>;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Factory function type for creating sandbox runners.
|
|
1184
|
+
* Exported by platform adapters.
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* ```typescript
|
|
1188
|
+
* // In a platform adapter module
|
|
1189
|
+
* export const createSandboxRunner: SandboxRunnerFactory = (options) => {
|
|
1190
|
+
* return new NodeSandboxRunner(options);
|
|
1191
|
+
* };
|
|
1192
|
+
* ```
|
|
1193
|
+
*/
|
|
1194
|
+
type SandboxRunnerFactory = (options: SandboxOptions) => SandboxRunner;
|
|
1195
|
+
//#endregion
|
|
1196
|
+
//#region src/content/converters/types.d.ts
|
|
1197
|
+
/**
|
|
1198
|
+
* Portable Text Types
|
|
1199
|
+
*
|
|
1200
|
+
* Defines the structure of Portable Text blocks used in Dineway.
|
|
1201
|
+
*/
|
|
1202
|
+
/**
|
|
1203
|
+
* Base span (inline text)
|
|
1204
|
+
*/
|
|
1205
|
+
interface PortableTextSpan {
|
|
1206
|
+
_type: "span";
|
|
1207
|
+
_key: string;
|
|
1208
|
+
text: string;
|
|
1209
|
+
marks?: string[];
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Mark definition (bold, italic, link, etc.)
|
|
1213
|
+
*/
|
|
1214
|
+
interface PortableTextMarkDef {
|
|
1215
|
+
_type: string;
|
|
1216
|
+
_key: string;
|
|
1217
|
+
[key: string]: unknown;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Link mark definition
|
|
1221
|
+
*/
|
|
1222
|
+
interface PortableTextLinkMark extends PortableTextMarkDef {
|
|
1223
|
+
_type: "link";
|
|
1224
|
+
href: string;
|
|
1225
|
+
blank?: boolean;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Text block (paragraph, heading, etc.)
|
|
1229
|
+
*/
|
|
1230
|
+
interface PortableTextTextBlock {
|
|
1231
|
+
_type: "block";
|
|
1232
|
+
_key: string;
|
|
1233
|
+
style?: "normal" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "blockquote";
|
|
1234
|
+
listItem?: "bullet" | "number";
|
|
1235
|
+
level?: number;
|
|
1236
|
+
children: PortableTextSpan[];
|
|
1237
|
+
markDefs?: PortableTextMarkDef[];
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Image block
|
|
1241
|
+
*/
|
|
1242
|
+
interface PortableTextImageBlock {
|
|
1243
|
+
_type: "image";
|
|
1244
|
+
_key: string;
|
|
1245
|
+
asset: {
|
|
1246
|
+
_ref: string;
|
|
1247
|
+
url?: string; /** Provider ID for external media (e.g., "image-cdn") */
|
|
1248
|
+
provider?: string;
|
|
1249
|
+
};
|
|
1250
|
+
alt?: string;
|
|
1251
|
+
caption?: string;
|
|
1252
|
+
/** Original image width */
|
|
1253
|
+
width?: number;
|
|
1254
|
+
/** Original image height */
|
|
1255
|
+
height?: number;
|
|
1256
|
+
/** Display width for this instance (overrides original) */
|
|
1257
|
+
displayWidth?: number;
|
|
1258
|
+
/** Display height for this instance (overrides original) */
|
|
1259
|
+
displayHeight?: number;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Code block
|
|
1263
|
+
*/
|
|
1264
|
+
interface PortableTextCodeBlock {
|
|
1265
|
+
_type: "code";
|
|
1266
|
+
_key: string;
|
|
1267
|
+
code: string;
|
|
1268
|
+
language?: string;
|
|
1269
|
+
filename?: string;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Unknown/custom block (preserved for plugin compatibility)
|
|
1273
|
+
*/
|
|
1274
|
+
interface PortableTextUnknownBlock {
|
|
1275
|
+
_type: string;
|
|
1276
|
+
_key: string;
|
|
1277
|
+
[key: string]: unknown;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Any Portable Text block
|
|
1281
|
+
*/
|
|
1282
|
+
type PortableTextBlock$1 = PortableTextTextBlock | PortableTextImageBlock | PortableTextCodeBlock | PortableTextUnknownBlock;
|
|
1283
|
+
/**
|
|
1284
|
+
* ProseMirror JSON types (simplified for TipTap)
|
|
1285
|
+
*/
|
|
1286
|
+
interface ProseMirrorMark {
|
|
1287
|
+
type: string;
|
|
1288
|
+
attrs?: Record<string, unknown>;
|
|
1289
|
+
}
|
|
1290
|
+
interface ProseMirrorNode {
|
|
1291
|
+
type: string;
|
|
1292
|
+
attrs?: Record<string, unknown>;
|
|
1293
|
+
content?: ProseMirrorNode[];
|
|
1294
|
+
marks?: ProseMirrorMark[];
|
|
1295
|
+
text?: string;
|
|
1296
|
+
}
|
|
1297
|
+
interface ProseMirrorDocument {
|
|
1298
|
+
type: "doc";
|
|
1299
|
+
content: ProseMirrorNode[];
|
|
1300
|
+
}
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/content/converters/prosemirror-to-portable-text.d.ts
|
|
1303
|
+
/**
|
|
1304
|
+
* Convert ProseMirror document to Portable Text
|
|
1305
|
+
*/
|
|
1306
|
+
declare function prosemirrorToPortableText(doc: ProseMirrorDocument): PortableTextBlock$1[];
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region src/content/converters/portable-text-to-prosemirror.d.ts
|
|
1309
|
+
/**
|
|
1310
|
+
* Convert Portable Text to ProseMirror document
|
|
1311
|
+
*/
|
|
1312
|
+
declare function portableTextToProsemirror(blocks: PortableTextBlock$1[]): ProseMirrorDocument;
|
|
1313
|
+
//#endregion
|
|
1314
|
+
//#region src/utils/hash.d.ts
|
|
1315
|
+
/**
|
|
1316
|
+
* SHA-256 hash of a string, truncated to 16 hex chars (64 bits).
|
|
1317
|
+
* For cache invalidation / ETags — not for security.
|
|
1318
|
+
*/
|
|
1319
|
+
declare function hashString(content: string): Promise<string>;
|
|
1320
|
+
/**
|
|
1321
|
+
* Compute content hash using Web Crypto API
|
|
1322
|
+
*
|
|
1323
|
+
* Uses SHA-1 which is the fastest option in SubtleCrypto.
|
|
1324
|
+
* SHA-1 is cryptographically weak but fine for content deduplication
|
|
1325
|
+
* where we only need to detect identical files, not resist attacks.
|
|
1326
|
+
*
|
|
1327
|
+
* Returns hex string prefixed with "sha1:" for future-proofing
|
|
1328
|
+
*/
|
|
1329
|
+
declare function computeContentHash(content: Uint8Array | ArrayBuffer): Promise<string>;
|
|
1330
|
+
//#endregion
|
|
1331
|
+
//#region src/utils/url.d.ts
|
|
1332
|
+
/**
|
|
1333
|
+
* URL scheme validation utilities
|
|
1334
|
+
*
|
|
1335
|
+
* Prevents XSS via dangerous URL schemes (javascript:, data:, vbscript:, etc.)
|
|
1336
|
+
* by allowlisting known-safe schemes before rendering into href attributes.
|
|
1337
|
+
*/
|
|
1338
|
+
/**
|
|
1339
|
+
* Returns the URL unchanged if it uses a safe scheme, otherwise returns "#".
|
|
1340
|
+
*
|
|
1341
|
+
* Use this at the render layer as the primary defense against XSS via
|
|
1342
|
+
* dangerous URL schemes like `javascript:`, `data:`, or `vbscript:`.
|
|
1343
|
+
*
|
|
1344
|
+
* @example
|
|
1345
|
+
* ```ts
|
|
1346
|
+
* sanitizeHref("https://example.com") // "https://example.com"
|
|
1347
|
+
* sanitizeHref("/about") // "/about"
|
|
1348
|
+
* sanitizeHref("#section") // "#section"
|
|
1349
|
+
* sanitizeHref("mailto:a@b.com") // "mailto:a@b.com"
|
|
1350
|
+
* sanitizeHref("javascript:alert(1)") // "#"
|
|
1351
|
+
* sanitizeHref("data:text/html,<script>") // "#"
|
|
1352
|
+
* sanitizeHref("") // "#"
|
|
1353
|
+
* ```
|
|
1354
|
+
*/
|
|
1355
|
+
declare function sanitizeHref(url: string | undefined | null): string;
|
|
1356
|
+
/**
|
|
1357
|
+
* Returns true if the URL uses a safe scheme for rendering in href attributes.
|
|
1358
|
+
*/
|
|
1359
|
+
declare function isSafeHref(url: string): boolean;
|
|
1360
|
+
//#endregion
|
|
1361
|
+
//#region src/visual-editing/editable.d.ts
|
|
1362
|
+
/**
|
|
1363
|
+
* Visual editing annotation system
|
|
1364
|
+
*
|
|
1365
|
+
* Creates Proxy objects that emit data-dineway-ref attributes when spread onto elements.
|
|
1366
|
+
*/
|
|
1367
|
+
interface CMSAnnotation {
|
|
1368
|
+
collection: string;
|
|
1369
|
+
id: string;
|
|
1370
|
+
field?: string;
|
|
1371
|
+
/** Entry status — only present on entry-level annotations (not field-level) */
|
|
1372
|
+
status?: string;
|
|
1373
|
+
/** Whether the entry has unpublished draft changes */
|
|
1374
|
+
hasDraft?: boolean;
|
|
1375
|
+
}
|
|
1376
|
+
/** The shape returned when spreading an edit annotation onto an element */
|
|
1377
|
+
interface FieldAnnotation {
|
|
1378
|
+
"data-dineway-ref": string;
|
|
1379
|
+
}
|
|
1380
|
+
interface EditableOptions {
|
|
1381
|
+
/** Entry status: "draft", "published", "scheduled" */
|
|
1382
|
+
status?: string;
|
|
1383
|
+
/** true when draftRevisionId exists and differs from liveRevisionId */
|
|
1384
|
+
hasDraft?: boolean;
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Create an editable proxy for an entry.
|
|
1388
|
+
*
|
|
1389
|
+
* Usage:
|
|
1390
|
+
* - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)
|
|
1391
|
+
* - `{...entry.edit.title}` - field-level annotation
|
|
1392
|
+
* - `{...entry.edit['nested.field']}` - nested field (bracket notation)
|
|
1393
|
+
*/
|
|
1394
|
+
declare function createEditable(collection: string, id: string, options?: EditableOptions): EditProxy;
|
|
1395
|
+
/**
|
|
1396
|
+
* Create a noop proxy for production mode.
|
|
1397
|
+
* Spreading this produces no attributes.
|
|
1398
|
+
*/
|
|
1399
|
+
declare function createNoop(): EditProxy;
|
|
1400
|
+
/**
|
|
1401
|
+
* Visual editing proxy type.
|
|
1402
|
+
*
|
|
1403
|
+
* Spread directly onto elements for entry-level annotations: `{...entry.edit}`
|
|
1404
|
+
* Access a field for field-level annotations: `{...entry.edit.title}`
|
|
1405
|
+
*
|
|
1406
|
+
* In production, spreading produces no attributes (noop).
|
|
1407
|
+
*/
|
|
1408
|
+
type EditProxy = {
|
|
1409
|
+
readonly [field: string]: Partial<FieldAnnotation>;
|
|
1410
|
+
};
|
|
1411
|
+
//#endregion
|
|
1412
|
+
//#region src/query.d.ts
|
|
1413
|
+
/**
|
|
1414
|
+
* Collection type registry for type-safe queries.
|
|
1415
|
+
*
|
|
1416
|
+
* This interface is extended by the generated dineway-env.d.ts file
|
|
1417
|
+
* to provide type inference for collection names and their data shapes.
|
|
1418
|
+
*
|
|
1419
|
+
* @example
|
|
1420
|
+
* ```ts
|
|
1421
|
+
* // In dineway-env.d.ts (generated):
|
|
1422
|
+
* declare module "dineway" {
|
|
1423
|
+
* interface DinewayCollections {
|
|
1424
|
+
* posts: { title: string; content: PortableTextBlock[]; };
|
|
1425
|
+
* pages: { title: string; body: PortableTextBlock[]; };
|
|
1426
|
+
* }
|
|
1427
|
+
* }
|
|
1428
|
+
*
|
|
1429
|
+
* // Then in your code:
|
|
1430
|
+
* const { entries } = await getDinewayCollection("posts");
|
|
1431
|
+
* // entries[0].data.title is typed as string
|
|
1432
|
+
* ```
|
|
1433
|
+
*/
|
|
1434
|
+
interface DinewayCollections {}
|
|
1435
|
+
/**
|
|
1436
|
+
* Helper type to infer the data type for a collection.
|
|
1437
|
+
* Returns the registered type if known, otherwise falls back to Record<string, unknown>.
|
|
1438
|
+
*/
|
|
1439
|
+
type InferCollectionData<T extends string> = T extends keyof DinewayCollections ? DinewayCollections[T] : Record<string, unknown>;
|
|
1440
|
+
/**
|
|
1441
|
+
* Sort direction
|
|
1442
|
+
*/
|
|
1443
|
+
type SortDirection$1 = "asc" | "desc";
|
|
1444
|
+
/**
|
|
1445
|
+
* Order by specification - field name to direction
|
|
1446
|
+
* @example { created_at: "desc" } - Sort by created_at descending
|
|
1447
|
+
* @example { title: "asc" } - Sort by title ascending
|
|
1448
|
+
* @example { published_at: "desc", title: "asc" } - Multi-field sort
|
|
1449
|
+
*/
|
|
1450
|
+
type OrderBySpec$1 = Record<string, SortDirection$1>;
|
|
1451
|
+
interface CollectionFilter$1 {
|
|
1452
|
+
status?: "draft" | "published" | "archived";
|
|
1453
|
+
limit?: number;
|
|
1454
|
+
/**
|
|
1455
|
+
* Opaque cursor for keyset pagination.
|
|
1456
|
+
* Pass the `nextCursor` value from a previous result to fetch the next page.
|
|
1457
|
+
* @example
|
|
1458
|
+
* ```ts
|
|
1459
|
+
* const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
|
|
1460
|
+
* const { entries, nextCursor } = await getDinewayCollection("posts", {
|
|
1461
|
+
* limit: 10,
|
|
1462
|
+
* cursor,
|
|
1463
|
+
* });
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
cursor?: string;
|
|
1467
|
+
/**
|
|
1468
|
+
* Filter by field values or taxonomy terms
|
|
1469
|
+
* @example { category: 'news' } - Filter by taxonomy term
|
|
1470
|
+
* @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)
|
|
1471
|
+
*/
|
|
1472
|
+
where?: Record<string, string | string[]>;
|
|
1473
|
+
/**
|
|
1474
|
+
* Order results by field(s)
|
|
1475
|
+
* @default { created_at: "desc" }
|
|
1476
|
+
* @example { created_at: "desc" } - Sort by created_at descending (default)
|
|
1477
|
+
* @example { title: "asc" } - Sort by title ascending
|
|
1478
|
+
* @example { published_at: "desc", title: "asc" } - Multi-field sort
|
|
1479
|
+
*/
|
|
1480
|
+
orderBy?: OrderBySpec$1;
|
|
1481
|
+
/**
|
|
1482
|
+
* Filter by locale. When set, only returns entries in this locale.
|
|
1483
|
+
* Only relevant when i18n is configured.
|
|
1484
|
+
* @example "en" — English entries only
|
|
1485
|
+
* @example "fr" — French entries only
|
|
1486
|
+
*/
|
|
1487
|
+
locale?: string;
|
|
1488
|
+
}
|
|
1489
|
+
interface ContentEntry<T = Record<string, unknown>> {
|
|
1490
|
+
id: string;
|
|
1491
|
+
data: T;
|
|
1492
|
+
/** Visual editing annotations. Spread onto elements: {...entry.edit.title} */
|
|
1493
|
+
edit: EditProxy;
|
|
1494
|
+
}
|
|
1495
|
+
/** Cache hint returned by the content loader for route caching */
|
|
1496
|
+
interface CacheHint {
|
|
1497
|
+
tags?: string[];
|
|
1498
|
+
lastModified?: Date;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Result from getDinewayCollection
|
|
1502
|
+
*/
|
|
1503
|
+
interface CollectionResult<T> {
|
|
1504
|
+
/** The entries (empty array if error or none found) */
|
|
1505
|
+
entries: ContentEntry<T>[];
|
|
1506
|
+
/** Error if the query failed */
|
|
1507
|
+
error?: Error;
|
|
1508
|
+
/** Cache hint for route caching (pass to Astro.cache.set()) */
|
|
1509
|
+
cacheHint: CacheHint;
|
|
1510
|
+
/**
|
|
1511
|
+
* Opaque cursor for the next page.
|
|
1512
|
+
* Undefined when there are no more results.
|
|
1513
|
+
* Pass this as `cursor` in the next query to get the next page.
|
|
1514
|
+
*/
|
|
1515
|
+
nextCursor?: string;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Result from getDinewayEntry
|
|
1519
|
+
*/
|
|
1520
|
+
interface EntryResult<T> {
|
|
1521
|
+
/** The entry, or null if not found */
|
|
1522
|
+
entry: ContentEntry<T> | null;
|
|
1523
|
+
/** Error if the query failed (not set for "not found", only for actual errors) */
|
|
1524
|
+
error?: Error;
|
|
1525
|
+
/** Whether we're in preview mode (valid token was provided) */
|
|
1526
|
+
isPreview: boolean;
|
|
1527
|
+
/** Set when a fallback locale was used instead of the requested locale */
|
|
1528
|
+
fallbackLocale?: string;
|
|
1529
|
+
/** Cache hint for route caching (pass to Astro.cache.set()) */
|
|
1530
|
+
cacheHint: CacheHint;
|
|
1531
|
+
}
|
|
1532
|
+
/** Edit metadata attached to PT arrays in edit mode */
|
|
1533
|
+
interface EditFieldMeta {
|
|
1534
|
+
collection: string;
|
|
1535
|
+
id: string;
|
|
1536
|
+
field: string;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Read edit metadata from a value (returns undefined if not tagged).
|
|
1540
|
+
* Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property
|
|
1541
|
+
* without an unsafe type assertion.
|
|
1542
|
+
*/
|
|
1543
|
+
declare function getEditMeta(value: unknown): EditFieldMeta | undefined;
|
|
1544
|
+
/**
|
|
1545
|
+
* Get all entries of a content type
|
|
1546
|
+
*
|
|
1547
|
+
* Returns { entries, error } for graceful error handling.
|
|
1548
|
+
*
|
|
1549
|
+
* When dineway-env.d.ts is generated, the collection name will be
|
|
1550
|
+
* type-checked and the return type will be inferred automatically.
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```ts
|
|
1554
|
+
* import { getDinewayCollection } from "dineway";
|
|
1555
|
+
*
|
|
1556
|
+
* const { entries: posts, error } = await getDinewayCollection("posts");
|
|
1557
|
+
* if (error) {
|
|
1558
|
+
* console.error("Failed to load posts:", error);
|
|
1559
|
+
* return;
|
|
1560
|
+
* }
|
|
1561
|
+
* // posts[0].data.title is typed (if dineway-env.d.ts exists)
|
|
1562
|
+
*
|
|
1563
|
+
* // With filters
|
|
1564
|
+
* const { entries: drafts } = await getDinewayCollection("posts", { status: "draft" });
|
|
1565
|
+
* ```
|
|
1566
|
+
*/
|
|
1567
|
+
declare function getDinewayCollection<T extends string, D = InferCollectionData<T>>(type: T, filter?: CollectionFilter$1): Promise<CollectionResult<D>>;
|
|
1568
|
+
/**
|
|
1569
|
+
* Get a single entry by type and ID/slug
|
|
1570
|
+
*
|
|
1571
|
+
* Returns { entry, error, isPreview } for graceful error handling.
|
|
1572
|
+
* - entry is null if not found (not an error)
|
|
1573
|
+
* - error is set only for actual errors (db issues, etc.)
|
|
1574
|
+
*
|
|
1575
|
+
* Preview mode is detected automatically from request context (ALS).
|
|
1576
|
+
* When the URL has a valid `_preview` token, the middleware sets preview
|
|
1577
|
+
* context and this function serves draft revision data if available.
|
|
1578
|
+
*
|
|
1579
|
+
* @example
|
|
1580
|
+
* ```ts
|
|
1581
|
+
* import { getDinewayEntry } from "dineway";
|
|
1582
|
+
*
|
|
1583
|
+
* // Simple usage — preview just works via middleware
|
|
1584
|
+
* const { entry: post, isPreview, error } = await getDinewayEntry("posts", "my-slug");
|
|
1585
|
+
* if (!post) return Astro.redirect("/404");
|
|
1586
|
+
* ```
|
|
1587
|
+
*/
|
|
1588
|
+
declare function getDinewayEntry<T extends string, D = InferCollectionData<T>>(type: T, id: string, options?: {
|
|
1589
|
+
locale?: string;
|
|
1590
|
+
}): Promise<EntryResult<D>>;
|
|
1591
|
+
/**
|
|
1592
|
+
* Translation summary for a single locale variant
|
|
1593
|
+
*/
|
|
1594
|
+
interface TranslationSummary {
|
|
1595
|
+
/** Content item ID */
|
|
1596
|
+
id: string;
|
|
1597
|
+
/** Locale code (e.g. "en", "fr") */
|
|
1598
|
+
locale: string;
|
|
1599
|
+
/** URL slug */
|
|
1600
|
+
slug: string | null;
|
|
1601
|
+
/** Current status */
|
|
1602
|
+
status: string;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Result from getTranslations
|
|
1606
|
+
*/
|
|
1607
|
+
interface TranslationsResult {
|
|
1608
|
+
/** The translation group ID (shared across locales) */
|
|
1609
|
+
translationGroup: string;
|
|
1610
|
+
/** All locale variants in this group */
|
|
1611
|
+
translations: TranslationSummary[];
|
|
1612
|
+
/** Error if the query failed */
|
|
1613
|
+
error?: Error;
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get all translations of a content item.
|
|
1617
|
+
*
|
|
1618
|
+
* Given a content entry, returns all locale variants that share the same
|
|
1619
|
+
* translation group. This is useful for building language switcher UI.
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```ts
|
|
1623
|
+
* import { getDinewayEntry, getTranslations } from "dineway";
|
|
1624
|
+
*
|
|
1625
|
+
* const { entry: post } = await getDinewayEntry("posts", "hello-world", { locale: "en" });
|
|
1626
|
+
* const { translations } = await getTranslations("posts", post.data.id);
|
|
1627
|
+
* // translations = [{ id: "...", locale: "en", slug: "hello-world", status: "published" }, ...]
|
|
1628
|
+
* ```
|
|
1629
|
+
*/
|
|
1630
|
+
declare function getTranslations(type: string, id: string): Promise<TranslationsResult>;
|
|
1631
|
+
/**
|
|
1632
|
+
* Result from resolveDinewayPath
|
|
1633
|
+
*/
|
|
1634
|
+
interface ResolvePathResult<T = Record<string, unknown>> {
|
|
1635
|
+
/** The matched entry */
|
|
1636
|
+
entry: ContentEntry<T>;
|
|
1637
|
+
/** The collection slug that matched */
|
|
1638
|
+
collection: string;
|
|
1639
|
+
/** Extracted parameters from the URL pattern (e.g. { slug: "my-post" }) */
|
|
1640
|
+
params: Record<string, string>;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Resolve a URL path to a content entry by matching against collection URL patterns.
|
|
1644
|
+
*
|
|
1645
|
+
* Loads all collections with a `urlPattern` set, converts each pattern to a regex,
|
|
1646
|
+
* and tests the given path. On match, extracts the slug and fetches the entry.
|
|
1647
|
+
*
|
|
1648
|
+
* @example
|
|
1649
|
+
* ```ts
|
|
1650
|
+
* import { resolveDinewayPath } from "dineway";
|
|
1651
|
+
*
|
|
1652
|
+
* // Given pages with urlPattern "/{slug}" and posts with "/blog/{slug}":
|
|
1653
|
+
* const result = await resolveDinewayPath("/blog/hello-world");
|
|
1654
|
+
* if (result) {
|
|
1655
|
+
* console.log(result.collection); // "posts"
|
|
1656
|
+
* console.log(result.params.slug); // "hello-world"
|
|
1657
|
+
* console.log(result.entry.data); // post data
|
|
1658
|
+
* }
|
|
1659
|
+
* ```
|
|
1660
|
+
*/
|
|
1661
|
+
declare function resolveDinewayPath<T = Record<string, unknown>>(path: string): Promise<ResolvePathResult<T> | null>;
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region src/i18n/config.d.ts
|
|
1664
|
+
/**
|
|
1665
|
+
* Dineway i18n Configuration
|
|
1666
|
+
*
|
|
1667
|
+
* Reads locale configuration from the virtual module (sourced from Astro config).
|
|
1668
|
+
* Initialized during runtime startup, then available via getI18nConfig().
|
|
1669
|
+
*/
|
|
1670
|
+
interface I18nConfig {
|
|
1671
|
+
defaultLocale: string;
|
|
1672
|
+
locales: string[];
|
|
1673
|
+
fallback?: Record<string, string>;
|
|
1674
|
+
prefixDefaultLocale?: boolean;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Get the current i18n config.
|
|
1678
|
+
* Returns null if i18n is not configured.
|
|
1679
|
+
*/
|
|
1680
|
+
declare function getI18nConfig(): I18nConfig | null;
|
|
1681
|
+
/**
|
|
1682
|
+
* Check if i18n is enabled.
|
|
1683
|
+
* Returns true when multiple locales are configured.
|
|
1684
|
+
*/
|
|
1685
|
+
declare function isI18nEnabled(): boolean;
|
|
1686
|
+
/**
|
|
1687
|
+
* Resolve fallback locale chain for a given locale.
|
|
1688
|
+
* Returns array of locales to try, from most preferred to least.
|
|
1689
|
+
* Always ends with defaultLocale.
|
|
1690
|
+
*/
|
|
1691
|
+
declare function getFallbackChain(locale: string): string[];
|
|
1692
|
+
//#endregion
|
|
1693
|
+
//#region src/preview/sidecar-client.d.ts
|
|
1694
|
+
/**
|
|
1695
|
+
* Preview sidecar protocol helpers.
|
|
1696
|
+
*
|
|
1697
|
+
* These utilities define the signed URL and snapshot-auth contract shared
|
|
1698
|
+
* by preview sidecars and the source site serving snapshot exports.
|
|
1699
|
+
*/
|
|
1700
|
+
interface PreviewSidecarSignature {
|
|
1701
|
+
source: string;
|
|
1702
|
+
exp: number;
|
|
1703
|
+
sig: string;
|
|
1704
|
+
}
|
|
1705
|
+
interface PreviewSidecarClient {
|
|
1706
|
+
signPreviewUrl(previewBase: string, source: string, secret: string, ttl?: number): Promise<string>;
|
|
1707
|
+
buildSnapshotSignatureHeader(signature: PreviewSidecarSignature): string;
|
|
1708
|
+
}
|
|
1709
|
+
declare function signPreviewUrl(previewBase: string, source: string, secret: string, ttl?: number): Promise<string>;
|
|
1710
|
+
declare function buildPreviewSignatureHeader(signature: PreviewSidecarSignature): string;
|
|
1711
|
+
declare function parsePreviewSignatureHeader(header: string): PreviewSidecarSignature | null;
|
|
1712
|
+
declare function verifyPreviewSignature(source: string, exp: number, sig: string, secret: string): Promise<boolean>;
|
|
1713
|
+
declare const defaultPreviewSidecarClient: PreviewSidecarClient;
|
|
1714
|
+
//#endregion
|
|
1715
|
+
//#region src/preview/session-database-factory.d.ts
|
|
1716
|
+
interface SessionDatabaseInfo {
|
|
1717
|
+
id: string;
|
|
1718
|
+
dbPath: string;
|
|
1719
|
+
createdAt: number;
|
|
1720
|
+
expiresAt: number;
|
|
1721
|
+
lastTouchedAt: number;
|
|
1722
|
+
}
|
|
1723
|
+
interface SessionDatabaseHandle {
|
|
1724
|
+
created: boolean;
|
|
1725
|
+
session: SessionDatabaseInfo;
|
|
1726
|
+
db: Kysely<Database>;
|
|
1727
|
+
close(): Promise<void>;
|
|
1728
|
+
}
|
|
1729
|
+
interface SessionCleanupResult {
|
|
1730
|
+
removedSessionIds: string[];
|
|
1731
|
+
removedPaths: string[];
|
|
1732
|
+
}
|
|
1733
|
+
interface SessionOpenOptions {
|
|
1734
|
+
id: string;
|
|
1735
|
+
now?: number;
|
|
1736
|
+
}
|
|
1737
|
+
interface SessionOpenOrCreateOptions extends SessionOpenOptions {
|
|
1738
|
+
ttlMs: number;
|
|
1739
|
+
}
|
|
1740
|
+
interface SessionDatabaseFactory {
|
|
1741
|
+
open(input: SessionOpenOptions): Promise<SessionDatabaseHandle | null>;
|
|
1742
|
+
openOrCreate(input: SessionOpenOrCreateOptions): Promise<SessionDatabaseHandle>;
|
|
1743
|
+
destroy(id: string): Promise<boolean>;
|
|
1744
|
+
cleanup(now?: number): Promise<SessionCleanupResult>;
|
|
1745
|
+
close(): Promise<void>;
|
|
1746
|
+
}
|
|
1747
|
+
interface FileSessionDatabaseFactoryOptions {
|
|
1748
|
+
rootDir: string;
|
|
1749
|
+
maxDiskBytes?: number;
|
|
1750
|
+
registryFileName?: string;
|
|
1751
|
+
sessionsDirName?: string;
|
|
1752
|
+
}
|
|
1753
|
+
declare class SessionDatabaseLimitError extends Error {
|
|
1754
|
+
constructor(message?: string);
|
|
1755
|
+
}
|
|
1756
|
+
declare class FileSessionDatabaseFactory implements SessionDatabaseFactory {
|
|
1757
|
+
private readonly options;
|
|
1758
|
+
private readonly registry;
|
|
1759
|
+
private readonly sessionsDir;
|
|
1760
|
+
private readonly maxDiskBytes;
|
|
1761
|
+
constructor(options: FileSessionDatabaseFactoryOptions);
|
|
1762
|
+
open(input: SessionOpenOptions): Promise<SessionDatabaseHandle | null>;
|
|
1763
|
+
openOrCreate(input: SessionOpenOrCreateOptions): Promise<SessionDatabaseHandle>;
|
|
1764
|
+
destroy(id: string): Promise<boolean>;
|
|
1765
|
+
cleanup(now?: number): Promise<SessionCleanupResult>;
|
|
1766
|
+
close(): Promise<void>;
|
|
1767
|
+
private createHandle;
|
|
1768
|
+
private getSessionRow;
|
|
1769
|
+
private toSessionInfo;
|
|
1770
|
+
private deleteSession;
|
|
1771
|
+
private getActiveDiskBytes;
|
|
1772
|
+
private getDbPath;
|
|
1773
|
+
private getManagedPaths;
|
|
1774
|
+
private listSessionRows;
|
|
1775
|
+
private listDbPathRows;
|
|
1776
|
+
}
|
|
1777
|
+
//#endregion
|
|
1778
|
+
//#region src/preview/middleware.d.ts
|
|
1779
|
+
interface FilePreviewMiddlewareConfig {
|
|
1780
|
+
rootDir?: string;
|
|
1781
|
+
secret: string;
|
|
1782
|
+
ttlSeconds?: number;
|
|
1783
|
+
cookieName?: string;
|
|
1784
|
+
snapshotPath?: string;
|
|
1785
|
+
maxSessionDiskBytes?: number;
|
|
1786
|
+
sessionFactory?: SessionDatabaseFactory;
|
|
1787
|
+
fetch?: typeof globalThis.fetch;
|
|
1788
|
+
}
|
|
1789
|
+
declare function createFilePreviewMiddleware(config: FilePreviewMiddlewareConfig): MiddlewareHandler;
|
|
1790
|
+
//#endregion
|
|
1791
|
+
//#region src/loader.d.ts
|
|
1792
|
+
/**
|
|
1793
|
+
* Entry data type - generic object
|
|
1794
|
+
*/
|
|
1795
|
+
type EntryData = Record<string, unknown>;
|
|
1796
|
+
/**
|
|
1797
|
+
* Sort direction
|
|
1798
|
+
*/
|
|
1799
|
+
type SortDirection = "asc" | "desc";
|
|
1800
|
+
/**
|
|
1801
|
+
* Order by specification - field name to direction
|
|
1802
|
+
* @example { created_at: "desc" } - Sort by created_at descending
|
|
1803
|
+
* @example { title: "asc" } - Sort by title ascending
|
|
1804
|
+
*/
|
|
1805
|
+
type OrderBySpec = Record<string, SortDirection>;
|
|
1806
|
+
/**
|
|
1807
|
+
* Filter for loadCollection - type is required
|
|
1808
|
+
*/
|
|
1809
|
+
interface CollectionFilter {
|
|
1810
|
+
type: string;
|
|
1811
|
+
status?: "draft" | "published" | "archived";
|
|
1812
|
+
limit?: number;
|
|
1813
|
+
/**
|
|
1814
|
+
* Opaque cursor for keyset pagination.
|
|
1815
|
+
* Pass the `nextCursor` value from a previous result to fetch the next page.
|
|
1816
|
+
*/
|
|
1817
|
+
cursor?: string;
|
|
1818
|
+
/**
|
|
1819
|
+
* Filter by field values or taxonomy terms
|
|
1820
|
+
*/
|
|
1821
|
+
where?: Record<string, string | string[]>;
|
|
1822
|
+
/**
|
|
1823
|
+
* Order results by field(s)
|
|
1824
|
+
* @default { created_at: "desc" }
|
|
1825
|
+
*/
|
|
1826
|
+
orderBy?: OrderBySpec;
|
|
1827
|
+
/**
|
|
1828
|
+
* Filter by locale (e.g. 'en', 'fr').
|
|
1829
|
+
* When set, only returns content in this locale.
|
|
1830
|
+
*/
|
|
1831
|
+
locale?: string;
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Filter for loadEntry - type and id are required
|
|
1835
|
+
*/
|
|
1836
|
+
interface EntryFilter {
|
|
1837
|
+
type: string;
|
|
1838
|
+
id: string;
|
|
1839
|
+
/**
|
|
1840
|
+
* When set, fetch content data from this revision instead of the content table.
|
|
1841
|
+
* Used by preview mode to serve draft revision data.
|
|
1842
|
+
*/
|
|
1843
|
+
revisionId?: string;
|
|
1844
|
+
/**
|
|
1845
|
+
* Locale to scope slug lookup. Only affects slug resolution;
|
|
1846
|
+
* IDs are globally unique and always resolve regardless of locale.
|
|
1847
|
+
*/
|
|
1848
|
+
locale?: string;
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Get the database instance. Used by query wrapper functions and middleware.
|
|
1852
|
+
*
|
|
1853
|
+
* Checks the ALS request context first — if a per-request DB override is set
|
|
1854
|
+
* (e.g. by preview or playground middleware), it takes precedence over the
|
|
1855
|
+
* module-level cached instance. This allows preview flows to route queries
|
|
1856
|
+
* to an isolated session database without modifying any calling code.
|
|
1857
|
+
*
|
|
1858
|
+
* Initializes the default database on first call using config from virtual module.
|
|
1859
|
+
*/
|
|
1860
|
+
declare function getDb(): Promise<Kysely<Database>>;
|
|
1861
|
+
/**
|
|
1862
|
+
* Create a Dineway Live Collections loader
|
|
1863
|
+
*
|
|
1864
|
+
* This loader handles ALL content types in a single Astro collection.
|
|
1865
|
+
* Use `getDinewayCollection()` and `getDinewayEntry()` to query
|
|
1866
|
+
* specific content types.
|
|
1867
|
+
*
|
|
1868
|
+
* Database is configured in astro.config.mjs via the dineway() integration.
|
|
1869
|
+
*
|
|
1870
|
+
* @example
|
|
1871
|
+
* ```ts
|
|
1872
|
+
* // src/live.config.ts
|
|
1873
|
+
* import { defineLiveCollection } from "astro:content";
|
|
1874
|
+
* import { dinewayLoader } from "dineway";
|
|
1875
|
+
*
|
|
1876
|
+
* export const collections = {
|
|
1877
|
+
* dineway: defineLiveCollection({
|
|
1878
|
+
* loader: dinewayLoader(),
|
|
1879
|
+
* }),
|
|
1880
|
+
* };
|
|
1881
|
+
* ```
|
|
1882
|
+
*/
|
|
1883
|
+
declare function dinewayLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter>;
|
|
1884
|
+
//#endregion
|
|
1885
|
+
//#region src/cli/wxr/parser.d.ts
|
|
1886
|
+
/**
|
|
1887
|
+
* Parsed WordPress export data
|
|
1888
|
+
*/
|
|
1889
|
+
interface WxrData {
|
|
1890
|
+
/** Site metadata */
|
|
1891
|
+
site: WxrSite;
|
|
1892
|
+
/** Posts (including custom post types) */
|
|
1893
|
+
posts: WxrPost[];
|
|
1894
|
+
/** Media attachments */
|
|
1895
|
+
attachments: WxrAttachment[];
|
|
1896
|
+
/** Categories */
|
|
1897
|
+
categories: WxrCategory[];
|
|
1898
|
+
/** Tags */
|
|
1899
|
+
tags: WxrTag[];
|
|
1900
|
+
/** Authors */
|
|
1901
|
+
authors: WxrAuthor[];
|
|
1902
|
+
/** All taxonomy terms (including custom taxonomies and nav_menu) */
|
|
1903
|
+
terms: WxrTerm[];
|
|
1904
|
+
/** Parsed navigation menus */
|
|
1905
|
+
navMenus: WxrNavMenu[];
|
|
1906
|
+
}
|
|
1907
|
+
interface WxrSite {
|
|
1908
|
+
title?: string;
|
|
1909
|
+
link?: string;
|
|
1910
|
+
description?: string;
|
|
1911
|
+
language?: string;
|
|
1912
|
+
baseSiteUrl?: string;
|
|
1913
|
+
baseBlogUrl?: string;
|
|
1914
|
+
}
|
|
1915
|
+
interface WxrPost {
|
|
1916
|
+
id?: number;
|
|
1917
|
+
title?: string;
|
|
1918
|
+
link?: string;
|
|
1919
|
+
pubDate?: string;
|
|
1920
|
+
creator?: string;
|
|
1921
|
+
guid?: string;
|
|
1922
|
+
description?: string;
|
|
1923
|
+
content?: string;
|
|
1924
|
+
excerpt?: string;
|
|
1925
|
+
postDate?: string;
|
|
1926
|
+
postDateGmt?: string;
|
|
1927
|
+
postModified?: string;
|
|
1928
|
+
postModifiedGmt?: string;
|
|
1929
|
+
commentStatus?: string;
|
|
1930
|
+
pingStatus?: string;
|
|
1931
|
+
status?: string;
|
|
1932
|
+
postType?: string;
|
|
1933
|
+
postName?: string;
|
|
1934
|
+
postPassword?: string;
|
|
1935
|
+
isSticky?: boolean;
|
|
1936
|
+
/** Parent post ID for hierarchical content (pages) */
|
|
1937
|
+
postParent?: number;
|
|
1938
|
+
/** Menu order for sorting */
|
|
1939
|
+
menuOrder?: number;
|
|
1940
|
+
categories: string[];
|
|
1941
|
+
tags: string[];
|
|
1942
|
+
/** Custom taxonomy assignments beyond categories/tags */
|
|
1943
|
+
customTaxonomies?: Map<string, string[]>;
|
|
1944
|
+
meta: Map<string, string>;
|
|
1945
|
+
}
|
|
1946
|
+
interface WxrAttachment {
|
|
1947
|
+
id?: number;
|
|
1948
|
+
title?: string;
|
|
1949
|
+
url?: string;
|
|
1950
|
+
postDate?: string;
|
|
1951
|
+
meta: Map<string, string>;
|
|
1952
|
+
}
|
|
1953
|
+
interface WxrCategory {
|
|
1954
|
+
id?: number;
|
|
1955
|
+
nicename?: string;
|
|
1956
|
+
name?: string;
|
|
1957
|
+
parent?: string;
|
|
1958
|
+
description?: string;
|
|
1959
|
+
}
|
|
1960
|
+
interface WxrTag {
|
|
1961
|
+
id?: number;
|
|
1962
|
+
slug?: string;
|
|
1963
|
+
name?: string;
|
|
1964
|
+
description?: string;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Generic taxonomy term (categories, tags, nav_menu, custom taxonomies)
|
|
1968
|
+
*/
|
|
1969
|
+
interface WxrTerm {
|
|
1970
|
+
id: number;
|
|
1971
|
+
taxonomy: string;
|
|
1972
|
+
slug: string;
|
|
1973
|
+
name: string;
|
|
1974
|
+
parent?: string;
|
|
1975
|
+
description?: string;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Navigation menu structure
|
|
1979
|
+
*/
|
|
1980
|
+
interface WxrNavMenu {
|
|
1981
|
+
id: number;
|
|
1982
|
+
name: string;
|
|
1983
|
+
label: string;
|
|
1984
|
+
items: WxrNavMenuItem[];
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Navigation menu item
|
|
1988
|
+
*/
|
|
1989
|
+
interface WxrNavMenuItem {
|
|
1990
|
+
id: number;
|
|
1991
|
+
menuId: number;
|
|
1992
|
+
parentId?: number;
|
|
1993
|
+
sortOrder: number;
|
|
1994
|
+
type: "custom" | "post_type" | "taxonomy";
|
|
1995
|
+
objectType?: string;
|
|
1996
|
+
objectId?: number;
|
|
1997
|
+
url?: string;
|
|
1998
|
+
title: string;
|
|
1999
|
+
target?: string;
|
|
2000
|
+
classes?: string;
|
|
2001
|
+
}
|
|
2002
|
+
interface WxrAuthor {
|
|
2003
|
+
id?: number;
|
|
2004
|
+
login?: string;
|
|
2005
|
+
email?: string;
|
|
2006
|
+
displayName?: string;
|
|
2007
|
+
firstName?: string;
|
|
2008
|
+
lastName?: string;
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Parse a WordPress WXR export file
|
|
2012
|
+
*/
|
|
2013
|
+
declare function parseWxr(stream: Readable): Promise<WxrData>;
|
|
2014
|
+
/**
|
|
2015
|
+
* Parse a WordPress WXR export from a string
|
|
2016
|
+
*
|
|
2017
|
+
* Uses the non-streaming SAX parser API for compatibility with
|
|
2018
|
+
* environments that don't expose Node.js streams.
|
|
2019
|
+
*/
|
|
2020
|
+
declare function parseWxrString(xml: string): Promise<WxrData>;
|
|
2021
|
+
//#endregion
|
|
2022
|
+
//#region src/plugins/define-plugin.d.ts
|
|
2023
|
+
/**
|
|
2024
|
+
* Define a Dineway plugin.
|
|
2025
|
+
*
|
|
2026
|
+
* **Standard format** -- the canonical format for plugins that work in both
|
|
2027
|
+
* trusted and sandboxed modes. No id/version -- those come from the descriptor.
|
|
2028
|
+
*
|
|
2029
|
+
* @example
|
|
2030
|
+
* ```typescript
|
|
2031
|
+
* import { definePlugin } from "dineway";
|
|
2032
|
+
*
|
|
2033
|
+
* export default definePlugin({
|
|
2034
|
+
* hooks: {
|
|
2035
|
+
* "content:afterSave": {
|
|
2036
|
+
* handler: async (event, ctx) => {
|
|
2037
|
+
* await ctx.kv.set("lastSave", Date.now());
|
|
2038
|
+
* },
|
|
2039
|
+
* },
|
|
2040
|
+
* },
|
|
2041
|
+
* routes: {
|
|
2042
|
+
* status: {
|
|
2043
|
+
* handler: async (routeCtx, ctx) => ({ ok: true }),
|
|
2044
|
+
* },
|
|
2045
|
+
* },
|
|
2046
|
+
* });
|
|
2047
|
+
* ```
|
|
2048
|
+
*
|
|
2049
|
+
* **Native format** -- for plugins that need React admin, direct DB access,
|
|
2050
|
+
* or other capabilities not available in the sandbox.
|
|
2051
|
+
*
|
|
2052
|
+
* @example
|
|
2053
|
+
* ```typescript
|
|
2054
|
+
* import { definePlugin } from "dineway";
|
|
2055
|
+
*
|
|
2056
|
+
* export default definePlugin({
|
|
2057
|
+
* id: "my-plugin",
|
|
2058
|
+
* version: "1.0.0",
|
|
2059
|
+
* capabilities: ["read:content"],
|
|
2060
|
+
* hooks: {
|
|
2061
|
+
* "content:beforeSave": async (event, ctx) => {
|
|
2062
|
+
* ctx.log.info("Saving content", { collection: event.collection });
|
|
2063
|
+
* return event.content;
|
|
2064
|
+
* }
|
|
2065
|
+
* },
|
|
2066
|
+
* routes: {
|
|
2067
|
+
* "sync": {
|
|
2068
|
+
* handler: async (ctx) => {
|
|
2069
|
+
* return { status: "ok" };
|
|
2070
|
+
* }
|
|
2071
|
+
* }
|
|
2072
|
+
* }
|
|
2073
|
+
* });
|
|
2074
|
+
* ```
|
|
2075
|
+
*/
|
|
2076
|
+
declare function definePlugin<TStorage extends PluginStorageConfig>(definition: PluginDefinition<TStorage>): ResolvedPlugin<TStorage>;
|
|
2077
|
+
declare function definePlugin(definition: StandardPluginDefinition): StandardPluginDefinition;
|
|
2078
|
+
//#endregion
|
|
2079
|
+
//#region src/auth/types.d.ts
|
|
2080
|
+
/**
|
|
2081
|
+
* Auth Provider Types
|
|
2082
|
+
*
|
|
2083
|
+
* Defines the interfaces for pluggable authentication providers.
|
|
2084
|
+
* External auth adapters implement these interfaces.
|
|
2085
|
+
*/
|
|
2086
|
+
/**
|
|
2087
|
+
* Result of authenticating a request via an external auth provider
|
|
2088
|
+
*/
|
|
2089
|
+
interface AuthResult {
|
|
2090
|
+
/** User's email address */
|
|
2091
|
+
email: string;
|
|
2092
|
+
/** User's display name */
|
|
2093
|
+
name: string;
|
|
2094
|
+
/** Resolved role level (e.g., 50 for Admin, 30 for Editor) */
|
|
2095
|
+
role: number;
|
|
2096
|
+
/** Provider-specific subject ID */
|
|
2097
|
+
subject?: string;
|
|
2098
|
+
/** Additional provider-specific data */
|
|
2099
|
+
metadata?: Record<string, unknown>;
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Auth descriptor - returned by auth adapter functions (e.g., access())
|
|
2103
|
+
*
|
|
2104
|
+
* Similar to DatabaseDescriptor and StorageDescriptor, this allows
|
|
2105
|
+
* auth providers to be configured at build time and loaded at runtime.
|
|
2106
|
+
*/
|
|
2107
|
+
interface AuthDescriptor {
|
|
2108
|
+
/**
|
|
2109
|
+
* Auth provider type identifier
|
|
2110
|
+
* @example "header-auth", "okta", "auth0"
|
|
2111
|
+
*/
|
|
2112
|
+
type: string;
|
|
2113
|
+
/**
|
|
2114
|
+
* Module specifier to import at runtime
|
|
2115
|
+
* The module must export an `authenticate` function.
|
|
2116
|
+
* @example "@acme/dineway-header-auth"
|
|
2117
|
+
*/
|
|
2118
|
+
entrypoint: string;
|
|
2119
|
+
/**
|
|
2120
|
+
* Provider-specific configuration (JSON-serializable)
|
|
2121
|
+
*/
|
|
2122
|
+
config: unknown;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Auth provider module interface
|
|
2126
|
+
*
|
|
2127
|
+
* Modules specified by AuthDescriptor.entrypoint must export
|
|
2128
|
+
* an `authenticate` function matching this signature.
|
|
2129
|
+
*/
|
|
2130
|
+
interface AuthProviderModule {
|
|
2131
|
+
/**
|
|
2132
|
+
* Authenticate a request using the provider
|
|
2133
|
+
*
|
|
2134
|
+
* @param request - The incoming HTTP request
|
|
2135
|
+
* @param config - Provider-specific configuration from AuthDescriptor
|
|
2136
|
+
* @returns Authentication result if valid, throws if invalid
|
|
2137
|
+
*/
|
|
2138
|
+
authenticate(request: Request, config: unknown): Promise<AuthResult>;
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Configuration options common to external auth providers
|
|
2142
|
+
*/
|
|
2143
|
+
interface ExternalAuthConfig {
|
|
2144
|
+
/**
|
|
2145
|
+
* Automatically create Dineway users on first login
|
|
2146
|
+
* @default true
|
|
2147
|
+
*/
|
|
2148
|
+
autoProvision?: boolean;
|
|
2149
|
+
/**
|
|
2150
|
+
* Role level for users not matching any group in roleMapping
|
|
2151
|
+
* @default 30 (Editor)
|
|
2152
|
+
*/
|
|
2153
|
+
defaultRole?: number;
|
|
2154
|
+
/**
|
|
2155
|
+
* Update user's role on each login based on current IdP groups
|
|
2156
|
+
* When false, role is only set on first provisioning
|
|
2157
|
+
* @default false
|
|
2158
|
+
*/
|
|
2159
|
+
syncRoles?: boolean;
|
|
2160
|
+
/**
|
|
2161
|
+
* Map IdP group names to Dineway role levels
|
|
2162
|
+
* First match wins if user is in multiple groups
|
|
2163
|
+
*
|
|
2164
|
+
* @example
|
|
2165
|
+
* ```ts
|
|
2166
|
+
* roleMapping: {
|
|
2167
|
+
* "Admins": 50, // Admin
|
|
2168
|
+
* "Developers": 40, // Developer
|
|
2169
|
+
* "Content Team": 30, // Editor
|
|
2170
|
+
* }
|
|
2171
|
+
* ```
|
|
2172
|
+
*/
|
|
2173
|
+
roleMapping?: Record<string, number>;
|
|
2174
|
+
}
|
|
2175
|
+
//#endregion
|
|
2176
|
+
//#region src/astro/storage/types.d.ts
|
|
2177
|
+
/**
|
|
2178
|
+
* Serializable storage configuration descriptor
|
|
2179
|
+
*/
|
|
2180
|
+
interface StorageDescriptor {
|
|
2181
|
+
/** Module path exporting createStorage function */
|
|
2182
|
+
entrypoint: string;
|
|
2183
|
+
/** Serializable config passed to createStorage at runtime */
|
|
2184
|
+
config: unknown;
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* S3-compatible storage configuration
|
|
2188
|
+
*/
|
|
2189
|
+
interface S3StorageConfig {
|
|
2190
|
+
/** S3 endpoint URL */
|
|
2191
|
+
endpoint: string;
|
|
2192
|
+
/** Bucket name */
|
|
2193
|
+
bucket: string;
|
|
2194
|
+
/** Access key ID */
|
|
2195
|
+
accessKeyId: string;
|
|
2196
|
+
/** Secret access key */
|
|
2197
|
+
secretAccessKey: string;
|
|
2198
|
+
/** Optional region (defaults to "auto") */
|
|
2199
|
+
region?: string;
|
|
2200
|
+
/** Optional public URL prefix for CDN */
|
|
2201
|
+
publicUrl?: string;
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Local filesystem storage configuration
|
|
2205
|
+
*/
|
|
2206
|
+
interface LocalStorageConfig {
|
|
2207
|
+
/** Directory path for storing files */
|
|
2208
|
+
directory: string;
|
|
2209
|
+
/** Base URL for serving files */
|
|
2210
|
+
baseUrl: string;
|
|
2211
|
+
}
|
|
2212
|
+
//#endregion
|
|
2213
|
+
//#region src/astro/integration/runtime.d.ts
|
|
2214
|
+
/**
|
|
2215
|
+
* Admin page definition (copied from plugins/types to avoid circular deps)
|
|
2216
|
+
*/
|
|
2217
|
+
interface PluginAdminPage {
|
|
2218
|
+
path: string;
|
|
2219
|
+
label: string;
|
|
2220
|
+
icon?: string;
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Dashboard widget definition (copied from plugins/types to avoid circular deps)
|
|
2224
|
+
*/
|
|
2225
|
+
interface PluginDashboardWidget {
|
|
2226
|
+
id: string;
|
|
2227
|
+
size?: "full" | "half" | "third";
|
|
2228
|
+
title?: string;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Plugin descriptor - returned by plugin factory functions
|
|
2232
|
+
*
|
|
2233
|
+
* Contains all static metadata needed for manifest and admin UI,
|
|
2234
|
+
* plus the entrypoint for runtime instantiation.
|
|
2235
|
+
*
|
|
2236
|
+
* @example
|
|
2237
|
+
* ```ts
|
|
2238
|
+
* export function myPlugin(options?: MyPluginOptions): PluginDescriptor {
|
|
2239
|
+
* return {
|
|
2240
|
+
* id: "my-plugin",
|
|
2241
|
+
* version: "1.0.0",
|
|
2242
|
+
* entrypoint: "@my-org/dineway-plugin-foo",
|
|
2243
|
+
* options: options ?? {},
|
|
2244
|
+
* adminEntry: "@my-org/dineway-plugin-foo/admin",
|
|
2245
|
+
* adminPages: [{ path: "/settings", label: "Settings" }],
|
|
2246
|
+
* };
|
|
2247
|
+
* }
|
|
2248
|
+
* ```
|
|
2249
|
+
*/
|
|
2250
|
+
/**
|
|
2251
|
+
* Storage collection declaration for sandboxed plugins
|
|
2252
|
+
*/
|
|
2253
|
+
interface StorageCollectionDeclaration {
|
|
2254
|
+
indexes?: string[];
|
|
2255
|
+
uniqueIndexes?: string[];
|
|
2256
|
+
}
|
|
2257
|
+
interface PluginDescriptor<TOptions = Record<string, unknown>> {
|
|
2258
|
+
/** Unique plugin identifier */
|
|
2259
|
+
id: string;
|
|
2260
|
+
/** Plugin version (semver) */
|
|
2261
|
+
version: string;
|
|
2262
|
+
/** Module specifier to import (e.g., "@dineway-ai/plugin-api-test") */
|
|
2263
|
+
entrypoint: string;
|
|
2264
|
+
/**
|
|
2265
|
+
* Options to pass to createPlugin(). Native format only.
|
|
2266
|
+
* Standard-format plugins configure themselves via KV settings
|
|
2267
|
+
* and Block Kit admin pages -- not constructor options.
|
|
2268
|
+
*/
|
|
2269
|
+
options?: TOptions;
|
|
2270
|
+
/**
|
|
2271
|
+
* Plugin format. Determines how the entrypoint is loaded:
|
|
2272
|
+
* - `"standard"` -- exports `definePlugin({ hooks, routes })` as default.
|
|
2273
|
+
* Wrapped with `adaptSandboxEntry` for in-process execution. Can run in both
|
|
2274
|
+
* `plugins: []` (in-process) and `sandboxed: []` (isolate).
|
|
2275
|
+
* - `"native"` -- exports `createPlugin(options)` returning a `ResolvedPlugin`.
|
|
2276
|
+
* Can only run in `plugins: []`. Cannot be sandboxed or published to marketplace.
|
|
2277
|
+
*
|
|
2278
|
+
* Defaults to `"native"` when unset.
|
|
2279
|
+
*
|
|
2280
|
+
*/
|
|
2281
|
+
format?: "standard" | "native";
|
|
2282
|
+
/** Admin UI module specifier (e.g., "@dineway-ai/plugin-audit-log/admin") */
|
|
2283
|
+
adminEntry?: string;
|
|
2284
|
+
/** Module specifier for site-side Astro rendering components (must export `blockComponents`) */
|
|
2285
|
+
componentsEntry?: string;
|
|
2286
|
+
/** Admin pages for navigation */
|
|
2287
|
+
adminPages?: PluginAdminPage[];
|
|
2288
|
+
/** Dashboard widgets */
|
|
2289
|
+
adminWidgets?: PluginDashboardWidget[];
|
|
2290
|
+
/**
|
|
2291
|
+
* Capabilities the plugin requests.
|
|
2292
|
+
* For standard-format plugins, capabilities are enforced in both trusted and
|
|
2293
|
+
* sandboxed modes via the PluginContextFactory.
|
|
2294
|
+
*/
|
|
2295
|
+
capabilities?: string[];
|
|
2296
|
+
/**
|
|
2297
|
+
* Allowed hosts for network:fetch capability
|
|
2298
|
+
* Supports wildcards like "*.example.com"
|
|
2299
|
+
*/
|
|
2300
|
+
allowedHosts?: string[];
|
|
2301
|
+
/**
|
|
2302
|
+
* Storage collections the plugin declares
|
|
2303
|
+
* Sandboxed plugins can only access declared collections.
|
|
2304
|
+
*/
|
|
2305
|
+
storage?: Record<string, StorageCollectionDeclaration>;
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Sandboxed plugin descriptor - same format as PluginDescriptor
|
|
2309
|
+
*
|
|
2310
|
+
* These run in isolated environments provided by the configured sandbox runner.
|
|
2311
|
+
* The `entrypoint` is resolved to a file and bundled at build time.
|
|
2312
|
+
*/
|
|
2313
|
+
type SandboxedPluginDescriptor<TOptions = Record<string, unknown>> = PluginDescriptor<TOptions>;
|
|
2314
|
+
interface DinewayConfig {
|
|
2315
|
+
/**
|
|
2316
|
+
* Database configuration
|
|
2317
|
+
*
|
|
2318
|
+
* Use one of the adapter functions:
|
|
2319
|
+
* - `libsql({ url: process.env.DINEWAY_DATABASE_URL || "file:./data.db" })` - Default Node/libSQL path
|
|
2320
|
+
* - `sqlite({ url: "file:./data.db" })` - Local SQLite only
|
|
2321
|
+
* - `postgres({ connectionString: process.env.DATABASE_URL! })` - PostgreSQL deployments
|
|
2322
|
+
*
|
|
2323
|
+
* @example
|
|
2324
|
+
* ```ts
|
|
2325
|
+
* import { libsql } from "dineway/db";
|
|
2326
|
+
*
|
|
2327
|
+
* dineway({
|
|
2328
|
+
* database: libsql({ url: process.env.DINEWAY_DATABASE_URL || "file:./data.db" }),
|
|
2329
|
+
* })
|
|
2330
|
+
* ```
|
|
2331
|
+
*/
|
|
2332
|
+
database?: DatabaseDescriptor;
|
|
2333
|
+
/**
|
|
2334
|
+
* Storage configuration (for media)
|
|
2335
|
+
*/
|
|
2336
|
+
storage?: StorageDescriptor;
|
|
2337
|
+
/**
|
|
2338
|
+
* Trusted plugins to load (run in main isolate)
|
|
2339
|
+
*
|
|
2340
|
+
* @example
|
|
2341
|
+
* ```ts
|
|
2342
|
+
* import { auditLogPlugin } from "@dineway-ai/plugin-audit-log";
|
|
2343
|
+
* import { webhookNotifierPlugin } from "@dineway-ai/plugin-webhook-notifier";
|
|
2344
|
+
*
|
|
2345
|
+
* dineway({
|
|
2346
|
+
* plugins: [
|
|
2347
|
+
* auditLogPlugin(),
|
|
2348
|
+
* webhookNotifierPlugin({ url: "https://example.com/webhook" }),
|
|
2349
|
+
* ],
|
|
2350
|
+
* })
|
|
2351
|
+
* ```
|
|
2352
|
+
*/
|
|
2353
|
+
plugins?: PluginDescriptor[];
|
|
2354
|
+
/**
|
|
2355
|
+
* Sandboxed plugins to load (run behind the configured `SandboxRunner`)
|
|
2356
|
+
*
|
|
2357
|
+
* Uses the same format as `plugins`; the configured sandbox runner decides
|
|
2358
|
+
* how isolation is enforced for the current runtime.
|
|
2359
|
+
*
|
|
2360
|
+
* @example
|
|
2361
|
+
* ```ts
|
|
2362
|
+
* import { untrustedPlugin } from "some-third-party-plugin";
|
|
2363
|
+
*
|
|
2364
|
+
* dineway({
|
|
2365
|
+
* plugins: [trustedPlugin()], // runs in host
|
|
2366
|
+
* sandboxed: [untrustedPlugin()], // runs in the default Node sandbox
|
|
2367
|
+
* })
|
|
2368
|
+
* ```
|
|
2369
|
+
*/
|
|
2370
|
+
sandboxed?: SandboxedPluginDescriptor[];
|
|
2371
|
+
/**
|
|
2372
|
+
* Optional module that exports a custom sandbox runner factory.
|
|
2373
|
+
* When omitted, Dineway uses its built-in Node sandbox runner.
|
|
2374
|
+
*
|
|
2375
|
+
* @example
|
|
2376
|
+
* ```ts
|
|
2377
|
+
* import { createNodeSandboxRunner } from "dineway";
|
|
2378
|
+
*
|
|
2379
|
+
* export const createSandboxRunner = createNodeSandboxRunner;
|
|
2380
|
+
* ```
|
|
2381
|
+
*
|
|
2382
|
+
* @example
|
|
2383
|
+
* ```ts
|
|
2384
|
+
* dineway({
|
|
2385
|
+
* sandboxRunner: "./src/sandbox-runner.ts",
|
|
2386
|
+
* })
|
|
2387
|
+
* ```
|
|
2388
|
+
*/
|
|
2389
|
+
sandboxRunner?: string;
|
|
2390
|
+
/**
|
|
2391
|
+
* Authentication configuration
|
|
2392
|
+
*
|
|
2393
|
+
* Use an auth descriptor or an adapter function from a provider package.
|
|
2394
|
+
*
|
|
2395
|
+
* When an external auth provider is configured, passkey auth is disabled.
|
|
2396
|
+
*
|
|
2397
|
+
* @example
|
|
2398
|
+
* ```ts
|
|
2399
|
+
* dineway({
|
|
2400
|
+
* auth: {
|
|
2401
|
+
* type: "header-auth",
|
|
2402
|
+
* entrypoint: "./src/header-auth.ts",
|
|
2403
|
+
* config: {
|
|
2404
|
+
* userHeader: "x-forwarded-user-email",
|
|
2405
|
+
* roleHeader: "x-forwarded-user-role",
|
|
2406
|
+
* },
|
|
2407
|
+
* },
|
|
2408
|
+
* })
|
|
2409
|
+
* ```
|
|
2410
|
+
*/
|
|
2411
|
+
auth?: AuthDescriptor;
|
|
2412
|
+
/**
|
|
2413
|
+
* Enable the MCP (Model Context Protocol) server endpoint.
|
|
2414
|
+
*
|
|
2415
|
+
* When enabled, exposes an MCP Streamable HTTP server at
|
|
2416
|
+
* `/_dineway/api/mcp` that allows AI agents and tools to interact
|
|
2417
|
+
* with the CMS using the standardized MCP protocol.
|
|
2418
|
+
*
|
|
2419
|
+
* Authentication is handled by the existing Dineway auth middleware —
|
|
2420
|
+
* agents must authenticate with an API token or session cookie.
|
|
2421
|
+
*
|
|
2422
|
+
* @default false
|
|
2423
|
+
*
|
|
2424
|
+
* @example
|
|
2425
|
+
* ```ts
|
|
2426
|
+
* dineway({
|
|
2427
|
+
* mcp: true,
|
|
2428
|
+
* })
|
|
2429
|
+
* ```
|
|
2430
|
+
*/
|
|
2431
|
+
mcp?: boolean;
|
|
2432
|
+
/**
|
|
2433
|
+
* Plugin marketplace URL
|
|
2434
|
+
*
|
|
2435
|
+
* When set, enables the marketplace features: browse, install, update,
|
|
2436
|
+
* and uninstall plugins from a remote marketplace.
|
|
2437
|
+
*
|
|
2438
|
+
* Must be an HTTPS URL in production, or localhost/127.0.0.1 in dev.
|
|
2439
|
+
* Marketplace plugins run in the built-in Node sandbox runner unless
|
|
2440
|
+
* `sandboxRunner` is set to override it.
|
|
2441
|
+
*
|
|
2442
|
+
* @example
|
|
2443
|
+
* ```ts
|
|
2444
|
+
* dineway({
|
|
2445
|
+
* marketplace: "https://marketplace.example.com",
|
|
2446
|
+
* })
|
|
2447
|
+
* ```
|
|
2448
|
+
*/
|
|
2449
|
+
marketplace?: string;
|
|
2450
|
+
/**
|
|
2451
|
+
* Public browser-facing origin for the site.
|
|
2452
|
+
*
|
|
2453
|
+
* Use when `Astro.url` / `request.url` do not match what users open — common with a
|
|
2454
|
+
* **TLS-terminating reverse proxy**: the app often sees `http://` on the internal hop
|
|
2455
|
+
* while the browser uses `https://`, which breaks WebAuthn, CSRF, OAuth, and redirect URLs.
|
|
2456
|
+
*
|
|
2457
|
+
* Set to the full origin users type in the address bar (no path), e.g.
|
|
2458
|
+
* `https://mysite.example.com`. When not set, falls back to environment variables
|
|
2459
|
+
* `DINEWAY_SITE_URL` > `SITE_URL`, then to the request URL's origin.
|
|
2460
|
+
*
|
|
2461
|
+
* Replaces `passkeyPublicOrigin` (which only fixed passkeys).
|
|
2462
|
+
*/
|
|
2463
|
+
siteUrl?: string;
|
|
2464
|
+
/**
|
|
2465
|
+
* Enable playground mode for ephemeral "try Dineway" sites.
|
|
2466
|
+
*
|
|
2467
|
+
* When set, the integration injects a playground middleware (order: "pre")
|
|
2468
|
+
* that runs BEFORE the normal Dineway middleware chain. It creates an
|
|
2469
|
+
* isolated session database per visitor, runs migrations, applies the
|
|
2470
|
+
* seed, creates an anonymous admin user, and stashes the request-scoped
|
|
2471
|
+
* database on locals so the runtime and request-context middleware can
|
|
2472
|
+
* pick it up.
|
|
2473
|
+
*
|
|
2474
|
+
* Setup and auth middleware are skipped because the playground middleware
|
|
2475
|
+
* handles bootstrap and user injection.
|
|
2476
|
+
*
|
|
2477
|
+
* For the first portable release, playground sidecars are explicitly
|
|
2478
|
+
* single-instance only.
|
|
2479
|
+
*
|
|
2480
|
+
* @example
|
|
2481
|
+
* ```ts
|
|
2482
|
+
* import { fileURLToPath } from "node:url";
|
|
2483
|
+
* import { libsql } from "dineway/db";
|
|
2484
|
+
*
|
|
2485
|
+
* dineway({
|
|
2486
|
+
* database: libsql({ url: process.env.DINEWAY_DATABASE_URL || "file:./playground.db" }),
|
|
2487
|
+
* playground: {
|
|
2488
|
+
* middlewareEntrypoint: fileURLToPath(
|
|
2489
|
+
* new URL("./src/playground-middleware.ts", import.meta.url)
|
|
2490
|
+
* ),
|
|
2491
|
+
* },
|
|
2492
|
+
* })
|
|
2493
|
+
* ```
|
|
2494
|
+
*/
|
|
2495
|
+
playground?: {
|
|
2496
|
+
/** Module path for the playground middleware. */middlewareEntrypoint: string;
|
|
2497
|
+
};
|
|
2498
|
+
/**
|
|
2499
|
+
* Media providers for browsing and uploading media
|
|
2500
|
+
*
|
|
2501
|
+
* The local media provider (using storage adapter) is available by default.
|
|
2502
|
+
* Additional providers can be added for external services like Unsplash,
|
|
2503
|
+
* Cloudinary, Mux, and other provider packages.
|
|
2504
|
+
*
|
|
2505
|
+
* @example
|
|
2506
|
+
* ```ts
|
|
2507
|
+
* import { unsplash } from "@dineway-ai/provider-unsplash";
|
|
2508
|
+
*
|
|
2509
|
+
* dineway({
|
|
2510
|
+
* mediaProviders: [
|
|
2511
|
+
* unsplash({ accessKey: "..." }),
|
|
2512
|
+
* ],
|
|
2513
|
+
* })
|
|
2514
|
+
* ```
|
|
2515
|
+
*/
|
|
2516
|
+
mediaProviders?: MediaProviderDescriptor[];
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Get stored config from global
|
|
2520
|
+
* This is set by the virtual module at build time
|
|
2521
|
+
*/
|
|
2522
|
+
declare function getStoredConfig(): DinewayConfig | null;
|
|
2523
|
+
declare global {
|
|
2524
|
+
var __dinewayConfig: DinewayConfig | undefined;
|
|
2525
|
+
}
|
|
2526
|
+
//#endregion
|
|
2527
|
+
//#region src/plugins/manifest-schema.d.ts
|
|
2528
|
+
/**
|
|
2529
|
+
* Zod schema matching the PluginManifest interface from types.ts.
|
|
2530
|
+
*
|
|
2531
|
+
* Every JSON.parse of a manifest.json should validate through this.
|
|
2532
|
+
*/
|
|
2533
|
+
declare const pluginManifestSchema: z$1.ZodObject<{
|
|
2534
|
+
id: z$1.ZodString;
|
|
2535
|
+
version: z$1.ZodString;
|
|
2536
|
+
capabilities: z$1.ZodArray<z$1.ZodEnum<{
|
|
2537
|
+
"network:fetch": "network:fetch";
|
|
2538
|
+
"network:fetch:any": "network:fetch:any";
|
|
2539
|
+
"read:content": "read:content";
|
|
2540
|
+
"write:content": "write:content";
|
|
2541
|
+
"read:media": "read:media";
|
|
2542
|
+
"write:media": "write:media";
|
|
2543
|
+
"read:users": "read:users";
|
|
2544
|
+
"email:send": "email:send";
|
|
2545
|
+
"email:provide": "email:provide";
|
|
2546
|
+
"email:intercept": "email:intercept";
|
|
2547
|
+
"page:inject": "page:inject";
|
|
2548
|
+
}>>;
|
|
2549
|
+
allowedHosts: z$1.ZodArray<z$1.ZodString>;
|
|
2550
|
+
storage: z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
|
|
2551
|
+
indexes: z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodArray<z$1.ZodString>]>>;
|
|
2552
|
+
uniqueIndexes: z$1.ZodOptional<z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodArray<z$1.ZodString>]>>>;
|
|
2553
|
+
}, z$1.core.$strip>>;
|
|
2554
|
+
hooks: z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodEnum<{
|
|
2555
|
+
"plugin:install": "plugin:install";
|
|
2556
|
+
"plugin:activate": "plugin:activate";
|
|
2557
|
+
"plugin:deactivate": "plugin:deactivate";
|
|
2558
|
+
"plugin:uninstall": "plugin:uninstall";
|
|
2559
|
+
"content:beforeSave": "content:beforeSave";
|
|
2560
|
+
"content:afterSave": "content:afterSave";
|
|
2561
|
+
"content:beforeDelete": "content:beforeDelete";
|
|
2562
|
+
"content:afterDelete": "content:afterDelete";
|
|
2563
|
+
"content:afterPublish": "content:afterPublish";
|
|
2564
|
+
"content:afterUnpublish": "content:afterUnpublish";
|
|
2565
|
+
"media:beforeUpload": "media:beforeUpload";
|
|
2566
|
+
"media:afterUpload": "media:afterUpload";
|
|
2567
|
+
cron: "cron";
|
|
2568
|
+
"email:beforeSend": "email:beforeSend";
|
|
2569
|
+
"email:deliver": "email:deliver";
|
|
2570
|
+
"email:afterSend": "email:afterSend";
|
|
2571
|
+
"comment:beforeCreate": "comment:beforeCreate";
|
|
2572
|
+
"comment:moderate": "comment:moderate";
|
|
2573
|
+
"comment:afterCreate": "comment:afterCreate";
|
|
2574
|
+
"comment:afterModerate": "comment:afterModerate";
|
|
2575
|
+
"page:metadata": "page:metadata";
|
|
2576
|
+
"page:fragments": "page:fragments";
|
|
2577
|
+
}>, z$1.ZodObject<{
|
|
2578
|
+
name: z$1.ZodEnum<{
|
|
2579
|
+
"plugin:install": "plugin:install";
|
|
2580
|
+
"plugin:activate": "plugin:activate";
|
|
2581
|
+
"plugin:deactivate": "plugin:deactivate";
|
|
2582
|
+
"plugin:uninstall": "plugin:uninstall";
|
|
2583
|
+
"content:beforeSave": "content:beforeSave";
|
|
2584
|
+
"content:afterSave": "content:afterSave";
|
|
2585
|
+
"content:beforeDelete": "content:beforeDelete";
|
|
2586
|
+
"content:afterDelete": "content:afterDelete";
|
|
2587
|
+
"content:afterPublish": "content:afterPublish";
|
|
2588
|
+
"content:afterUnpublish": "content:afterUnpublish";
|
|
2589
|
+
"media:beforeUpload": "media:beforeUpload";
|
|
2590
|
+
"media:afterUpload": "media:afterUpload";
|
|
2591
|
+
cron: "cron";
|
|
2592
|
+
"email:beforeSend": "email:beforeSend";
|
|
2593
|
+
"email:deliver": "email:deliver";
|
|
2594
|
+
"email:afterSend": "email:afterSend";
|
|
2595
|
+
"comment:beforeCreate": "comment:beforeCreate";
|
|
2596
|
+
"comment:moderate": "comment:moderate";
|
|
2597
|
+
"comment:afterCreate": "comment:afterCreate";
|
|
2598
|
+
"comment:afterModerate": "comment:afterModerate";
|
|
2599
|
+
"page:metadata": "page:metadata";
|
|
2600
|
+
"page:fragments": "page:fragments";
|
|
2601
|
+
}>;
|
|
2602
|
+
exclusive: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
2603
|
+
priority: z$1.ZodOptional<z$1.ZodNumber>;
|
|
2604
|
+
timeout: z$1.ZodOptional<z$1.ZodNumber>;
|
|
2605
|
+
}, z$1.core.$strip>]>>;
|
|
2606
|
+
routes: z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodObject<{
|
|
2607
|
+
name: z$1.ZodString;
|
|
2608
|
+
public: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
2609
|
+
}, z$1.core.$strip>]>>;
|
|
2610
|
+
admin: z$1.ZodObject<{
|
|
2611
|
+
entry: z$1.ZodOptional<z$1.ZodString>;
|
|
2612
|
+
settingsSchema: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{
|
|
2613
|
+
type: z$1.ZodLiteral<"string">;
|
|
2614
|
+
default: z$1.ZodOptional<z$1.ZodString>;
|
|
2615
|
+
multiline: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
2616
|
+
label: z$1.ZodString;
|
|
2617
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2618
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2619
|
+
type: z$1.ZodLiteral<"number">;
|
|
2620
|
+
default: z$1.ZodOptional<z$1.ZodNumber>;
|
|
2621
|
+
min: z$1.ZodOptional<z$1.ZodNumber>;
|
|
2622
|
+
max: z$1.ZodOptional<z$1.ZodNumber>;
|
|
2623
|
+
label: z$1.ZodString;
|
|
2624
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2625
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2626
|
+
type: z$1.ZodLiteral<"boolean">;
|
|
2627
|
+
default: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
2628
|
+
label: z$1.ZodString;
|
|
2629
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2630
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2631
|
+
type: z$1.ZodLiteral<"select">;
|
|
2632
|
+
options: z$1.ZodArray<z$1.ZodObject<{
|
|
2633
|
+
value: z$1.ZodString;
|
|
2634
|
+
label: z$1.ZodString;
|
|
2635
|
+
}, z$1.core.$strip>>;
|
|
2636
|
+
default: z$1.ZodOptional<z$1.ZodString>;
|
|
2637
|
+
label: z$1.ZodString;
|
|
2638
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2639
|
+
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2640
|
+
type: z$1.ZodLiteral<"secret">;
|
|
2641
|
+
label: z$1.ZodString;
|
|
2642
|
+
description: z$1.ZodOptional<z$1.ZodString>;
|
|
2643
|
+
}, z$1.core.$strip>], "type">>>;
|
|
2644
|
+
pages: z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
|
|
2645
|
+
path: z$1.ZodString;
|
|
2646
|
+
label: z$1.ZodString;
|
|
2647
|
+
icon: z$1.ZodOptional<z$1.ZodString>;
|
|
2648
|
+
}, z$1.core.$strip>>>;
|
|
2649
|
+
widgets: z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
|
|
2650
|
+
id: z$1.ZodString;
|
|
2651
|
+
size: z$1.ZodOptional<z$1.ZodEnum<{
|
|
2652
|
+
full: "full";
|
|
2653
|
+
half: "half";
|
|
2654
|
+
third: "third";
|
|
2655
|
+
}>>;
|
|
2656
|
+
title: z$1.ZodOptional<z$1.ZodString>;
|
|
2657
|
+
}, z$1.core.$strip>>>;
|
|
2658
|
+
fieldWidgets: z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
|
|
2659
|
+
name: z$1.ZodString;
|
|
2660
|
+
label: z$1.ZodString;
|
|
2661
|
+
fieldTypes: z$1.ZodArray<z$1.ZodEnum<{
|
|
2662
|
+
string: "string";
|
|
2663
|
+
number: "number";
|
|
2664
|
+
boolean: "boolean";
|
|
2665
|
+
text: "text";
|
|
2666
|
+
integer: "integer";
|
|
2667
|
+
datetime: "datetime";
|
|
2668
|
+
select: "select";
|
|
2669
|
+
multiSelect: "multiSelect";
|
|
2670
|
+
portableText: "portableText";
|
|
2671
|
+
image: "image";
|
|
2672
|
+
file: "file";
|
|
2673
|
+
reference: "reference";
|
|
2674
|
+
json: "json";
|
|
2675
|
+
slug: "slug";
|
|
2676
|
+
repeater: "repeater";
|
|
2677
|
+
}>>;
|
|
2678
|
+
elements: z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
|
|
2679
|
+
type: z$1.ZodString;
|
|
2680
|
+
action_id: z$1.ZodString;
|
|
2681
|
+
label: z$1.ZodOptional<z$1.ZodString>;
|
|
2682
|
+
}, z$1.core.$loose>>>;
|
|
2683
|
+
}, z$1.core.$strip>>>;
|
|
2684
|
+
}, z$1.core.$strip>;
|
|
2685
|
+
}, z$1.core.$strip>;
|
|
2686
|
+
type ValidatedPluginManifest = z$1.infer<typeof pluginManifestSchema>;
|
|
2687
|
+
//#endregion
|
|
2688
|
+
//#region src/plugins/hooks.d.ts
|
|
2689
|
+
type HookNameV2 = "plugin:install" | "plugin:activate" | "plugin:deactivate" | "plugin:uninstall" | "content:beforeSave" | "content:afterSave" | "content:beforeDelete" | "content:afterDelete" | "content:afterPublish" | "content:afterUnpublish" | "media:beforeUpload" | "media:afterUpload" | "cron" | "email:beforeSend" | "email:deliver" | "email:afterSend" | "comment:beforeCreate" | "comment:moderate" | "comment:afterCreate" | "comment:afterModerate" | "page:metadata" | "page:fragments";
|
|
2690
|
+
/**
|
|
2691
|
+
* Hook execution result
|
|
2692
|
+
*/
|
|
2693
|
+
interface HookResult<T> {
|
|
2694
|
+
success: boolean;
|
|
2695
|
+
value?: T;
|
|
2696
|
+
error?: Error;
|
|
2697
|
+
pluginId: string;
|
|
2698
|
+
duration: number;
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Hook pipeline for executing hooks in order
|
|
2702
|
+
*/
|
|
2703
|
+
declare class HookPipeline {
|
|
2704
|
+
private hooks;
|
|
2705
|
+
private pluginMap;
|
|
2706
|
+
private contextFactory;
|
|
2707
|
+
/** Stored so setContextFactory can merge incrementally. */
|
|
2708
|
+
private contextFactoryOptions;
|
|
2709
|
+
/** Hook names where at least one handler declared exclusive: true */
|
|
2710
|
+
private exclusiveHookNames;
|
|
2711
|
+
/**
|
|
2712
|
+
* Selected provider plugin ID for each exclusive hook.
|
|
2713
|
+
* Set by the PluginManager after resolution.
|
|
2714
|
+
*/
|
|
2715
|
+
private exclusiveSelections;
|
|
2716
|
+
constructor(plugins: ResolvedPlugin[], factoryOptions?: PluginContextFactoryOptions);
|
|
2717
|
+
/**
|
|
2718
|
+
* Set or update the context factory options.
|
|
2719
|
+
*
|
|
2720
|
+
* When called on a pipeline that already has a factory, the new options
|
|
2721
|
+
* are merged on top of the existing ones so that callers don't need to
|
|
2722
|
+
* repeat every field (e.g. adding `cronReschedule` without losing
|
|
2723
|
+
* `storage` / `getUploadUrl`).
|
|
2724
|
+
*/
|
|
2725
|
+
setContextFactory(options: Partial<PluginContextFactoryOptions>): void;
|
|
2726
|
+
/**
|
|
2727
|
+
* Get context for a plugin
|
|
2728
|
+
*/
|
|
2729
|
+
private getContext;
|
|
2730
|
+
/**
|
|
2731
|
+
* Get typed hooks for a specific hook name.
|
|
2732
|
+
* The internal map stores ResolvedHook<unknown>, but we know each name
|
|
2733
|
+
* maps to a specific handler type via HookHandlerMap.
|
|
2734
|
+
*
|
|
2735
|
+
* Exclusive hooks that have a selected provider are filtered out — they
|
|
2736
|
+
* should only run via invokeExclusiveHook(), not in the regular pipeline.
|
|
2737
|
+
*/
|
|
2738
|
+
private getTypedHooks;
|
|
2739
|
+
/**
|
|
2740
|
+
* Register all hooks from plugins.
|
|
2741
|
+
*
|
|
2742
|
+
* Registers each hook name individually to preserve type safety. The
|
|
2743
|
+
* internal map stores ResolvedHook<unknown> since it's keyed by string,
|
|
2744
|
+
* but getTypedHooks() restores the correct handler type on retrieval.
|
|
2745
|
+
*/
|
|
2746
|
+
private registerPlugins;
|
|
2747
|
+
/**
|
|
2748
|
+
* Maps hook names to the capability required to register them.
|
|
2749
|
+
*
|
|
2750
|
+
* Hooks not listed here have no capability requirement (e.g. lifecycle
|
|
2751
|
+
* hooks, cron). Any plugin declaring a listed hook without the required
|
|
2752
|
+
* capability will have that hook silently skipped at registration time.
|
|
2753
|
+
*/
|
|
2754
|
+
private static readonly HOOK_REQUIRED_CAPABILITY;
|
|
2755
|
+
/**
|
|
2756
|
+
* Register a single plugin's hook by name
|
|
2757
|
+
*/
|
|
2758
|
+
private registerPluginHook;
|
|
2759
|
+
/**
|
|
2760
|
+
* Register a single hook
|
|
2761
|
+
*/
|
|
2762
|
+
private registerHook;
|
|
2763
|
+
/**
|
|
2764
|
+
* Sort hooks by priority and dependencies
|
|
2765
|
+
*/
|
|
2766
|
+
private sortHooks;
|
|
2767
|
+
/**
|
|
2768
|
+
* Execute a hook with timeout
|
|
2769
|
+
*/
|
|
2770
|
+
private executeWithTimeout;
|
|
2771
|
+
/**
|
|
2772
|
+
* Run plugin:install hooks
|
|
2773
|
+
*/
|
|
2774
|
+
runPluginInstall(pluginId: string): Promise<HookResult<void>[]>;
|
|
2775
|
+
/**
|
|
2776
|
+
* Run plugin:activate hooks
|
|
2777
|
+
*/
|
|
2778
|
+
runPluginActivate(pluginId: string): Promise<HookResult<void>[]>;
|
|
2779
|
+
/**
|
|
2780
|
+
* Run plugin:deactivate hooks
|
|
2781
|
+
*/
|
|
2782
|
+
runPluginDeactivate(pluginId: string): Promise<HookResult<void>[]>;
|
|
2783
|
+
/**
|
|
2784
|
+
* Run plugin:uninstall hooks
|
|
2785
|
+
*/
|
|
2786
|
+
runPluginUninstall(pluginId: string, deleteData: boolean): Promise<HookResult<void>[]>;
|
|
2787
|
+
private runLifecycleHook;
|
|
2788
|
+
/**
|
|
2789
|
+
* Run content:beforeSave hooks
|
|
2790
|
+
* Returns modified content from the pipeline
|
|
2791
|
+
*/
|
|
2792
|
+
runContentBeforeSave(content: Record<string, unknown>, collection: string, isNew: boolean): Promise<{
|
|
2793
|
+
content: Record<string, unknown>;
|
|
2794
|
+
results: HookResult<Record<string, unknown>>[];
|
|
2795
|
+
}>;
|
|
2796
|
+
/**
|
|
2797
|
+
* Run content:afterSave hooks
|
|
2798
|
+
*/
|
|
2799
|
+
runContentAfterSave(content: Record<string, unknown>, collection: string, isNew: boolean): Promise<HookResult<void>[]>;
|
|
2800
|
+
/**
|
|
2801
|
+
* Run content:beforeDelete hooks
|
|
2802
|
+
* Returns whether deletion is allowed
|
|
2803
|
+
*/
|
|
2804
|
+
runContentBeforeDelete(id: string, collection: string): Promise<{
|
|
2805
|
+
allowed: boolean;
|
|
2806
|
+
results: HookResult<boolean>[];
|
|
2807
|
+
}>;
|
|
2808
|
+
/**
|
|
2809
|
+
* Run content:afterDelete hooks
|
|
2810
|
+
*/
|
|
2811
|
+
runContentAfterDelete(id: string, collection: string): Promise<HookResult<void>[]>;
|
|
2812
|
+
/**
|
|
2813
|
+
* Run content:afterPublish hooks (fire-and-forget).
|
|
2814
|
+
*/
|
|
2815
|
+
runContentAfterPublish(content: Record<string, unknown>, collection: string): Promise<HookResult<void>[]>;
|
|
2816
|
+
/**
|
|
2817
|
+
* Run content:afterUnpublish hooks (fire-and-forget).
|
|
2818
|
+
*/
|
|
2819
|
+
runContentAfterUnpublish(content: Record<string, unknown>, collection: string): Promise<HookResult<void>[]>;
|
|
2820
|
+
/**
|
|
2821
|
+
* Run media:beforeUpload hooks
|
|
2822
|
+
*/
|
|
2823
|
+
runMediaBeforeUpload(file: {
|
|
2824
|
+
name: string;
|
|
2825
|
+
type: string;
|
|
2826
|
+
size: number;
|
|
2827
|
+
}): Promise<{
|
|
2828
|
+
file: {
|
|
2829
|
+
name: string;
|
|
2830
|
+
type: string;
|
|
2831
|
+
size: number;
|
|
2832
|
+
};
|
|
2833
|
+
results: HookResult<{
|
|
2834
|
+
name: string;
|
|
2835
|
+
type: string;
|
|
2836
|
+
size: number;
|
|
2837
|
+
}>[];
|
|
2838
|
+
}>;
|
|
2839
|
+
/**
|
|
2840
|
+
* Run media:afterUpload hooks
|
|
2841
|
+
*/
|
|
2842
|
+
runMediaAfterUpload(media: {
|
|
2843
|
+
id: string;
|
|
2844
|
+
filename: string;
|
|
2845
|
+
mimeType: string;
|
|
2846
|
+
size: number | null;
|
|
2847
|
+
url: string;
|
|
2848
|
+
createdAt: string;
|
|
2849
|
+
}): Promise<HookResult<void>[]>;
|
|
2850
|
+
/**
|
|
2851
|
+
* Invoke the cron hook for a specific plugin.
|
|
2852
|
+
*
|
|
2853
|
+
* Unlike other hooks which broadcast to all plugins, the cron hook is
|
|
2854
|
+
* dispatched only to the target plugin — the one that owns the task.
|
|
2855
|
+
*/
|
|
2856
|
+
invokeCronHook(pluginId: string, event: CronEvent): Promise<HookResult<void>>;
|
|
2857
|
+
/**
|
|
2858
|
+
* Run email:beforeSend hooks (middleware pipeline).
|
|
2859
|
+
*
|
|
2860
|
+
* Each handler receives the message and returns a modified message or
|
|
2861
|
+
* `false` to cancel delivery. The pipeline chains message transformations —
|
|
2862
|
+
* each handler receives the output of the previous one.
|
|
2863
|
+
*/
|
|
2864
|
+
runEmailBeforeSend(message: EmailMessage, source: string): Promise<{
|
|
2865
|
+
message: EmailMessage | false;
|
|
2866
|
+
results: HookResult<EmailMessage | false>[];
|
|
2867
|
+
}>;
|
|
2868
|
+
/**
|
|
2869
|
+
* Run email:afterSend hooks (fire-and-forget).
|
|
2870
|
+
*
|
|
2871
|
+
* Errors are logged but don't propagate — they don't affect the caller.
|
|
2872
|
+
*/
|
|
2873
|
+
runEmailAfterSend(message: EmailMessage, source: string): Promise<HookResult<void>[]>;
|
|
2874
|
+
/**
|
|
2875
|
+
* Run comment:beforeCreate hooks (middleware pipeline).
|
|
2876
|
+
*
|
|
2877
|
+
* Each handler receives the event and returns a modified event or
|
|
2878
|
+
* `false` to reject the comment. The pipeline chains transformations —
|
|
2879
|
+
* each handler receives the output of the previous one.
|
|
2880
|
+
*/
|
|
2881
|
+
runCommentBeforeCreate(event: CommentBeforeCreateEvent): Promise<CommentBeforeCreateEvent | false>;
|
|
2882
|
+
/**
|
|
2883
|
+
* Run comment:afterCreate hooks (fire-and-forget).
|
|
2884
|
+
*
|
|
2885
|
+
* Errors are logged but don't propagate — they don't affect the caller.
|
|
2886
|
+
*/
|
|
2887
|
+
runCommentAfterCreate(event: CommentAfterCreateEvent): Promise<void>;
|
|
2888
|
+
/**
|
|
2889
|
+
* Run comment:afterModerate hooks (fire-and-forget).
|
|
2890
|
+
*
|
|
2891
|
+
* Errors are logged but don't propagate — they don't affect the caller.
|
|
2892
|
+
*/
|
|
2893
|
+
runCommentAfterModerate(event: CommentAfterModerateEvent): Promise<void>;
|
|
2894
|
+
/**
|
|
2895
|
+
* Run page:metadata hooks. Each handler returns contributions that are
|
|
2896
|
+
* merged by the metadata collector. Errors are logged but don't propagate.
|
|
2897
|
+
*/
|
|
2898
|
+
runPageMetadata(event: PageMetadataEvent): Promise<Array<{
|
|
2899
|
+
pluginId: string;
|
|
2900
|
+
contributions: PageMetadataContribution[];
|
|
2901
|
+
}>>;
|
|
2902
|
+
/**
|
|
2903
|
+
* Run page:fragments hooks. Only trusted plugins should be registered
|
|
2904
|
+
* for this hook. Errors are logged but don't propagate.
|
|
2905
|
+
*/
|
|
2906
|
+
runPageFragments(event: PageFragmentEvent): Promise<Array<{
|
|
2907
|
+
pluginId: string;
|
|
2908
|
+
contributions: PageFragmentContribution[];
|
|
2909
|
+
}>>;
|
|
2910
|
+
/**
|
|
2911
|
+
* Check if any hooks are registered for a given name
|
|
2912
|
+
*/
|
|
2913
|
+
hasHooks(name: HookNameV2): boolean;
|
|
2914
|
+
/**
|
|
2915
|
+
* Get hook count for debugging
|
|
2916
|
+
*/
|
|
2917
|
+
getHookCount(name: HookNameV2): number;
|
|
2918
|
+
/**
|
|
2919
|
+
* Get all registered hook names
|
|
2920
|
+
*/
|
|
2921
|
+
getRegisteredHooks(): HookNameV2[];
|
|
2922
|
+
/**
|
|
2923
|
+
* Returns hook names where at least one handler declared exclusive: true
|
|
2924
|
+
*/
|
|
2925
|
+
getRegisteredExclusiveHooks(): string[];
|
|
2926
|
+
/**
|
|
2927
|
+
* Check if a hook is exclusive
|
|
2928
|
+
*/
|
|
2929
|
+
isExclusiveHook(name: string): boolean;
|
|
2930
|
+
/**
|
|
2931
|
+
* Set the selected provider for an exclusive hook.
|
|
2932
|
+
* Called by PluginManager after resolution.
|
|
2933
|
+
*/
|
|
2934
|
+
setExclusiveSelection(hookName: string, pluginId: string): void;
|
|
2935
|
+
/**
|
|
2936
|
+
* Clear the selected provider for an exclusive hook.
|
|
2937
|
+
*/
|
|
2938
|
+
clearExclusiveSelection(hookName: string): void;
|
|
2939
|
+
/**
|
|
2940
|
+
* Get the selected provider for an exclusive hook (if any).
|
|
2941
|
+
*/
|
|
2942
|
+
getExclusiveSelection(hookName: string): string | undefined;
|
|
2943
|
+
/**
|
|
2944
|
+
* Get all plugins that registered a handler for a given exclusive hook.
|
|
2945
|
+
*/
|
|
2946
|
+
getExclusiveHookProviders(hookName: string): Array<{
|
|
2947
|
+
pluginId: string;
|
|
2948
|
+
}>;
|
|
2949
|
+
/**
|
|
2950
|
+
* Invoke an exclusive hook — dispatch only to the selected provider.
|
|
2951
|
+
* Returns null if no provider is selected or if the selected hook
|
|
2952
|
+
* is not found in the pipeline.
|
|
2953
|
+
*
|
|
2954
|
+
* This is a generic dispatch used by the email pipeline and other
|
|
2955
|
+
* exclusive hook consumers. The handler type is unknown — callers
|
|
2956
|
+
* must know the expected signature.
|
|
2957
|
+
*
|
|
2958
|
+
* Errors are isolated: a failing handler returns an error result
|
|
2959
|
+
* instead of propagating the exception to the caller.
|
|
2960
|
+
*/
|
|
2961
|
+
invokeExclusiveHook(hookName: string, event: unknown): Promise<{
|
|
2962
|
+
result: unknown;
|
|
2963
|
+
pluginId: string;
|
|
2964
|
+
error?: Error;
|
|
2965
|
+
duration: number;
|
|
2966
|
+
} | null>;
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* Create a hook pipeline from plugins
|
|
2970
|
+
*/
|
|
2971
|
+
declare function createHookPipeline(plugins: ResolvedPlugin[], factoryOptions?: PluginContextFactoryOptions): HookPipeline;
|
|
2972
|
+
//#endregion
|
|
2973
|
+
//#region src/plugins/email.d.ts
|
|
2974
|
+
/**
|
|
2975
|
+
* EmailPipeline orchestrates email delivery through the plugin hook system.
|
|
2976
|
+
*
|
|
2977
|
+
* The pipeline runs in three stages:
|
|
2978
|
+
* 1. email:beforeSend — middleware hooks that can transform or cancel messages
|
|
2979
|
+
* 2. email:deliver — exclusive hook dispatching to the selected provider
|
|
2980
|
+
* 3. email:afterSend — fire-and-forget hooks for logging/analytics
|
|
2981
|
+
*/
|
|
2982
|
+
declare class EmailPipeline {
|
|
2983
|
+
private pipeline;
|
|
2984
|
+
constructor(pipeline: HookPipeline);
|
|
2985
|
+
/**
|
|
2986
|
+
* Replace the underlying hook pipeline.
|
|
2987
|
+
*
|
|
2988
|
+
* Called by the runtime when rebuilding the hook pipeline after a
|
|
2989
|
+
* plugin is enabled or disabled, so the email pipeline dispatches
|
|
2990
|
+
* to the current set of active hooks.
|
|
2991
|
+
*/
|
|
2992
|
+
setPipeline(pipeline: HookPipeline): void;
|
|
2993
|
+
/**
|
|
2994
|
+
* Send an email through the full pipeline.
|
|
2995
|
+
*
|
|
2996
|
+
* @param message - The email to send
|
|
2997
|
+
* @param source - Where the email originated ("system" for auth, plugin ID for plugins)
|
|
2998
|
+
* @throws EmailNotConfiguredError if no provider is selected
|
|
2999
|
+
* @throws EmailRecursionError if called re-entrantly from within a hook
|
|
3000
|
+
* @throws Error if the provider handler throws
|
|
3001
|
+
*/
|
|
3002
|
+
send(message: EmailMessage, source: string): Promise<void>;
|
|
3003
|
+
/**
|
|
3004
|
+
* Inner send implementation, separated from the recursion guard.
|
|
3005
|
+
*/
|
|
3006
|
+
private sendInner;
|
|
3007
|
+
/**
|
|
3008
|
+
* Check if an email provider is configured and available.
|
|
3009
|
+
*
|
|
3010
|
+
* Returns true if an email:deliver provider is selected in the exclusive
|
|
3011
|
+
* hook system. Plugins and auth code use this to decide whether to show
|
|
3012
|
+
* "send invite" vs "copy invite link" UI.
|
|
3013
|
+
*/
|
|
3014
|
+
isAvailable(): boolean;
|
|
3015
|
+
}
|
|
3016
|
+
//#endregion
|
|
3017
|
+
//#region src/plugins/context.d.ts
|
|
3018
|
+
/**
|
|
3019
|
+
* Options for creating site info
|
|
3020
|
+
*/
|
|
3021
|
+
interface SiteInfoOptions {
|
|
3022
|
+
/** Site name from options table */
|
|
3023
|
+
siteName?: string;
|
|
3024
|
+
/** Site URL from options table or Astro config */
|
|
3025
|
+
siteUrl?: string;
|
|
3026
|
+
/** Site locale from options table */
|
|
3027
|
+
locale?: string;
|
|
3028
|
+
}
|
|
3029
|
+
interface PluginContextFactoryOptions {
|
|
3030
|
+
db: Kysely<Database>;
|
|
3031
|
+
/**
|
|
3032
|
+
* Storage backend for direct media uploads.
|
|
3033
|
+
* If not provided, upload() will throw.
|
|
3034
|
+
*/
|
|
3035
|
+
storage?: Storage;
|
|
3036
|
+
/**
|
|
3037
|
+
* Function to generate upload URLs for media.
|
|
3038
|
+
* If not provided, media write operations will throw.
|
|
3039
|
+
*/
|
|
3040
|
+
getUploadUrl?: (filename: string, contentType: string) => Promise<{
|
|
3041
|
+
uploadUrl: string;
|
|
3042
|
+
mediaId: string;
|
|
3043
|
+
}>;
|
|
3044
|
+
/**
|
|
3045
|
+
* Site information for ctx.site and ctx.url().
|
|
3046
|
+
* If not provided, site info will have empty defaults.
|
|
3047
|
+
*/
|
|
3048
|
+
siteInfo?: SiteInfoOptions;
|
|
3049
|
+
/**
|
|
3050
|
+
* Callback to notify the cron scheduler that the next due time may have changed.
|
|
3051
|
+
* If not provided, ctx.cron will not be available.
|
|
3052
|
+
*/
|
|
3053
|
+
cronReschedule?: () => void;
|
|
3054
|
+
/**
|
|
3055
|
+
* Email pipeline instance for ctx.email.
|
|
3056
|
+
* If not provided (or no provider configured), ctx.email will be undefined.
|
|
3057
|
+
*/
|
|
3058
|
+
emailPipeline?: EmailPipeline;
|
|
3059
|
+
}
|
|
3060
|
+
//#endregion
|
|
3061
|
+
//#region src/plugins/routes.d.ts
|
|
3062
|
+
/**
|
|
3063
|
+
* Result from a route invocation
|
|
3064
|
+
*/
|
|
3065
|
+
interface RouteResult<T = unknown> {
|
|
3066
|
+
success: boolean;
|
|
3067
|
+
data?: T;
|
|
3068
|
+
error?: {
|
|
3069
|
+
code: string;
|
|
3070
|
+
message: string;
|
|
3071
|
+
details?: unknown;
|
|
3072
|
+
};
|
|
3073
|
+
status: number;
|
|
3074
|
+
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Route invocation options
|
|
3077
|
+
*/
|
|
3078
|
+
interface InvokeRouteOptions {
|
|
3079
|
+
/** The original request */
|
|
3080
|
+
request: Request;
|
|
3081
|
+
/** Request body (already parsed) */
|
|
3082
|
+
body?: unknown;
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Error class for plugin routes
|
|
3086
|
+
* Allows plugins to return structured errors with specific HTTP status codes
|
|
3087
|
+
*/
|
|
3088
|
+
declare class PluginRouteError extends Error {
|
|
3089
|
+
code: string;
|
|
3090
|
+
status: number;
|
|
3091
|
+
details?: unknown | undefined;
|
|
3092
|
+
constructor(code: string, message: string, status?: number, details?: unknown | undefined);
|
|
3093
|
+
/**
|
|
3094
|
+
* Create a bad request error (400)
|
|
3095
|
+
*/
|
|
3096
|
+
static badRequest(message: string, details?: unknown): PluginRouteError;
|
|
3097
|
+
/**
|
|
3098
|
+
* Create an unauthorized error (401)
|
|
3099
|
+
*/
|
|
3100
|
+
static unauthorized(message?: string): PluginRouteError;
|
|
3101
|
+
/**
|
|
3102
|
+
* Create a forbidden error (403)
|
|
3103
|
+
*/
|
|
3104
|
+
static forbidden(message?: string): PluginRouteError;
|
|
3105
|
+
/**
|
|
3106
|
+
* Create a not found error (404)
|
|
3107
|
+
*/
|
|
3108
|
+
static notFound(message?: string): PluginRouteError;
|
|
3109
|
+
/**
|
|
3110
|
+
* Create a conflict error (409)
|
|
3111
|
+
*/
|
|
3112
|
+
static conflict(message: string, details?: unknown): PluginRouteError;
|
|
3113
|
+
/**
|
|
3114
|
+
* Create an internal error (500)
|
|
3115
|
+
*/
|
|
3116
|
+
static internal(message?: string): PluginRouteError;
|
|
3117
|
+
}
|
|
3118
|
+
//#endregion
|
|
3119
|
+
//#region src/plugins/manager.d.ts
|
|
3120
|
+
/**
|
|
3121
|
+
* Plugin state in the manager
|
|
3122
|
+
*/
|
|
3123
|
+
type PluginState = "registered" | "installed" | "active" | "inactive";
|
|
3124
|
+
/**
|
|
3125
|
+
* Plugin manager options
|
|
3126
|
+
*/
|
|
3127
|
+
interface PluginManagerOptions {
|
|
3128
|
+
/** Database instance */
|
|
3129
|
+
db: Kysely<Database>;
|
|
3130
|
+
/** Storage backend for direct media uploads */
|
|
3131
|
+
storage?: Storage;
|
|
3132
|
+
/** Function to generate upload URLs for media */
|
|
3133
|
+
getUploadUrl?: (filename: string, contentType: string) => Promise<{
|
|
3134
|
+
uploadUrl: string;
|
|
3135
|
+
mediaId: string;
|
|
3136
|
+
}>;
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Plugin Manager v2
|
|
3140
|
+
*
|
|
3141
|
+
* Manages the full lifecycle of plugins and coordinates hooks/routes.
|
|
3142
|
+
*/
|
|
3143
|
+
declare class PluginManager {
|
|
3144
|
+
private options;
|
|
3145
|
+
private plugins;
|
|
3146
|
+
private hookPipeline;
|
|
3147
|
+
private routeRegistry;
|
|
3148
|
+
private factoryOptions;
|
|
3149
|
+
private initialized;
|
|
3150
|
+
constructor(options: PluginManagerOptions);
|
|
3151
|
+
/**
|
|
3152
|
+
* Set the email pipeline used when creating plugin contexts.
|
|
3153
|
+
* Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
|
|
3154
|
+
*/
|
|
3155
|
+
setEmailPipeline(pipeline: EmailPipeline): void;
|
|
3156
|
+
/**
|
|
3157
|
+
* Register a plugin definition
|
|
3158
|
+
* This resolves the definition and adds it to the manager, but doesn't install it
|
|
3159
|
+
*/
|
|
3160
|
+
register<TStorage extends PluginStorageConfig>(definition: PluginDefinition<TStorage>): ResolvedPlugin<TStorage>;
|
|
3161
|
+
/**
|
|
3162
|
+
* Register multiple plugins
|
|
3163
|
+
*/
|
|
3164
|
+
registerAll(definitions: PluginDefinition[]): void;
|
|
3165
|
+
/**
|
|
3166
|
+
* Unregister a plugin
|
|
3167
|
+
* Plugin must be inactive or just registered
|
|
3168
|
+
*/
|
|
3169
|
+
unregister(pluginId: string): boolean;
|
|
3170
|
+
/**
|
|
3171
|
+
* Install a plugin (run install hooks, set up storage)
|
|
3172
|
+
*/
|
|
3173
|
+
install(pluginId: string): Promise<HookResult<void>[]>;
|
|
3174
|
+
/**
|
|
3175
|
+
* Activate a plugin (run activate hooks, enable hooks/routes)
|
|
3176
|
+
*/
|
|
3177
|
+
activate(pluginId: string): Promise<HookResult<void>[]>;
|
|
3178
|
+
/**
|
|
3179
|
+
* Deactivate a plugin (run deactivate hooks, disable hooks/routes)
|
|
3180
|
+
*/
|
|
3181
|
+
deactivate(pluginId: string): Promise<HookResult<void>[]>;
|
|
3182
|
+
/**
|
|
3183
|
+
* Uninstall a plugin (run uninstall hooks, optionally delete data)
|
|
3184
|
+
*/
|
|
3185
|
+
uninstall(pluginId: string, deleteData?: boolean): Promise<HookResult<void>[]>;
|
|
3186
|
+
/**
|
|
3187
|
+
* Run content:beforeSave hooks across all active plugins
|
|
3188
|
+
*/
|
|
3189
|
+
runContentBeforeSave(content: Record<string, unknown>, collection: string, isNew: boolean): Promise<{
|
|
3190
|
+
content: Record<string, unknown>;
|
|
3191
|
+
results: HookResult<Record<string, unknown>>[];
|
|
3192
|
+
}>;
|
|
3193
|
+
/**
|
|
3194
|
+
* Run content:afterSave hooks across all active plugins
|
|
3195
|
+
*/
|
|
3196
|
+
runContentAfterSave(content: Record<string, unknown>, collection: string, isNew: boolean): Promise<HookResult<void>[]>;
|
|
3197
|
+
/**
|
|
3198
|
+
* Run content:beforeDelete hooks across all active plugins
|
|
3199
|
+
*/
|
|
3200
|
+
runContentBeforeDelete(id: string, collection: string): Promise<{
|
|
3201
|
+
allowed: boolean;
|
|
3202
|
+
results: HookResult<boolean>[];
|
|
3203
|
+
}>;
|
|
3204
|
+
/**
|
|
3205
|
+
* Run content:afterDelete hooks across all active plugins
|
|
3206
|
+
*/
|
|
3207
|
+
runContentAfterDelete(id: string, collection: string): Promise<HookResult<void>[]>;
|
|
3208
|
+
/**
|
|
3209
|
+
* Run content:afterPublish hooks across all active plugins
|
|
3210
|
+
*/
|
|
3211
|
+
runContentAfterPublish(content: Record<string, unknown>, collection: string): Promise<HookResult<void>[]>;
|
|
3212
|
+
/**
|
|
3213
|
+
* Run content:afterUnpublish hooks across all active plugins
|
|
3214
|
+
*/
|
|
3215
|
+
runContentAfterUnpublish(content: Record<string, unknown>, collection: string): Promise<HookResult<void>[]>;
|
|
3216
|
+
/**
|
|
3217
|
+
* Run media:beforeUpload hooks across all active plugins
|
|
3218
|
+
*/
|
|
3219
|
+
runMediaBeforeUpload(file: {
|
|
3220
|
+
name: string;
|
|
3221
|
+
type: string;
|
|
3222
|
+
size: number;
|
|
3223
|
+
}): Promise<{
|
|
3224
|
+
file: {
|
|
3225
|
+
name: string;
|
|
3226
|
+
type: string;
|
|
3227
|
+
size: number;
|
|
3228
|
+
};
|
|
3229
|
+
results: HookResult<{
|
|
3230
|
+
name: string;
|
|
3231
|
+
type: string;
|
|
3232
|
+
size: number;
|
|
3233
|
+
}>[];
|
|
3234
|
+
}>;
|
|
3235
|
+
/**
|
|
3236
|
+
* Run media:afterUpload hooks across all active plugins
|
|
3237
|
+
*/
|
|
3238
|
+
runMediaAfterUpload(media: MediaItem$1): Promise<HookResult<void>[]>;
|
|
3239
|
+
/**
|
|
3240
|
+
* Invoke the cron hook for a specific plugin (per-plugin dispatch).
|
|
3241
|
+
* Used as the InvokeCronHookFn callback for CronExecutor.
|
|
3242
|
+
*/
|
|
3243
|
+
invokeCronHook(pluginId: string, event: CronEvent): Promise<void>;
|
|
3244
|
+
/**
|
|
3245
|
+
* Invoke a plugin route
|
|
3246
|
+
*/
|
|
3247
|
+
invokeRoute(pluginId: string, routeName: string, options: InvokeRouteOptions): Promise<RouteResult>;
|
|
3248
|
+
/**
|
|
3249
|
+
* Get all routes for a plugin
|
|
3250
|
+
*/
|
|
3251
|
+
getPluginRoutes(pluginId: string): string[];
|
|
3252
|
+
/**
|
|
3253
|
+
* Get a plugin by ID
|
|
3254
|
+
*/
|
|
3255
|
+
getPlugin(pluginId: string): ResolvedPlugin | undefined;
|
|
3256
|
+
/**
|
|
3257
|
+
* Get plugin state
|
|
3258
|
+
*/
|
|
3259
|
+
getPluginState(pluginId: string): PluginState | undefined;
|
|
3260
|
+
/**
|
|
3261
|
+
* Get all registered plugins
|
|
3262
|
+
*/
|
|
3263
|
+
getAllPlugins(): Array<{
|
|
3264
|
+
plugin: ResolvedPlugin;
|
|
3265
|
+
state: PluginState;
|
|
3266
|
+
}>;
|
|
3267
|
+
/**
|
|
3268
|
+
* Get all active plugins
|
|
3269
|
+
*/
|
|
3270
|
+
getActivePlugins(): ResolvedPlugin[];
|
|
3271
|
+
/**
|
|
3272
|
+
* Check if a plugin exists
|
|
3273
|
+
*/
|
|
3274
|
+
hasPlugin(pluginId: string): boolean;
|
|
3275
|
+
/**
|
|
3276
|
+
* Check if a plugin is active
|
|
3277
|
+
*/
|
|
3278
|
+
isActive(pluginId: string): boolean;
|
|
3279
|
+
/**
|
|
3280
|
+
* Get all plugins that registered a handler for an exclusive hook.
|
|
3281
|
+
*/
|
|
3282
|
+
getExclusiveHookProviders(hookName: string): Array<{
|
|
3283
|
+
pluginId: string;
|
|
3284
|
+
pluginName: string;
|
|
3285
|
+
}>;
|
|
3286
|
+
/**
|
|
3287
|
+
* Read the selected provider for an exclusive hook from the options table.
|
|
3288
|
+
*/
|
|
3289
|
+
getExclusiveHookSelection(hookName: string): Promise<string | null>;
|
|
3290
|
+
/**
|
|
3291
|
+
* Set the selected provider for an exclusive hook in the options table.
|
|
3292
|
+
* Pass null to clear the selection.
|
|
3293
|
+
*/
|
|
3294
|
+
setExclusiveHookSelection(hookName: string, pluginId: string | null): Promise<void>;
|
|
3295
|
+
/**
|
|
3296
|
+
* Resolution algorithm for exclusive hooks.
|
|
3297
|
+
*
|
|
3298
|
+
* Delegates to the shared resolveExclusiveHooks() function.
|
|
3299
|
+
* See hooks.ts for the full algorithm description.
|
|
3300
|
+
*/
|
|
3301
|
+
resolveExclusiveHooks(preferredHints?: Map<string, string[]>): Promise<void>;
|
|
3302
|
+
/**
|
|
3303
|
+
* Get all exclusive hooks with their providers and current selections.
|
|
3304
|
+
* Used by the admin API.
|
|
3305
|
+
*/
|
|
3306
|
+
getExclusiveHooksInfo(): Promise<Array<{
|
|
3307
|
+
hookName: string;
|
|
3308
|
+
providers: Array<{
|
|
3309
|
+
pluginId: string;
|
|
3310
|
+
}>;
|
|
3311
|
+
selectedPluginId: string | null;
|
|
3312
|
+
}>>;
|
|
3313
|
+
/**
|
|
3314
|
+
* Initialize or reinitialize the hook pipeline and route registry
|
|
3315
|
+
*/
|
|
3316
|
+
private ensureInitialized;
|
|
3317
|
+
/**
|
|
3318
|
+
* Force reinitialization (useful after plugin state changes)
|
|
3319
|
+
*/
|
|
3320
|
+
reinitialize(): void;
|
|
3321
|
+
/**
|
|
3322
|
+
* Delete all cron tasks for a plugin.
|
|
3323
|
+
* Used during uninstall.
|
|
3324
|
+
*/
|
|
3325
|
+
private deleteCronTasks;
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Create a plugin manager
|
|
3329
|
+
*/
|
|
3330
|
+
declare function createPluginManager(options: PluginManagerOptions): PluginManager;
|
|
3331
|
+
//#endregion
|
|
3332
|
+
//#region src/plugins/sandbox/noop.d.ts
|
|
3333
|
+
/**
|
|
3334
|
+
* Error thrown when attempting to use sandboxing on an unsupported platform.
|
|
3335
|
+
*/
|
|
3336
|
+
declare class SandboxNotAvailableError extends Error {
|
|
3337
|
+
constructor();
|
|
3338
|
+
}
|
|
3339
|
+
/**
|
|
3340
|
+
* No-op sandbox runner for platforms without isolation support.
|
|
3341
|
+
*
|
|
3342
|
+
* - `isAvailable()` returns false
|
|
3343
|
+
* - `load()` throws SandboxNotAvailableError
|
|
3344
|
+
* - `terminateAll()` is a no-op
|
|
3345
|
+
*
|
|
3346
|
+
* This is the default runner when no platform adapter is configured.
|
|
3347
|
+
*/
|
|
3348
|
+
declare class NoopSandboxRunner implements SandboxRunner {
|
|
3349
|
+
/**
|
|
3350
|
+
* Always returns false - sandboxing is not available.
|
|
3351
|
+
*/
|
|
3352
|
+
isAvailable(): boolean;
|
|
3353
|
+
/**
|
|
3354
|
+
* Always throws - can't load sandboxed plugins without isolation.
|
|
3355
|
+
*/
|
|
3356
|
+
load(_manifest: PluginManifest, _code: string): Promise<SandboxedPlugin>;
|
|
3357
|
+
/**
|
|
3358
|
+
* No-op - sandboxing not available, email callback is irrelevant.
|
|
3359
|
+
*/
|
|
3360
|
+
setEmailSend(): void;
|
|
3361
|
+
/**
|
|
3362
|
+
* No-op - nothing to terminate.
|
|
3363
|
+
*/
|
|
3364
|
+
terminateAll(): Promise<void>;
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Create a no-op sandbox runner.
|
|
3368
|
+
* This is used as the default when no platform adapter is configured.
|
|
3369
|
+
*/
|
|
3370
|
+
declare function createNoopSandboxRunner(_options?: SandboxOptions): SandboxRunner;
|
|
3371
|
+
//#endregion
|
|
3372
|
+
//#region src/plugins/sandbox/runtime-options.d.ts
|
|
3373
|
+
interface SandboxRunnerRuntimeOptions extends SandboxOptions {
|
|
3374
|
+
mediaStorage?: Storage;
|
|
3375
|
+
cronReschedule?: () => void;
|
|
3376
|
+
}
|
|
3377
|
+
//#endregion
|
|
3378
|
+
//#region src/plugins/sandbox/node.d.ts
|
|
3379
|
+
declare class NodeSandboxRunner implements SandboxRunner {
|
|
3380
|
+
private readonly options;
|
|
3381
|
+
private readonly limits;
|
|
3382
|
+
private readonly activeWorkers;
|
|
3383
|
+
private emailSend;
|
|
3384
|
+
constructor(options: SandboxRunnerRuntimeOptions);
|
|
3385
|
+
isAvailable(): boolean;
|
|
3386
|
+
load(manifest: PluginManifest, code: string): Promise<SandboxedPlugin>;
|
|
3387
|
+
setEmailSend(callback: SandboxEmailSendCallback | null): void;
|
|
3388
|
+
terminateAll(): Promise<void>;
|
|
3389
|
+
}
|
|
3390
|
+
declare function createNodeSandboxRunner(options: SandboxRunnerRuntimeOptions): SandboxRunner;
|
|
3391
|
+
//#endregion
|
|
3392
|
+
//#region src/import/types.d.ts
|
|
3393
|
+
/** Author info from WordPress */
|
|
3394
|
+
interface WpAuthorInfo {
|
|
3395
|
+
id?: number;
|
|
3396
|
+
login?: string;
|
|
3397
|
+
email?: string;
|
|
3398
|
+
displayName?: string;
|
|
3399
|
+
postCount: number;
|
|
3400
|
+
}
|
|
3401
|
+
/** File-based input (WXR upload) */
|
|
3402
|
+
interface FileInput {
|
|
3403
|
+
type: "file";
|
|
3404
|
+
file: File;
|
|
3405
|
+
}
|
|
3406
|
+
/** URL-based input (REST API probe) */
|
|
3407
|
+
interface UrlInput {
|
|
3408
|
+
type: "url";
|
|
3409
|
+
url: string;
|
|
3410
|
+
/** Optional auth token for authenticated requests */
|
|
3411
|
+
token?: string;
|
|
3412
|
+
}
|
|
3413
|
+
/** OAuth-based input (WordPress.com) */
|
|
3414
|
+
interface OAuthInput {
|
|
3415
|
+
type: "oauth";
|
|
3416
|
+
url: string;
|
|
3417
|
+
accessToken: string;
|
|
3418
|
+
/** Site ID for WordPress.com */
|
|
3419
|
+
siteId?: string;
|
|
3420
|
+
}
|
|
3421
|
+
type SourceInput = FileInput | UrlInput | OAuthInput;
|
|
3422
|
+
/** Auth requirements for an import source */
|
|
3423
|
+
interface SourceAuth {
|
|
3424
|
+
type: "oauth" | "token" | "password" | "none";
|
|
3425
|
+
/** OAuth provider identifier */
|
|
3426
|
+
provider?: string;
|
|
3427
|
+
/** OAuth authorization URL */
|
|
3428
|
+
oauthUrl?: string;
|
|
3429
|
+
/** Human-readable instructions */
|
|
3430
|
+
instructions?: string;
|
|
3431
|
+
}
|
|
3432
|
+
/** What the source can provide */
|
|
3433
|
+
interface SourceCapabilities {
|
|
3434
|
+
/** Can fetch published content without auth */
|
|
3435
|
+
publicContent: boolean;
|
|
3436
|
+
/** Can fetch drafts/private (may need auth) */
|
|
3437
|
+
privateContent: boolean;
|
|
3438
|
+
/** Can fetch all custom post types */
|
|
3439
|
+
customPostTypes: boolean;
|
|
3440
|
+
/** Can fetch all meta fields */
|
|
3441
|
+
allMeta: boolean;
|
|
3442
|
+
/** Can stream media directly */
|
|
3443
|
+
mediaStream: boolean;
|
|
3444
|
+
}
|
|
3445
|
+
/** Suggested next action after probe */
|
|
3446
|
+
type SuggestedAction = {
|
|
3447
|
+
type: "proceed";
|
|
3448
|
+
} | {
|
|
3449
|
+
type: "oauth";
|
|
3450
|
+
url: string;
|
|
3451
|
+
provider: string;
|
|
3452
|
+
} | {
|
|
3453
|
+
type: "upload";
|
|
3454
|
+
instructions: string;
|
|
3455
|
+
} | {
|
|
3456
|
+
type: "install-plugin";
|
|
3457
|
+
instructions: string;
|
|
3458
|
+
};
|
|
3459
|
+
/** Detected i18n/multilingual plugin info */
|
|
3460
|
+
interface I18nDetection {
|
|
3461
|
+
/** Multilingual plugin name (e.g. "wpml", "polylang") */
|
|
3462
|
+
plugin: string;
|
|
3463
|
+
/** BCP 47 default locale */
|
|
3464
|
+
defaultLocale: string;
|
|
3465
|
+
/** All configured locales */
|
|
3466
|
+
locales: string[];
|
|
3467
|
+
}
|
|
3468
|
+
/** Result of probing a URL for a specific source */
|
|
3469
|
+
interface SourceProbeResult {
|
|
3470
|
+
/** Which source can handle this */
|
|
3471
|
+
sourceId: string;
|
|
3472
|
+
/** Confidence level */
|
|
3473
|
+
confidence: "definite" | "likely" | "possible";
|
|
3474
|
+
/** What we detected */
|
|
3475
|
+
detected: {
|
|
3476
|
+
platform: string;
|
|
3477
|
+
version?: string;
|
|
3478
|
+
siteTitle?: string;
|
|
3479
|
+
siteUrl?: string;
|
|
3480
|
+
};
|
|
3481
|
+
/** What capabilities are available */
|
|
3482
|
+
capabilities: SourceCapabilities;
|
|
3483
|
+
/** What auth is needed, if any */
|
|
3484
|
+
auth?: SourceAuth;
|
|
3485
|
+
/** Suggested next step */
|
|
3486
|
+
suggestedAction: SuggestedAction;
|
|
3487
|
+
/** Preview data if available (e.g., post counts from REST API) */
|
|
3488
|
+
preview?: {
|
|
3489
|
+
posts?: number;
|
|
3490
|
+
pages?: number;
|
|
3491
|
+
media?: number;
|
|
3492
|
+
};
|
|
3493
|
+
/** Detected multilingual plugin. Absent when none detected. */
|
|
3494
|
+
i18n?: I18nDetection;
|
|
3495
|
+
}
|
|
3496
|
+
/** Combined probe result from all sources */
|
|
3497
|
+
interface ProbeResult {
|
|
3498
|
+
url: string;
|
|
3499
|
+
isWordPress: boolean;
|
|
3500
|
+
/** Best matching source (highest confidence) */
|
|
3501
|
+
bestMatch: SourceProbeResult | null;
|
|
3502
|
+
/** All matching sources */
|
|
3503
|
+
allMatches: SourceProbeResult[];
|
|
3504
|
+
}
|
|
3505
|
+
/** Field definition for import */
|
|
3506
|
+
interface ImportFieldDef {
|
|
3507
|
+
slug: string;
|
|
3508
|
+
label: string;
|
|
3509
|
+
type: string;
|
|
3510
|
+
required: boolean;
|
|
3511
|
+
searchable?: boolean;
|
|
3512
|
+
}
|
|
3513
|
+
/** Field compatibility with existing schema */
|
|
3514
|
+
type FieldCompatibility = "compatible" | "type_mismatch" | "missing";
|
|
3515
|
+
/** Schema status for a collection */
|
|
3516
|
+
interface CollectionSchemaStatus {
|
|
3517
|
+
exists: boolean;
|
|
3518
|
+
fieldStatus: Record<string, {
|
|
3519
|
+
status: FieldCompatibility;
|
|
3520
|
+
existingType?: string;
|
|
3521
|
+
requiredType: string;
|
|
3522
|
+
}>;
|
|
3523
|
+
canImport: boolean;
|
|
3524
|
+
reason?: string;
|
|
3525
|
+
}
|
|
3526
|
+
/** Analysis of a single post type */
|
|
3527
|
+
interface PostTypeAnalysis {
|
|
3528
|
+
name: string;
|
|
3529
|
+
count: number;
|
|
3530
|
+
suggestedCollection: string;
|
|
3531
|
+
requiredFields: ImportFieldDef[];
|
|
3532
|
+
schemaStatus: CollectionSchemaStatus;
|
|
3533
|
+
}
|
|
3534
|
+
/** Attachment/media info */
|
|
3535
|
+
interface AttachmentInfo {
|
|
3536
|
+
id?: number;
|
|
3537
|
+
title?: string;
|
|
3538
|
+
url?: string;
|
|
3539
|
+
filename?: string;
|
|
3540
|
+
mimeType?: string;
|
|
3541
|
+
alt?: string;
|
|
3542
|
+
caption?: string;
|
|
3543
|
+
width?: number;
|
|
3544
|
+
height?: number;
|
|
3545
|
+
}
|
|
3546
|
+
/** Navigation menu analysis */
|
|
3547
|
+
interface NavMenuAnalysis {
|
|
3548
|
+
/** Menu name/slug */
|
|
3549
|
+
name: string;
|
|
3550
|
+
/** Menu display label */
|
|
3551
|
+
label: string;
|
|
3552
|
+
/** Number of items in this menu */
|
|
3553
|
+
itemCount: number;
|
|
3554
|
+
}
|
|
3555
|
+
/** Custom taxonomy analysis */
|
|
3556
|
+
interface TaxonomyAnalysis {
|
|
3557
|
+
/** Taxonomy slug (e.g., 'genre', 'portfolio_category') */
|
|
3558
|
+
slug: string;
|
|
3559
|
+
/** Number of terms in this taxonomy */
|
|
3560
|
+
termCount: number;
|
|
3561
|
+
/** Sample term names */
|
|
3562
|
+
sampleTerms: string[];
|
|
3563
|
+
}
|
|
3564
|
+
/** Reusable block analysis (wp_block post type) */
|
|
3565
|
+
interface ReusableBlockAnalysis {
|
|
3566
|
+
/** Original WP ID */
|
|
3567
|
+
id: number;
|
|
3568
|
+
/** Block title */
|
|
3569
|
+
title: string;
|
|
3570
|
+
/** Block slug */
|
|
3571
|
+
slug: string;
|
|
3572
|
+
}
|
|
3573
|
+
/** Normalized analysis result - same format for all sources */
|
|
3574
|
+
interface ImportAnalysis {
|
|
3575
|
+
/** Source that produced this analysis */
|
|
3576
|
+
sourceId: string;
|
|
3577
|
+
site: {
|
|
3578
|
+
title: string;
|
|
3579
|
+
url: string;
|
|
3580
|
+
};
|
|
3581
|
+
postTypes: PostTypeAnalysis[];
|
|
3582
|
+
attachments: {
|
|
3583
|
+
count: number;
|
|
3584
|
+
items: AttachmentInfo[];
|
|
3585
|
+
};
|
|
3586
|
+
categories: number;
|
|
3587
|
+
tags: number;
|
|
3588
|
+
authors: WpAuthorInfo[];
|
|
3589
|
+
/** Navigation menus found in the export */
|
|
3590
|
+
navMenus?: NavMenuAnalysis[];
|
|
3591
|
+
/** Custom taxonomies (beyond categories/tags) */
|
|
3592
|
+
customTaxonomies?: TaxonomyAnalysis[];
|
|
3593
|
+
/** Reusable blocks (wp_block post type) - will be imported as sections */
|
|
3594
|
+
reusableBlocks?: ReusableBlockAnalysis[];
|
|
3595
|
+
/** Source-specific custom fields analysis */
|
|
3596
|
+
customFields?: Array<{
|
|
3597
|
+
key: string;
|
|
3598
|
+
count: number;
|
|
3599
|
+
samples: string[];
|
|
3600
|
+
suggestedField: string;
|
|
3601
|
+
suggestedType: "string" | "number" | "boolean" | "date" | "json";
|
|
3602
|
+
isInternal: boolean;
|
|
3603
|
+
}>;
|
|
3604
|
+
/** Detected multilingual plugin. Absent when none detected. */
|
|
3605
|
+
i18n?: I18nDetection;
|
|
3606
|
+
}
|
|
3607
|
+
/** Normalized content item - produced by all sources */
|
|
3608
|
+
interface NormalizedItem {
|
|
3609
|
+
/** Original ID from source */
|
|
3610
|
+
sourceId: string | number;
|
|
3611
|
+
/** WordPress post type */
|
|
3612
|
+
postType: string;
|
|
3613
|
+
/** Content status */
|
|
3614
|
+
status: "publish" | "draft" | "pending" | "private" | "future";
|
|
3615
|
+
/** URL slug */
|
|
3616
|
+
slug: string;
|
|
3617
|
+
/** Title */
|
|
3618
|
+
title: string;
|
|
3619
|
+
/** Content as Portable Text (already converted) */
|
|
3620
|
+
content: PortableTextBlock[];
|
|
3621
|
+
/** Excerpt/summary */
|
|
3622
|
+
excerpt?: string;
|
|
3623
|
+
/** Publication date */
|
|
3624
|
+
date: Date;
|
|
3625
|
+
/** Last modified date */
|
|
3626
|
+
modified?: Date;
|
|
3627
|
+
/** Author identifier */
|
|
3628
|
+
author?: string;
|
|
3629
|
+
/** Category slugs */
|
|
3630
|
+
categories?: string[];
|
|
3631
|
+
/** Tag slugs */
|
|
3632
|
+
tags?: string[];
|
|
3633
|
+
/** Custom meta fields */
|
|
3634
|
+
meta?: Record<string, unknown>;
|
|
3635
|
+
/** Featured image URL */
|
|
3636
|
+
featuredImage?: string;
|
|
3637
|
+
/** Parent post ID (for hierarchical content like pages) */
|
|
3638
|
+
parentId?: string | number;
|
|
3639
|
+
/** Menu order for sorting */
|
|
3640
|
+
menuOrder?: number;
|
|
3641
|
+
/** Custom taxonomy assignments beyond categories/tags */
|
|
3642
|
+
customTaxonomies?: Record<string, string[]>;
|
|
3643
|
+
/** BCP 47 locale code. When omitted, defaults to defaultLocale. */
|
|
3644
|
+
locale?: string;
|
|
3645
|
+
/**
|
|
3646
|
+
* Source-side translation group ID (opaque string from the origin system).
|
|
3647
|
+
* Items sharing the same translationGroup are linked as translations.
|
|
3648
|
+
* Resolved to a Dineway translation_group ULID during execute.
|
|
3649
|
+
*/
|
|
3650
|
+
translationGroup?: string;
|
|
3651
|
+
}
|
|
3652
|
+
/** Post type mapping configuration */
|
|
3653
|
+
interface PostTypeMapping {
|
|
3654
|
+
enabled: boolean;
|
|
3655
|
+
collection: string;
|
|
3656
|
+
}
|
|
3657
|
+
/** Import configuration */
|
|
3658
|
+
interface ImportConfig {
|
|
3659
|
+
postTypeMappings: Record<string, PostTypeMapping>;
|
|
3660
|
+
skipExisting?: boolean;
|
|
3661
|
+
}
|
|
3662
|
+
/** Options for fetching content */
|
|
3663
|
+
interface FetchOptions {
|
|
3664
|
+
/** Post types to fetch */
|
|
3665
|
+
postTypes: string[];
|
|
3666
|
+
/** Whether to include drafts */
|
|
3667
|
+
includeDrafts?: boolean;
|
|
3668
|
+
/** Limit number of items (for testing) */
|
|
3669
|
+
limit?: number;
|
|
3670
|
+
}
|
|
3671
|
+
/** Import result */
|
|
3672
|
+
interface ImportResult {
|
|
3673
|
+
success: boolean;
|
|
3674
|
+
imported: number;
|
|
3675
|
+
skipped: number;
|
|
3676
|
+
errors: Array<{
|
|
3677
|
+
title: string;
|
|
3678
|
+
error: string;
|
|
3679
|
+
}>;
|
|
3680
|
+
byCollection: Record<string, number>;
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* An import source provides content from an external system.
|
|
3684
|
+
* All sources produce the same normalized analysis and content format.
|
|
3685
|
+
*/
|
|
3686
|
+
interface ImportSource {
|
|
3687
|
+
/** Unique identifier */
|
|
3688
|
+
id: string;
|
|
3689
|
+
/** Display name */
|
|
3690
|
+
name: string;
|
|
3691
|
+
/** Description for UI */
|
|
3692
|
+
description: string;
|
|
3693
|
+
/** Icon identifier */
|
|
3694
|
+
icon: "upload" | "globe" | "wordpress" | "plug";
|
|
3695
|
+
/** Whether this source requires a file upload */
|
|
3696
|
+
requiresFile?: boolean;
|
|
3697
|
+
/** Whether this source can probe URLs */
|
|
3698
|
+
canProbe?: boolean;
|
|
3699
|
+
/**
|
|
3700
|
+
* Probe a URL to see if this source can handle it.
|
|
3701
|
+
* Returns null if not applicable.
|
|
3702
|
+
*/
|
|
3703
|
+
probe?(url: string): Promise<SourceProbeResult | null>;
|
|
3704
|
+
/**
|
|
3705
|
+
* Analyze content from this source.
|
|
3706
|
+
* Returns normalized ImportAnalysis.
|
|
3707
|
+
*/
|
|
3708
|
+
analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis>;
|
|
3709
|
+
/**
|
|
3710
|
+
* Stream content items for import.
|
|
3711
|
+
* Yields normalized content items.
|
|
3712
|
+
*/
|
|
3713
|
+
fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem>;
|
|
3714
|
+
/**
|
|
3715
|
+
* Fetch a media item's data.
|
|
3716
|
+
* Used for media import.
|
|
3717
|
+
*/
|
|
3718
|
+
fetchMedia?(url: string, input: SourceInput): Promise<Blob>;
|
|
3719
|
+
}
|
|
3720
|
+
/** Context passed to import sources */
|
|
3721
|
+
interface ImportContext {
|
|
3722
|
+
/** Database connection for schema checks */
|
|
3723
|
+
db?: unknown;
|
|
3724
|
+
/** Function to check existing collections */
|
|
3725
|
+
getExistingCollections?: () => Promise<Map<string, {
|
|
3726
|
+
slug: string;
|
|
3727
|
+
fields: Map<string, {
|
|
3728
|
+
type: string;
|
|
3729
|
+
}>;
|
|
3730
|
+
}>>;
|
|
3731
|
+
}
|
|
3732
|
+
//#endregion
|
|
3733
|
+
//#region src/import/sections.d.ts
|
|
3734
|
+
/**
|
|
3735
|
+
* Result of sections import operation
|
|
3736
|
+
*/
|
|
3737
|
+
interface SectionsImportResult {
|
|
3738
|
+
/** Number of sections created */
|
|
3739
|
+
sectionsCreated: number;
|
|
3740
|
+
/** Number of sections skipped (already exist) */
|
|
3741
|
+
sectionsSkipped: number;
|
|
3742
|
+
/** Errors encountered during import */
|
|
3743
|
+
errors: Array<{
|
|
3744
|
+
title: string;
|
|
3745
|
+
error: string;
|
|
3746
|
+
}>;
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Import reusable blocks (wp_block post type) from WXR as sections
|
|
3750
|
+
*
|
|
3751
|
+
* @param posts - All posts from WXR (will filter to wp_block)
|
|
3752
|
+
* @param db - Database connection
|
|
3753
|
+
* @returns Import result with counts
|
|
3754
|
+
*/
|
|
3755
|
+
declare function importReusableBlocksAsSections(posts: WxrPost[], db: Kysely<Database>): Promise<SectionsImportResult>;
|
|
3756
|
+
//#endregion
|
|
3757
|
+
//#region src/import/registry.d.ts
|
|
3758
|
+
/**
|
|
3759
|
+
* Register an import source
|
|
3760
|
+
*/
|
|
3761
|
+
declare function registerSource(source: ImportSource): void;
|
|
3762
|
+
/**
|
|
3763
|
+
* Get a source by ID
|
|
3764
|
+
*/
|
|
3765
|
+
declare function getSource(id: string): ImportSource | undefined;
|
|
3766
|
+
/**
|
|
3767
|
+
* Get all registered sources
|
|
3768
|
+
*/
|
|
3769
|
+
declare function getAllSources(): ImportSource[];
|
|
3770
|
+
/**
|
|
3771
|
+
* Get sources that can handle file uploads
|
|
3772
|
+
*/
|
|
3773
|
+
declare function getFileSources(): ImportSource[];
|
|
3774
|
+
/**
|
|
3775
|
+
* Get sources that can probe URLs
|
|
3776
|
+
*/
|
|
3777
|
+
declare function getUrlSources(): ImportSource[];
|
|
3778
|
+
/**
|
|
3779
|
+
* Probe a URL against all registered sources
|
|
3780
|
+
*
|
|
3781
|
+
* Returns probe results sorted by confidence (definite > likely > possible)
|
|
3782
|
+
*/
|
|
3783
|
+
declare function probeUrl(url: string): Promise<ProbeResult>;
|
|
3784
|
+
/**
|
|
3785
|
+
* Clear all registered sources (useful for testing)
|
|
3786
|
+
*/
|
|
3787
|
+
declare function clearSources(): void;
|
|
3788
|
+
//#endregion
|
|
3789
|
+
//#region src/import/sources/wxr.d.ts
|
|
3790
|
+
declare const wxrSource: ImportSource;
|
|
3791
|
+
/**
|
|
3792
|
+
* Parse a WXR date with the correct fallback chain:
|
|
3793
|
+
* 1. GMT date (always UTC, most reliable)
|
|
3794
|
+
* 2. pubDate (RFC 2822, includes timezone offset)
|
|
3795
|
+
* 3. Site-local date (MySQL datetime without timezone, imprecise but best available)
|
|
3796
|
+
*
|
|
3797
|
+
* Returns undefined when none of the inputs yield a valid date.
|
|
3798
|
+
* Callers that need a guaranteed Date should use `?? new Date()`.
|
|
3799
|
+
*/
|
|
3800
|
+
declare function parseWxrDate(gmtDate: string | undefined, pubDate: string | undefined, localDate: string | undefined): Date | undefined;
|
|
3801
|
+
//#endregion
|
|
3802
|
+
//#region src/import/sources/wordpress-rest.d.ts
|
|
3803
|
+
declare const wordpressRestSource: ImportSource;
|
|
3804
|
+
//#endregion
|
|
3805
|
+
//#region src/preview/tokens.d.ts
|
|
3806
|
+
/**
|
|
3807
|
+
* Preview token generation and verification
|
|
3808
|
+
*
|
|
3809
|
+
* Tokens are compact, URL-safe, and HMAC-signed.
|
|
3810
|
+
* Format: base64url(JSON payload).base64url(HMAC signature)
|
|
3811
|
+
*
|
|
3812
|
+
* Payload: { cid: contentId, exp: expiryTimestamp, iat: issuedAt }
|
|
3813
|
+
*/
|
|
3814
|
+
/**
|
|
3815
|
+
* Preview token payload
|
|
3816
|
+
*/
|
|
3817
|
+
interface PreviewTokenPayload {
|
|
3818
|
+
/** Content ID in format "collection:id" (e.g., "posts:abc123") */
|
|
3819
|
+
cid: string;
|
|
3820
|
+
/** Expiry timestamp (seconds since epoch) */
|
|
3821
|
+
exp: number;
|
|
3822
|
+
/** Issued at timestamp (seconds since epoch) */
|
|
3823
|
+
iat: number;
|
|
3824
|
+
}
|
|
3825
|
+
/**
|
|
3826
|
+
* Options for generating a preview token
|
|
3827
|
+
*/
|
|
3828
|
+
interface GeneratePreviewTokenOptions {
|
|
3829
|
+
/** Content ID in format "collection:id" */
|
|
3830
|
+
contentId: string;
|
|
3831
|
+
/** How long the token is valid. Accepts "1h", "30m", "1d", or seconds as number. Default: "1h" */
|
|
3832
|
+
expiresIn?: string | number;
|
|
3833
|
+
/** Secret key for signing. Should be from environment variable. */
|
|
3834
|
+
secret: string;
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Generate a preview token for content
|
|
3838
|
+
*
|
|
3839
|
+
* @example
|
|
3840
|
+
* ```ts
|
|
3841
|
+
* const token = await generatePreviewToken({
|
|
3842
|
+
* contentId: "posts:abc123",
|
|
3843
|
+
* expiresIn: "1h",
|
|
3844
|
+
* secret: process.env.PREVIEW_SECRET!,
|
|
3845
|
+
* });
|
|
3846
|
+
* ```
|
|
3847
|
+
*/
|
|
3848
|
+
declare function generatePreviewToken(options: GeneratePreviewTokenOptions): Promise<string>;
|
|
3849
|
+
/**
|
|
3850
|
+
* Result of verifying a preview token
|
|
3851
|
+
*/
|
|
3852
|
+
type VerifyPreviewTokenResult = {
|
|
3853
|
+
valid: true;
|
|
3854
|
+
payload: PreviewTokenPayload;
|
|
3855
|
+
} | {
|
|
3856
|
+
valid: false;
|
|
3857
|
+
error: "invalid" | "expired" | "malformed" | "none";
|
|
3858
|
+
};
|
|
3859
|
+
/**
|
|
3860
|
+
* Options for verifyPreviewToken
|
|
3861
|
+
*/
|
|
3862
|
+
type VerifyPreviewTokenOptions = {
|
|
3863
|
+
/** Secret key for verifying tokens */secret: string;
|
|
3864
|
+
} & ({
|
|
3865
|
+
url: URL;
|
|
3866
|
+
} | {
|
|
3867
|
+
/** Preview token string (can be null) */token: string | null | undefined;
|
|
3868
|
+
});
|
|
3869
|
+
/**
|
|
3870
|
+
* Verify a preview token and return the payload
|
|
3871
|
+
*
|
|
3872
|
+
* @example
|
|
3873
|
+
* ```ts
|
|
3874
|
+
* // With URL (extracts _preview query param)
|
|
3875
|
+
* const result = await verifyPreviewToken({
|
|
3876
|
+
* url: Astro.url,
|
|
3877
|
+
* secret: import.meta.env.PREVIEW_SECRET,
|
|
3878
|
+
* });
|
|
3879
|
+
*
|
|
3880
|
+
* // With token directly
|
|
3881
|
+
* const result = await verifyPreviewToken({
|
|
3882
|
+
* token: someToken,
|
|
3883
|
+
* secret: import.meta.env.PREVIEW_SECRET,
|
|
3884
|
+
* });
|
|
3885
|
+
*
|
|
3886
|
+
* if (result.valid) {
|
|
3887
|
+
* console.log(result.payload.cid); // "posts:abc123"
|
|
3888
|
+
* }
|
|
3889
|
+
* ```
|
|
3890
|
+
*/
|
|
3891
|
+
declare function verifyPreviewToken(options: VerifyPreviewTokenOptions): Promise<VerifyPreviewTokenResult>;
|
|
3892
|
+
/**
|
|
3893
|
+
* Parse a content ID into collection and id
|
|
3894
|
+
*/
|
|
3895
|
+
declare function parseContentId(contentId: string): {
|
|
3896
|
+
collection: string;
|
|
3897
|
+
id: string;
|
|
3898
|
+
};
|
|
3899
|
+
//#endregion
|
|
3900
|
+
//#region src/preview/urls.d.ts
|
|
3901
|
+
/**
|
|
3902
|
+
* Preview URL generation
|
|
3903
|
+
*
|
|
3904
|
+
* Creates preview URLs that include a signed token for accessing draft content.
|
|
3905
|
+
*/
|
|
3906
|
+
/**
|
|
3907
|
+
* Options for generating a preview URL
|
|
3908
|
+
*/
|
|
3909
|
+
interface GetPreviewUrlOptions {
|
|
3910
|
+
/** Collection slug (e.g., "posts") */
|
|
3911
|
+
collection: string;
|
|
3912
|
+
/** Content ID or slug */
|
|
3913
|
+
id: string;
|
|
3914
|
+
/** Secret key for signing the token */
|
|
3915
|
+
secret: string;
|
|
3916
|
+
/** How long the preview URL is valid. Default: "1h" */
|
|
3917
|
+
expiresIn?: string | number;
|
|
3918
|
+
/** Base URL of the site. If not provided, returns a relative URL. */
|
|
3919
|
+
baseUrl?: string;
|
|
3920
|
+
/** Custom path pattern. Use {collection} and {id} as placeholders. Default: "/{collection}/{id}" */
|
|
3921
|
+
pathPattern?: string;
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Generate a preview URL for content
|
|
3925
|
+
*
|
|
3926
|
+
* The URL includes a `_preview` query parameter with a signed token.
|
|
3927
|
+
*
|
|
3928
|
+
* @example
|
|
3929
|
+
* ```ts
|
|
3930
|
+
* const url = await getPreviewUrl({
|
|
3931
|
+
* collection: "posts",
|
|
3932
|
+
* id: "hello-world",
|
|
3933
|
+
* secret: process.env.PREVIEW_SECRET!,
|
|
3934
|
+
* });
|
|
3935
|
+
* // Returns: /posts/hello-world?_preview=eyJj...
|
|
3936
|
+
*
|
|
3937
|
+
* // With base URL:
|
|
3938
|
+
* const fullUrl = await getPreviewUrl({
|
|
3939
|
+
* collection: "posts",
|
|
3940
|
+
* id: "hello-world",
|
|
3941
|
+
* secret: process.env.PREVIEW_SECRET!,
|
|
3942
|
+
* baseUrl: "https://example.com",
|
|
3943
|
+
* });
|
|
3944
|
+
* // Returns: https://example.com/posts/hello-world?_preview=eyJj...
|
|
3945
|
+
*
|
|
3946
|
+
* // Custom path pattern:
|
|
3947
|
+
* const customUrl = await getPreviewUrl({
|
|
3948
|
+
* collection: "posts",
|
|
3949
|
+
* id: "hello-world",
|
|
3950
|
+
* secret: process.env.PREVIEW_SECRET!,
|
|
3951
|
+
* pathPattern: "/blog/{id}",
|
|
3952
|
+
* });
|
|
3953
|
+
* // Returns: /blog/hello-world?_preview=eyJj...
|
|
3954
|
+
* ```
|
|
3955
|
+
*/
|
|
3956
|
+
declare function getPreviewUrl(options: GetPreviewUrlOptions): Promise<string>;
|
|
3957
|
+
/**
|
|
3958
|
+
* Build a preview URL from a token (when you already have the token)
|
|
3959
|
+
*
|
|
3960
|
+
* @example
|
|
3961
|
+
* ```ts
|
|
3962
|
+
* const url = buildPreviewUrl({
|
|
3963
|
+
* path: "/posts/hello-world",
|
|
3964
|
+
* token: existingToken,
|
|
3965
|
+
* });
|
|
3966
|
+
* ```
|
|
3967
|
+
*/
|
|
3968
|
+
declare function buildPreviewUrl(options: {
|
|
3969
|
+
path: string;
|
|
3970
|
+
token: string;
|
|
3971
|
+
baseUrl?: string;
|
|
3972
|
+
}): string;
|
|
3973
|
+
//#endregion
|
|
3974
|
+
//#region src/preview/helpers.d.ts
|
|
3975
|
+
/**
|
|
3976
|
+
* Preview helpers for Astro pages
|
|
3977
|
+
*/
|
|
3978
|
+
/**
|
|
3979
|
+
* Check if a request is a preview request
|
|
3980
|
+
*
|
|
3981
|
+
* @example
|
|
3982
|
+
* ```ts
|
|
3983
|
+
* const isPreview = isPreviewRequest(Astro.url);
|
|
3984
|
+
* ```
|
|
3985
|
+
*/
|
|
3986
|
+
declare function isPreviewRequest(url: URL): boolean;
|
|
3987
|
+
/**
|
|
3988
|
+
* Get the preview token from a URL
|
|
3989
|
+
*
|
|
3990
|
+
* @example
|
|
3991
|
+
* ```ts
|
|
3992
|
+
* const token = getPreviewToken(Astro.url);
|
|
3993
|
+
* ```
|
|
3994
|
+
*/
|
|
3995
|
+
declare function getPreviewToken(url: URL): string | null;
|
|
3996
|
+
//#endregion
|
|
3997
|
+
//#region src/preview/routes.d.ts
|
|
3998
|
+
/**
|
|
3999
|
+
* Preview mode route gating.
|
|
4000
|
+
*
|
|
4001
|
+
* Preview is read-only with no authenticated user. All `/_dineway/`
|
|
4002
|
+
* routes are blocked by default. Only specific read-only API prefixes
|
|
4003
|
+
* are allowlisted.
|
|
4004
|
+
*/
|
|
4005
|
+
declare function isBlockedInPreview(pathname: string): boolean;
|
|
4006
|
+
//#endregion
|
|
4007
|
+
//#region src/preview/loading.d.ts
|
|
4008
|
+
declare function renderPreviewLoadingPage(): string;
|
|
4009
|
+
//#endregion
|
|
4010
|
+
//#region src/preview/snapshot-types.d.ts
|
|
4011
|
+
interface Snapshot {
|
|
4012
|
+
tables: Record<string, Record<string, unknown>[]>;
|
|
4013
|
+
schema: Record<string, {
|
|
4014
|
+
columns: string[];
|
|
4015
|
+
types?: Record<string, string>;
|
|
4016
|
+
}>;
|
|
4017
|
+
generatedAt: string;
|
|
4018
|
+
}
|
|
4019
|
+
//#endregion
|
|
4020
|
+
//#region src/preview/snapshot-database.d.ts
|
|
4021
|
+
interface AppliedSnapshotMeta {
|
|
4022
|
+
fetchedAt: number;
|
|
4023
|
+
generatedAt: string;
|
|
4024
|
+
}
|
|
4025
|
+
interface ApplySnapshotToDatabaseOptions {
|
|
4026
|
+
fetchedAt?: number;
|
|
4027
|
+
reset?: boolean;
|
|
4028
|
+
}
|
|
4029
|
+
declare function dropSessionDatabaseTables(db: Kysely<Database>): Promise<void>;
|
|
4030
|
+
declare function applySnapshotToDatabase(db: Kysely<Database>, snapshot: Snapshot, options?: ApplySnapshotToDatabaseOptions): Promise<void>;
|
|
4031
|
+
declare function getAppliedSnapshotMeta(db: Kysely<Database>): Promise<AppliedSnapshotMeta | null>;
|
|
4032
|
+
//#endregion
|
|
4033
|
+
//#region src/preview/toolbar.d.ts
|
|
4034
|
+
/**
|
|
4035
|
+
* Preview toolbar injected into preview responses.
|
|
4036
|
+
*
|
|
4037
|
+
* Plain HTML with inline styles/script so sidecar middleware can use it
|
|
4038
|
+
* without any framework/runtime dependency.
|
|
4039
|
+
*/
|
|
4040
|
+
interface PreviewToolbarConfig {
|
|
4041
|
+
generatedAt?: string;
|
|
4042
|
+
source?: string;
|
|
4043
|
+
error?: string;
|
|
4044
|
+
}
|
|
4045
|
+
declare function renderPreviewToolbar(config: PreviewToolbarConfig): string;
|
|
4046
|
+
//#endregion
|
|
4047
|
+
//#region src/settings/index.d.ts
|
|
4048
|
+
/**
|
|
4049
|
+
* Get a single site setting by key
|
|
4050
|
+
*
|
|
4051
|
+
* Returns `undefined` if the setting has not been configured.
|
|
4052
|
+
* For media settings (logo, favicon), the URL is resolved automatically.
|
|
4053
|
+
*
|
|
4054
|
+
* @param key - The setting key (e.g., "title", "logo", "social")
|
|
4055
|
+
* @returns The setting value, or undefined if not set
|
|
4056
|
+
*
|
|
4057
|
+
* @example
|
|
4058
|
+
* ```ts
|
|
4059
|
+
* import { getSiteSetting } from "dineway";
|
|
4060
|
+
*
|
|
4061
|
+
* const title = await getSiteSetting("title");
|
|
4062
|
+
* const logo = await getSiteSetting("logo");
|
|
4063
|
+
* console.log(logo?.url); // Resolved URL
|
|
4064
|
+
* ```
|
|
4065
|
+
*/
|
|
4066
|
+
declare function getSiteSetting<K extends SiteSettingKey>(key: K): Promise<SiteSettings[K] | undefined>;
|
|
4067
|
+
/**
|
|
4068
|
+
* Get all site settings
|
|
4069
|
+
*
|
|
4070
|
+
* Returns all configured settings. Unset values are undefined.
|
|
4071
|
+
* Media references (logo/favicon) are resolved to include URLs.
|
|
4072
|
+
*
|
|
4073
|
+
* @example
|
|
4074
|
+
* ```ts
|
|
4075
|
+
* import { getSiteSettings } from "dineway";
|
|
4076
|
+
*
|
|
4077
|
+
* const settings = await getSiteSettings();
|
|
4078
|
+
* console.log(settings.title); // "My Site"
|
|
4079
|
+
* console.log(settings.logo?.url); // "/_dineway/api/media/file/abc123"
|
|
4080
|
+
* ```
|
|
4081
|
+
*/
|
|
4082
|
+
declare function getSiteSettings(): Promise<Partial<SiteSettings>>;
|
|
4083
|
+
/**
|
|
4084
|
+
* Set site settings (internal function used by admin API)
|
|
4085
|
+
*
|
|
4086
|
+
* Merges provided settings with existing ones. Only provided fields are updated.
|
|
4087
|
+
* Media references should include just the mediaId; URLs are resolved on read.
|
|
4088
|
+
*
|
|
4089
|
+
* @param settings - Partial settings object with values to update
|
|
4090
|
+
* @param db - Kysely database instance
|
|
4091
|
+
* @returns Promise that resolves when settings are saved
|
|
4092
|
+
*
|
|
4093
|
+
* @internal
|
|
4094
|
+
*
|
|
4095
|
+
* @example
|
|
4096
|
+
* ```ts
|
|
4097
|
+
* // Update multiple settings at once
|
|
4098
|
+
* await setSiteSettings({
|
|
4099
|
+
* title: "My Site",
|
|
4100
|
+
* tagline: "Welcome",
|
|
4101
|
+
* logo: { mediaId: "med_123", alt: "Logo" }
|
|
4102
|
+
* }, db);
|
|
4103
|
+
* ```
|
|
4104
|
+
*/
|
|
4105
|
+
declare function setSiteSettings(settings: Partial<SiteSettings>, db: Kysely<Database>): Promise<void>;
|
|
4106
|
+
/**
|
|
4107
|
+
* Get a single plugin setting by key.
|
|
4108
|
+
*
|
|
4109
|
+
* Plugin settings are stored in the options table under
|
|
4110
|
+
* `plugin:<pluginId>:settings:<key>`.
|
|
4111
|
+
*/
|
|
4112
|
+
declare function getPluginSetting<T = unknown>(pluginId: string, key: string): Promise<T | undefined>;
|
|
4113
|
+
/**
|
|
4114
|
+
* Get all persisted plugin settings for a plugin.
|
|
4115
|
+
*
|
|
4116
|
+
* Defaults declared in `admin.settingsSchema` are not materialized
|
|
4117
|
+
* automatically; callers should apply their own fallback defaults.
|
|
4118
|
+
*/
|
|
4119
|
+
declare function getPluginSettings(pluginId: string): Promise<Record<string, unknown>>;
|
|
4120
|
+
//#endregion
|
|
4121
|
+
//#region src/comments/query.d.ts
|
|
4122
|
+
interface GetCommentsOptions {
|
|
4123
|
+
collection: string;
|
|
4124
|
+
contentId: string;
|
|
4125
|
+
threaded?: boolean;
|
|
4126
|
+
}
|
|
4127
|
+
interface GetCommentsResult {
|
|
4128
|
+
items: PublicComment[];
|
|
4129
|
+
total: number;
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Get approved comments for a content item.
|
|
4133
|
+
*
|
|
4134
|
+
* @example
|
|
4135
|
+
* ```ts
|
|
4136
|
+
* import { getComments } from "dineway";
|
|
4137
|
+
*
|
|
4138
|
+
* const { items, total } = await getComments({
|
|
4139
|
+
* collection: "posts",
|
|
4140
|
+
* contentId: post.id,
|
|
4141
|
+
* threaded: true,
|
|
4142
|
+
* });
|
|
4143
|
+
* ```
|
|
4144
|
+
*/
|
|
4145
|
+
declare function getComments(options: GetCommentsOptions): Promise<GetCommentsResult>;
|
|
4146
|
+
/**
|
|
4147
|
+
* Get the count of approved comments for a content item.
|
|
4148
|
+
*
|
|
4149
|
+
* @example
|
|
4150
|
+
* ```ts
|
|
4151
|
+
* import { getCommentCount } from "dineway";
|
|
4152
|
+
*
|
|
4153
|
+
* const count = await getCommentCount("posts", post.id);
|
|
4154
|
+
* ```
|
|
4155
|
+
*/
|
|
4156
|
+
declare function getCommentCount(collection: string, contentId: string): Promise<number>;
|
|
4157
|
+
//#endregion
|
|
4158
|
+
//#region src/menus/types.d.ts
|
|
4159
|
+
/**
|
|
4160
|
+
* Menu item types
|
|
4161
|
+
*/
|
|
4162
|
+
type MenuItemType = string;
|
|
4163
|
+
/**
|
|
4164
|
+
* Menu item as returned to templates (with resolved URL)
|
|
4165
|
+
*/
|
|
4166
|
+
interface MenuItem {
|
|
4167
|
+
id: string;
|
|
4168
|
+
label: string;
|
|
4169
|
+
url: string;
|
|
4170
|
+
target?: string;
|
|
4171
|
+
titleAttr?: string;
|
|
4172
|
+
cssClasses?: string;
|
|
4173
|
+
children: MenuItem[];
|
|
4174
|
+
}
|
|
4175
|
+
/**
|
|
4176
|
+
* Menu as returned to templates
|
|
4177
|
+
*/
|
|
4178
|
+
interface Menu {
|
|
4179
|
+
id: string;
|
|
4180
|
+
name: string;
|
|
4181
|
+
label: string;
|
|
4182
|
+
items: MenuItem[];
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* Input for creating a menu item
|
|
4186
|
+
*/
|
|
4187
|
+
interface CreateMenuItemInput {
|
|
4188
|
+
type: MenuItemType;
|
|
4189
|
+
label: string;
|
|
4190
|
+
referenceCollection?: string;
|
|
4191
|
+
referenceId?: string;
|
|
4192
|
+
customUrl?: string;
|
|
4193
|
+
target?: string;
|
|
4194
|
+
titleAttr?: string;
|
|
4195
|
+
cssClasses?: string;
|
|
4196
|
+
parentId?: string;
|
|
4197
|
+
sortOrder?: number;
|
|
4198
|
+
}
|
|
4199
|
+
/**
|
|
4200
|
+
* Input for updating a menu item
|
|
4201
|
+
*/
|
|
4202
|
+
interface UpdateMenuItemInput {
|
|
4203
|
+
label?: string;
|
|
4204
|
+
customUrl?: string;
|
|
4205
|
+
target?: string;
|
|
4206
|
+
titleAttr?: string;
|
|
4207
|
+
cssClasses?: string;
|
|
4208
|
+
parentId?: string | null;
|
|
4209
|
+
sortOrder?: number;
|
|
4210
|
+
}
|
|
4211
|
+
/**
|
|
4212
|
+
* Input for creating a menu
|
|
4213
|
+
*/
|
|
4214
|
+
interface CreateMenuInput {
|
|
4215
|
+
name: string;
|
|
4216
|
+
label: string;
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Input for updating a menu
|
|
4220
|
+
*/
|
|
4221
|
+
interface UpdateMenuInput {
|
|
4222
|
+
label?: string;
|
|
4223
|
+
}
|
|
4224
|
+
/**
|
|
4225
|
+
* Input for reordering menu items
|
|
4226
|
+
*/
|
|
4227
|
+
interface ReorderMenuItemsInput {
|
|
4228
|
+
items: Array<{
|
|
4229
|
+
id: string;
|
|
4230
|
+
parentId: string | null;
|
|
4231
|
+
sortOrder: number;
|
|
4232
|
+
}>;
|
|
4233
|
+
}
|
|
4234
|
+
//#endregion
|
|
4235
|
+
//#region src/menus/index.d.ts
|
|
4236
|
+
/**
|
|
4237
|
+
* Get menu by name with resolved URLs
|
|
4238
|
+
*
|
|
4239
|
+
* @example
|
|
4240
|
+
* ```ts
|
|
4241
|
+
* import { getMenu } from "dineway";
|
|
4242
|
+
*
|
|
4243
|
+
* const menu = await getMenu("primary");
|
|
4244
|
+
* if (menu) {
|
|
4245
|
+
* console.log(menu.items); // Array of MenuItem with resolved URLs
|
|
4246
|
+
* }
|
|
4247
|
+
* ```
|
|
4248
|
+
*/
|
|
4249
|
+
declare function getMenu(name: string): Promise<Menu | null>;
|
|
4250
|
+
/**
|
|
4251
|
+
* Get all menus (without items - for admin list)
|
|
4252
|
+
*
|
|
4253
|
+
* @example
|
|
4254
|
+
* ```ts
|
|
4255
|
+
* import { getMenus } from "dineway";
|
|
4256
|
+
*
|
|
4257
|
+
* const menus = await getMenus();
|
|
4258
|
+
* console.log(menus); // [{ id, name, label }]
|
|
4259
|
+
* ```
|
|
4260
|
+
*/
|
|
4261
|
+
declare function getMenus(): Promise<Array<{
|
|
4262
|
+
id: string;
|
|
4263
|
+
name: string;
|
|
4264
|
+
label: string;
|
|
4265
|
+
}>>;
|
|
4266
|
+
//#endregion
|
|
4267
|
+
//#region src/bylines/index.d.ts
|
|
4268
|
+
/**
|
|
4269
|
+
* Get a byline by ID.
|
|
4270
|
+
*
|
|
4271
|
+
* @example
|
|
4272
|
+
* ```ts
|
|
4273
|
+
* import { getByline } from "dineway";
|
|
4274
|
+
*
|
|
4275
|
+
* const byline = await getByline("01HXYZ...");
|
|
4276
|
+
* if (byline) {
|
|
4277
|
+
* console.log(byline.displayName);
|
|
4278
|
+
* }
|
|
4279
|
+
* ```
|
|
4280
|
+
*/
|
|
4281
|
+
declare function getByline(id: string): Promise<BylineSummary | null>;
|
|
4282
|
+
/**
|
|
4283
|
+
* Get a byline by slug.
|
|
4284
|
+
*
|
|
4285
|
+
* @example
|
|
4286
|
+
* ```ts
|
|
4287
|
+
* import { getBylineBySlug } from "dineway";
|
|
4288
|
+
*
|
|
4289
|
+
* const byline = await getBylineBySlug("jane-doe");
|
|
4290
|
+
* if (byline) {
|
|
4291
|
+
* console.log(byline.displayName); // "Jane Doe"
|
|
4292
|
+
* }
|
|
4293
|
+
* ```
|
|
4294
|
+
*/
|
|
4295
|
+
declare function getBylineBySlug(slug: string): Promise<BylineSummary | null>;
|
|
4296
|
+
//#endregion
|
|
4297
|
+
//#region src/taxonomies/types.d.ts
|
|
4298
|
+
/**
|
|
4299
|
+
* Taxonomy types for Dineway Agentic Web builder
|
|
4300
|
+
*/
|
|
4301
|
+
/**
|
|
4302
|
+
* Taxonomy definition - describes a taxonomy like "category" or "tag"
|
|
4303
|
+
*/
|
|
4304
|
+
interface TaxonomyDef {
|
|
4305
|
+
id: string;
|
|
4306
|
+
name: string;
|
|
4307
|
+
label: string;
|
|
4308
|
+
labelSingular?: string;
|
|
4309
|
+
hierarchical: boolean;
|
|
4310
|
+
collections: string[];
|
|
4311
|
+
}
|
|
4312
|
+
/**
|
|
4313
|
+
* Taxonomy term - a specific term within a taxonomy (e.g., "News" in "category")
|
|
4314
|
+
*/
|
|
4315
|
+
interface TaxonomyTerm {
|
|
4316
|
+
id: string;
|
|
4317
|
+
name: string;
|
|
4318
|
+
slug: string;
|
|
4319
|
+
label: string;
|
|
4320
|
+
parentId?: string;
|
|
4321
|
+
description?: string;
|
|
4322
|
+
children: TaxonomyTerm[];
|
|
4323
|
+
count?: number;
|
|
4324
|
+
}
|
|
4325
|
+
/**
|
|
4326
|
+
* Flat version for DB row
|
|
4327
|
+
*/
|
|
4328
|
+
interface TaxonomyTermRow {
|
|
4329
|
+
id: string;
|
|
4330
|
+
name: string;
|
|
4331
|
+
slug: string;
|
|
4332
|
+
label: string;
|
|
4333
|
+
parent_id: string | null;
|
|
4334
|
+
data: string | null;
|
|
4335
|
+
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Input for creating a term
|
|
4338
|
+
*/
|
|
4339
|
+
interface CreateTermInput {
|
|
4340
|
+
slug: string;
|
|
4341
|
+
label: string;
|
|
4342
|
+
parentId?: string;
|
|
4343
|
+
description?: string;
|
|
4344
|
+
}
|
|
4345
|
+
/**
|
|
4346
|
+
* Input for updating a term
|
|
4347
|
+
*/
|
|
4348
|
+
interface UpdateTermInput {
|
|
4349
|
+
slug?: string;
|
|
4350
|
+
label?: string;
|
|
4351
|
+
parentId?: string | null;
|
|
4352
|
+
description?: string;
|
|
4353
|
+
}
|
|
4354
|
+
//#endregion
|
|
4355
|
+
//#region src/taxonomies/index.d.ts
|
|
4356
|
+
/**
|
|
4357
|
+
* Get all taxonomy definitions
|
|
4358
|
+
*/
|
|
4359
|
+
declare function getTaxonomyDefs(): Promise<TaxonomyDef[]>;
|
|
4360
|
+
/**
|
|
4361
|
+
* Get a single taxonomy definition by name
|
|
4362
|
+
*/
|
|
4363
|
+
declare function getTaxonomyDef(name: string): Promise<TaxonomyDef | null>;
|
|
4364
|
+
/**
|
|
4365
|
+
* Get all terms for a taxonomy (as tree for hierarchical, flat for tags)
|
|
4366
|
+
*/
|
|
4367
|
+
declare function getTaxonomyTerms(taxonomyName: string): Promise<TaxonomyTerm[]>;
|
|
4368
|
+
/**
|
|
4369
|
+
* Get a single term by taxonomy and slug
|
|
4370
|
+
*/
|
|
4371
|
+
declare function getTerm(taxonomyName: string, slug: string): Promise<TaxonomyTerm | null>;
|
|
4372
|
+
/**
|
|
4373
|
+
* Get terms assigned to an entry
|
|
4374
|
+
*/
|
|
4375
|
+
declare function getEntryTerms(collection: string, entryId: string, taxonomyName?: string): Promise<TaxonomyTerm[]>;
|
|
4376
|
+
/**
|
|
4377
|
+
* Get terms for multiple entries in a single query (batched API)
|
|
4378
|
+
*
|
|
4379
|
+
* This is more efficient than calling getEntryTerms for each entry
|
|
4380
|
+
* when you need terms for a list of entries.
|
|
4381
|
+
*
|
|
4382
|
+
* @param collection - The collection type (e.g., "posts")
|
|
4383
|
+
* @param entryIds - Array of entry IDs
|
|
4384
|
+
* @param taxonomyName - The taxonomy name (e.g., "categories")
|
|
4385
|
+
* @returns Map from entry ID to array of terms
|
|
4386
|
+
*/
|
|
4387
|
+
declare function getTermsForEntries(collection: string, entryIds: string[], taxonomyName: string): Promise<Map<string, TaxonomyTerm[]>>;
|
|
4388
|
+
/**
|
|
4389
|
+
* Get entries by term (wraps getDinewayCollection)
|
|
4390
|
+
*/
|
|
4391
|
+
declare function getEntriesByTerm(collection: string, taxonomyName: string, termSlug: string): Promise<Array<{
|
|
4392
|
+
id: string;
|
|
4393
|
+
data: Record<string, unknown>;
|
|
4394
|
+
}>>;
|
|
4395
|
+
//#endregion
|
|
4396
|
+
//#region src/widgets/types.d.ts
|
|
4397
|
+
type WidgetType = "content" | "menu" | "component";
|
|
4398
|
+
interface Widget {
|
|
4399
|
+
id: string;
|
|
4400
|
+
type: WidgetType;
|
|
4401
|
+
title?: string;
|
|
4402
|
+
content?: PortableTextBlock$2[];
|
|
4403
|
+
menuName?: string;
|
|
4404
|
+
componentId?: string;
|
|
4405
|
+
componentProps?: Record<string, unknown>;
|
|
4406
|
+
}
|
|
4407
|
+
interface WidgetArea {
|
|
4408
|
+
id: string;
|
|
4409
|
+
name: string;
|
|
4410
|
+
label: string;
|
|
4411
|
+
description?: string;
|
|
4412
|
+
widgets: Widget[];
|
|
4413
|
+
}
|
|
4414
|
+
interface WidgetComponentDef {
|
|
4415
|
+
id: string;
|
|
4416
|
+
label: string;
|
|
4417
|
+
description?: string;
|
|
4418
|
+
props: Record<string, PropDef>;
|
|
4419
|
+
}
|
|
4420
|
+
interface PropDef {
|
|
4421
|
+
type: "string" | "number" | "boolean" | "select";
|
|
4422
|
+
label: string;
|
|
4423
|
+
default?: unknown;
|
|
4424
|
+
options?: Array<{
|
|
4425
|
+
value: string;
|
|
4426
|
+
label: string;
|
|
4427
|
+
}>;
|
|
4428
|
+
}
|
|
4429
|
+
interface CreateWidgetAreaInput {
|
|
4430
|
+
name: string;
|
|
4431
|
+
label: string;
|
|
4432
|
+
description?: string;
|
|
4433
|
+
}
|
|
4434
|
+
interface CreateWidgetInput {
|
|
4435
|
+
type: WidgetType;
|
|
4436
|
+
title?: string;
|
|
4437
|
+
content?: PortableTextBlock$2[];
|
|
4438
|
+
menuName?: string;
|
|
4439
|
+
componentId?: string;
|
|
4440
|
+
componentProps?: Record<string, unknown>;
|
|
4441
|
+
}
|
|
4442
|
+
interface UpdateWidgetInput extends Partial<CreateWidgetInput> {}
|
|
4443
|
+
interface ReorderWidgetsInput {
|
|
4444
|
+
widgetIds: string[];
|
|
4445
|
+
}
|
|
4446
|
+
//#endregion
|
|
4447
|
+
//#region src/widgets/index.d.ts
|
|
4448
|
+
/**
|
|
4449
|
+
* Get a widget area by name, with all its widgets
|
|
4450
|
+
*/
|
|
4451
|
+
declare function getWidgetArea(name: string): Promise<WidgetArea | null>;
|
|
4452
|
+
/**
|
|
4453
|
+
* Get all widget areas with their widgets
|
|
4454
|
+
*/
|
|
4455
|
+
declare function getWidgetAreas(): Promise<WidgetArea[]>;
|
|
4456
|
+
/**
|
|
4457
|
+
* Get available widget components (for admin UI)
|
|
4458
|
+
*/
|
|
4459
|
+
declare function getWidgetComponents(): WidgetComponentDef[];
|
|
4460
|
+
//#endregion
|
|
4461
|
+
//#region src/search/types.d.ts
|
|
4462
|
+
/**
|
|
4463
|
+
* Search Types
|
|
4464
|
+
*
|
|
4465
|
+
* Type definitions for the Dineway search system.
|
|
4466
|
+
*/
|
|
4467
|
+
/**
|
|
4468
|
+
* Search configuration for a collection
|
|
4469
|
+
*/
|
|
4470
|
+
interface SearchConfig {
|
|
4471
|
+
/** Whether search is enabled for this collection */
|
|
4472
|
+
enabled: boolean;
|
|
4473
|
+
/** Field weights for ranking (higher = more important) */
|
|
4474
|
+
weights?: Record<string, number>;
|
|
4475
|
+
}
|
|
4476
|
+
/**
|
|
4477
|
+
* Options for search queries
|
|
4478
|
+
*/
|
|
4479
|
+
interface SearchOptions {
|
|
4480
|
+
/** Collections to search (defaults to all searchable collections) */
|
|
4481
|
+
collections?: string[];
|
|
4482
|
+
/** Filter by content status (defaults to 'published') */
|
|
4483
|
+
status?: string;
|
|
4484
|
+
/** Filter by locale (omit to search all locales) */
|
|
4485
|
+
locale?: string;
|
|
4486
|
+
/** Maximum results to return (defaults to 20) */
|
|
4487
|
+
limit?: number;
|
|
4488
|
+
/** Pagination cursor */
|
|
4489
|
+
cursor?: string;
|
|
4490
|
+
}
|
|
4491
|
+
/**
|
|
4492
|
+
* Options for collection-specific search
|
|
4493
|
+
*/
|
|
4494
|
+
interface CollectionSearchOptions {
|
|
4495
|
+
/** Filter by content status (defaults to 'published') */
|
|
4496
|
+
status?: string;
|
|
4497
|
+
/** Filter by locale (omit to search all locales) */
|
|
4498
|
+
locale?: string;
|
|
4499
|
+
/** Maximum results to return (defaults to 20) */
|
|
4500
|
+
limit?: number;
|
|
4501
|
+
/** Pagination cursor */
|
|
4502
|
+
cursor?: string;
|
|
4503
|
+
}
|
|
4504
|
+
/**
|
|
4505
|
+
* A single search result
|
|
4506
|
+
*/
|
|
4507
|
+
interface SearchResult {
|
|
4508
|
+
/** Collection the result belongs to */
|
|
4509
|
+
collection: string;
|
|
4510
|
+
/** Entry ID */
|
|
4511
|
+
id: string;
|
|
4512
|
+
/** Entry slug */
|
|
4513
|
+
slug: string | null;
|
|
4514
|
+
/** Content locale */
|
|
4515
|
+
locale: string;
|
|
4516
|
+
/** Entry title (if available) */
|
|
4517
|
+
title?: string;
|
|
4518
|
+
/** Highlighted snippet showing match context */
|
|
4519
|
+
snippet?: string;
|
|
4520
|
+
/** Relevance score (higher = more relevant) */
|
|
4521
|
+
score: number;
|
|
4522
|
+
}
|
|
4523
|
+
/**
|
|
4524
|
+
* Response from a search query
|
|
4525
|
+
*/
|
|
4526
|
+
interface SearchResponse {
|
|
4527
|
+
/** Search results */
|
|
4528
|
+
items: SearchResult[];
|
|
4529
|
+
/** Cursor for next page of results */
|
|
4530
|
+
nextCursor?: string;
|
|
4531
|
+
}
|
|
4532
|
+
/**
|
|
4533
|
+
* Options for suggestion/autocomplete queries
|
|
4534
|
+
*/
|
|
4535
|
+
interface SuggestOptions {
|
|
4536
|
+
/** Collections to search (defaults to all searchable collections) */
|
|
4537
|
+
collections?: string[];
|
|
4538
|
+
/** Filter by locale (omit to search all locales) */
|
|
4539
|
+
locale?: string;
|
|
4540
|
+
/** Maximum suggestions to return (defaults to 5) */
|
|
4541
|
+
limit?: number;
|
|
4542
|
+
}
|
|
4543
|
+
/**
|
|
4544
|
+
* A single suggestion result
|
|
4545
|
+
*/
|
|
4546
|
+
interface Suggestion {
|
|
4547
|
+
/** Collection the suggestion belongs to */
|
|
4548
|
+
collection: string;
|
|
4549
|
+
/** Entry ID */
|
|
4550
|
+
id: string;
|
|
4551
|
+
/** Entry title */
|
|
4552
|
+
title: string;
|
|
4553
|
+
}
|
|
4554
|
+
/**
|
|
4555
|
+
* Search index statistics
|
|
4556
|
+
*/
|
|
4557
|
+
interface SearchStats {
|
|
4558
|
+
collections: Record<string, {
|
|
4559
|
+
/** Number of indexed entries */indexed: number; /** When the index was last rebuilt */
|
|
4560
|
+
lastRebuilt?: string;
|
|
4561
|
+
}>;
|
|
4562
|
+
}
|
|
4563
|
+
//#endregion
|
|
4564
|
+
//#region src/search/fts-manager.d.ts
|
|
4565
|
+
/**
|
|
4566
|
+
* FTS5 Manager
|
|
4567
|
+
*
|
|
4568
|
+
* Handles creation, deletion, and management of FTS5 virtual tables
|
|
4569
|
+
* for full-text search on content collections.
|
|
4570
|
+
*/
|
|
4571
|
+
declare class FTSManager {
|
|
4572
|
+
private db;
|
|
4573
|
+
constructor(db: Kysely<Database>);
|
|
4574
|
+
/**
|
|
4575
|
+
* Validate a collection slug and its searchable field names.
|
|
4576
|
+
* Must be called before any raw SQL interpolation.
|
|
4577
|
+
*/
|
|
4578
|
+
private validateInputs;
|
|
4579
|
+
/**
|
|
4580
|
+
* Get the FTS table name for a collection
|
|
4581
|
+
* Uses _dineway_ prefix to clearly mark as internal/system table
|
|
4582
|
+
*/
|
|
4583
|
+
getFtsTableName(collectionSlug: string): string;
|
|
4584
|
+
/**
|
|
4585
|
+
* Get the content table name for a collection
|
|
4586
|
+
*/
|
|
4587
|
+
getContentTableName(collectionSlug: string): string;
|
|
4588
|
+
/**
|
|
4589
|
+
* Check if an FTS table exists for a collection
|
|
4590
|
+
*/
|
|
4591
|
+
ftsTableExists(collectionSlug: string): Promise<boolean>;
|
|
4592
|
+
/**
|
|
4593
|
+
* Create an FTS5 virtual table for a collection.
|
|
4594
|
+
* FTS5 is SQLite-only; on other dialects this is a no-op.
|
|
4595
|
+
*
|
|
4596
|
+
* @param collectionSlug - The collection slug
|
|
4597
|
+
* @param searchableFields - Array of field names to index
|
|
4598
|
+
* @param weights - Optional field weights for ranking
|
|
4599
|
+
*/
|
|
4600
|
+
createFtsTable(collectionSlug: string, searchableFields: string[], _weights?: Record<string, number>): Promise<void>;
|
|
4601
|
+
/**
|
|
4602
|
+
* Create triggers to keep FTS table in sync with content table
|
|
4603
|
+
*/
|
|
4604
|
+
private createTriggers;
|
|
4605
|
+
/**
|
|
4606
|
+
* Drop triggers for a collection
|
|
4607
|
+
*/
|
|
4608
|
+
private dropTriggers;
|
|
4609
|
+
/**
|
|
4610
|
+
* Drop the FTS table and triggers for a collection
|
|
4611
|
+
*/
|
|
4612
|
+
dropFtsTable(collectionSlug: string): Promise<void>;
|
|
4613
|
+
/**
|
|
4614
|
+
* Rebuild the FTS index for a collection
|
|
4615
|
+
*
|
|
4616
|
+
* This is useful after bulk imports or if the index gets out of sync.
|
|
4617
|
+
*/
|
|
4618
|
+
rebuildIndex(collectionSlug: string, searchableFields: string[], weights?: Record<string, number>): Promise<void>;
|
|
4619
|
+
/**
|
|
4620
|
+
* Populate the FTS table from existing content
|
|
4621
|
+
*/
|
|
4622
|
+
populateFromContent(collectionSlug: string, searchableFields: string[]): Promise<void>;
|
|
4623
|
+
/**
|
|
4624
|
+
* Get the search configuration for a collection
|
|
4625
|
+
*/
|
|
4626
|
+
getSearchConfig(collectionSlug: string): Promise<SearchConfig | null>;
|
|
4627
|
+
/**
|
|
4628
|
+
* Update the search configuration for a collection
|
|
4629
|
+
*/
|
|
4630
|
+
setSearchConfig(collectionSlug: string, config: SearchConfig): Promise<void>;
|
|
4631
|
+
/**
|
|
4632
|
+
* Get searchable fields for a collection
|
|
4633
|
+
*/
|
|
4634
|
+
getSearchableFields(collectionSlug: string): Promise<string[]>;
|
|
4635
|
+
/**
|
|
4636
|
+
* Enable search for a collection
|
|
4637
|
+
*
|
|
4638
|
+
* Creates the FTS table and triggers, and populates from existing content.
|
|
4639
|
+
*/
|
|
4640
|
+
enableSearch(collectionSlug: string, options?: {
|
|
4641
|
+
weights?: Record<string, number>;
|
|
4642
|
+
}): Promise<void>;
|
|
4643
|
+
/**
|
|
4644
|
+
* Disable search for a collection
|
|
4645
|
+
*
|
|
4646
|
+
* Drops the FTS table and triggers.
|
|
4647
|
+
*/
|
|
4648
|
+
disableSearch(collectionSlug: string): Promise<void>;
|
|
4649
|
+
/**
|
|
4650
|
+
* Get index statistics for a collection
|
|
4651
|
+
*/
|
|
4652
|
+
getIndexStats(collectionSlug: string): Promise<{
|
|
4653
|
+
indexed: number;
|
|
4654
|
+
lastRebuilt?: string;
|
|
4655
|
+
} | null>;
|
|
4656
|
+
/**
|
|
4657
|
+
* Verify FTS index integrity and rebuild if corrupted.
|
|
4658
|
+
*
|
|
4659
|
+
* Checks for row count mismatch between content table and FTS table.
|
|
4660
|
+
*
|
|
4661
|
+
* Returns true if the index was rebuilt, false if it was healthy.
|
|
4662
|
+
*/
|
|
4663
|
+
verifyAndRepairIndex(collectionSlug: string): Promise<boolean>;
|
|
4664
|
+
/**
|
|
4665
|
+
* Verify and repair FTS indexes for all search-enabled collections.
|
|
4666
|
+
*
|
|
4667
|
+
* Intended to run at startup to auto-heal any corruption from
|
|
4668
|
+
* previous process crashes.
|
|
4669
|
+
*/
|
|
4670
|
+
verifyAndRepairAll(): Promise<number>;
|
|
4671
|
+
}
|
|
4672
|
+
//#endregion
|
|
4673
|
+
//#region src/search/query.d.ts
|
|
4674
|
+
/**
|
|
4675
|
+
* Search across multiple collections
|
|
4676
|
+
*
|
|
4677
|
+
* Public API that auto-injects the database.
|
|
4678
|
+
*
|
|
4679
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
4680
|
+
* @param options - Search options
|
|
4681
|
+
* @returns Search results with pagination
|
|
4682
|
+
*
|
|
4683
|
+
* @example
|
|
4684
|
+
* ```typescript
|
|
4685
|
+
* import { search } from "dineway";
|
|
4686
|
+
*
|
|
4687
|
+
* const results = await search("hello world", {
|
|
4688
|
+
* collections: ["posts", "pages"],
|
|
4689
|
+
* limit: 20
|
|
4690
|
+
* });
|
|
4691
|
+
* ```
|
|
4692
|
+
*/
|
|
4693
|
+
declare function search(query: string, options?: SearchOptions): Promise<SearchResponse>;
|
|
4694
|
+
/**
|
|
4695
|
+
* Search across multiple collections (with explicit db)
|
|
4696
|
+
*
|
|
4697
|
+
* @internal Use `search()` in templates. This variant is for admin routes
|
|
4698
|
+
* that already have a database handle.
|
|
4699
|
+
*
|
|
4700
|
+
* @param db - Kysely database instance
|
|
4701
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
4702
|
+
* @param options - Search options
|
|
4703
|
+
* @returns Search results with pagination
|
|
4704
|
+
*/
|
|
4705
|
+
declare function searchWithDb(db: Kysely<Database>, query: string, options?: SearchOptions): Promise<SearchResponse>;
|
|
4706
|
+
/**
|
|
4707
|
+
* Search within a single collection
|
|
4708
|
+
*
|
|
4709
|
+
* @param db - Kysely database instance
|
|
4710
|
+
* @param collection - Collection slug
|
|
4711
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
4712
|
+
* @param options - Search options
|
|
4713
|
+
* @returns Search results with pagination
|
|
4714
|
+
*
|
|
4715
|
+
* @example
|
|
4716
|
+
* ```typescript
|
|
4717
|
+
* const results = await searchCollection(db, "posts", "hello world", {
|
|
4718
|
+
* limit: 10
|
|
4719
|
+
* });
|
|
4720
|
+
* ```
|
|
4721
|
+
*/
|
|
4722
|
+
declare function searchCollection(db: Kysely<Database>, collection: string, query: string, options?: CollectionSearchOptions): Promise<SearchResponse>;
|
|
4723
|
+
/**
|
|
4724
|
+
* Get search suggestions for autocomplete
|
|
4725
|
+
*
|
|
4726
|
+
* @param db - Kysely database instance
|
|
4727
|
+
* @param query - Partial search query
|
|
4728
|
+
* @param options - Suggestion options
|
|
4729
|
+
* @returns Array of suggestions
|
|
4730
|
+
*/
|
|
4731
|
+
declare function getSuggestions(db: Kysely<Database>, query: string, options?: SuggestOptions): Promise<Suggestion[]>;
|
|
4732
|
+
/**
|
|
4733
|
+
* Get search statistics for all collections
|
|
4734
|
+
*/
|
|
4735
|
+
declare function getSearchStats(db: Kysely<Database>): Promise<SearchStats>;
|
|
4736
|
+
//#endregion
|
|
4737
|
+
//#region src/search/text-extraction.d.ts
|
|
4738
|
+
/**
|
|
4739
|
+
* Extract plain text from Portable Text blocks
|
|
4740
|
+
*
|
|
4741
|
+
* Uses @portabletext/toolkit's toPlainText for standard blocks,
|
|
4742
|
+
* plus extracts text from custom block types (code, images with alt/caption).
|
|
4743
|
+
*
|
|
4744
|
+
* @param blocks - Array of Portable Text blocks (or a JSON string)
|
|
4745
|
+
* @returns Plain text content
|
|
4746
|
+
*
|
|
4747
|
+
* @example
|
|
4748
|
+
* ```typescript
|
|
4749
|
+
* const text = extractPlainText([
|
|
4750
|
+
* {
|
|
4751
|
+
* _type: "block",
|
|
4752
|
+
* _key: "abc",
|
|
4753
|
+
* children: [{ _type: "span", _key: "s1", text: "Hello World" }]
|
|
4754
|
+
* }
|
|
4755
|
+
* ]);
|
|
4756
|
+
* // Returns: "Hello World"
|
|
4757
|
+
* ```
|
|
4758
|
+
*/
|
|
4759
|
+
declare function extractPlainText(blocks: PortableTextBlock$1[] | string | null | undefined): string;
|
|
4760
|
+
/**
|
|
4761
|
+
* Extract searchable text from a content entry
|
|
4762
|
+
*
|
|
4763
|
+
* Extracts text from specified fields, handling both plain text and Portable Text.
|
|
4764
|
+
*
|
|
4765
|
+
* @param entry - Content entry data
|
|
4766
|
+
* @param fields - Field names to extract text from
|
|
4767
|
+
* @returns Object mapping field names to extracted text
|
|
4768
|
+
*/
|
|
4769
|
+
declare function extractSearchableFields(entry: Record<string, unknown>, fields: string[]): Record<string, string>;
|
|
4770
|
+
//#endregion
|
|
4771
|
+
export { GetCommentsOptions as $, ManifestResponse as $i, SessionDatabaseFactory as $n, SandboxEmailMessage as $r, OAuthInput as $t, getEntryTerms as A, handleContentCountScheduled as Ai, AuthProviderModule as An, getTranslations as Ar, wordpressRestSource as At, UpdateTermInput as B, handleContentPermanentDelete as Bi, WxrTag as Bn, hashString as Br, importReusableBlocksAsSections as Bt, ReorderWidgetsInput as C, RevisionListResponse as Ci, DinewayConfig as Cn, InferCollectionData as Cr, GeneratePreviewTokenOptions as Ct, WidgetComponentDef as D, handleRevisionRestore as Di, S3StorageConfig as Dn, getDinewayCollection as Dr, generatePreviewToken as Dt, WidgetArea as E, handleRevisionList as Ei, LocalStorageConfig as En, TranslationsResult as Er, VerifyPreviewTokenResult as Et, getTermsForEntries as F, handleContentDuplicate as Fi, WxrAuthor as Fn, createEditable as Fr, getFileSources as Ft, CreateMenuInput as G, handleContentUnpublish as Gi, EntryFilter as Gn, PortableTextLinkMark as Gr, FileInput as Gt, getBylineBySlug as H, handleContentRestore as Hi, parseWxrString as Hn, prosemirrorToPortableText as Hr, CollectionSchemaStatus as Ht, CreateTermInput as I, handleContentGet as Ii, WxrCategory as In, createNoop as Ir, getSource as It, MenuItem as J, ApiContext as Ji, FilePreviewMiddlewareConfig as Jn, PortableTextTextBlock as Jr, ImportContext as Jt, CreateMenuItemInput as K, handleContentUnschedule as Ki, dinewayLoader as Kn, PortableTextMarkDef as Kr, ImportAnalysis as Kt, TaxonomyDef as L, handleContentGetIncludingTrashed as Li, WxrData as Ln, isSafeHref as Lr, getUrlSources as Lt, getTaxonomyDefs as M, handleContentCreate as Mi, ExternalAuthConfig as Mn, CMSAnnotation as Mr, wxrSource as Mt, getTaxonomyTerms as N, handleContentDelete as Ni, definePlugin as Nn, EditProxy as Nr, clearSources as Nt, WidgetType as O, generateManifest as Oi, StorageDescriptor as On, getDinewayEntry as Or, parseContentId as Ot, getTerm as P, handleContentDiscardDraft as Pi, WxrAttachment as Pn, FieldAnnotation as Pr, getAllSources as Pt, UpdateMenuItemInput as Q, ListResponse as Qi, SessionCleanupResult as Qn, ProseMirrorNode as Qr, NormalizedItem as Qt, TaxonomyTerm as R, handleContentList as Ri, WxrPost as Rn, sanitizeHref as Rr, probeUrl as Rt, PropDef as S, handleMediaUpdate as Si, pluginManifestSchema as Sn, EntryResult as Sr, getPreviewUrl as St, Widget as T, handleRevisionGet as Ti, getStoredConfig as Tn, TranslationSummary as Tr, VerifyPreviewTokenOptions as Tt, getMenu as U, handleContentSchedule as Ui, CollectionFilter as Un, PortableTextCodeBlock as Ur, FetchOptions as Ut, getByline as V, handleContentPublish as Vi, parseWxr as Vn, portableTextToProsemirror as Vr, AttachmentInfo as Vt, getMenus as W, handleContentTranslations as Wi, EntryData as Wn, PortableTextImageBlock as Wr, FieldCompatibility as Wt, ReorderMenuItemsInput as X, ContentResponse as Xi, FileSessionDatabaseFactory as Xn, ProseMirrorDocument as Xr, ImportResult as Xt, MenuItemType as Y, ContentListResponse as Yi, createFilePreviewMiddleware as Yn, PortableTextUnknownBlock as Yr, ImportFieldDef as Yt, UpdateMenuInput as Z, FieldDescriptor as Zi, FileSessionDatabaseFactoryOptions as Zn, ProseMirrorMark as Zr, ImportSource as Zt, getWidgetArea as _, MediaResponse as _i, EmailPipeline as _n, CollectionFilter$1 as _r, isBlockedInPreview as _t, search as a, FileValue as aa, SerializedRequest as ai, SourceInput as an, PreviewSidecarClient as ar, getSiteSetting as at, CreateWidgetAreaInput as b, handleMediaGet as bi, createHookPipeline as bn, DinewayCollections as br, GetPreviewUrlOptions as bt, FTSManager as c, CreateMediaInput as ca, CreateSectionInput as ci, UrlInput as cn, defaultPreviewSidecarClient as cr, PreviewToolbarConfig as ct, SearchOptions as d, ContentRepository as da, SectionSource as di, NoopSandboxRunner as dn, verifyPreviewSignature as dr, ApplySnapshotToDatabaseOptions as dt, portableText as ea, SandboxEmailSendCallback as ei, PostTypeAnalysis as en, SessionDatabaseHandle as er, GetCommentsResult as et, SearchResponse as f, DatabaseConfig as fa, UpdateSectionInput as fi, SandboxNotAvailableError as fn, I18nConfig as fr, applySnapshotToDatabase as ft, Suggestion as g, MediaListResponse as gi, PluginRouteError as gn, CacheHint as gr, renderPreviewLoadingPage as gt, SuggestOptions as h, SchemaRegistry as hi, createPluginManager as hn, isI18nEnabled as hr, Snapshot as ht, getSuggestions as i, FieldUIHints as ia, SandboxedPlugin as ii, SourceCapabilities as in, SessionOpenOrCreateOptions as ir, getPluginSettings as it, getTaxonomyDef as j, handleContentCountTrashed as ji, AuthResult as jn, resolveDinewayPath as jr, parseWxrDate as jt, getEntriesByTerm as k, handleContentCompare as ki, AuthDescriptor as kn, getEditMeta as kr, verifyPreviewToken as kt, CollectionSearchOptions as l, MediaItem as la, GetSectionsOptions as li, NodeSandboxRunner as ln, parsePreviewSignatureHeader as lr, renderPreviewToolbar as lt, SearchStats as m, SchemaError as mi, PluginManager as mn, getI18nConfig as mr, getAppliedSnapshotMeta as mt, extractSearchableFields as n, image as na, SandboxRunner as ni, ProbeResult as nn, SessionDatabaseLimitError as nr, getComments as nt, searchCollection as o, ImageValue as oa, getSection as oi, SourceProbeResult as on, PreviewSidecarSignature as or, getSiteSettings as ot, SearchResult as p, DinewayDatabaseError as pa, getCollectionInfo as pi, createNoopSandboxRunner as pn, getFallbackChain as pr, dropSessionDatabaseTables as pt, Menu as q, handleContentUpdate as qi, getDb as qn, PortableTextSpan as qr, ImportConfig as qt, getSearchStats as r, FieldDefinition as ra, SandboxRunnerFactory as ri, SourceAuth as rn, SessionOpenOptions as rr, getPluginSetting as rt, searchWithDb as s, PortableTextBlock$2 as sa, getSections as si, SuggestedAction as sn, buildPreviewSignatureHeader as sr, setSiteSettings as st, extractPlainText as t, reference as ta, SandboxOptions as ti, PostTypeMapping as tn, SessionDatabaseInfo as tr, getCommentCount as tt, SearchConfig as u, MediaRepository as ua, Section as ui, createNodeSandboxRunner as un, signPreviewUrl as ur, AppliedSnapshotMeta as ut, getWidgetAreas as v, handleMediaCreate as vi, HookPipeline as vn, CollectionResult as vr, getPreviewToken as vt, UpdateWidgetInput as w, RevisionResponse as wi, PluginDescriptor as wn, ResolvePathResult as wr, PreviewTokenPayload as wt, CreateWidgetInput as x, handleMediaList as xi, ValidatedPluginManifest as xn, EditFieldMeta as xr, buildPreviewUrl as xt, getWidgetComponents as y, handleMediaDelete as yi, HookResult as yn, ContentEntry as yr, isPreviewRequest as yt, TaxonomyTermRow as z, handleContentListTrashed as zi, WxrSite as zn, computeContentHash as zr, registerSource as zt };
|