@karibsen/eponyme 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -12
- package/dist/eponyme.mjs +2 -1
- package/dist/module.d.mts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +55 -36
- package/dist/runtime/components/editor/EponymeCollectionPage.vue +80 -18
- package/dist/runtime/components/editor/EponymeForm.vue +2 -2
- package/dist/runtime/components/editor/EponymeFormPage.vue +7 -3
- package/dist/runtime/components/editor/EponymeSidebar.vue +24 -32
- package/dist/runtime/components/editor/EponymeSidebarTree.d.vue.ts +2 -0
- package/dist/runtime/components/editor/EponymeSidebarTree.vue +3 -1
- package/dist/runtime/components/editor/EponymeSidebarTree.vue.d.ts +2 -0
- package/dist/runtime/components/fields/RichTextField.vue +51 -10
- package/dist/runtime/components/ui/EPLinkDialog.d.vue.ts +15 -0
- package/dist/runtime/components/ui/EPLinkDialog.vue +142 -0
- package/dist/runtime/components/ui/EPLinkDialog.vue.d.ts +15 -0
- package/dist/runtime/components/ui/EPLinkEditor.vue +10 -91
- package/dist/runtime/composables/useEponyme.js +5 -3
- package/dist/runtime/composables/useEponymeCollection.js +3 -2
- package/dist/runtime/composables/useEponymeForm.d.ts +4 -0
- package/dist/runtime/composables/useEponymeForm.js +11 -1
- package/dist/runtime/composables/useEponymeHistory.d.ts +2 -2
- package/dist/runtime/composables/useEponymeHistory.js +2 -1
- package/dist/runtime/pages/EponymeDetailPage.vue +1 -1
- package/dist/runtime/pages/EponymeIndexPage.vue +173 -10
- package/dist/runtime/server/api/eponyme/[name].patch.js +17 -1
- package/dist/runtime/server/api/eponyme-collections/[path].delete.js +7 -2
- package/dist/runtime/server/api/eponyme-export.get.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-export.get.js +11 -0
- package/dist/runtime/server/api/eponyme-forms/[path].post.js +29 -1
- package/dist/runtime/server/api/eponyme-history/[path].patch.d.ts +1 -1
- package/dist/runtime/server/api/eponyme-history/[path].patch.js +14 -2
- package/dist/runtime/server/api/eponyme-import.post.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-import.post.js +31 -0
- package/dist/runtime/server/api/eponyme-trash/[path].delete.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-trash/[path].delete.js +16 -0
- package/dist/runtime/server/api/eponyme-trash/[path].get.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-trash/[path].get.js +11 -0
- package/dist/runtime/server/api/eponyme-trash/[path].patch.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-trash/[path].patch.js +16 -0
- package/dist/runtime/server/services/eponyme-form-store.js +2 -1
- package/dist/runtime/server/services/eponyme-service.js +8 -1
- package/dist/runtime/server/services/eponyme-store.d.ts +118 -5
- package/dist/runtime/server/services/eponyme-store.js +344 -45
- package/dist/runtime/server/utils/eponyme-captcha.d.ts +6 -0
- package/dist/runtime/server/utils/eponyme-captcha.js +14 -0
- package/dist/runtime/server/utils/eponyme-entry.d.ts +9 -0
- package/dist/runtime/server/utils/eponyme-entry.js +7 -0
- package/dist/runtime/server/utils/eponyme-hooks.d.ts +16 -0
- package/dist/runtime/server/utils/eponyme-hooks.js +24 -0
- package/dist/runtime/types/captcha.d.ts +16 -0
- package/dist/runtime/types/captcha.js +0 -0
- package/dist/runtime/types/eponyme-config.d.ts +7 -0
- package/dist/runtime/types/eponyme.d.ts +3 -0
- package/dist/runtime/types/field.d.ts +1 -0
- package/dist/runtime/types/hooks.d.ts +67 -0
- package/dist/runtime/types/hooks.js +0 -0
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/nuxt.d.ts +10 -0
- package/dist/runtime/utils/build-navigation-tree.d.ts +22 -0
- package/dist/runtime/utils/build-navigation-tree.js +30 -0
- package/dist/runtime/utils/date-locale.d.ts +10 -0
- package/dist/runtime/utils/date-locale.js +1 -0
- package/dist/runtime/utils/downloadable-link.d.ts +2 -0
- package/dist/runtime/utils/downloadable-link.js +35 -0
- package/dist/runtime/utils/filter-navigation-tree.d.ts +7 -0
- package/dist/runtime/utils/filter-navigation-tree.js +39 -0
- package/dist/runtime/utils/get-field-default-value.js +1 -1
- package/dist/runtime/utils/hydration-cache.d.ts +11 -0
- package/dist/runtime/utils/hydration-cache.js +3 -0
- package/dist/runtime/utils/no-captcha.d.ts +7 -0
- package/dist/runtime/utils/no-captcha.js +5 -0
- package/dist/runtime/utils/validate-eponyme-data.js +1 -1
- package/dist/runtime/utils/variables.d.ts +8 -1
- package/dist/runtime/utils/variables.js +17 -4
- package/package.json +3 -1
|
@@ -16,7 +16,7 @@ export interface EponymeVersionAuthor {
|
|
|
16
16
|
}
|
|
17
17
|
export interface EponymeHistoryEntry {
|
|
18
18
|
id: number;
|
|
19
|
-
action: EponymeAction | 'restore';
|
|
19
|
+
action: EponymeAction | 'restore' | 'import';
|
|
20
20
|
status: EponymeStatus;
|
|
21
21
|
createdAt: string;
|
|
22
22
|
user: EponymeVersionAuthor | null;
|
|
@@ -33,11 +33,47 @@ export interface EponymeCollectionEntry<Data extends Record<string, unknown> = R
|
|
|
33
33
|
status: EponymeStatus;
|
|
34
34
|
publishedAt: string | null;
|
|
35
35
|
updatedAt: string | null;
|
|
36
|
+
/** Only set for entries read from the trash. */
|
|
37
|
+
deletedAt?: string | null;
|
|
36
38
|
}
|
|
37
39
|
export interface EponymeSitemapEntry {
|
|
38
40
|
loc: string;
|
|
39
41
|
lastmod?: string;
|
|
40
42
|
}
|
|
43
|
+
/** One entry of an export file: the complete stored state, without the envelope. */
|
|
44
|
+
export interface EponymeExportEntry {
|
|
45
|
+
name: string;
|
|
46
|
+
/** Set for collection entries, so a reader can group them without re-parsing the name. */
|
|
47
|
+
collection?: string;
|
|
48
|
+
draft: Record<string, unknown>;
|
|
49
|
+
published: Record<string, unknown>;
|
|
50
|
+
status: EponymeStatus;
|
|
51
|
+
publishedAt: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface EponymeExportFile {
|
|
54
|
+
eponyme: {
|
|
55
|
+
format: 1;
|
|
56
|
+
exportedAt: string;
|
|
57
|
+
/** Schema fingerprint per singleton and per collection, checked before an import writes anything. */
|
|
58
|
+
schemas: Record<string, string>;
|
|
59
|
+
};
|
|
60
|
+
entries: EponymeExportEntry[];
|
|
61
|
+
}
|
|
62
|
+
export interface EponymeImportResult {
|
|
63
|
+
dryRun: boolean;
|
|
64
|
+
created: number;
|
|
65
|
+
updated: number;
|
|
66
|
+
skipped: Array<{
|
|
67
|
+
name: string;
|
|
68
|
+
reason: string;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
/** The import was refused as a whole, before any write. */
|
|
72
|
+
export interface EponymeImportRejection {
|
|
73
|
+
errors: string[];
|
|
74
|
+
/** Names whose configured schema differs from the one the file was exported with. */
|
|
75
|
+
schemaMismatch?: string[];
|
|
76
|
+
}
|
|
41
77
|
export type EponymeSortDirection = 'asc' | 'desc';
|
|
42
78
|
export interface EponymeCollectionQuery {
|
|
43
79
|
take?: number;
|
|
@@ -55,6 +91,11 @@ export type PrismaEponymeRow = {
|
|
|
55
91
|
name: string;
|
|
56
92
|
data: unknown;
|
|
57
93
|
updatedAt?: Date | string;
|
|
94
|
+
deletedAt?: Date | string | null;
|
|
95
|
+
};
|
|
96
|
+
/** `null` keeps the live rows, `{ not: null }` the trashed ones. */
|
|
97
|
+
export type PrismaDeletedAtFilter = null | {
|
|
98
|
+
not: null;
|
|
58
99
|
};
|
|
59
100
|
export type PrismaEponymeVersionRow = {
|
|
60
101
|
id: number;
|
|
@@ -96,9 +137,11 @@ export type PrismaEponymeClient = {
|
|
|
96
137
|
where: {
|
|
97
138
|
name: string;
|
|
98
139
|
updatedAt?: Date | string;
|
|
140
|
+
deletedAt?: PrismaDeletedAtFilter;
|
|
99
141
|
};
|
|
100
142
|
data: {
|
|
101
|
-
data
|
|
143
|
+
data?: Record<string, unknown>;
|
|
144
|
+
deletedAt?: Date | null;
|
|
102
145
|
};
|
|
103
146
|
}): Promise<{
|
|
104
147
|
count: number;
|
|
@@ -119,6 +162,7 @@ export type PrismaEponymeClient = {
|
|
|
119
162
|
name: {
|
|
120
163
|
startsWith: string;
|
|
121
164
|
};
|
|
165
|
+
deletedAt?: PrismaDeletedAtFilter;
|
|
122
166
|
};
|
|
123
167
|
orderBy: Array<{
|
|
124
168
|
updatedAt: 'desc';
|
|
@@ -165,17 +209,51 @@ export declare class EponymeService {
|
|
|
165
209
|
private readonly client;
|
|
166
210
|
private readonly schemas;
|
|
167
211
|
private readonly collections;
|
|
168
|
-
|
|
212
|
+
/** Entries a read has already persisted the normalized shape for, so it only attempts it once. */
|
|
213
|
+
private readonly healedByRead;
|
|
214
|
+
/**
|
|
215
|
+
* Read-path cache of normalized rows, so a page that reads the same entry from a
|
|
216
|
+
* singleton, a listing and a layout pays one round trip instead of one per read.
|
|
217
|
+
* Writers never read from it, so the `updatedAt` they lock on always comes from the
|
|
218
|
+
* database and a stale key can never turn into a spurious conflict.
|
|
219
|
+
*/
|
|
220
|
+
private readonly cache;
|
|
221
|
+
private readonly cacheMs;
|
|
222
|
+
constructor(config: EponymeConfig, client: PrismaEponymeClient, options?: {
|
|
223
|
+
cacheSeconds?: number;
|
|
224
|
+
});
|
|
225
|
+
private cached;
|
|
226
|
+
/**
|
|
227
|
+
* Drops what a write to `name` invalidates: the entry itself, and the listing of the
|
|
228
|
+
* collection that contains it, whose row set just changed.
|
|
229
|
+
*/
|
|
230
|
+
private invalidate;
|
|
169
231
|
getSchema(name: string): EponymeSchema | undefined;
|
|
170
232
|
getCollection(name: string): EponymeCollectionDefinitionBase | undefined;
|
|
171
233
|
private getCollectionEntry;
|
|
172
|
-
/** Reconcile every configured eponyme at application startup. */
|
|
234
|
+
/** Reconcile every configured eponyme at application startup. This is the one place that heals. */
|
|
173
235
|
syncAll(): Promise<void>;
|
|
174
236
|
private reconcile;
|
|
175
237
|
private createState;
|
|
176
238
|
private normalizeState;
|
|
177
|
-
/**
|
|
239
|
+
/**
|
|
240
|
+
* A singleton row is created on first read, but only when it is genuinely missing.
|
|
241
|
+
* An `upsert` would send a write on every read instead, and each one costs a database
|
|
242
|
+
* round trip on the critical path of a public page.
|
|
243
|
+
*/
|
|
244
|
+
private loadSingletonRow;
|
|
245
|
+
/**
|
|
246
|
+
* Reads the stored state along with the `updatedAt` it was read at, used as the optimistic lock token.
|
|
247
|
+
*
|
|
248
|
+
* `heal` persists the normalized state when it differs from what is stored. It has to stay on for
|
|
249
|
+
* writers, because healing moves `updatedAt` and so decides the lock token they are about to compare.
|
|
250
|
+
*
|
|
251
|
+
* `cache` is only ever set for published content. Draft reads serve the preview panel, which has to
|
|
252
|
+
* show a save immediately, and they come from the dashboard rather than from a public page — so
|
|
253
|
+
* there is nothing to gain by caching them and a stale preview to lose.
|
|
254
|
+
*/
|
|
178
255
|
private loadRow;
|
|
256
|
+
private readRow;
|
|
179
257
|
private loadState;
|
|
180
258
|
/**
|
|
181
259
|
* Writes a new state only if the row still carries the `updatedAt` we read.
|
|
@@ -183,6 +261,12 @@ export declare class EponymeService {
|
|
|
183
261
|
* instead of silently discarding their changes.
|
|
184
262
|
*/
|
|
185
263
|
private writeState;
|
|
264
|
+
/**
|
|
265
|
+
* The live rows of a collection, shared by the public listing and the sitemap so both
|
|
266
|
+
* read them once. The trash and the export deliberately query around it: one wants the
|
|
267
|
+
* rows this filter excludes, the other must not see a cached view of the content.
|
|
268
|
+
*/
|
|
269
|
+
private liveCollectionRows;
|
|
186
270
|
get(name: string, version?: EponymeVersionSelector): Promise<Record<string, unknown> | undefined>;
|
|
187
271
|
getResult(name: string, version?: EponymeVersionSelector): Promise<EponymeResult | undefined>;
|
|
188
272
|
/** Content as it was in a given version, for previewing without restoring it. */
|
|
@@ -197,13 +281,40 @@ export declare class EponymeService {
|
|
|
197
281
|
* one query per collection rather than one query per entry.
|
|
198
282
|
*/
|
|
199
283
|
getSitemapEntries(previewPaths: Record<string, string>): Promise<EponymeSitemapEntry[]>;
|
|
284
|
+
/**
|
|
285
|
+
* Every singleton and every live collection entry, as a portable file.
|
|
286
|
+
* Collections are read with one query each rather than one per entry.
|
|
287
|
+
*/
|
|
288
|
+
exportContent(): Promise<EponymeExportFile>;
|
|
289
|
+
/**
|
|
290
|
+
* Applies an export file on top of the current content: every entry it carries is
|
|
291
|
+
* upserted, everything else is left alone. Nothing is ever deleted.
|
|
292
|
+
*
|
|
293
|
+
* The schema check runs first and refuses the whole file, so an import can never
|
|
294
|
+
* land half of its entries into a configuration that no longer matches.
|
|
295
|
+
*/
|
|
296
|
+
importContent(file: unknown, options?: {
|
|
297
|
+
dryRun?: boolean;
|
|
298
|
+
actorId?: string;
|
|
299
|
+
}): Promise<EponymeImportResult | EponymeImportRejection>;
|
|
200
300
|
createCollectionEntry(name: string, payload: unknown, actorId?: string): Promise<(EponymeResult & {
|
|
201
301
|
slug: string;
|
|
202
302
|
errors?: never;
|
|
203
303
|
}) | {
|
|
204
304
|
errors: ValidationErrors;
|
|
205
305
|
} | undefined>;
|
|
306
|
+
/**
|
|
307
|
+
* A trashed entry keeps its row, so it keeps its slug: reusing one has to be
|
|
308
|
+
* refused with an error the editor can act on rather than the generic message.
|
|
309
|
+
*/
|
|
310
|
+
private slugTakenError;
|
|
311
|
+
/** Moves an entry to the trash. Its history is kept, so it stays restorable. */
|
|
206
312
|
deleteCollectionEntry(name: string): Promise<boolean>;
|
|
313
|
+
restoreCollectionEntry(name: string): Promise<boolean>;
|
|
314
|
+
/** Definitive: `onDelete: Cascade` takes the entry's whole history with it. */
|
|
315
|
+
purgeCollectionEntry(name: string): Promise<boolean>;
|
|
316
|
+
/** Trashed entries of a collection, most recently updated first. */
|
|
317
|
+
listCollectionTrash(name: string): Promise<EponymeCollectionPage | undefined>;
|
|
207
318
|
patch(name: string, payload: unknown, action?: EponymeAction, actorId?: string): Promise<(EponymeResult & {
|
|
208
319
|
errors?: never;
|
|
209
320
|
conflict?: never;
|
|
@@ -214,4 +325,6 @@ export declare class EponymeService {
|
|
|
214
325
|
history(name: string, limit?: number): Promise<EponymeHistoryEntry[] | undefined>;
|
|
215
326
|
restore(name: string, versionId: number, actorId?: string): Promise<EponymeResult | EponymeConflict | undefined>;
|
|
216
327
|
}
|
|
328
|
+
/** Stable hash of a schema's shape, embedded in an export and checked on import. */
|
|
329
|
+
export declare function schemaFingerprint(schema: EponymeSchema): string;
|
|
217
330
|
export declare function isPrismaError(error: unknown, code: string): boolean;
|