@panneau/protocol 5.0.0-alpha.5 → 5.0.0-alpha.6

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.
@@ -2,10 +2,10 @@
2
2
  export declare const BUILT_IN_ACTIONS: readonly ["show", "edit", "duplicate", "delete", "restore", "export", "import"];
3
3
  export type BuiltInAction = (typeof BUILT_IN_ACTIONS)[number];
4
4
  /** Field types every Panneau frontend renders. Plugins add others. */
5
- export declare const BUILT_IN_FIELD_TYPES: readonly ["text", "textarea", "richtext", "number", "boolean", "date", "datetime", "time", "select", "email", "url", "color", "media", "image", "relation", "group", "repeater", "slug"];
5
+ export declare const BUILT_IN_FIELD_TYPES: readonly ["text", "textarea", "richtext", "number", "boolean", "date", "datetime", "time", "select", "email", "url", "color", "media", "image", "relation", "group", "repeater", "slug", "tel", "range", "duration", "json", "tags", "embed", "location"];
6
6
  export type BuiltInFieldType = (typeof BUILT_IN_FIELD_TYPES)[number];
7
7
  /** Validation rules that both frontends and backends check. */
8
- export declare const PORTABLE_RULES: readonly ["required", "min", "max", "pattern", "email", "url", "in", "accept", "maxSize", "before", "after", "crop"];
8
+ export declare const PORTABLE_RULES: readonly ["required", "min", "max", "pattern", "email", "url", "json", "in", "accept", "maxSize", "before", "after", "crop"];
9
9
  export type PortableRule = (typeof PORTABLE_RULES)[number];
10
10
  /** Layout node types every Panneau frontend renders. */
11
11
  export declare const BUILT_IN_LAYOUT_NODE_TYPES: readonly ["row", "column", "section", "tabs", "tab", "sidebar", "field", "text", "component"];
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,2BAA2B;AAC3B,eAAO,MAAM,gBAAgB,iFAQnB,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,0LAmBvB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,+DAA+D;AAC/D,eAAO,MAAM,cAAc,sHAajB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,wDAAwD;AACxD,eAAO,MAAM,0BAA0B,+FAU7B,CAAC;AACX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,oDAAqD,CAAC;AACxF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,sBAAsB,iBAAiB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,2BAA2B;AAC3B,eAAO,MAAM,gBAAgB,iFAQnB,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,2PA0BvB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,+DAA+D;AAC/D,eAAO,MAAM,cAAc,8HAcjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,wDAAwD;AACxD,eAAO,MAAM,0BAA0B,+FAU7B,CAAC;AACX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,oDAAqD,CAAC;AACxF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,sBAAsB,iBAAiB,CAAC"}
package/dist/constants.js CHANGED
@@ -28,7 +28,14 @@ var BUILT_IN_FIELD_TYPES = [
28
28
  "relation",
29
29
  "group",
30
30
  "repeater",
31
- "slug"
31
+ "slug",
32
+ "tel",
33
+ "range",
34
+ "duration",
35
+ "json",
36
+ "tags",
37
+ "embed",
38
+ "location"
32
39
  ];
33
40
  /** Validation rules that both frontends and backends check. */
