@karibsen/eponyme 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -16
- package/dist/eponyme.d.mts +22 -2
- package/dist/eponyme.mjs +5 -1
- package/dist/module.d.mts +3 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +73 -43
- package/dist/runtime/components/editor/EponymeCollectionPage.vue +81 -19
- package/dist/runtime/components/editor/EponymeForm.vue +3 -3
- package/dist/runtime/components/editor/EponymeFormPage.vue +7 -3
- package/dist/runtime/components/editor/EponymeSidebar.vue +25 -33
- 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 +80 -8
- 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.d.ts +2 -0
- package/dist/runtime/composables/useEponyme.js +7 -5
- package/dist/runtime/composables/useEponymeCollection.d.ts +12 -2
- package/dist/runtime/composables/useEponymeCollection.js +15 -5
- package/dist/runtime/composables/useEponymeForm.js +4 -1
- package/dist/runtime/composables/useEponymeHistory.d.ts +2 -2
- package/dist/runtime/composables/useEponymeHistory.js +2 -1
- package/dist/runtime/composables/useEponymeVariables.d.ts +3 -0
- package/dist/runtime/composables/useEponymeVariables.js +5 -0
- package/dist/runtime/pages/EponymeDetailPage.vue +1 -1
- package/dist/runtime/pages/EponymeIndexPage.vue +173 -10
- package/dist/runtime/server/api/eponyme/[name].get.d.ts +5 -1
- package/dist/runtime/server/api/eponyme/[name].get.js +3 -1
- 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-collections/[path].get.d.ts +1 -3
- package/dist/runtime/server/api/eponyme-collections/[path].get.js +31 -4
- 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 +14 -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.d.ts +1 -0
- package/dist/runtime/server/services/eponyme-store.d.ts +91 -3
- package/dist/runtime/server/services/eponyme-store.js +272 -9
- 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/server/utils/eponyme-variables.d.ts +5 -0
- package/dist/runtime/server/utils/eponyme-variables.js +5 -0
- package/dist/runtime/types/eponyme-config.d.ts +7 -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/types/variables.d.ts +17 -0
- package/dist/runtime/types/variables.js +0 -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/empty-variables.d.ts +2 -0
- package/dist/runtime/utils/empty-variables.js +1 -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/validate-eponyme-data.js +1 -1
- package/dist/runtime/utils/variables.d.ts +18 -0
- package/dist/runtime/utils/variables.js +52 -0
- package/dist/types.d.mts +2 -2
- package/package.json +3 -1
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
2
|
-
entries: import("../../services/eponyme-store.js").EponymeCollectionEntry<Record<string, unknown>>[];
|
|
3
|
-
}>>;
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("../../services/eponyme-store.js").EponymeCollectionPage<Record<string, unknown>>>>;
|
|
4
2
|
export default _default;
|
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, getRequestURL } from "h3";
|
|
2
2
|
import { useEponymeService } from "../../services/eponyme-service.js";
|
|
3
3
|
import { requireEponymeUser } from "../../utils/auth.js";
|
|
4
|
+
import { interpolateEponymeContent } from "../../utils/eponyme-variables.js";
|
|
5
|
+
const MAX_TAKE = 200;
|
|
6
|
+
function readCount(raw, min, max) {
|
|
7
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
8
|
+
const value = Number(raw);
|
|
9
|
+
if (!Number.isFinite(value)) return void 0;
|
|
10
|
+
return Math.min(max, Math.max(min, Math.trunc(value)));
|
|
11
|
+
}
|
|
4
12
|
export default defineEventHandler(async (event) => {
|
|
5
13
|
const name = decodeURIComponent(getRequestURL(event).pathname.replace(/^\/api\/eponyme-collections\//, ""));
|
|
6
|
-
const
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
const version = query.version === "draft" ? "draft" : "published";
|
|
7
16
|
if (version === "draft") await requireEponymeUser(event);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
if (!name) throw createError({ statusCode: 404, statusMessage: "Eponyme collection not found." });
|
|
18
|
+
const service = useEponymeService();
|
|
19
|
+
const orderBy = query.orderBy === void 0 || query.orderBy === "" ? void 0 : String(query.orderBy);
|
|
20
|
+
if (orderBy) {
|
|
21
|
+
const allowed = service.collectionSortKeys(name);
|
|
22
|
+
if (!allowed) throw createError({ statusCode: 404, statusMessage: "Eponyme collection not found." });
|
|
23
|
+
if (!allowed.includes(orderBy)) {
|
|
24
|
+
throw createError({
|
|
25
|
+
statusCode: 400,
|
|
26
|
+
statusMessage: `Unknown sort key "${orderBy}". Available keys: ${allowed.join(", ")}.`
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const page = await service.listCollection(name, version, {
|
|
31
|
+
take: readCount(query.take, 1, MAX_TAKE),
|
|
32
|
+
skip: readCount(query.skip, 0, Number.MAX_SAFE_INTEGER),
|
|
33
|
+
orderBy,
|
|
34
|
+
order: query.order === "asc" ? "asc" : query.order === "desc" ? "desc" : void 0
|
|
35
|
+
});
|
|
36
|
+
if (!page) throw createError({ statusCode: 404, statusMessage: "Eponyme collection not found." });
|
|
37
|
+
return query.raw ? page : interpolateEponymeContent(page);
|
|
11
38
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineEventHandler, setResponseHeader } from "h3";
|
|
2
|
+
import { useEponymeService } from "../services/eponyme-service.js";
|
|
3
|
+
import { requireEponymeUser } from "../utils/auth.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
await requireEponymeUser(event, { roles: ["editor", "owner"] });
|
|
6
|
+
setResponseHeader(event, "Cache-Control", "no-store");
|
|
7
|
+
const file = await useEponymeService().exportContent();
|
|
8
|
+
const day = file.eponyme.exportedAt.slice(0, 10);
|
|
9
|
+
setResponseHeader(event, "Content-Disposition", `attachment; filename="eponyme-export-${day}.json"`);
|
|
10
|
+
return file;
|
|
11
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getRequestHeader, readRawBody, setResponseHeader, setResponseStatus } from "h3";
|
|
2
2
|
import { useEponymeFormService } from "../../services/eponyme-form-service.js";
|
|
3
3
|
import { readEponymeFormRoute } from "../../utils/form-route.js";
|
|
4
|
+
import { callEponymeBlockingHook, callEponymeHook } from "../../utils/eponyme-hooks.js";
|
|
4
5
|
export default defineEventHandler(async (event) => {
|
|
5
6
|
const route = readEponymeFormRoute(event);
|
|
6
7
|
const service = useEponymeFormService();
|
|
@@ -25,12 +26,24 @@ export default defineEventHandler(async (event) => {
|
|
|
25
26
|
setResponseStatus(event, 201);
|
|
26
27
|
return { submitted: true };
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
const validated = service.validate(route.name, payload);
|
|
30
|
+
if (validated && "errors" in validated) {
|
|
31
|
+
setResponseStatus(event, 422);
|
|
32
|
+
return { errors: validated.errors };
|
|
33
|
+
}
|
|
34
|
+
const beforeSubmit = { form: route.name, data: validated?.data ?? {} };
|
|
35
|
+
await callEponymeBlockingHook("eponyme:form:beforeSubmit", beforeSubmit);
|
|
36
|
+
const result = await service.submit(route.name, beforeSubmit.data);
|
|
29
37
|
if (!result) throw createError({ statusCode: 404, statusMessage: "Eponyme form not found." });
|
|
30
38
|
if ("errors" in result) {
|
|
31
39
|
setResponseStatus(event, 422);
|
|
32
40
|
return { errors: result.errors };
|
|
33
41
|
}
|
|
42
|
+
await callEponymeHook("eponyme:form:submitted", {
|
|
43
|
+
form: route.name,
|
|
44
|
+
data: result.submission.data,
|
|
45
|
+
id: result.submission.id
|
|
46
|
+
});
|
|
34
47
|
setResponseStatus(event, 201);
|
|
35
48
|
return { submitted: true, id: result.submission.id };
|
|
36
49
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("../../services/eponyme-store.js").EponymeResult
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("../../services/eponyme-store.js").EponymeResult>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getRequestURL } from "h3";
|
|
2
2
|
import { useEponymeService } from "../../services/eponyme-service.js";
|
|
3
3
|
import { assertEponymeMutationOrigin, requireEponymeUser } from "../../utils/auth.js";
|
|
4
|
+
import { callEponymeHook } from "../../utils/eponyme-hooks.js";
|
|
5
|
+
import { splitEponymeCollectionEntry } from "../../utils/eponyme-entry.js";
|
|
4
6
|
export default defineEventHandler(async (event) => {
|
|
5
7
|
assertEponymeMutationOrigin(event);
|
|
6
8
|
const user = await requireEponymeUser(event, { roles: ["editor", "owner"] });
|
|
@@ -10,9 +12,19 @@ export default defineEventHandler(async (event) => {
|
|
|
10
12
|
const [, name, rawVersionId] = match;
|
|
11
13
|
const versionId = Number(rawVersionId);
|
|
12
14
|
if (!Number.isSafeInteger(versionId)) throw createError({ statusCode: 404, statusMessage: "Eponyme version not found." });
|
|
13
|
-
const
|
|
15
|
+
const service = useEponymeService();
|
|
16
|
+
const result = await service.restore(name, versionId, user.id);
|
|
14
17
|
if (!result) throw createError({ statusCode: 404, statusMessage: "Eponyme version not found." });
|
|
15
|
-
if ("conflict" in result
|
|
18
|
+
if ("conflict" in result)
|
|
16
19
|
throw createError({ statusCode: 409, message: "This entry was changed by someone else. Reload the page to get the latest version." });
|
|
20
|
+
await callEponymeHook("eponyme:entry:restored", {
|
|
21
|
+
name,
|
|
22
|
+
collection: splitEponymeCollectionEntry(service, name),
|
|
23
|
+
action: "restore",
|
|
24
|
+
status: result.status,
|
|
25
|
+
publishedAt: result.publishedAt,
|
|
26
|
+
data: result.data,
|
|
27
|
+
userId: user.id
|
|
28
|
+
});
|
|
17
29
|
return result;
|
|
18
30
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getQuery, getRequestHeader, readRawBody, setResponseHeader } from "h3";
|
|
2
|
+
import { useEponymeService } from "../services/eponyme-service.js";
|
|
3
|
+
import { assertEponymeMutationOrigin, requireEponymeUser } from "../utils/auth.js";
|
|
4
|
+
const MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
assertEponymeMutationOrigin(event);
|
|
7
|
+
const user = await requireEponymeUser(event, { roles: ["owner"] });
|
|
8
|
+
setResponseHeader(event, "Cache-Control", "no-store");
|
|
9
|
+
const declaredLength = Number(getRequestHeader(event, "content-length"));
|
|
10
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_BODY_BYTES)
|
|
11
|
+
throw createError({ statusCode: 413, statusMessage: "Import file is too large." });
|
|
12
|
+
const raw = await readRawBody(event, "utf8");
|
|
13
|
+
if (raw && Buffer.byteLength(raw, "utf8") > MAX_BODY_BYTES)
|
|
14
|
+
throw createError({ statusCode: 413, statusMessage: "Import file is too large." });
|
|
15
|
+
let payload;
|
|
16
|
+
try {
|
|
17
|
+
payload = raw ? JSON.parse(raw) : void 0;
|
|
18
|
+
} catch {
|
|
19
|
+
throw createError({ statusCode: 400, statusMessage: "Import file must be valid JSON." });
|
|
20
|
+
}
|
|
21
|
+
const dryRun = Boolean(getQuery(event).dryRun);
|
|
22
|
+
const result = await useEponymeService().importContent(payload, { dryRun, actorId: user.id });
|
|
23
|
+
if ("errors" in result) {
|
|
24
|
+
throw createError({
|
|
25
|
+
statusCode: result.schemaMismatch?.length ? 409 : 400,
|
|
26
|
+
statusMessage: result.errors[0] ?? "This import could not be applied.",
|
|
27
|
+
data: { errors: result.errors, schemaMismatch: result.schemaMismatch ?? [] }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getRequestURL } from "h3";
|
|
2
|
+
import { useEponymeService } from "../../services/eponyme-service.js";
|
|
3
|
+
import { assertEponymeMutationOrigin, requireEponymeUser } from "../../utils/auth.js";
|
|
4
|
+
import { callEponymeHook } from "../../utils/eponyme-hooks.js";
|
|
5
|
+
import { splitEponymeCollectionEntry } from "../../utils/eponyme-entry.js";
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
assertEponymeMutationOrigin(event);
|
|
8
|
+
const user = await requireEponymeUser(event, { roles: ["owner"] });
|
|
9
|
+
const name = decodeURIComponent(getRequestURL(event).pathname.replace(/^\/api\/eponyme-trash\//, ""));
|
|
10
|
+
const service = useEponymeService();
|
|
11
|
+
const collection = name ? splitEponymeCollectionEntry(service, name) : void 0;
|
|
12
|
+
if (!collection || !await service.purgeCollectionEntry(name))
|
|
13
|
+
throw createError({ statusCode: 404, statusMessage: "Eponyme trashed entry not found." });
|
|
14
|
+
await callEponymeHook("eponyme:entry:purged", { name, collection, userId: user.id });
|
|
15
|
+
return { purged: true };
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getRequestURL } from "h3";
|
|
2
|
+
import { useEponymeService } from "../../services/eponyme-service.js";
|
|
3
|
+
import { requireEponymeUser } from "../../utils/auth.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
await requireEponymeUser(event, { roles: ["editor", "owner"] });
|
|
6
|
+
const name = decodeURIComponent(getRequestURL(event).pathname.replace(/^\/api\/eponyme-trash\//, ""));
|
|
7
|
+
if (!name) throw createError({ statusCode: 404, statusMessage: "Eponyme collection not found." });
|
|
8
|
+
const page = await useEponymeService().listCollectionTrash(name);
|
|
9
|
+
if (!page) throw createError({ statusCode: 404, statusMessage: "Eponyme collection not found." });
|
|
10
|
+
return page;
|
|
11
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getRequestURL } from "h3";
|
|
2
|
+
import { useEponymeService } from "../../services/eponyme-service.js";
|
|
3
|
+
import { assertEponymeMutationOrigin, requireEponymeUser } from "../../utils/auth.js";
|
|
4
|
+
import { callEponymeHook } from "../../utils/eponyme-hooks.js";
|
|
5
|
+
import { splitEponymeCollectionEntry } from "../../utils/eponyme-entry.js";
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
assertEponymeMutationOrigin(event);
|
|
8
|
+
const user = await requireEponymeUser(event, { roles: ["editor", "owner"] });
|
|
9
|
+
const name = decodeURIComponent(getRequestURL(event).pathname.replace(/^\/api\/eponyme-trash\//, ""));
|
|
10
|
+
const service = useEponymeService();
|
|
11
|
+
const collection = name ? splitEponymeCollectionEntry(service, name) : void 0;
|
|
12
|
+
if (!collection || !await service.restoreCollectionEntry(name))
|
|
13
|
+
throw createError({ statusCode: 404, statusMessage: "Eponyme trashed entry not found." });
|
|
14
|
+
await callEponymeHook("eponyme:entry:untrashed", { name, collection, userId: user.id });
|
|
15
|
+
return { restored: true };
|
|
16
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EponymeConfig, EponymeFormDefinitionBase } from '../../types/index.js';
|
|
2
2
|
import { type ValidationErrors } from '../../utils/validate-eponyme-data.js';
|
|
3
|
+
/** Kept here rather than imported, so the store stays free of H3 imports. */
|
|
3
4
|
type DateValue = Date | string;
|
|
4
5
|
export interface EponymeFormSubmission {
|
|
5
6
|
id: string;
|
|
@@ -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,15 +33,69 @@ 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
|
+
}
|
|
77
|
+
export type EponymeSortDirection = 'asc' | 'desc';
|
|
78
|
+
export interface EponymeCollectionQuery {
|
|
79
|
+
take?: number;
|
|
80
|
+
skip?: number;
|
|
81
|
+
orderBy?: string;
|
|
82
|
+
order?: EponymeSortDirection;
|
|
83
|
+
}
|
|
84
|
+
export interface EponymeCollectionPage<Data extends Record<string, unknown> = Record<string, unknown>> {
|
|
85
|
+
entries: EponymeCollectionEntry<Data>[];
|
|
86
|
+
/** Matching entries before `take` and `skip`, so it can drive a pager. */
|
|
87
|
+
total: number;
|
|
88
|
+
}
|
|
89
|
+
export declare const COLLECTION_METADATA_KEYS: readonly ["updatedAt", "publishedAt", "title", "slug"];
|
|
41
90
|
export type PrismaEponymeRow = {
|
|
42
91
|
name: string;
|
|
43
92
|
data: unknown;
|
|
44
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;
|
|
45
99
|
};
|
|
46
100
|
export type PrismaEponymeVersionRow = {
|
|
47
101
|
id: number;
|
|
@@ -83,9 +137,11 @@ export type PrismaEponymeClient = {
|
|
|
83
137
|
where: {
|
|
84
138
|
name: string;
|
|
85
139
|
updatedAt?: Date | string;
|
|
140
|
+
deletedAt?: PrismaDeletedAtFilter;
|
|
86
141
|
};
|
|
87
142
|
data: {
|
|
88
|
-
data
|
|
143
|
+
data?: Record<string, unknown>;
|
|
144
|
+
deletedAt?: Date | null;
|
|
89
145
|
};
|
|
90
146
|
}): Promise<{
|
|
91
147
|
count: number;
|
|
@@ -106,6 +162,7 @@ export type PrismaEponymeClient = {
|
|
|
106
162
|
name: {
|
|
107
163
|
startsWith: string;
|
|
108
164
|
};
|
|
165
|
+
deletedAt?: PrismaDeletedAtFilter;
|
|
109
166
|
};
|
|
110
167
|
orderBy: Array<{
|
|
111
168
|
updatedAt: 'desc';
|
|
@@ -175,20 +232,49 @@ export declare class EponymeService {
|
|
|
175
232
|
/** Content as it was in a given version, for previewing without restoring it. */
|
|
176
233
|
private getVersionResult;
|
|
177
234
|
statuses(): Promise<Record<string, EponymeStatus>>;
|
|
178
|
-
|
|
235
|
+
/** Metadata keys that can be sorted on, alongside every field of the collection. */
|
|
236
|
+
collectionSortKeys(name: string): string[] | undefined;
|
|
237
|
+
listCollection(name: string, version?: EponymeVersion, query?: EponymeCollectionQuery): Promise<EponymeCollectionPage | undefined>;
|
|
179
238
|
/**
|
|
180
239
|
* Builds the public URL list configured by `previewPaths`.
|
|
181
240
|
* Collections are resolved directly from their stored rows so this performs
|
|
182
241
|
* one query per collection rather than one query per entry.
|
|
183
242
|
*/
|
|
184
243
|
getSitemapEntries(previewPaths: Record<string, string>): Promise<EponymeSitemapEntry[]>;
|
|
244
|
+
/**
|
|
245
|
+
* Every singleton and every live collection entry, as a portable file.
|
|
246
|
+
* Collections are read with one query each rather than one per entry.
|
|
247
|
+
*/
|
|
248
|
+
exportContent(): Promise<EponymeExportFile>;
|
|
249
|
+
/**
|
|
250
|
+
* Applies an export file on top of the current content: every entry it carries is
|
|
251
|
+
* upserted, everything else is left alone. Nothing is ever deleted.
|
|
252
|
+
*
|
|
253
|
+
* The schema check runs first and refuses the whole file, so an import can never
|
|
254
|
+
* land half of its entries into a configuration that no longer matches.
|
|
255
|
+
*/
|
|
256
|
+
importContent(file: unknown, options?: {
|
|
257
|
+
dryRun?: boolean;
|
|
258
|
+
actorId?: string;
|
|
259
|
+
}): Promise<EponymeImportResult | EponymeImportRejection>;
|
|
185
260
|
createCollectionEntry(name: string, payload: unknown, actorId?: string): Promise<(EponymeResult & {
|
|
186
261
|
slug: string;
|
|
187
262
|
errors?: never;
|
|
188
263
|
}) | {
|
|
189
264
|
errors: ValidationErrors;
|
|
190
265
|
} | undefined>;
|
|
266
|
+
/**
|
|
267
|
+
* A trashed entry keeps its row, so it keeps its slug: reusing one has to be
|
|
268
|
+
* refused with an error the editor can act on rather than the generic message.
|
|
269
|
+
*/
|
|
270
|
+
private slugTakenError;
|
|
271
|
+
/** Moves an entry to the trash. Its history is kept, so it stays restorable. */
|
|
191
272
|
deleteCollectionEntry(name: string): Promise<boolean>;
|
|
273
|
+
restoreCollectionEntry(name: string): Promise<boolean>;
|
|
274
|
+
/** Definitive: `onDelete: Cascade` takes the entry's whole history with it. */
|
|
275
|
+
purgeCollectionEntry(name: string): Promise<boolean>;
|
|
276
|
+
/** Trashed entries of a collection, most recently updated first. */
|
|
277
|
+
listCollectionTrash(name: string): Promise<EponymeCollectionPage | undefined>;
|
|
192
278
|
patch(name: string, payload: unknown, action?: EponymeAction, actorId?: string): Promise<(EponymeResult & {
|
|
193
279
|
errors?: never;
|
|
194
280
|
conflict?: never;
|
|
@@ -199,4 +285,6 @@ export declare class EponymeService {
|
|
|
199
285
|
history(name: string, limit?: number): Promise<EponymeHistoryEntry[] | undefined>;
|
|
200
286
|
restore(name: string, versionId: number, actorId?: string): Promise<EponymeResult | EponymeConflict | undefined>;
|
|
201
287
|
}
|
|
288
|
+
/** Stable hash of a schema's shape, embedded in an export and checked on import. */
|
|
289
|
+
export declare function schemaFingerprint(schema: EponymeSchema): string;
|
|
202
290
|
export declare function isPrismaError(error: unknown, code: string): boolean;
|