@panneau/protocol 5.0.0-alpha.4 → 5.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +121 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +5 -2
- package/dist/libraries.d.ts +16 -0
- package/dist/libraries.d.ts.map +1 -0
- package/dist/libraries.js +15 -0
- package/dist/libraries.js.map +1 -0
- package/dist/lint.d.ts +2 -2
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +15 -9
- package/dist/lint.js.map +1 -1
- package/dist/mock/example.d.ts.map +1 -1
- package/dist/mock/example.js +8 -0
- package/dist/mock/example.js.map +1 -1
- package/dist/mock/index.d.ts +9 -1
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +119 -13
- package/dist/mock/index.js.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +37 -7
- package/dist/openapi.js.map +1 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +17 -0
- package/dist/rules.js.map +1 -1
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +4 -1
- package/dist/schemas/action.d.ts +17 -1
- package/dist/schemas/action.d.ts.map +1 -1
- package/dist/schemas/api.d.ts +87 -0
- package/dist/schemas/api.d.ts.map +1 -1
- package/dist/schemas/api.js +28 -9
- package/dist/schemas/api.js.map +1 -1
- package/dist/schemas/definition.d.ts +38 -2
- package/dist/schemas/definition.d.ts.map +1 -1
- package/dist/schemas/definition.js +2 -1
- package/dist/schemas/definition.js.map +1 -1
- package/dist/schemas/field.d.ts +24 -1
- package/dist/schemas/field.d.ts.map +1 -1
- package/dist/schemas/field.js +22 -4
- package/dist/schemas/field.js.map +1 -1
- package/dist/schemas/index.d.ts +201 -6
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +4 -2
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/resource.d.ts +36 -2
- package/dist/schemas/resource.d.ts.map +1 -1
- package/dist/schemas/resource.js +3 -1
- package/dist/schemas/resource.js.map +1 -1
- package/dist/search.d.ts +51 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +53 -0
- package/dist/search.js.map +1 -0
- package/dist/transforms.d.ts +17 -0
- package/dist/transforms.d.ts.map +1 -0
- package/dist/transforms.js +34 -0
- package/dist/transforms.js.map +1 -0
- package/package.json +1 -1
- package/schema/boot.schema.json +80 -1
- package/schema/definition.schema.json +80 -1
- package/schema/openapi.json +342 -13
package/dist/mock/example.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus\n * authors with a photo, and a media library. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n {\n name: 'attachment',\n type: 'file',\n label: 'Pièce jointe',\n rules: [{ rule: 'accept', value: ['application/pdf'] }],\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n {\n type: 'sidebar',\n children: [\n { type: 'field', name: 'published_at' },\n { type: 'field', name: 'attachment' },\n ],\n },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n dryRun: true,\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [\n { name: 'name', type: 'text', rules: [{ rule: 'required' }] },\n { name: 'photo', type: 'media', accept: ['image/*'] },\n ],\n },\n {\n id: 'media',\n kind: 'media',\n label: { singular: 'média', plural: 'médias' },\n itemTitle: 'name',\n fields: [\n {\n name: 'file',\n type: 'file',\n accept: ['image/*', 'video/*', 'application/pdf'],\n rules: [{ rule: 'required' }],\n },\n { name: 'name', type: 'text' },\n { name: 'alt', type: 'text', localized: true },\n ],\n list: {\n filters: [\n {\n id: 'type',\n type: 'select',\n options: [\n { value: 'image', label: 'Images' },\n { value: 'video', label: 'Vidéos' },\n { value: 'document', label: 'Documents' },\n ],\n },\n ],\n search: true,\n },\n abilities: { viewAny: true, create: true },\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n uploads: { mode: 'presigned' },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus\n * authors with a photo, and a media library. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n {\n name: 'attachment',\n type: 'file',\n label: 'Pièce jointe',\n rules: [{ rule: 'accept', value: ['application/pdf'] }],\n },\n {\n name: 'slug',\n type: 'slug',\n from: 'title',\n localized: true,\n check: true,\n settings: { prefix: 'https://example.com/' },\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n {\n type: 'sidebar',\n children: [\n { type: 'field', name: 'published_at' },\n { type: 'field', name: 'attachment' },\n ],\n },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n dryRun: true,\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [\n { name: 'name', type: 'text', rules: [{ rule: 'required' }] },\n { name: 'photo', type: 'media', accept: ['image/*'] },\n ],\n },\n {\n id: 'media',\n kind: 'media',\n label: { singular: 'média', plural: 'médias' },\n itemTitle: 'name',\n fields: [\n {\n name: 'file',\n type: 'file',\n accept: ['image/*', 'video/*', 'application/pdf'],\n rules: [{ rule: 'required' }],\n },\n { name: 'name', type: 'text' },\n { name: 'alt', type: 'text', localized: true },\n ],\n list: {\n filters: [\n {\n id: 'type',\n type: 'select',\n options: [\n { value: 'image', label: 'Images' },\n { value: 'video', label: 'Vidéos' },\n { value: 'document', label: 'Documents' },\n ],\n },\n ],\n search: true,\n },\n abilities: { viewAny: true, create: true },\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n uploads: { mode: 'presigned' },\n };\n}\n"],"mappings":"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"}
|
package/dist/mock/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ export interface MockBackendOptions {
|
|
|
6
6
|
definition: DefinitionInput;
|
|
7
7
|
/** Initial values of the items, per resource id. Items without an `id` get one. */
|
|
8
8
|
items?: Record<string, Values[]>;
|
|
9
|
+
/**
|
|
10
|
+
* The URL that `POST /resources/{resource}/{id}/preview` returns, for the item with the posted
|
|
11
|
+
* values. Defaults to a `data:` URL of a plain page with the item's values.
|
|
12
|
+
*/
|
|
13
|
+
previewUrl?: (context: {
|
|
14
|
+
resource: string;
|
|
15
|
+
item: Item;
|
|
16
|
+
}) => string;
|
|
9
17
|
}
|
|
10
18
|
export interface MockBackend {
|
|
11
19
|
/** A `fetch`-compatible function that answers protocol requests. */
|
|
@@ -19,6 +27,6 @@ export interface MockBackend {
|
|
|
19
27
|
* with the portable rules. Used to test the conformance kit, and to develop frontends without a
|
|
20
28
|
* real backend.
|
|
21
29
|
*/
|
|
22
|
-
export declare function createMockBackend(
|
|
30
|
+
export declare function createMockBackend(options: MockBackendOptions): MockBackend;
|
|
23
31
|
export { createExampleDefinition } from './example';
|
|
24
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/mock/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAGR,IAAI,EAUP,MAAM,gBAAgB,CAAC;AAKxB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IAC/B,kGAAkG;IAClG,UAAU,EAAE,eAAe,CAAC;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,KAAK,MAAM,CAAC;CACtE;AAED,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChF,uCAAuC;IACvC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;CACvC;AA2KD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAshB1E;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/mock/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { PROBLEM_CONTENT_TYPE } from "../version.js";
|
|
2
2
|
import { isRecord } from "../values.js";
|
|
3
3
|
import { checkValues } from "../rules.js";
|
|
4
|
+
import { getMediaLibraries } from "../libraries.js";
|
|
4
5
|
import { fromSearchParams, isFilterRange, matchesFilterRange } from "../query.js";
|
|
6
|
+
import { readSearchQuery } from "../search.js";
|
|
5
7
|
import { createExampleDefinition } from "./example.js";
|
|
6
8
|
//#region src/mock/index.ts
|
|
7
9
|
var RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9,6 +11,15 @@ var RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
|
9
11
|
"version",
|
|
10
12
|
"abilities"
|
|
11
13
|
]);
|
|
14
|
+
function escapeHtml(text) {
|
|
15
|
+
return text.replace(/[&<>"']/g, (character) => `&#${character.charCodeAt(0)};`);
|
|
16
|
+
}
|
|
17
|
+
/** A plain page with the values of an item, as a `data:` URL, for iframe previews. */
|
|
18
|
+
function defaultPreviewUrl(item) {
|
|
19
|
+
const rows = Object.entries(item).filter(([key]) => !RESERVED_KEYS.has(key)).map(([key, value]) => `<dt>${escapeHtml(key)}</dt><dd>${escapeHtml(typeof value === "string" ? value : JSON.stringify(value))}</dd>`).join("");
|
|
20
|
+
const html = `<!doctype html><meta charset="utf-8"><title>Preview</title><body style="font-family:system-ui;margin:2rem"><h1>Item ${escapeHtml(item.id)}</h1><dl>${rows}</dl></body>`;
|
|
21
|
+
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
|
22
|
+
}
|
|
12
23
|
var DEFAULT_MESSAGES = {
|
|
13
24
|
required: () => "This field is required.",
|
|
14
25
|
min: (failure) => `This value must be at least ${String(failure.value)}.`,
|
|
@@ -20,7 +31,11 @@ var DEFAULT_MESSAGES = {
|
|
|
20
31
|
accept: () => "This file type is not accepted.",
|
|
21
32
|
maxSize: () => "This file is too large.",
|
|
22
33
|
before: () => "This date is too late.",
|
|
23
|
-
after: () => "This date is too early."
|
|
34
|
+
after: () => "This date is too early.",
|
|
35
|
+
crop: (failure) => {
|
|
36
|
+
const ratios = isRecord(failure.value) ? failure.value.ratios : void 0;
|
|
37
|
+
return Array.isArray(ratios) && ratios.length > 0 ? `Crop this image to ${ratios.join(" or ")}.` : "Crop this image.";
|
|
38
|
+
}
|
|
24
39
|
};
|
|
25
40
|
function message(failure) {
|
|
26
41
|
return failure.message ?? DEFAULT_MESSAGES[failure.rule]?.(failure) ?? "This value is invalid.";
|
|
@@ -113,12 +128,28 @@ function mediaOf(file) {
|
|
|
113
128
|
};
|
|
114
129
|
}
|
|
115
130
|
/**
|
|
131
|
+
* A media item with its own transform (its `transform` field) in its `media`, where references
|
|
132
|
+
* give it to clients (decision 0031).
|
|
133
|
+
*/
|
|
134
|
+
function withOwnTransform(resource, item) {
|
|
135
|
+
if (resource.kind !== "media" || !isRecord(item.media)) return item;
|
|
136
|
+
const { transform: _previous, ...media } = item.media;
|
|
137
|
+
return {
|
|
138
|
+
...item,
|
|
139
|
+
media: isRecord(item.transform) ? {
|
|
140
|
+
...media,
|
|
141
|
+
transform: item.transform
|
|
142
|
+
} : media
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
116
146
|
* An in-memory backend that implements the protocol's resource routes: definition, list, show,
|
|
117
147
|
* create, update (with `If-Match`), delete and the built-in `delete` action. Values are checked
|
|
118
148
|
* with the portable rules. Used to test the conformance kit, and to develop frontends without a
|
|
119
149
|
* real backend.
|
|
120
150
|
*/
|
|
121
|
-
function createMockBackend(
|
|
151
|
+
function createMockBackend(options) {
|
|
152
|
+
const { definition, items = {} } = options;
|
|
122
153
|
const store = /* @__PURE__ */ new Map();
|
|
123
154
|
let nextId = 1;
|
|
124
155
|
const abilitiesFor = (resource) => Object.fromEntries([
|
|
@@ -226,29 +257,36 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
226
257
|
};
|
|
227
258
|
const missingMedia = (paths) => problem(422, "The given data was invalid.", {
|
|
228
259
|
type: "urn:panneau:problem:validation",
|
|
229
|
-
errors: Object.fromEntries(paths.map((path) => [path, [
|
|
260
|
+
errors: Object.fromEntries(Object.entries(paths).map(([path, message]) => [path, [message]]))
|
|
230
261
|
});
|
|
231
262
|
const missingUploads = (paths) => problem(422, "The given data was invalid.", {
|
|
232
263
|
type: "urn:panneau:problem:validation",
|
|
233
264
|
errors: Object.fromEntries(paths.map((path) => [path, ["This upload does not exist."]]))
|
|
234
265
|
});
|
|
235
266
|
const resolveMedia = (resource, values) => {
|
|
236
|
-
const missing =
|
|
267
|
+
const missing = {};
|
|
237
268
|
const resolved = { ...values };
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const library = libraries.find((candidate) => field.resource !== void 0 ? candidate.id === field.resource : true);
|
|
269
|
+
resource.fields.filter((field) => (field.type === "media" || field.type === "image") && field.name in values).forEach((field) => {
|
|
270
|
+
const allowed = getMediaLibraries(definition, field);
|
|
241
271
|
const toReference = (value, path) => {
|
|
242
272
|
const id = isRecord(value) ? value.id : value;
|
|
243
273
|
if (typeof id !== "string" && typeof id !== "number") return value;
|
|
274
|
+
const named = isRecord(value) && typeof value.resource === "string" ? value.resource : void 0;
|
|
275
|
+
if (named === void 0 && allowed.length > 1) {
|
|
276
|
+
missing[path] = "This field allows several libraries: name the library.";
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
const libraryId = named ?? allowed[0];
|
|
280
|
+
const library = definition.resources.find((candidate) => candidate.id === libraryId && allowed.includes(candidate.id));
|
|
244
281
|
const media = (store.get(library?.id ?? "") ?? []).find((candidate) => candidate.id === String(id));
|
|
245
282
|
if (media === void 0 || library === void 0) {
|
|
246
|
-
missing.
|
|
283
|
+
missing[path] = "This media does not exist.";
|
|
247
284
|
return value;
|
|
248
285
|
}
|
|
249
286
|
const title = media[library.itemTitle ?? "name"];
|
|
250
287
|
return {
|
|
251
288
|
id: media.id,
|
|
289
|
+
...allowed.length > 1 ? { resource: library.id } : {},
|
|
252
290
|
...typeof title === "string" ? { label: title } : {},
|
|
253
291
|
...isRecord(media.media) ? { media: media.media } : {},
|
|
254
292
|
...isRecord(value) && isRecord(value.transform) ? { transform: value.transform } : {}
|
|
@@ -271,7 +309,7 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
271
309
|
problem: missingUploads(files.missing)
|
|
272
310
|
};
|
|
273
311
|
const media = resolveMedia(resource, files.values);
|
|
274
|
-
if (media.missing.length > 0) return {
|
|
312
|
+
if (Object.keys(media.missing).length > 0) return {
|
|
275
313
|
values: body,
|
|
276
314
|
problem: missingMedia(media.missing)
|
|
277
315
|
};
|
|
@@ -282,6 +320,26 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
282
320
|
problem: null
|
|
283
321
|
};
|
|
284
322
|
};
|
|
323
|
+
const search = (params) => {
|
|
324
|
+
const read = readSearchQuery(params, definition);
|
|
325
|
+
if (read.errors !== void 0) return problem(422, "The given data was invalid.", {
|
|
326
|
+
type: "urn:panneau:problem:validation",
|
|
327
|
+
errors: read.errors
|
|
328
|
+
});
|
|
329
|
+
const { q, limit, resources } = read.query;
|
|
330
|
+
let left = 20;
|
|
331
|
+
return json(200, { data: resources.flatMap((id) => {
|
|
332
|
+
if (left === 0) return [];
|
|
333
|
+
const found = applyQuery(store.get(id) ?? [], { q });
|
|
334
|
+
const items = found.slice(0, Math.min(limit, left));
|
|
335
|
+
left -= items.length;
|
|
336
|
+
return items.length > 0 ? [{
|
|
337
|
+
resource: id,
|
|
338
|
+
items,
|
|
339
|
+
total: found.length
|
|
340
|
+
}] : [];
|
|
341
|
+
}) });
|
|
342
|
+
};
|
|
285
343
|
const handle = async (request) => {
|
|
286
344
|
const url = new URL(request.url, "http://panneau.test");
|
|
287
345
|
if (!url.pathname.startsWith(basePath)) return problem(404, "Not found.");
|
|
@@ -292,6 +350,7 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
292
350
|
return isRecord(body) ? body : {};
|
|
293
351
|
};
|
|
294
352
|
if (segments.length === 1 && segments[0] === "definition" && method === "GET") return json(200, definition);
|
|
353
|
+
if (segments.length === 1 && segments[0] === "search" && method === "GET") return search(url.searchParams);
|
|
295
354
|
if (segments[0] === "uploads") return handleUploads(segments, method, readBody);
|
|
296
355
|
if (segments[0] !== "resources" || segments[1] === void 0) return problem(404, "Not found.");
|
|
297
356
|
const resource = definition.resources.find((candidate) => candidate.id === segments[1]);
|
|
@@ -323,15 +382,45 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
323
382
|
if (invalid !== null) return invalid;
|
|
324
383
|
const { values, problem: unresolved } = resolveValues(resource, body);
|
|
325
384
|
if (unresolved !== null) return unresolved;
|
|
326
|
-
const item = makeItem(resource, {
|
|
385
|
+
const item = withOwnTransform(resource, makeItem(resource, {
|
|
327
386
|
...values,
|
|
328
387
|
id: void 0
|
|
329
|
-
});
|
|
388
|
+
}));
|
|
330
389
|
list.push(item);
|
|
331
390
|
return json(201, { data: item }, etag(item));
|
|
332
391
|
}
|
|
333
392
|
return problem(405, "Method not allowed.");
|
|
334
393
|
}
|
|
394
|
+
if (segments[2] === "fields" && segments[4] === "check" && segments.length === 5) {
|
|
395
|
+
if (method !== "POST") return problem(405, "Method not allowed.");
|
|
396
|
+
const field = resource.fields.find((candidate) => candidate.name === segments[3] && candidate.check === true);
|
|
397
|
+
if (field === void 0) return problem(404, "Field not found.");
|
|
398
|
+
const body = await readBody();
|
|
399
|
+
if (typeof body.value !== "string") return problem(422, "The given data was invalid.", {
|
|
400
|
+
type: "urn:panneau:problem:validation",
|
|
401
|
+
errors: { value: ["The value is required."] }
|
|
402
|
+
});
|
|
403
|
+
const locale = typeof body.locale === "string" ? body.locale : null;
|
|
404
|
+
const id = typeof body.id === "string" || typeof body.id === "number" ? String(body.id) : null;
|
|
405
|
+
const valueOf = (item) => {
|
|
406
|
+
const value = item[field.name];
|
|
407
|
+
return locale !== null && isRecord(value) ? value[locale] : value;
|
|
408
|
+
};
|
|
409
|
+
const taken = (candidate) => list.some((item) => item.id !== id && valueOf(item) === candidate);
|
|
410
|
+
const available = !taken(body.value);
|
|
411
|
+
let suggestion = null;
|
|
412
|
+
for (let index = 2; !available && suggestion === null; index += 1) {
|
|
413
|
+
const candidate = `${body.value}-${String(index)}`;
|
|
414
|
+
suggestion = taken(candidate) ? null : candidate;
|
|
415
|
+
}
|
|
416
|
+
const prefix = typeof field.settings?.prefix === "string" ? field.settings.prefix : null;
|
|
417
|
+
return json(200, {
|
|
418
|
+
available,
|
|
419
|
+
suggestion,
|
|
420
|
+
prefix,
|
|
421
|
+
url: prefix !== null ? `${prefix}${body.value}` : null
|
|
422
|
+
});
|
|
423
|
+
}
|
|
335
424
|
if (segments[2] === "actions" && segments[3] !== void 0 && segments.length === 4) {
|
|
336
425
|
if (method !== "POST") return problem(405, "Method not allowed.");
|
|
337
426
|
const actionId = segments[3];
|
|
@@ -367,6 +456,23 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
367
456
|
refresh: "list"
|
|
368
457
|
});
|
|
369
458
|
}
|
|
459
|
+
if (segments.length === 4 && segments[3] === "preview" && segments[2] !== void 0) {
|
|
460
|
+
const current = list.find((item) => item.id === segments[2]);
|
|
461
|
+
if (current === void 0) return problem(404, "Item not found.");
|
|
462
|
+
if (method !== "POST") return problem(405, "Method not allowed.");
|
|
463
|
+
const body = await readBody();
|
|
464
|
+
const item = {
|
|
465
|
+
...current,
|
|
466
|
+
...isRecord(body) ? body : {}
|
|
467
|
+
};
|
|
468
|
+
return json(200, {
|
|
469
|
+
url: options.previewUrl?.({
|
|
470
|
+
resource: resource.id,
|
|
471
|
+
item
|
|
472
|
+
}) ?? defaultPreviewUrl(item),
|
|
473
|
+
expiresAt: new Date(Date.now() + 3e5).toISOString()
|
|
474
|
+
});
|
|
475
|
+
}
|
|
370
476
|
if (segments.length !== 3 || segments[2] === void 0) return problem(404, "Not found.");
|
|
371
477
|
const id = segments[2];
|
|
372
478
|
const index = list.findIndex((item) => item.id === id);
|
|
@@ -385,11 +491,11 @@ function createMockBackend({ definition, items = {} }) {
|
|
|
385
491
|
const { values, problem: unresolved } = resolveValues(resource, body);
|
|
386
492
|
if (unresolved !== null) return unresolved;
|
|
387
493
|
const updates = Object.fromEntries(Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)));
|
|
388
|
-
const updated = {
|
|
494
|
+
const updated = withOwnTransform(resource, {
|
|
389
495
|
...current,
|
|
390
496
|
...updates,
|
|
391
497
|
version: current.version === null ? null : String(Number(current.version) + 1)
|
|
392
|
-
};
|
|
498
|
+
});
|
|
393
499
|
list[index] = updated;
|
|
394
500
|
return json(200, { data: updated }, etag(updated));
|
|
395
501
|
}
|
package/dist/mock/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/mock/index.ts"],"sourcesContent":["import { type ListQuery, fromSearchParams, isFilterRange, matchesFilterRange } from '../query';\nimport { type RuleFailure, checkValues } from '../rules';\nimport type { DefinitionInput, ResourceInput } from '../schemas';\nimport type {\n ActionResult,\n Item,\n MediaInfo,\n MediaReference,\n Problem,\n Selection,\n StoredFile,\n Upload,\n UploadSignResponse,\n} from '../schemas/api';\nimport { isRecord } from '../values';\nimport { PROBLEM_CONTENT_TYPE } from '../version';\n\ntype Values = Record<string, unknown>;\n\nexport interface MockBackendOptions {\n /** The definition served by `GET /definition`. Its `api.baseUrl` is the prefix of every route. */\n definition: DefinitionInput;\n /** Initial values of the items, per resource id. Items without an `id` get one. */\n items?: Record<string, Values[]>;\n}\n\nexport interface MockBackend {\n /** A `fetch`-compatible function that answers protocol requests. */\n fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n /** The current items of a resource. */\n items: (resource: string) => Item[];\n}\n\nconst RESERVED_KEYS = new Set(['id', 'version', 'abilities']);\n\nconst DEFAULT_MESSAGES: Record<string, (failure: RuleFailure) => string> = {\n required: () => 'This field is required.',\n min: (failure) => `This value must be at least ${String(failure.value)}.`,\n max: (failure) => `This value must be at most ${String(failure.value)}.`,\n pattern: () => 'This value has an invalid format.',\n email: () => 'This value must be an email address.',\n url: () => 'This value must be a URL.',\n in: () => 'This value is not one of the choices.',\n accept: () => 'This file type is not accepted.',\n maxSize: () => 'This file is too large.',\n before: () => 'This date is too late.',\n after: () => 'This date is too early.',\n};\n\nfunction message(failure: RuleFailure): string {\n return failure.message ?? DEFAULT_MESSAGES[failure.rule]?.(failure) ?? 'This value is invalid.';\n}\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(status === 204 ? null : JSON.stringify(body), {\n status,\n headers: status === 204 ? headers : { 'Content-Type': 'application/json', ...headers },\n });\n}\n\nfunction problem(status: number, title: string, extra: Partial<Problem> = {}): Response {\n const body: Problem = { type: 'about:blank', title, status, ...extra };\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': PROBLEM_CONTENT_TYPE },\n });\n}\n\nfunction etag(item: Item): Record<string, string> {\n return item.version === null ? {} : { ETag: `\"${item.version}\"` };\n}\n\nfunction parseEtag(value: string): string {\n return value.replace(/^W\\//, '').replace(/^\"(.*)\"$/, '$1');\n}\n\nfunction searchableText(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n if (isRecord(value)) {\n return Object.values(value).map(searchableText).join(' ');\n }\n return '';\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const left = isRecord(a) ? searchableText(a) : a;\n const right = isRecord(b) ? searchableText(b) : b;\n if (left === right) {\n return 0;\n }\n if (left === undefined || left === null) {\n return 1;\n }\n if (right === undefined || right === null) {\n return -1;\n }\n return String(left).localeCompare(String(right), undefined, { numeric: true });\n}\n\nfunction applyQuery(items: Item[], query: Pick<ListQuery, 'filter' | 'q' | 'sort'>): Item[] {\n let result = items;\n Object.entries(query.filter ?? {}).forEach(([key, expected]) => {\n if (isFilterRange(expected)) {\n result = result.filter((item) => matchesFilterRange(item[key], expected));\n return;\n }\n const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);\n result = result.filter((item) => {\n // A filter that isn't a value of the item reads its `media`: `filter[type]=image`.\n const value =\n key in item ? item[key] : isRecord(item.media) ? item.media[key] : undefined;\n const values = Array.isArray(value) ? value : [value];\n return values.some((candidate) => accepted.includes(String(candidate)));\n });\n });\n if (query.q !== undefined && query.q !== '') {\n const terms = query.q.toLowerCase();\n result = result.filter((item) =>\n Object.entries(item)\n .filter(([key]) => !RESERVED_KEYS.has(key))\n .some(([, value]) => searchableText(value).toLowerCase().includes(terms)),\n );\n }\n const sort = query.sort ?? [];\n if (sort.length > 0) {\n result = [...result].sort((a, b) => {\n for (const part of sort) {\n const descending = part.startsWith('-');\n const key = descending ? part.slice(1) : part;\n const order = compare(a[key], b[key]);\n if (order !== 0) {\n return descending ? -order : order;\n }\n }\n return 0;\n });\n }\n return result;\n}\n\n/** The type of a media, from its MIME type. */\nfunction mediaType(mime: string | null | undefined): string {\n const family = (mime ?? '').split('/')[0] ?? '';\n if (family === 'image' || family === 'video' || family === 'audio') {\n return family;\n }\n return mime === 'application/pdf' || family === 'text' ? 'document' : 'other';\n}\n\n/** What the mock computes about a stored file: no renditions, ready at once. */\nfunction mediaOf(file: StoredFile): MediaInfo {\n const type = mediaType(file.mime);\n return {\n type,\n mime: file.mime ?? null,\n size: file.size ?? null,\n url: file.url ?? null,\n thumbnailUrl: type === 'image' ? (file.url ?? null) : null,\n status: 'ready',\n };\n}\n\n/**\n * An in-memory backend that implements the protocol's resource routes: definition, list, show,\n * create, update (with `If-Match`), delete and the built-in `delete` action. Values are checked\n * with the portable rules. Used to test the conformance kit, and to develop frontends without a\n * real backend.\n */\nexport function createMockBackend({ definition, items = {} }: MockBackendOptions): MockBackend {\n const store = new Map<string, Item[]>();\n let nextId = 1;\n\n const abilitiesFor = (resource: ResourceInput) =>\n Object.fromEntries([\n ['update', true],\n ['delete', true],\n ...(resource.actions ?? []).map((action) => [action.id, true]),\n ]);\n\n const makeItem = (resource: ResourceInput, values: Values): Item => {\n const id =\n typeof values.id === 'string' || typeof values.id === 'number'\n ? String(values.id)\n : String(nextId++);\n const rest = Object.fromEntries(\n Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)),\n );\n return { ...rest, id, version: '1', abilities: abilitiesFor(resource) };\n };\n\n definition.resources.forEach((resource) => {\n store.set(\n resource.id,\n (items[resource.id] ?? []).map((values) => makeItem(resource, values)),\n );\n });\n\n const basePath = new URL(definition.api.baseUrl, 'http://panneau.test').pathname.replace(\n /\\/$/,\n '',\n );\n\n const validationProblem = (\n resource: ResourceInput,\n values: Values,\n mode: 'create' | 'update',\n ) => {\n const failures = checkValues(resource.fields, values, {\n mode,\n partial: mode === 'update',\n locales: definition.locales,\n });\n const paths = Object.keys(failures);\n if (paths.length === 0) {\n return null;\n }\n return problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(\n paths.map((path) => [path, (failures[path] ?? []).map(message)]),\n ),\n });\n };\n\n const selectItems = (list: Item[], selection: Selection | undefined): Item[] => {\n if (selection === undefined) {\n return [];\n }\n if ('ids' in selection) {\n const ids = selection.ids as string[];\n return list.filter((item) => ids.includes(item.id));\n }\n const except = selection.except ?? [];\n const { filter, q } = selection.query;\n const query: Pick<ListQuery, 'filter' | 'q'> = {};\n if (filter !== undefined) {\n query.filter = filter as NonNullable<ListQuery['filter']>;\n }\n if (q !== undefined) {\n query.q = q;\n }\n return applyQuery(list, query).filter((item) => !except.includes(item.id));\n };\n\n // Direct uploads: the open uploads by id, with their key. The mock signs fake URLs, and\n // trusts the client when it completes an upload.\n const uploads = new Map<string, Upload>();\n let nextUpload = 1;\n\n const handleUploads = async (\n segments: string[],\n method: string,\n readBody: () => Promise<Values>,\n ): Promise<Response> => {\n if (definition.uploads?.mode !== 'presigned') {\n return problem(404, 'Not found.');\n }\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n if (segments.length === 2 && segments[1] === 'sign') {\n const body = await readBody();\n const opens =\n body.uploadId === undefined && (body.method === 'POST' || body.method === 'PUT');\n if (opens) {\n const id = `upl_${nextUpload++}`;\n const upload: Upload = { id, key: `uploads/${id}` };\n uploads.set(id, upload);\n const response: UploadSignResponse = {\n url: `https://storage.test/${upload.key}?signature=mock`,\n key: upload.key,\n upload: id,\n };\n return json(200, response);\n }\n const upload = [...uploads.values()].find((candidate) => candidate.key === body.key);\n if (upload === undefined) {\n return problem(404, 'Upload not found.');\n }\n return json(200, { url: `https://storage.test/${upload.key}?signature=mock` });\n }\n if (segments.length === 3 && segments[2] === 'complete' && segments[1] !== undefined) {\n const upload = uploads.get(segments[1]);\n if (upload === undefined) {\n return problem(404, 'Upload not found.');\n }\n const { name } = await readBody();\n if (typeof name === 'string') {\n upload.name = name;\n }\n return json(200, { data: upload });\n }\n return problem(404, 'Not found.');\n };\n\n // The values of `file` fields at the top of a resource: an upload becomes a stored file, and\n // a stored file or null is kept. Returns the paths of uploads that don't exist.\n const resolveFiles = (\n resource: ResourceInput,\n values: Values,\n ): { values: Values; missing: string[] } => {\n const missing: string[] = [];\n const resolved = { ...values };\n const toFile = (value: unknown, path: string): unknown => {\n if (!isRecord(value) || typeof value.upload !== 'string') {\n return value;\n }\n const upload = uploads.get(value.upload);\n if (upload === undefined) {\n missing.push(path);\n return value;\n }\n const file: StoredFile = {\n name: upload.name ?? (typeof value.name === 'string' ? value.name : upload.id),\n url: `https://storage.test/${upload.key ?? upload.id}`,\n mime: upload.mime ?? (typeof value.mime === 'string' ? value.mime : null),\n size: upload.size ?? (typeof value.size === 'number' ? value.size : null),\n };\n return file;\n };\n resource.fields\n .filter((field) => field.type === 'file' && field.name in values)\n .forEach((field) => {\n const value = values[field.name];\n resolved[field.name] = Array.isArray(value)\n ? value.map((entry, index) => toFile(entry, `${field.name}.${String(index)}`))\n : toFile(value, field.name);\n });\n return { values: resolved, missing };\n };\n\n const missingMedia = (paths: string[]) =>\n problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(paths.map((path) => [path, ['This media does not exist.']])),\n });\n\n const missingUploads = (paths: string[]) =>\n problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(\n paths.map((path) => [path, ['This upload does not exist.']]),\n ),\n });\n\n // The values of `media` fields at the top of a resource: an id, or `{ id, transform }`,\n // becomes a reference to a media of its library. Returns the paths of media that don't exist.\n const resolveMedia = (\n resource: ResourceInput,\n values: Values,\n ): { values: Values; missing: string[] } => {\n const missing: string[] = [];\n const resolved = { ...values };\n const libraries = definition.resources.filter((candidate) => candidate.kind === 'media');\n resource.fields\n .filter((field) => field.type === 'media' && field.name in values)\n .forEach((field) => {\n const library = libraries.find((candidate) =>\n field.resource !== undefined ? candidate.id === field.resource : true,\n );\n const toReference = (value: unknown, path: string): unknown => {\n const id = isRecord(value) ? value.id : value;\n if (typeof id !== 'string' && typeof id !== 'number') {\n return value;\n }\n const media = (store.get(library?.id ?? '') ?? []).find(\n (candidate) => candidate.id === String(id),\n );\n if (media === undefined || library === undefined) {\n missing.push(path);\n return value;\n }\n const title = media[library.itemTitle ?? 'name'];\n const reference: MediaReference = {\n id: media.id,\n ...(typeof title === 'string' ? { label: title } : {}),\n ...(isRecord(media.media) ? { media: media.media as MediaInfo } : {}),\n ...(isRecord(value) && isRecord(value.transform)\n ? { transform: value.transform }\n : {}),\n };\n return reference;\n };\n const value = values[field.name];\n resolved[field.name] = Array.isArray(value)\n ? value.map((entry, index) =>\n toReference(entry, `${field.name}.${String(index)}`),\n )\n : toReference(value, field.name);\n });\n return { values: resolved, missing };\n };\n\n // Resolves the files and media of the values, and computes the `media` of a media item from\n // its file. The client never sets `media`.\n const resolveValues = (\n resource: ResourceInput,\n body: Values,\n ): { values: Values; problem: Response | null } => {\n const sent = { ...body };\n if (resource.kind === 'media') {\n delete sent.media;\n }\n const files = resolveFiles(resource, sent);\n if (files.missing.length > 0) {\n return { values: body, problem: missingUploads(files.missing) };\n }\n const media = resolveMedia(resource, files.values);\n if (media.missing.length > 0) {\n return { values: body, problem: missingMedia(media.missing) };\n }\n const values = media.values;\n if (resource.kind === 'media' && isRecord(values.file)) {\n values.media = mediaOf(values.file as StoredFile);\n }\n return { values, problem: null };\n };\n\n const handle = async (request: Request): Promise<Response> => {\n const url = new URL(request.url, 'http://panneau.test');\n if (!url.pathname.startsWith(basePath)) {\n return problem(404, 'Not found.');\n }\n const segments = url.pathname\n .slice(basePath.length)\n .split('/')\n .filter((segment) => segment !== '')\n .map(decodeURIComponent);\n const method = request.method.toUpperCase();\n const readBody = async (): Promise<Values> => {\n const body: unknown = await request.json().catch(() => null);\n return isRecord(body) ? body : {};\n };\n\n if (segments.length === 1 && segments[0] === 'definition' && method === 'GET') {\n return json(200, definition);\n }\n if (segments[0] === 'uploads') {\n return handleUploads(segments, method, readBody);\n }\n if (segments[0] !== 'resources' || segments[1] === undefined) {\n return problem(404, 'Not found.');\n }\n\n const resource = definition.resources.find((candidate) => candidate.id === segments[1]);\n const list = resource !== undefined ? store.get(resource.id) : undefined;\n if (resource === undefined || list === undefined) {\n return problem(404, 'Resource not found.');\n }\n\n if (segments.length === 2) {\n if (method === 'GET') {\n const query = fromSearchParams(url.searchParams);\n const perPage = query.perPage ?? resource.list?.perPage ?? 20;\n const page = query.page ?? 1;\n const sort = query.sort ?? resource.list?.defaultSort?.split(',');\n const matching = applyQuery(list, {\n ...query,\n ...(sort !== undefined ? { sort } : {}),\n });\n return json(200, {\n data: matching.slice((page - 1) * perPage, page * perPage),\n meta: {\n page,\n perPage,\n total: matching.length,\n lastPage: Math.max(1, Math.ceil(matching.length / perPage)),\n },\n });\n }\n if (method === 'POST') {\n const body = await readBody();\n const invalid = validationProblem(resource, body, 'create');\n if (invalid !== null) {\n return invalid;\n }\n const { values, problem: unresolved } = resolveValues(resource, body);\n if (unresolved !== null) {\n return unresolved;\n }\n const item = makeItem(resource, { ...values, id: undefined });\n list.push(item);\n return json(201, { data: item }, etag(item));\n }\n return problem(405, 'Method not allowed.');\n }\n\n if (segments[2] === 'actions' && segments[3] !== undefined && segments.length === 4) {\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n const actionId = segments[3];\n const action = (resource.actions ?? []).find((candidate) => candidate.id === actionId);\n const body = await readBody();\n if (body.dryRun === true) {\n // A dry run counts what the action would do, and changes nothing.\n if (action?.dryRun !== true) {\n return problem(400, 'This action does not support dry runs.');\n }\n const selected = selectItems(list, body.selection as Selection | undefined);\n const allowed = selected.filter((item) => item.abilities?.[actionId] !== false);\n const result: ActionResult = {\n summary: {\n succeeded: allowed.length,\n failed: 0,\n skipped: selected.length - allowed.length,\n },\n results: selected\n .filter((item) => !allowed.includes(item))\n .slice(0, 20)\n .map((item) => ({ id: item.id, status: 'skipped', reason: 'forbidden' })),\n };\n return json(200, result);\n }\n if (actionId !== 'delete') {\n return problem(404, 'Action not found.');\n }\n const selected = selectItems(list, body.selection as Selection | undefined);\n store.set(\n resource.id,\n list.filter((item) => !selected.includes(item)),\n );\n const result: ActionResult = {\n message: `${selected.length} deleted.`,\n summary: { succeeded: selected.length, failed: 0, skipped: 0 },\n refresh: 'list',\n };\n return json(200, result);\n }\n\n if (segments.length !== 3 || segments[2] === undefined) {\n return problem(404, 'Not found.');\n }\n const id = segments[2];\n const index = list.findIndex((item) => item.id === id);\n const current = list[index];\n if (current === undefined) {\n return problem(404, 'Item not found.');\n }\n\n if (method === 'GET') {\n return json(200, { data: current }, etag(current));\n }\n if (method === 'PATCH') {\n const ifMatch = request.headers.get('If-Match');\n if (\n ifMatch !== null &&\n current.version !== null &&\n parseEtag(ifMatch) !== current.version\n ) {\n return problem(412, 'The item was changed by someone else.', {\n type: 'urn:panneau:problem:version-conflict',\n current,\n });\n }\n const body = await readBody();\n const invalid = validationProblem(resource, body, 'update');\n if (invalid !== null) {\n return invalid;\n }\n const { values, problem: unresolved } = resolveValues(resource, body);\n if (unresolved !== null) {\n return unresolved;\n }\n const updates = Object.fromEntries(\n Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)),\n );\n const updated: Item = {\n ...current,\n ...updates,\n version: current.version === null ? null : String(Number(current.version) + 1),\n };\n list[index] = updated;\n return json(200, { data: updated }, etag(updated));\n }\n if (method === 'DELETE') {\n list.splice(index, 1);\n return json(204, null);\n }\n return problem(405, 'Method not allowed.');\n };\n\n return {\n fetch: (input, init) => {\n if (input instanceof Request) {\n return handle(input);\n }\n // Relative URLs are accepted, as a browser would resolve them.\n return handle(new Request(new URL(input, 'http://panneau.test'), init));\n },\n items: (resource) => [...(store.get(resource) ?? [])],\n };\n}\n\nexport { createExampleDefinition } from './example';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/mock/index.ts"],"sourcesContent":["import { getMediaLibraries } from '../libraries';\nimport { type ListQuery, fromSearchParams, isFilterRange, matchesFilterRange } from '../query';\nimport { type RuleFailure, checkValues } from '../rules';\nimport type { DefinitionInput, ResourceInput } from '../schemas';\nimport type {\n ActionResult,\n FieldCheckResponse,\n Item,\n MediaInfo,\n MediaReference,\n PreviewResponse,\n Problem,\n SearchResponse,\n Selection,\n StoredFile,\n Upload,\n UploadSignResponse,\n} from '../schemas/api';\nimport { SEARCH_MAX_LIMIT, readSearchQuery } from '../search';\nimport { isRecord } from '../values';\nimport { PROBLEM_CONTENT_TYPE } from '../version';\n\ntype Values = Record<string, unknown>;\n\nexport interface MockBackendOptions {\n /** The definition served by `GET /definition`. Its `api.baseUrl` is the prefix of every route. */\n definition: DefinitionInput;\n /** Initial values of the items, per resource id. Items without an `id` get one. */\n items?: Record<string, Values[]>;\n /**\n * The URL that `POST /resources/{resource}/{id}/preview` returns, for the item with the posted\n * values. Defaults to a `data:` URL of a plain page with the item's values.\n */\n previewUrl?: (context: { resource: string; item: Item }) => string;\n}\n\nexport interface MockBackend {\n /** A `fetch`-compatible function that answers protocol requests. */\n fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n /** The current items of a resource. */\n items: (resource: string) => Item[];\n}\n\nconst RESERVED_KEYS = new Set(['id', 'version', 'abilities']);\n\nfunction escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (character) => `&#${character.charCodeAt(0)};`);\n}\n\n/** A plain page with the values of an item, as a `data:` URL, for iframe previews. */\nfunction defaultPreviewUrl(item: Item): string {\n const rows = Object.entries(item)\n .filter(([key]) => !RESERVED_KEYS.has(key))\n .map(\n ([key, value]) =>\n `<dt>${escapeHtml(key)}</dt><dd>${escapeHtml(typeof value === 'string' ? value : JSON.stringify(value))}</dd>`,\n )\n .join('');\n const html = `<!doctype html><meta charset=\"utf-8\"><title>Preview</title><body style=\"font-family:system-ui;margin:2rem\"><h1>Item ${escapeHtml(item.id)}</h1><dl>${rows}</dl></body>`;\n return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;\n}\n\nconst DEFAULT_MESSAGES: Record<string, (failure: RuleFailure) => string> = {\n required: () => 'This field is required.',\n min: (failure) => `This value must be at least ${String(failure.value)}.`,\n max: (failure) => `This value must be at most ${String(failure.value)}.`,\n pattern: () => 'This value has an invalid format.',\n email: () => 'This value must be an email address.',\n url: () => 'This value must be a URL.',\n in: () => 'This value is not one of the choices.',\n accept: () => 'This file type is not accepted.',\n maxSize: () => 'This file is too large.',\n before: () => 'This date is too late.',\n after: () => 'This date is too early.',\n crop: (failure) => {\n const ratios = isRecord(failure.value) ? failure.value.ratios : undefined;\n return Array.isArray(ratios) && ratios.length > 0\n ? `Crop this image to ${ratios.join(' or ')}.`\n : 'Crop this image.';\n },\n};\n\nfunction message(failure: RuleFailure): string {\n return failure.message ?? DEFAULT_MESSAGES[failure.rule]?.(failure) ?? 'This value is invalid.';\n}\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(status === 204 ? null : JSON.stringify(body), {\n status,\n headers: status === 204 ? headers : { 'Content-Type': 'application/json', ...headers },\n });\n}\n\nfunction problem(status: number, title: string, extra: Partial<Problem> = {}): Response {\n const body: Problem = { type: 'about:blank', title, status, ...extra };\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': PROBLEM_CONTENT_TYPE },\n });\n}\n\nfunction etag(item: Item): Record<string, string> {\n return item.version === null ? {} : { ETag: `\"${item.version}\"` };\n}\n\nfunction parseEtag(value: string): string {\n return value.replace(/^W\\//, '').replace(/^\"(.*)\"$/, '$1');\n}\n\nfunction searchableText(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n if (isRecord(value)) {\n return Object.values(value).map(searchableText).join(' ');\n }\n return '';\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const left = isRecord(a) ? searchableText(a) : a;\n const right = isRecord(b) ? searchableText(b) : b;\n if (left === right) {\n return 0;\n }\n if (left === undefined || left === null) {\n return 1;\n }\n if (right === undefined || right === null) {\n return -1;\n }\n return String(left).localeCompare(String(right), undefined, { numeric: true });\n}\n\nfunction applyQuery(items: Item[], query: Pick<ListQuery, 'filter' | 'q' | 'sort'>): Item[] {\n let result = items;\n Object.entries(query.filter ?? {}).forEach(([key, expected]) => {\n if (isFilterRange(expected)) {\n result = result.filter((item) => matchesFilterRange(item[key], expected));\n return;\n }\n const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);\n result = result.filter((item) => {\n // A filter that isn't a value of the item reads its `media`: `filter[type]=image`.\n const value =\n key in item ? item[key] : isRecord(item.media) ? item.media[key] : undefined;\n const values = Array.isArray(value) ? value : [value];\n return values.some((candidate) => accepted.includes(String(candidate)));\n });\n });\n if (query.q !== undefined && query.q !== '') {\n const terms = query.q.toLowerCase();\n result = result.filter((item) =>\n Object.entries(item)\n .filter(([key]) => !RESERVED_KEYS.has(key))\n .some(([, value]) => searchableText(value).toLowerCase().includes(terms)),\n );\n }\n const sort = query.sort ?? [];\n if (sort.length > 0) {\n result = [...result].sort((a, b) => {\n for (const part of sort) {\n const descending = part.startsWith('-');\n const key = descending ? part.slice(1) : part;\n const order = compare(a[key], b[key]);\n if (order !== 0) {\n return descending ? -order : order;\n }\n }\n return 0;\n });\n }\n return result;\n}\n\n/** The type of a media, from its MIME type. */\nfunction mediaType(mime: string | null | undefined): string {\n const family = (mime ?? '').split('/')[0] ?? '';\n if (family === 'image' || family === 'video' || family === 'audio') {\n return family;\n }\n return mime === 'application/pdf' || family === 'text' ? 'document' : 'other';\n}\n\n/** What the mock computes about a stored file: no renditions, ready at once. */\nfunction mediaOf(file: StoredFile): MediaInfo {\n const type = mediaType(file.mime);\n return {\n type,\n mime: file.mime ?? null,\n size: file.size ?? null,\n url: file.url ?? null,\n thumbnailUrl: type === 'image' ? (file.url ?? null) : null,\n status: 'ready',\n };\n}\n\n/**\n * A media item with its own transform (its `transform` field) in its `media`, where references\n * give it to clients (decision 0031).\n */\nfunction withOwnTransform(resource: ResourceInput, item: Item): Item {\n if (resource.kind !== 'media' || !isRecord(item.media)) {\n return item;\n }\n const { transform: _previous, ...media } = item.media;\n return {\n ...item,\n media: isRecord(item.transform) ? { ...media, transform: item.transform } : media,\n };\n}\n\n/**\n * An in-memory backend that implements the protocol's resource routes: definition, list, show,\n * create, update (with `If-Match`), delete and the built-in `delete` action. Values are checked\n * with the portable rules. Used to test the conformance kit, and to develop frontends without a\n * real backend.\n */\nexport function createMockBackend(options: MockBackendOptions): MockBackend {\n const { definition, items = {} } = options;\n const store = new Map<string, Item[]>();\n let nextId = 1;\n\n const abilitiesFor = (resource: ResourceInput) =>\n Object.fromEntries([\n ['update', true],\n ['delete', true],\n ...(resource.actions ?? []).map((action) => [action.id, true]),\n ]);\n\n const makeItem = (resource: ResourceInput, values: Values): Item => {\n const id =\n typeof values.id === 'string' || typeof values.id === 'number'\n ? String(values.id)\n : String(nextId++);\n const rest = Object.fromEntries(\n Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)),\n );\n return { ...rest, id, version: '1', abilities: abilitiesFor(resource) };\n };\n\n definition.resources.forEach((resource) => {\n store.set(\n resource.id,\n (items[resource.id] ?? []).map((values) => makeItem(resource, values)),\n );\n });\n\n const basePath = new URL(definition.api.baseUrl, 'http://panneau.test').pathname.replace(\n /\\/$/,\n '',\n );\n\n const validationProblem = (\n resource: ResourceInput,\n values: Values,\n mode: 'create' | 'update',\n ) => {\n const failures = checkValues(resource.fields, values, {\n mode,\n partial: mode === 'update',\n locales: definition.locales,\n });\n const paths = Object.keys(failures);\n if (paths.length === 0) {\n return null;\n }\n return problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(\n paths.map((path) => [path, (failures[path] ?? []).map(message)]),\n ),\n });\n };\n\n const selectItems = (list: Item[], selection: Selection | undefined): Item[] => {\n if (selection === undefined) {\n return [];\n }\n if ('ids' in selection) {\n const ids = selection.ids as string[];\n return list.filter((item) => ids.includes(item.id));\n }\n const except = selection.except ?? [];\n const { filter, q } = selection.query;\n const query: Pick<ListQuery, 'filter' | 'q'> = {};\n if (filter !== undefined) {\n query.filter = filter as NonNullable<ListQuery['filter']>;\n }\n if (q !== undefined) {\n query.q = q;\n }\n return applyQuery(list, query).filter((item) => !except.includes(item.id));\n };\n\n // Direct uploads: the open uploads by id, with their key. The mock signs fake URLs, and\n // trusts the client when it completes an upload.\n const uploads = new Map<string, Upload>();\n let nextUpload = 1;\n\n const handleUploads = async (\n segments: string[],\n method: string,\n readBody: () => Promise<Values>,\n ): Promise<Response> => {\n if (definition.uploads?.mode !== 'presigned') {\n return problem(404, 'Not found.');\n }\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n if (segments.length === 2 && segments[1] === 'sign') {\n const body = await readBody();\n const opens =\n body.uploadId === undefined && (body.method === 'POST' || body.method === 'PUT');\n if (opens) {\n const id = `upl_${nextUpload++}`;\n const upload: Upload = { id, key: `uploads/${id}` };\n uploads.set(id, upload);\n const response: UploadSignResponse = {\n url: `https://storage.test/${upload.key}?signature=mock`,\n key: upload.key,\n upload: id,\n };\n return json(200, response);\n }\n const upload = [...uploads.values()].find((candidate) => candidate.key === body.key);\n if (upload === undefined) {\n return problem(404, 'Upload not found.');\n }\n return json(200, { url: `https://storage.test/${upload.key}?signature=mock` });\n }\n if (segments.length === 3 && segments[2] === 'complete' && segments[1] !== undefined) {\n const upload = uploads.get(segments[1]);\n if (upload === undefined) {\n return problem(404, 'Upload not found.');\n }\n const { name } = await readBody();\n if (typeof name === 'string') {\n upload.name = name;\n }\n return json(200, { data: upload });\n }\n return problem(404, 'Not found.');\n };\n\n // The values of `file` fields at the top of a resource: an upload becomes a stored file, and\n // a stored file or null is kept. Returns the paths of uploads that don't exist.\n const resolveFiles = (\n resource: ResourceInput,\n values: Values,\n ): { values: Values; missing: string[] } => {\n const missing: string[] = [];\n const resolved = { ...values };\n const toFile = (value: unknown, path: string): unknown => {\n if (!isRecord(value) || typeof value.upload !== 'string') {\n return value;\n }\n const upload = uploads.get(value.upload);\n if (upload === undefined) {\n missing.push(path);\n return value;\n }\n const file: StoredFile = {\n name: upload.name ?? (typeof value.name === 'string' ? value.name : upload.id),\n url: `https://storage.test/${upload.key ?? upload.id}`,\n mime: upload.mime ?? (typeof value.mime === 'string' ? value.mime : null),\n size: upload.size ?? (typeof value.size === 'number' ? value.size : null),\n };\n return file;\n };\n resource.fields\n .filter((field) => field.type === 'file' && field.name in values)\n .forEach((field) => {\n const value = values[field.name];\n resolved[field.name] = Array.isArray(value)\n ? value.map((entry, index) => toFile(entry, `${field.name}.${String(index)}`))\n : toFile(value, field.name);\n });\n return { values: resolved, missing };\n };\n\n const missingMedia = (paths: Record<string, string>) =>\n problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(\n Object.entries(paths).map(([path, message]) => [path, [message]]),\n ),\n });\n\n const missingUploads = (paths: string[]) =>\n problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: Object.fromEntries(\n paths.map((path) => [path, ['This upload does not exist.']]),\n ),\n });\n\n // The values of `media` and `image` fields at the top of a resource: an id, or\n // `{ id, resource, transform }`, becomes a reference to a media of one of its libraries. A\n // field that allows several libraries needs the library of each media (decision 0027).\n // Returns the problems by path.\n const resolveMedia = (\n resource: ResourceInput,\n values: Values,\n ): { values: Values; missing: Record<string, string> } => {\n const missing: Record<string, string> = {};\n const resolved = { ...values };\n resource.fields\n .filter(\n (field) =>\n (field.type === 'media' || field.type === 'image') && field.name in values,\n )\n .forEach((field) => {\n const allowed = getMediaLibraries(definition, field);\n const toReference = (value: unknown, path: string): unknown => {\n const id = isRecord(value) ? value.id : value;\n if (typeof id !== 'string' && typeof id !== 'number') {\n return value;\n }\n const named =\n isRecord(value) && typeof value.resource === 'string'\n ? value.resource\n : undefined;\n if (named === undefined && allowed.length > 1) {\n missing[path] = 'This field allows several libraries: name the library.';\n return value;\n }\n const libraryId = named ?? allowed[0];\n const library = definition.resources.find(\n (candidate) => candidate.id === libraryId && allowed.includes(candidate.id),\n );\n const media = (store.get(library?.id ?? '') ?? []).find(\n (candidate) => candidate.id === String(id),\n );\n if (media === undefined || library === undefined) {\n missing[path] = 'This media does not exist.';\n return value;\n }\n const title = media[library.itemTitle ?? 'name'];\n const reference: MediaReference = {\n id: media.id,\n ...(allowed.length > 1 ? { resource: library.id } : {}),\n ...(typeof title === 'string' ? { label: title } : {}),\n ...(isRecord(media.media) ? { media: media.media as MediaInfo } : {}),\n ...(isRecord(value) && isRecord(value.transform)\n ? { transform: value.transform }\n : {}),\n };\n return reference;\n };\n const value = values[field.name];\n resolved[field.name] = Array.isArray(value)\n ? value.map((entry, index) =>\n toReference(entry, `${field.name}.${String(index)}`),\n )\n : toReference(value, field.name);\n });\n return { values: resolved, missing };\n };\n\n // Resolves the files and media of the values, and computes the `media` of a media item from\n // its file. The client never sets `media`.\n const resolveValues = (\n resource: ResourceInput,\n body: Values,\n ): { values: Values; problem: Response | null } => {\n const sent = { ...body };\n if (resource.kind === 'media') {\n delete sent.media;\n }\n const files = resolveFiles(resource, sent);\n if (files.missing.length > 0) {\n return { values: body, problem: missingUploads(files.missing) };\n }\n const media = resolveMedia(resource, files.values);\n if (Object.keys(media.missing).length > 0) {\n return { values: body, problem: missingMedia(media.missing) };\n }\n const values = media.values;\n if (resource.kind === 'media' && isRecord(values.file)) {\n values.media = mediaOf(values.file as StoredFile);\n }\n return { values, problem: null };\n };\n\n // The global search (decision 0029): the searchable resources asked for, `limit` items each\n // (5 by default), 20 in all, from 2 characters.\n const search = (params: URLSearchParams): Response => {\n const read = readSearchQuery(params, definition);\n if (read.errors !== undefined) {\n return problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: read.errors,\n });\n }\n const { q, limit, resources } = read.query;\n let left = SEARCH_MAX_LIMIT;\n const data: SearchResponse['data'] = resources.flatMap((id) => {\n if (left === 0) {\n return [];\n }\n const found = applyQuery(store.get(id) ?? [], { q });\n const items = found.slice(0, Math.min(limit, left));\n left -= items.length;\n return items.length > 0 ? [{ resource: id, items, total: found.length }] : [];\n });\n return json(200, { data } satisfies SearchResponse);\n };\n\n const handle = async (request: Request): Promise<Response> => {\n const url = new URL(request.url, 'http://panneau.test');\n if (!url.pathname.startsWith(basePath)) {\n return problem(404, 'Not found.');\n }\n const segments = url.pathname\n .slice(basePath.length)\n .split('/')\n .filter((segment) => segment !== '')\n .map(decodeURIComponent);\n const method = request.method.toUpperCase();\n const readBody = async (): Promise<Values> => {\n const body: unknown = await request.json().catch(() => null);\n return isRecord(body) ? body : {};\n };\n\n if (segments.length === 1 && segments[0] === 'definition' && method === 'GET') {\n return json(200, definition);\n }\n if (segments.length === 1 && segments[0] === 'search' && method === 'GET') {\n return search(url.searchParams);\n }\n if (segments[0] === 'uploads') {\n return handleUploads(segments, method, readBody);\n }\n if (segments[0] !== 'resources' || segments[1] === undefined) {\n return problem(404, 'Not found.');\n }\n\n const resource = definition.resources.find((candidate) => candidate.id === segments[1]);\n const list = resource !== undefined ? store.get(resource.id) : undefined;\n if (resource === undefined || list === undefined) {\n return problem(404, 'Resource not found.');\n }\n\n if (segments.length === 2) {\n if (method === 'GET') {\n const query = fromSearchParams(url.searchParams);\n const perPage = query.perPage ?? resource.list?.perPage ?? 20;\n const page = query.page ?? 1;\n const sort = query.sort ?? resource.list?.defaultSort?.split(',');\n const matching = applyQuery(list, {\n ...query,\n ...(sort !== undefined ? { sort } : {}),\n });\n return json(200, {\n data: matching.slice((page - 1) * perPage, page * perPage),\n meta: {\n page,\n perPage,\n total: matching.length,\n lastPage: Math.max(1, Math.ceil(matching.length / perPage)),\n },\n });\n }\n if (method === 'POST') {\n const body = await readBody();\n const invalid = validationProblem(resource, body, 'create');\n if (invalid !== null) {\n return invalid;\n }\n const { values, problem: unresolved } = resolveValues(resource, body);\n if (unresolved !== null) {\n return unresolved;\n }\n const item = withOwnTransform(\n resource,\n makeItem(resource, { ...values, id: undefined }),\n );\n list.push(item);\n return json(201, { data: item }, etag(item));\n }\n return problem(405, 'Method not allowed.');\n }\n\n if (segments[2] === 'fields' && segments[4] === 'check' && segments.length === 5) {\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n const field = resource.fields.find(\n (candidate) => candidate.name === segments[3] && candidate.check === true,\n );\n if (field === undefined) {\n return problem(404, 'Field not found.');\n }\n const body = await readBody();\n if (typeof body.value !== 'string') {\n return problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: { value: ['The value is required.'] },\n });\n }\n const locale = typeof body.locale === 'string' ? body.locale : null;\n const id =\n typeof body.id === 'string' || typeof body.id === 'number' ? String(body.id) : null;\n const valueOf = (item: Item): unknown => {\n const value = item[field.name];\n return locale !== null && isRecord(value) ? value[locale] : value;\n };\n const taken = (candidate: string) =>\n list.some((item) => item.id !== id && valueOf(item) === candidate);\n const available = !taken(body.value);\n let suggestion: string | null = null;\n for (let index = 2; !available && suggestion === null; index += 1) {\n const candidate = `${body.value}-${String(index)}`;\n suggestion = taken(candidate) ? null : candidate;\n }\n // The mock gives a URL when the field has a fixed prefix\n const prefix =\n typeof field.settings?.prefix === 'string' ? field.settings.prefix : null;\n const response: FieldCheckResponse = {\n available,\n suggestion,\n prefix,\n url: prefix !== null ? `${prefix}${body.value}` : null,\n };\n return json(200, response);\n }\n\n if (segments[2] === 'actions' && segments[3] !== undefined && segments.length === 4) {\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n const actionId = segments[3];\n const action = (resource.actions ?? []).find((candidate) => candidate.id === actionId);\n const body = await readBody();\n if (body.dryRun === true) {\n // A dry run counts what the action would do, and changes nothing.\n if (action?.dryRun !== true) {\n return problem(400, 'This action does not support dry runs.');\n }\n const selected = selectItems(list, body.selection as Selection | undefined);\n const allowed = selected.filter((item) => item.abilities?.[actionId] !== false);\n const result: ActionResult = {\n summary: {\n succeeded: allowed.length,\n failed: 0,\n skipped: selected.length - allowed.length,\n },\n results: selected\n .filter((item) => !allowed.includes(item))\n .slice(0, 20)\n .map((item) => ({ id: item.id, status: 'skipped', reason: 'forbidden' })),\n };\n return json(200, result);\n }\n if (actionId !== 'delete') {\n return problem(404, 'Action not found.');\n }\n const selected = selectItems(list, body.selection as Selection | undefined);\n store.set(\n resource.id,\n list.filter((item) => !selected.includes(item)),\n );\n const result: ActionResult = {\n message: `${selected.length} deleted.`,\n summary: { succeeded: selected.length, failed: 0, skipped: 0 },\n refresh: 'list',\n };\n return json(200, result);\n }\n\n if (segments.length === 4 && segments[3] === 'preview' && segments[2] !== undefined) {\n const current = list.find((item) => item.id === segments[2]);\n if (current === undefined) {\n return problem(404, 'Item not found.');\n }\n if (method !== 'POST') {\n return problem(405, 'Method not allowed.');\n }\n const body = await readBody();\n const item: Item = { ...current, ...(isRecord(body) ? body : {}) };\n const url =\n options.previewUrl?.({ resource: resource.id, item }) ?? defaultPreviewUrl(item);\n return json(200, {\n url,\n expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),\n } satisfies PreviewResponse);\n }\n\n if (segments.length !== 3 || segments[2] === undefined) {\n return problem(404, 'Not found.');\n }\n const id = segments[2];\n const index = list.findIndex((item) => item.id === id);\n const current = list[index];\n if (current === undefined) {\n return problem(404, 'Item not found.');\n }\n\n if (method === 'GET') {\n return json(200, { data: current }, etag(current));\n }\n if (method === 'PATCH') {\n const ifMatch = request.headers.get('If-Match');\n if (\n ifMatch !== null &&\n current.version !== null &&\n parseEtag(ifMatch) !== current.version\n ) {\n return problem(412, 'The item was changed by someone else.', {\n type: 'urn:panneau:problem:version-conflict',\n current,\n });\n }\n const body = await readBody();\n const invalid = validationProblem(resource, body, 'update');\n if (invalid !== null) {\n return invalid;\n }\n const { values, problem: unresolved } = resolveValues(resource, body);\n if (unresolved !== null) {\n return unresolved;\n }\n const updates = Object.fromEntries(\n Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)),\n );\n const updated = withOwnTransform(resource, {\n ...current,\n ...updates,\n version: current.version === null ? null : String(Number(current.version) + 1),\n });\n list[index] = updated;\n return json(200, { data: updated }, etag(updated));\n }\n if (method === 'DELETE') {\n list.splice(index, 1);\n return json(204, null);\n }\n return problem(405, 'Method not allowed.');\n };\n\n return {\n fetch: (input, init) => {\n if (input instanceof Request) {\n return handle(input);\n }\n // Relative URLs are accepted, as a browser would resolve them.\n return handle(new Request(new URL(input, 'http://panneau.test'), init));\n },\n items: (resource) => [...(store.get(resource) ?? [])],\n };\n}\n\nexport { createExampleDefinition } from './example';\n"],"mappings":"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"}
|
package/dist/openapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAcA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA6E1C,oEAAoE;AACpE,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAWnE;AAED,6DAA6D;AAC7D,wBAAgB,0BAA0B,IAAI,UAAU,CAQvD;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,IAAI,UAAU,CAQlD;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,IAAI,UAAU,CAQvD;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,UAAU,CA2XlD"}
|
package/dist/openapi.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PROBLEM_CONTENT_TYPE } from "./version.js";
|
|
2
|
+
import "./search.js";
|
|
2
3
|
import { Theme } from "./schemas/theme.js";
|
|
3
4
|
import { Definition } from "./schemas/definition.js";
|
|
4
5
|
import { BootConfig } from "./schemas/boot.js";
|
|
@@ -19,7 +20,8 @@ var PROBLEMS = {
|
|
|
19
20
|
404: "Not found.",
|
|
20
21
|
409: "Conflict with a business rule.",
|
|
21
22
|
412: "Version conflict: `If-Match` does not match the current version, which is in `current`.",
|
|
22
|
-
422: "Invalid values: messages are in `errors`, keyed by dot path."
|
|
23
|
+
422: "Invalid values: messages are in `errors`, keyed by dot path.",
|
|
24
|
+
429: "Too many requests: `Retry-After` gives the seconds to wait."
|
|
23
25
|
};
|
|
24
26
|
function problems(...statuses) {
|
|
25
27
|
return Object.fromEntries(statuses.map((status) => [String(status), {
|
|
@@ -225,6 +227,21 @@ function createOpenApiDocument() {
|
|
|
225
227
|
}
|
|
226
228
|
}
|
|
227
229
|
},
|
|
230
|
+
"/resources/{resource}/fields/{field}/check": {
|
|
231
|
+
parameters: [resource, pathParameter("field", "Name of a field with `check`.")],
|
|
232
|
+
post: {
|
|
233
|
+
operationId: "checkField",
|
|
234
|
+
summary: "Checks a value as it is typed: whether a slug is available, and its URL. With the permission to create, or to update the item `id`.",
|
|
235
|
+
requestBody: {
|
|
236
|
+
required: true,
|
|
237
|
+
content: json(ref("FieldCheckRequest"))
|
|
238
|
+
},
|
|
239
|
+
responses: {
|
|
240
|
+
200: ok("What the backend says about the value.", ref("FieldCheckResponse")),
|
|
241
|
+
...problems(401, 403, 404, 422)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
228
245
|
"/actions/runs": { get: {
|
|
229
246
|
operationId: "listActionRuns",
|
|
230
247
|
summary: "The current user's recent runs.",
|
|
@@ -444,13 +461,26 @@ function createOpenApiDocument() {
|
|
|
444
461
|
},
|
|
445
462
|
"/search": { get: {
|
|
446
463
|
operationId: "search",
|
|
447
|
-
parameters: [
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
464
|
+
parameters: [
|
|
465
|
+
{
|
|
466
|
+
...queryParameter("q", "Search terms, trimmed.", {
|
|
467
|
+
type: "string",
|
|
468
|
+
minLength: 2,
|
|
469
|
+
maxLength: 255
|
|
470
|
+
}),
|
|
471
|
+
required: true
|
|
472
|
+
},
|
|
473
|
+
queryParameter("limit", "Items per resource.", {
|
|
474
|
+
type: "integer",
|
|
475
|
+
minimum: 1,
|
|
476
|
+
maximum: 20,
|
|
477
|
+
default: 5
|
|
478
|
+
}),
|
|
479
|
+
queryParameter("resources", "Comma-separated ids of the searchable resources to search. Defaults to every one.", { type: "string" })
|
|
480
|
+
],
|
|
451
481
|
responses: {
|
|
452
|
-
200: ok(
|
|
453
|
-
...problems(401)
|
|
482
|
+
200: ok(`Results grouped by resource, in the order of the definition: at most \`limit\` items per resource, and 20 in all.`, ref("SearchResponse")),
|
|
483
|
+
...problems(401, 422, 429)
|
|
454
484
|
}
|
|
455
485
|
} },
|
|
456
486
|
"/dashboard/widgets/{widget}": {
|