34
41
  var PORTABLE_RULES = [
@@ -38,6 +45,7 @@ var PORTABLE_RULES = [
38
45
  "pattern",
39
46
  "email",
40
47
  "url",
48
+ "json",
41
49
  "in",
42
50
  "accept",
43
51
  "maxSize",
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["// Constants of the protocol. This module doesn't import zod, so that the main entry point stays\n// light: see `src/index.ts`.\n\n/** Built-in action ids. */\nexport const BUILT_IN_ACTIONS = [\n 'show',\n 'edit',\n 'duplicate',\n 'delete',\n 'restore',\n 'export',\n 'import',\n] as const;\nexport type BuiltInAction = (typeof BUILT_IN_ACTIONS)[number];\n\n/** Field types every Panneau frontend renders. Plugins add others. */\nexport const BUILT_IN_FIELD_TYPES = [\n 'text',\n 'textarea',\n 'richtext',\n 'number',\n 'boolean',\n 'date',\n 'datetime',\n 'time',\n 'select',\n 'email',\n 'url',\n 'color',\n 'media',\n 'image',\n 'relation',\n 'group',\n 'repeater',\n 'slug',\n] as const;\nexport type BuiltInFieldType = (typeof BUILT_IN_FIELD_TYPES)[number];\n\n/** Validation rules that both frontends and backends check. */\nexport const PORTABLE_RULES = [\n 'required',\n 'min',\n 'max',\n 'pattern',\n 'email',\n 'url',\n 'in',\n 'accept',\n 'maxSize',\n 'before',\n 'after',\n 'crop',\n] as const;\nexport type PortableRule = (typeof PORTABLE_RULES)[number];\n\n/** Layout node types every Panneau frontend renders. */\nexport const BUILT_IN_LAYOUT_NODE_TYPES = [\n 'row',\n 'column',\n 'section',\n 'tabs',\n 'tab',\n 'sidebar',\n 'field',\n 'text',\n 'component',\n] as const;\nexport type BuiltInLayoutNodeType = (typeof BUILT_IN_LAYOUT_NODE_TYPES)[number];\n\n/** List layouts every Panneau frontend renders. Plugins add others. */\nexport const BUILT_IN_LIST_LAYOUTS = ['table', 'cards', 'gallery', 'calendar'] as const;\nexport type BuiltInListLayout = (typeof BUILT_IN_LIST_LAYOUTS)[number];\n\n/**\n * Id of the element that holds the boot config in the page: a\n * `<script type=\"application/json\" id=\"panneau-boot\">` element whose text is the JSON object.\n */\nexport const BOOT_CONFIG_ELEMENT_ID = 'panneau-boot';\n"],"mappings":";;AAIA,IAAa,mBAAmB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,uBAAuB;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,iBAAiB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,6BAA6B;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,wBAAwB;CAAC;CAAS;CAAS;CAAW;AAAU;;;;;AAO7E,IAAa,yBAAyB"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["// Constants of the protocol. This module doesn't import zod, so that the main entry point stays\n// light: see `src/index.ts`.\n\n/** Built-in action ids. */\nexport const BUILT_IN_ACTIONS = [\n 'show',\n 'edit',\n 'duplicate',\n 'delete',\n 'restore',\n 'export',\n 'import',\n] as const;\nexport type BuiltInAction = (typeof BUILT_IN_ACTIONS)[number];\n\n/** Field types every Panneau frontend renders. Plugins add others. */\nexport const BUILT_IN_FIELD_TYPES = [\n 'text',\n 'textarea',\n 'richtext',\n 'number',\n 'boolean',\n 'date',\n 'datetime',\n 'time',\n 'select',\n 'email',\n 'url',\n 'color',\n 'media',\n 'image',\n 'relation',\n 'group',\n 'repeater',\n 'slug',\n 'tel',\n 'range',\n 'duration',\n 'json',\n 'tags',\n 'embed',\n 'location',\n] as const;\nexport type BuiltInFieldType = (typeof BUILT_IN_FIELD_TYPES)[number];\n\n/** Validation rules that both frontends and backends check. */\nexport const PORTABLE_RULES = [\n 'required',\n 'min',\n 'max',\n 'pattern',\n 'email',\n 'url',\n 'json',\n 'in',\n 'accept',\n 'maxSize',\n 'before',\n 'after',\n 'crop',\n] as const;\nexport type PortableRule = (typeof PORTABLE_RULES)[number];\n\n/** Layout node types every Panneau frontend renders. */\nexport const BUILT_IN_LAYOUT_NODE_TYPES = [\n 'row',\n 'column',\n 'section',\n 'tabs',\n 'tab',\n 'sidebar',\n 'field',\n 'text',\n 'component',\n] as const;\nexport type BuiltInLayoutNodeType = (typeof BUILT_IN_LAYOUT_NODE_TYPES)[number];\n\n/** List layouts every Panneau frontend renders. Plugins add others. */\nexport const BUILT_IN_LIST_LAYOUTS = ['table', 'cards', 'gallery', 'calendar'] as const;\nexport type BuiltInListLayout = (typeof BUILT_IN_LIST_LAYOUTS)[number];\n\n/**\n * Id of the element that holds the boot config in the page: a\n * `<script type=\"application/json\" id=\"panneau-boot\">` element whose text is the JSON object.\n */\nexport const BOOT_CONFIG_ELEMENT_ID = 'panneau-boot';\n"],"mappings":";;AAIA,IAAa,mBAAmB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,uBAAuB;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,iBAAiB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,6BAA6B;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAIA,IAAa,wBAAwB;CAAC;CAAS;CAAS;CAAW;AAAU;;;;;AAO7E,IAAa,yBAAyB"}
package/dist/index.js CHANGED
@@ -18,9 +18,9 @@ import { Column, Filter, ListDefinition } from "./schemas/list.js";
18
18
  import { Resource, ResourceFeatures } from "./schemas/resource.js";
19
19
  import { DefinitionTheme, Theme, ThemeFont, ThemeFontFace, ThemeFontStylesheet, ThemeTokens, ToneInput, ToneValues } from "./schemas/theme.js";
20
20
  import { Auth, AuthProvider, Definition, MenuItem, Page, Uploads, User, Widget } from "./schemas/definition.js";
21
- import { ActionItemResult, ActionRequest, ActionResult, ActionRun, ActionRunResponse, FieldCheckRequest, FieldCheckResponse, Item, ItemResponse, ListResponse, MediaInfo, MediaItem, MediaReference, MediaTransform, Pagination, PreviewResponse, Problem, SearchResponse, Selection, Session, StoredFile, Upload, UploadReference, UploadResponse, UploadSignRequest, UploadSignResponse } from "./schemas/api.js";
21
+ import { ActionItemResult, ActionRequest, ActionResult, ActionRun, ActionRunResponse, EmbedResponse, EmbedValue, FieldCheckRequest, FieldCheckResponse, Item, ItemResponse, ListResponse, LocationValue, MediaInfo, MediaItem, MediaReference, MediaTransform, Pagination, PreviewResponse, Problem, SearchResponse, Selection, Session, StoredFile, Upload, UploadReference, UploadResponse, UploadSignRequest, UploadSignResponse } from "./schemas/api.js";
22
22
  import { BootConfig } from "./schemas/boot.js";
23
23
  import { createSchemaRegistry, namedSchemas } from "./schemas/index.js";
24
24
  import { validate, validateBootConfig, validateDefinition } from "./validate.js";
25
25
  import { createBootConfigJsonSchema, createComponentSchemas, createDefinitionJsonSchema, createOpenApiDocument, createThemeJsonSchema } from "./openapi.js";
26
- export { Abilities, Action, ActionConfirm, ActionItemResult, ActionPlacement, ActionRequest, ActionResult, ActionRun, ActionRunResponse, ActionScope, Auth, AuthProvider, BOOT_CONFIG_ELEMENT_ID, BUILT_IN_ACTIONS, BUILT_IN_FIELD_TYPES, BUILT_IN_LAYOUT_NODE_TYPES, BUILT_IN_LIST_LAYOUTS, BlockType, BootConfig, Column, Condition, Definition, DefinitionTheme, Field, FieldCheckRequest, FieldCheckResponse, Filter, Id, Item, ItemResponse, LayoutNode, ListDefinition, ListResponse, MediaInfo, MediaItem, MediaReference, MediaTransform, MenuItem, Option, PORTABLE_RULES, PROBLEM_CONTENT_TYPE, PROTOCOL_VERSION, Page, Pagination, PreviewResponse, Problem, Resource, ResourceFeatures, RouteReference, Rule, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LENGTH, SEARCH_MAX_LIMIT, SEARCH_MIN_LENGTH, SearchResponse, Selection, Session, StoredFile, Theme, ThemeFont, ThemeFontFace, ThemeFontStylesheet, ThemeTokens, ToneInput, ToneValues, Upload, UploadReference, UploadResponse, UploadSignRequest, UploadSignResponse, Uploads, User, Widget, checkRules, checkValues, createBootConfigJsonSchema, createComponentSchemas, createDefinitionJsonSchema, createOpenApiDocument, createSchemaRegistry, createThemeJsonSchema, evaluateCondition, fromSearchParams, getMediaLibraries, getValue, isFilled, isFilterRange, isProblem, isSearchableResource, lintDefinition, matchesFilterRange, matchesRatio, mergeThemes, namedSchemas, parseRatio, readSearchQuery, renderedCropSize, toSearchParams, toSearchQueryParams, validate, validateBootConfig, validateDefinition };
26
+ export { Abilities, Action, ActionConfirm, ActionItemResult, ActionPlacement, ActionRequest, ActionResult, ActionRun, ActionRunResponse, ActionScope, Auth, AuthProvider, BOOT_CONFIG_ELEMENT_ID, BUILT_IN_ACTIONS, BUILT_IN_FIELD_TYPES, BUILT_IN_LAYOUT_NODE_TYPES, BUILT_IN_LIST_LAYOUTS, BlockType, BootConfig, Column, Condition, Definition, DefinitionTheme, EmbedResponse, EmbedValue, Field, FieldCheckRequest, FieldCheckResponse, Filter, Id, Item, ItemResponse, LayoutNode, ListDefinition, ListResponse, LocationValue, MediaInfo, MediaItem, MediaReference, MediaTransform, MenuItem, Option, PORTABLE_RULES, PROBLEM_CONTENT_TYPE, PROTOCOL_VERSION, Page, Pagination, PreviewResponse, Problem, Resource, ResourceFeatures, RouteReference, Rule, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LENGTH, SEARCH_MAX_LIMIT, SEARCH_MIN_LENGTH, SearchResponse, Selection, Session, StoredFile, Theme, ThemeFont, ThemeFontFace, ThemeFontStylesheet, ThemeTokens, ToneInput, ToneValues, Upload, UploadReference, UploadResponse, UploadSignRequest, UploadSignResponse, Uploads, User, Widget, checkRules, checkValues, createBootConfigJsonSchema, createComponentSchemas, createDefinitionJsonSchema, createOpenApiDocument, createSchemaRegistry, createThemeJsonSchema, evaluateCondition, fromSearchParams, getMediaLibraries, getValue, isFilled, isFilterRange, isProblem, isSearchableResource, lintDefinition, matchesFilterRange, matchesRatio, mergeThemes, namedSchemas, parseRatio, readSearchQuery, renderedCropSize, toSearchParams, toSearchQueryParams, validate, validateBootConfig, validateDefinition };
@@ -1,5 +1,5 @@
1
1
  import type { DefinitionInput } from '../schemas';
2
- import type { Item } from '../schemas/api';
2
+ import type { EmbedValue, Item } from '../schemas/api';
3
3
  type Values = Record<string, unknown>;
4
4
  export interface MockBackendOptions {
5
5
  /** The definition served by `GET /definition`. Its `api.baseUrl` is the prefix of every route. */
@@ -14,6 +14,11 @@ export interface MockBackendOptions {
14
14
  resource: string;
15
15
  item: Item;
16
16
  }) => string;
17
+ /**
18
+ * The value of `GET /embeds?url=` for a URL, or `null` to refuse it with a `422`. Defaults to
19
+ * a video for YouTube and Vimeo URLs, and a link for other web pages.
20
+ */
21
+ embed?: (url: string) => EmbedValue | null;
17
22
  }
18
23
  export interface MockBackend {
19
24
  /** A `fetch`-compatible function that answers protocol requests. */
@@ -1 +1 @@
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"}
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,EAER,UAAU,EAEV,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;IACnE;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;CAC9C;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;AAuND;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAgiB1E;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
@@ -20,6 +20,42 @@ function defaultPreviewUrl(item) {
20
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
21
  return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
22
22
  }
23
+ /** What the mock knows of a URL to embed: a video of YouTube or Vimeo, or a web page. */
24
+ function defaultEmbed(url) {
25
+ if (!URL.canParse(url)) return null;
26
+ const parsed = new URL(url);
27
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
28
+ const host = parsed.hostname.replace(/^www\./, "");
29
+ const youtube = host === "youtu.be" ? parsed.pathname.slice(1) : host.endsWith("youtube.com") ? parsed.searchParams.get("v") : null;
30
+ if (youtube !== null && youtube !== "") return {
31
+ url,
32
+ type: "video",
33
+ provider: "YouTube",
34
+ title: `YouTube video ${youtube}`,
35
+ thumbnailUrl: `https://i.ytimg.com/vi/${youtube}/hqdefault.jpg`,
36
+ html: `<iframe src="https://www.youtube.com/embed/${youtube}" width="560" height="315" allowfullscreen></iframe>`,
37
+ width: 560,
38
+ height: 315
39
+ };
40
+ if (host === "vimeo.com") {
41
+ const id = parsed.pathname.split("/").filter(Boolean)[0] ?? "";
42
+ return {
43
+ url,
44
+ type: "video",
45
+ provider: "Vimeo",
46
+ title: `Vimeo video ${id}`,
47
+ html: `<iframe src="https://player.vimeo.com/video/${id}" width="640" height="360" allowfullscreen></iframe>`,
48
+ width: 640,
49
+ height: 360
50
+ };
51
+ }
52
+ return {
53
+ url,
54
+ type: "link",
55
+ provider: host,
56
+ title: host
57
+ };
58
+ }
23
59
  var DEFAULT_MESSAGES = {
24
60
  required: () => "This field is required.",
25
61
  min: (failure) => `This value must be at least ${String(failure.value)}.`,
@@ -27,6 +63,7 @@ var DEFAULT_MESSAGES = {
27
63
  pattern: () => "This value has an invalid format.",
28
64
  email: () => "This value must be an email address.",
29
65
  url: () => "This value must be a URL.",
66
+ json: () => "This value must be valid JSON: an object or an array.",
30
67
  in: () => "This value is not one of the choices.",
31
68
  accept: () => "This file type is not accepted.",
32
69
  maxSize: () => "This file is too large.",
@@ -350,6 +387,14 @@ function createMockBackend(options) {
350
387
  return isRecord(body) ? body : {};
351
388
  };
352
389
  if (segments.length === 1 && segments[0] === "definition" && method === "GET") return json(200, definition);
390
+ if (segments.length === 1 && segments[0] === "embeds" && method === "GET") {
391
+ const url = new URL(request.url, "http://panneau.test").searchParams.get("url") ?? "";
392
+ const embed = url === "" ? null : (options.embed ?? defaultEmbed)(url);
393
+ return embed !== null ? json(200, { data: embed }) : problem(422, "The given data was invalid.", {
394
+ type: "urn:panneau:problem:validation",
395
+ errors: { url: ["This URL can’t be embedded."] }
396
+ });
397
+ }
353
398
  if (segments.length === 1 && segments[0] === "search" && method === "GET") return search(url.searchParams);
354
399
  if (segments[0] === "uploads") return handleUploads(segments, method, readBody);
355
400
  if (segments[0] !== "resources" || segments[1] === void 0) return problem(404, "Not found.");
@@ -1 +1 @@
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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"}
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 EmbedValue,\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 * The value of `GET /embeds?url=` for a URL, or `null` to refuse it with a `422`. Defaults to\n * a video for YouTube and Vimeo URLs, and a link for other web pages.\n */\n embed?: (url: string) => EmbedValue | null;\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\n/** What the mock knows of a URL to embed: a video of YouTube or Vimeo, or a web page. */\nfunction defaultEmbed(url: string): EmbedValue | null {\n if (!URL.canParse(url)) {\n return null;\n }\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n return null;\n }\n const host = parsed.hostname.replace(/^www\\./, '');\n const youtube =\n host === 'youtu.be'\n ? parsed.pathname.slice(1)\n : host.endsWith('youtube.com')\n ? parsed.searchParams.get('v')\n : null;\n if (youtube !== null && youtube !== '') {\n return {\n url,\n type: 'video',\n provider: 'YouTube',\n title: `YouTube video ${youtube}`,\n thumbnailUrl: `https://i.ytimg.com/vi/${youtube}/hqdefault.jpg`,\n html: `<iframe src=\"https://www.youtube.com/embed/${youtube}\" width=\"560\" height=\"315\" allowfullscreen></iframe>`,\n width: 560,\n height: 315,\n };\n }\n if (host === 'vimeo.com') {\n const id = parsed.pathname.split('/').filter(Boolean)[0] ?? '';\n return {\n url,\n type: 'video',\n provider: 'Vimeo',\n title: `Vimeo video ${id}`,\n html: `<iframe src=\"https://player.vimeo.com/video/${id}\" width=\"640\" height=\"360\" allowfullscreen></iframe>`,\n width: 640,\n height: 360,\n };\n }\n return { url, type: 'link', provider: host, title: host };\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 json: () => 'This value must be valid JSON: an object or an array.',\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] === 'embeds' && method === 'GET') {\n const url = new URL(request.url, 'http://panneau.test').searchParams.get('url') ?? '';\n const embed = url === '' ? null : (options.embed ?? defaultEmbed)(url);\n return embed !== null\n ? json(200, { data: embed })\n : problem(422, 'The given data was invalid.', {\n type: 'urn:panneau:problem:validation',\n errors: { url: ['This URL can’t be embedded.'] },\n });\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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"}
@@ -1 +1 @@
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"}
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,CA6YlD"}
package/dist/openapi.js CHANGED
@@ -483,6 +483,17 @@ function createOpenApiDocument() {
483
483
  ...problems(401, 422, 429)
484
484
  }
485
485
  } },
486
+ "/embeds": { get: {
487
+ operationId: "resolveEmbed",
488
+ parameters: [{
489
+ ...queryParameter("url", "The URL to embed.", { type: "string" }),
490
+ required: true
491
+ }],
492
+ responses: {
493
+ 200: ok("The value of an `embed` field for the URL, from its provider's oEmbed.", ref("EmbedResponse")),
494
+ ...problems(401, 422, 429)
495
+ }
496
+ } },
486
497
  "/dashboard/widgets/{widget}": {
487
498
  parameters: [pathParameter("widget", "Widget id.")],
488
499
  get: {
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","names":[],"sources":["../src/openapi.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { createSchemaRegistry } from './schemas';\nimport { BootConfig } from './schemas/boot';\nimport { Definition } from './schemas/definition';\nimport { Theme } from './schemas/theme';\nimport {\n SEARCH_DEFAULT_LIMIT,\n SEARCH_MAX_LENGTH,\n SEARCH_MAX_LIMIT,\n SEARCH_MIN_LENGTH,\n} from './search';\nimport { PROBLEM_CONTENT_TYPE, PROTOCOL_VERSION } from './version';\n\ntype JsonObject = Record<string, unknown>;\n\nconst ref = (id: string): JsonObject => ({ $ref: `#/components/schemas/${id}` });\n\nconst json = (schema: JsonObject): JsonObject => ({ 'application/json': { schema } });\n\nconst ok = (description: string, schema?: JsonObject, headers?: JsonObject): JsonObject => ({\n description,\n ...(schema !== undefined ? { content: json(schema) } : {}),\n ...(headers !== undefined ? { headers } : {}),\n});\n\nconst PROBLEMS: Record<number, string> = {\n 400: 'Bad request, such as a dry run of an action that does not declare `dryRun`.',\n 401: 'Not authenticated.',\n 403: 'Not allowed.',\n 404: 'Not found.',\n 409: 'Conflict with a business rule.',\n 412: 'Version conflict: `If-Match` does not match the current version, which is in `current`.',\n 422: 'Invalid values: messages are in `errors`, keyed by dot path.',\n 429: 'Too many requests: `Retry-After` gives the seconds to wait.',\n};\n\nfunction problems(...statuses: number[]): JsonObject {\n return Object.fromEntries(\n statuses.map((status) => [\n String(status),\n {\n description: PROBLEMS[status],\n content: { [PROBLEM_CONTENT_TYPE]: { schema: ref('Problem') } },\n },\n ]),\n );\n}\n\nconst etag = { ETag: { description: 'The version of the item.', schema: { type: 'string' } } };\n\nconst pathParameter = (name: string, description: string): JsonObject => ({\n name,\n in: 'path',\n required: true,\n description,\n schema: { type: 'string' },\n});\n\nconst queryParameter = (name: string, description: string, schema: JsonObject): JsonObject => ({\n name,\n in: 'query',\n required: false,\n description,\n schema,\n});\n\nconst listParameters = [\n queryParameter('page', 'Page number, from 1.', { type: 'integer', minimum: 1 }),\n queryParameter('perPage', 'Items per page.', { type: 'integer', minimum: 1 }),\n queryParameter('sort', 'Comma-separated fields; a `-` prefix sorts descending.', {\n type: 'string',\n }),\n {\n name: 'filter',\n in: 'query',\n required: false,\n style: 'deepObject',\n explode: true,\n description:\n '`filter[status]=published`; `filter[tags][]=a&filter[tags][]=b` for any of several values; `filter[published_at][from]=2026-01-01&filter[published_at][to]=2026-01-31` for the inclusive bounds of a range filter (`date-range`), without the open end.',\n schema: { type: 'object', additionalProperties: true },\n },\n queryParameter('q', 'Search.', { type: 'string' }),\n queryParameter('fields', 'Comma-separated fields to include in each item.', { type: 'string' }),\n];\n\nconst resource = pathParameter('resource', 'Resource id.');\nconst id = pathParameter('id', 'Item id.');\nconst values = { type: 'object', additionalProperties: true };\n\n/** Component schemas, generated from the protocol's Zod schemas. */\nexport function createComponentSchemas(): Record<string, JsonObject> {\n const { schemas } = z.toJSONSchema(createSchemaRegistry(), {\n io: 'input',\n uri: (schemaId) => `#/components/schemas/${schemaId}`,\n });\n return Object.fromEntries(\n Object.entries(schemas).map(([schemaId, schema]) => {\n const { $schema: _schema, $id: _id, ...rest } = schema as JsonObject;\n return [schemaId, rest];\n }),\n );\n}\n\n/** The JSON Schema of the definition (`GET /definition`). */\nexport function createDefinitionJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/definition.schema.json',\n ...(z.toJSONSchema(Definition, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The JSON Schema of a theme, for themes written as JSON files. */\nexport function createThemeJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/theme.schema.json',\n ...(z.toJSONSchema(Theme, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The JSON Schema of the boot config, which a server writes in the page of the app. */\nexport function createBootConfigJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/boot.schema.json',\n ...(z.toJSONSchema(BootConfig, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The OpenAPI 3.1 document of the Panneau HTTP API. */\nexport function createOpenApiDocument(): JsonObject {\n return {\n openapi: '3.1.0',\n info: {\n title: 'Panneau protocol',\n version: String(PROTOCOL_VERSION),\n description:\n 'The HTTP API between a Panneau frontend and its backend. Paths are relative to `api.baseUrl` of the definition. Authentication is up to the backend (session cookie or bearer token). Errors use `application/problem+json`.',\n },\n paths: {\n '/definition': {\n get: {\n operationId: 'getDefinition',\n summary: 'The definition for the current user, in the current locale.',\n responses: {\n 200: ok('The definition.', ref('Definition')),\n ...problems(401, 403),\n },\n },\n },\n '/resources/{resource}': {\n parameters: [resource],\n get: {\n operationId: 'listItems',\n parameters: listParameters,\n responses: {\n 200: ok('A page of items.', ref('ListResponse')),\n ...problems(401, 403, 404),\n },\n },\n post: {\n operationId: 'createItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 201: ok('The created item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 422),\n },\n },\n },\n '/resources/{resource}/{id}': {\n parameters: [resource, id],\n get: {\n operationId: 'getItem',\n responses: {\n 200: ok('The item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404),\n },\n },\n patch: {\n operationId: 'updateItem',\n description: 'Partial update: only the values sent are changed.',\n parameters: [\n {\n name: 'If-Match',\n in: 'header',\n required: false,\n description: 'The version the client edited.',\n schema: { type: 'string' },\n },\n ],\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 412, 422),\n },\n },\n delete: {\n operationId: 'deleteItem',\n responses: { 204: ok('Deleted.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/resources/{resource}/{id}/preview': {\n parameters: [resource, id],\n post: {\n operationId: 'previewItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('A short-lived preview URL.', ref('PreviewResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/resources/{resource}/actions/{action}': {\n parameters: [resource, pathParameter('action', 'Action id.')],\n post: {\n operationId: 'runAction',\n requestBody: {\n required: true,\n content: {\n ...json(ref('ActionRequest')),\n 'multipart/form-data': { schema: ref('ActionRequest') },\n },\n },\n responses: {\n 200: ok('The result.', ref('ActionResult')),\n 202: ok('The action runs in the background.', ref('ActionRunResponse')),\n ...problems(400, 401, 403, 404, 409, 422),\n },\n },\n },\n '/resources/{resource}/fields/{field}/check': {\n parameters: [resource, pathParameter('field', 'Name of a field with `check`.')],\n post: {\n operationId: 'checkField',\n summary:\n '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`.',\n requestBody: { required: true, content: json(ref('FieldCheckRequest')) },\n responses: {\n 200: ok(\n 'What the backend says about the value.',\n ref('FieldCheckResponse'),\n ),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/actions/runs': {\n get: {\n operationId: 'listActionRuns',\n summary: \"The current user's recent runs.\",\n responses: {\n 200: ok('Runs, newest first.', {\n type: 'object',\n required: ['data'],\n properties: { data: { type: 'array', items: ref('ActionRun') } },\n }),\n ...problems(401),\n },\n },\n },\n '/actions/runs/{run}': {\n parameters: [pathParameter('run', 'Run id.')],\n get: {\n operationId: 'getActionRun',\n responses: {\n 200: ok('The run.', ref('ActionRunResponse')),\n ...problems(401, 403, 404),\n },\n },\n delete: {\n operationId: 'cancelActionRun',\n responses: { 204: ok('Cancelled.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/uploads/sign': {\n post: {\n operationId: 'signUpload',\n summary:\n \"Signs a request of the browser's S3 client. The first request of a file creates its upload.\",\n requestBody: { required: true, content: json(ref('UploadSignRequest')) },\n responses: {\n 200: ok('The presigned request.', ref('UploadSignResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/uploads/{upload}/complete': {\n parameters: [pathParameter('upload', 'Upload id: `upl_…`.')],\n post: {\n operationId: 'completeUpload',\n summary:\n 'Once the browser completed the upload in storage, checks the object and gives the upload, which values reference.',\n requestBody: {\n required: false,\n content: json({\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description:\n \"The file's name, which the key doesn't keep, since the backend chose it.\",\n },\n },\n }),\n },\n responses: {\n 200: ok('The upload.', ref('UploadResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/auth/session': {\n get: {\n operationId: 'getSession',\n responses: { 200: ok('The current user.', ref('Session')), ...problems(401) },\n },\n },\n '/auth/login': {\n post: {\n operationId: 'login',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email', 'password'],\n properties: {\n email: { type: 'string' },\n password: { type: 'string' },\n remember: { type: 'boolean' },\n },\n }),\n },\n responses: {\n 200: ok('Logged in.', ref('Session')),\n 202: ok(\n 'A second factor is required: answer `/auth/two-factor/challenge`.',\n {\n type: 'object',\n properties: { twoFactor: { type: 'boolean', const: true } },\n },\n ),\n ...problems(422),\n },\n },\n },\n '/auth/logout': {\n post: { operationId: 'logout', responses: { 204: ok('Logged out.') } },\n },\n '/auth/providers/{provider}/redirect': {\n parameters: [pathParameter('provider', 'Provider id.')],\n get: {\n operationId: 'redirectToProvider',\n parameters: [\n queryParameter('next', 'Local path to go to after login.', {\n type: 'string',\n }),\n ],\n responses: {\n 302: { description: 'Redirect to the provider.' },\n ...problems(404),\n },\n },\n },\n '/auth/password/forgot': {\n post: {\n operationId: 'forgotPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email'],\n properties: { email: { type: 'string' } },\n }),\n },\n responses: {\n 204: ok('A reset link was sent, if the account exists.'),\n ...problems(422),\n },\n },\n },\n '/auth/password/reset': {\n post: {\n operationId: 'resetPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['token', 'email', 'password'],\n properties: {\n token: { type: 'string' },\n email: { type: 'string' },\n password: { type: 'string' },\n },\n }),\n },\n responses: { 204: ok('The password was reset.'), ...problems(422) },\n },\n },\n '/auth/two-factor/challenge': {\n post: {\n operationId: 'twoFactorChallenge',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n properties: {\n code: { type: 'string' },\n recoveryCode: { type: 'string' },\n },\n }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/auth/passkeys/options': {\n post: {\n operationId: 'passkeyOptions',\n responses: {\n 200: ok('WebAuthn request options.', {\n type: 'object',\n additionalProperties: true,\n }),\n },\n },\n },\n '/auth/passkeys/login': {\n post: {\n operationId: 'passkeyLogin',\n requestBody: {\n required: true,\n content: json({ type: 'object', additionalProperties: true }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/account': {\n get: {\n operationId: 'getAccount',\n responses: {\n 200: ok('The current user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401),\n },\n },\n patch: {\n operationId: 'updateAccount',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401, 422),\n },\n },\n },\n '/search': {\n get: {\n operationId: 'search',\n parameters: [\n {\n ...queryParameter('q', 'Search terms, trimmed.', {\n type: 'string',\n minLength: SEARCH_MIN_LENGTH,\n maxLength: SEARCH_MAX_LENGTH,\n }),\n required: true,\n },\n queryParameter('limit', 'Items per resource.', {\n type: 'integer',\n minimum: 1,\n maximum: SEARCH_MAX_LIMIT,\n default: SEARCH_DEFAULT_LIMIT,\n }),\n queryParameter(\n 'resources',\n 'Comma-separated ids of the searchable resources to search. Defaults to every one.',\n { type: 'string' },\n ),\n ],\n responses: {\n 200: ok(\n `Results grouped by resource, in the order of the definition: at most \\`limit\\` items per resource, and ${SEARCH_MAX_LIMIT} in all.`,\n ref('SearchResponse'),\n ),\n ...problems(401, 422, 429),\n },\n },\n },\n '/dashboard/widgets/{widget}': {\n parameters: [pathParameter('widget', 'Widget id.')],\n get: {\n operationId: 'getWidgetData',\n responses: {\n 200: ok(\"The widget's data. Its shape depends on the widget type.\", {\n type: 'object',\n additionalProperties: true,\n }),\n ...problems(401, 403, 404),\n },\n },\n },\n },\n components: { schemas: createComponentSchemas() },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"openapi.js","names":[],"sources":["../src/openapi.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { createSchemaRegistry } from './schemas';\nimport { BootConfig } from './schemas/boot';\nimport { Definition } from './schemas/definition';\nimport { Theme } from './schemas/theme';\nimport {\n SEARCH_DEFAULT_LIMIT,\n SEARCH_MAX_LENGTH,\n SEARCH_MAX_LIMIT,\n SEARCH_MIN_LENGTH,\n} from './search';\nimport { PROBLEM_CONTENT_TYPE, PROTOCOL_VERSION } from './version';\n\ntype JsonObject = Record<string, unknown>;\n\nconst ref = (id: string): JsonObject => ({ $ref: `#/components/schemas/${id}` });\n\nconst json = (schema: JsonObject): JsonObject => ({ 'application/json': { schema } });\n\nconst ok = (description: string, schema?: JsonObject, headers?: JsonObject): JsonObject => ({\n description,\n ...(schema !== undefined ? { content: json(schema) } : {}),\n ...(headers !== undefined ? { headers } : {}),\n});\n\nconst PROBLEMS: Record<number, string> = {\n 400: 'Bad request, such as a dry run of an action that does not declare `dryRun`.',\n 401: 'Not authenticated.',\n 403: 'Not allowed.',\n 404: 'Not found.',\n 409: 'Conflict with a business rule.',\n 412: 'Version conflict: `If-Match` does not match the current version, which is in `current`.',\n 422: 'Invalid values: messages are in `errors`, keyed by dot path.',\n 429: 'Too many requests: `Retry-After` gives the seconds to wait.',\n};\n\nfunction problems(...statuses: number[]): JsonObject {\n return Object.fromEntries(\n statuses.map((status) => [\n String(status),\n {\n description: PROBLEMS[status],\n content: { [PROBLEM_CONTENT_TYPE]: { schema: ref('Problem') } },\n },\n ]),\n );\n}\n\nconst etag = { ETag: { description: 'The version of the item.', schema: { type: 'string' } } };\n\nconst pathParameter = (name: string, description: string): JsonObject => ({\n name,\n in: 'path',\n required: true,\n description,\n schema: { type: 'string' },\n});\n\nconst queryParameter = (name: string, description: string, schema: JsonObject): JsonObject => ({\n name,\n in: 'query',\n required: false,\n description,\n schema,\n});\n\nconst listParameters = [\n queryParameter('page', 'Page number, from 1.', { type: 'integer', minimum: 1 }),\n queryParameter('perPage', 'Items per page.', { type: 'integer', minimum: 1 }),\n queryParameter('sort', 'Comma-separated fields; a `-` prefix sorts descending.', {\n type: 'string',\n }),\n {\n name: 'filter',\n in: 'query',\n required: false,\n style: 'deepObject',\n explode: true,\n description:\n '`filter[status]=published`; `filter[tags][]=a&filter[tags][]=b` for any of several values; `filter[published_at][from]=2026-01-01&filter[published_at][to]=2026-01-31` for the inclusive bounds of a range filter (`date-range`), without the open end.',\n schema: { type: 'object', additionalProperties: true },\n },\n queryParameter('q', 'Search.', { type: 'string' }),\n queryParameter('fields', 'Comma-separated fields to include in each item.', { type: 'string' }),\n];\n\nconst resource = pathParameter('resource', 'Resource id.');\nconst id = pathParameter('id', 'Item id.');\nconst values = { type: 'object', additionalProperties: true };\n\n/** Component schemas, generated from the protocol's Zod schemas. */\nexport function createComponentSchemas(): Record<string, JsonObject> {\n const { schemas } = z.toJSONSchema(createSchemaRegistry(), {\n io: 'input',\n uri: (schemaId) => `#/components/schemas/${schemaId}`,\n });\n return Object.fromEntries(\n Object.entries(schemas).map(([schemaId, schema]) => {\n const { $schema: _schema, $id: _id, ...rest } = schema as JsonObject;\n return [schemaId, rest];\n }),\n );\n}\n\n/** The JSON Schema of the definition (`GET /definition`). */\nexport function createDefinitionJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/definition.schema.json',\n ...(z.toJSONSchema(Definition, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The JSON Schema of a theme, for themes written as JSON files. */\nexport function createThemeJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/theme.schema.json',\n ...(z.toJSONSchema(Theme, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The JSON Schema of the boot config, which a server writes in the page of the app. */\nexport function createBootConfigJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/boot.schema.json',\n ...(z.toJSONSchema(BootConfig, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The OpenAPI 3.1 document of the Panneau HTTP API. */\nexport function createOpenApiDocument(): JsonObject {\n return {\n openapi: '3.1.0',\n info: {\n title: 'Panneau protocol',\n version: String(PROTOCOL_VERSION),\n description:\n 'The HTTP API between a Panneau frontend and its backend. Paths are relative to `api.baseUrl` of the definition. Authentication is up to the backend (session cookie or bearer token). Errors use `application/problem+json`.',\n },\n paths: {\n '/definition': {\n get: {\n operationId: 'getDefinition',\n summary: 'The definition for the current user, in the current locale.',\n responses: {\n 200: ok('The definition.', ref('Definition')),\n ...problems(401, 403),\n },\n },\n },\n '/resources/{resource}': {\n parameters: [resource],\n get: {\n operationId: 'listItems',\n parameters: listParameters,\n responses: {\n 200: ok('A page of items.', ref('ListResponse')),\n ...problems(401, 403, 404),\n },\n },\n post: {\n operationId: 'createItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 201: ok('The created item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 422),\n },\n },\n },\n '/resources/{resource}/{id}': {\n parameters: [resource, id],\n get: {\n operationId: 'getItem',\n responses: {\n 200: ok('The item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404),\n },\n },\n patch: {\n operationId: 'updateItem',\n description: 'Partial update: only the values sent are changed.',\n parameters: [\n {\n name: 'If-Match',\n in: 'header',\n required: false,\n description: 'The version the client edited.',\n schema: { type: 'string' },\n },\n ],\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 412, 422),\n },\n },\n delete: {\n operationId: 'deleteItem',\n responses: { 204: ok('Deleted.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/resources/{resource}/{id}/preview': {\n parameters: [resource, id],\n post: {\n operationId: 'previewItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('A short-lived preview URL.', ref('PreviewResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/resources/{resource}/actions/{action}': {\n parameters: [resource, pathParameter('action', 'Action id.')],\n post: {\n operationId: 'runAction',\n requestBody: {\n required: true,\n content: {\n ...json(ref('ActionRequest')),\n 'multipart/form-data': { schema: ref('ActionRequest') },\n },\n },\n responses: {\n 200: ok('The result.', ref('ActionResult')),\n 202: ok('The action runs in the background.', ref('ActionRunResponse')),\n ...problems(400, 401, 403, 404, 409, 422),\n },\n },\n },\n '/resources/{resource}/fields/{field}/check': {\n parameters: [resource, pathParameter('field', 'Name of a field with `check`.')],\n post: {\n operationId: 'checkField',\n summary:\n '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`.',\n requestBody: { required: true, content: json(ref('FieldCheckRequest')) },\n responses: {\n 200: ok(\n 'What the backend says about the value.',\n ref('FieldCheckResponse'),\n ),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/actions/runs': {\n get: {\n operationId: 'listActionRuns',\n summary: \"The current user's recent runs.\",\n responses: {\n 200: ok('Runs, newest first.', {\n type: 'object',\n required: ['data'],\n properties: { data: { type: 'array', items: ref('ActionRun') } },\n }),\n ...problems(401),\n },\n },\n },\n '/actions/runs/{run}': {\n parameters: [pathParameter('run', 'Run id.')],\n get: {\n operationId: 'getActionRun',\n responses: {\n 200: ok('The run.', ref('ActionRunResponse')),\n ...problems(401, 403, 404),\n },\n },\n delete: {\n operationId: 'cancelActionRun',\n responses: { 204: ok('Cancelled.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/uploads/sign': {\n post: {\n operationId: 'signUpload',\n summary:\n \"Signs a request of the browser's S3 client. The first request of a file creates its upload.\",\n requestBody: { required: true, content: json(ref('UploadSignRequest')) },\n responses: {\n 200: ok('The presigned request.', ref('UploadSignResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/uploads/{upload}/complete': {\n parameters: [pathParameter('upload', 'Upload id: `upl_…`.')],\n post: {\n operationId: 'completeUpload',\n summary:\n 'Once the browser completed the upload in storage, checks the object and gives the upload, which values reference.',\n requestBody: {\n required: false,\n content: json({\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description:\n \"The file's name, which the key doesn't keep, since the backend chose it.\",\n },\n },\n }),\n },\n responses: {\n 200: ok('The upload.', ref('UploadResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/auth/session': {\n get: {\n operationId: 'getSession',\n responses: { 200: ok('The current user.', ref('Session')), ...problems(401) },\n },\n },\n '/auth/login': {\n post: {\n operationId: 'login',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email', 'password'],\n properties: {\n email: { type: 'string' },\n password: { type: 'string' },\n remember: { type: 'boolean' },\n },\n }),\n },\n responses: {\n 200: ok('Logged in.', ref('Session')),\n 202: ok(\n 'A second factor is required: answer `/auth/two-factor/challenge`.',\n {\n type: 'object',\n properties: { twoFactor: { type: 'boolean', const: true } },\n },\n ),\n ...problems(422),\n },\n },\n },\n '/auth/logout': {\n post: { operationId: 'logout', responses: { 204: ok('Logged out.') } },\n },\n '/auth/providers/{provider}/redirect': {\n parameters: [pathParameter('provider', 'Provider id.')],\n get: {\n operationId: 'redirectToProvider',\n parameters: [\n queryParameter('next', 'Local path to go to after login.', {\n type: 'string',\n }),\n ],\n responses: {\n 302: { description: 'Redirect to the provider.' },\n ...problems(404),\n },\n },\n },\n '/auth/password/forgot': {\n post: {\n operationId: 'forgotPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email'],\n properties: { email: { type: 'string' } },\n }),\n },\n responses: {\n 204: ok('A reset link was sent, if the account exists.'),\n ...problems(422),\n },\n },\n },\n '/auth/password/reset': {\n post: {\n operationId: 'resetPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['token', 'email', 'password'],\n properties: {\n token: { type: 'string' },\n email: { type: 'string' },\n password: { type: 'string' },\n },\n }),\n },\n responses: { 204: ok('The password was reset.'), ...problems(422) },\n },\n },\n '/auth/two-factor/challenge': {\n post: {\n operationId: 'twoFactorChallenge',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n properties: {\n code: { type: 'string' },\n recoveryCode: { type: 'string' },\n },\n }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/auth/passkeys/options': {\n post: {\n operationId: 'passkeyOptions',\n responses: {\n 200: ok('WebAuthn request options.', {\n type: 'object',\n additionalProperties: true,\n }),\n },\n },\n },\n '/auth/passkeys/login': {\n post: {\n operationId: 'passkeyLogin',\n requestBody: {\n required: true,\n content: json({ type: 'object', additionalProperties: true }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/account': {\n get: {\n operationId: 'getAccount',\n responses: {\n 200: ok('The current user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401),\n },\n },\n patch: {\n operationId: 'updateAccount',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401, 422),\n },\n },\n },\n '/search': {\n get: {\n operationId: 'search',\n parameters: [\n {\n ...queryParameter('q', 'Search terms, trimmed.', {\n type: 'string',\n minLength: SEARCH_MIN_LENGTH,\n maxLength: SEARCH_MAX_LENGTH,\n }),\n required: true,\n },\n queryParameter('limit', 'Items per resource.', {\n type: 'integer',\n minimum: 1,\n maximum: SEARCH_MAX_LIMIT,\n default: SEARCH_DEFAULT_LIMIT,\n }),\n queryParameter(\n 'resources',\n 'Comma-separated ids of the searchable resources to search. Defaults to every one.',\n { type: 'string' },\n ),\n ],\n responses: {\n 200: ok(\n `Results grouped by resource, in the order of the definition: at most \\`limit\\` items per resource, and ${SEARCH_MAX_LIMIT} in all.`,\n ref('SearchResponse'),\n ),\n ...problems(401, 422, 429),\n },\n },\n },\n '/embeds': {\n get: {\n operationId: 'resolveEmbed',\n parameters: [\n {\n ...queryParameter('url', 'The URL to embed.', { type: 'string' }),\n required: true,\n },\n ],\n responses: {\n 200: ok(\n \"The value of an `embed` field for the URL, from its provider's oEmbed.\",\n ref('EmbedResponse'),\n ),\n ...problems(401, 422, 429),\n },\n },\n },\n '/dashboard/widgets/{widget}': {\n parameters: [pathParameter('widget', 'Widget id.')],\n get: {\n operationId: 'getWidgetData',\n responses: {\n 200: ok(\"The widget's data. Its shape depends on the widget type.\", {\n type: 'object',\n additionalProperties: true,\n }),\n ...problems(401, 403, 404),\n },\n },\n },\n },\n components: { schemas: createComponentSchemas() },\n };\n}\n"],"mappings":"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"}