@panneau/protocol 5.0.0-alpha.4 → 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.
Files changed (66) hide show
  1. package/dist/conformance/index.d.ts.map +1 -1
  2. package/dist/conformance/index.js +121 -1
  3. package/dist/conformance/index.js.map +1 -1
  4. package/dist/constants.d.ts +2 -2
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +12 -2
  7. package/dist/constants.js.map +1 -1
  8. package/dist/index.js +5 -2
  9. package/dist/libraries.d.ts +16 -0
  10. package/dist/libraries.d.ts.map +1 -0
  11. package/dist/libraries.js +15 -0
  12. package/dist/libraries.js.map +1 -0
  13. package/dist/lint.d.ts +2 -2
  14. package/dist/lint.d.ts.map +1 -1
  15. package/dist/lint.js +15 -9
  16. package/dist/lint.js.map +1 -1
  17. package/dist/mock/example.d.ts.map +1 -1
  18. package/dist/mock/example.js +8 -0
  19. package/dist/mock/example.js.map +1 -1
  20. package/dist/mock/index.d.ts +15 -2
  21. package/dist/mock/index.d.ts.map +1 -1
  22. package/dist/mock/index.js +164 -13
  23. package/dist/mock/index.js.map +1 -1
  24. package/dist/openapi.d.ts.map +1 -1
  25. package/dist/openapi.js +45 -4
  26. package/dist/openapi.js.map +1 -1
  27. package/dist/rules.d.ts.map +1 -1
  28. package/dist/rules.js +26 -1
  29. package/dist/rules.js.map +1 -1
  30. package/dist/runtime/index.d.ts +3 -0
  31. package/dist/runtime/index.d.ts.map +1 -1
  32. package/dist/runtime/index.js +4 -1
  33. package/dist/schemas/action.d.ts +18 -1
  34. package/dist/schemas/action.d.ts.map +1 -1
  35. package/dist/schemas/api.d.ts +141 -0
  36. package/dist/schemas/api.d.ts.map +1 -1
  37. package/dist/schemas/api.js +54 -9
  38. package/dist/schemas/api.js.map +1 -1
  39. package/dist/schemas/definition.d.ts +40 -2
  40. package/dist/schemas/definition.d.ts.map +1 -1
  41. package/dist/schemas/definition.js +2 -1
  42. package/dist/schemas/definition.js.map +1 -1
  43. package/dist/schemas/field.d.ts +26 -1
  44. package/dist/schemas/field.d.ts.map +1 -1
  45. package/dist/schemas/field.js +27 -5
  46. package/dist/schemas/field.js.map +1 -1
  47. package/dist/schemas/index.d.ts +259 -6
  48. package/dist/schemas/index.d.ts.map +1 -1
  49. package/dist/schemas/index.js +8 -3
  50. package/dist/schemas/index.js.map +1 -1
  51. package/dist/schemas/resource.d.ts +38 -2
  52. package/dist/schemas/resource.d.ts.map +1 -1
  53. package/dist/schemas/resource.js +3 -1
  54. package/dist/schemas/resource.js.map +1 -1
  55. package/dist/search.d.ts +51 -0
  56. package/dist/search.d.ts.map +1 -0
  57. package/dist/search.js +53 -0
  58. package/dist/search.js.map +1 -0
  59. package/dist/transforms.d.ts +17 -0
  60. package/dist/transforms.d.ts.map +1 -0
  61. package/dist/transforms.js +34 -0
  62. package/dist/transforms.js.map +1 -0
  63. package/package.json +1 -1
  64. package/schema/boot.schema.json +82 -2
  65. package/schema/definition.schema.json +82 -2
  66. package/schema/openapi.json +507 -14
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/schemas/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAmB3B,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0B6C,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/schemas/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAmB3B,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkC6C,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC"}
@@ -12,7 +12,7 @@ var ResourceFeatures = z.looseObject({
12
12
  mode: z.enum(["component", "iframe"]),
13
13
  component: z.string().optional().meta({ description: "`component`: the plugin preview." })
14
14
  })]).optional(),
15
- search: z.boolean().optional().meta({ description: "Whether the resource is in the global search." })
15
+ search: z.boolean().optional().meta({ description: "Whether the resource is in the global search. Defaults to whether its list has a search (`list.search`)." })
16
16
  });
17
17
  var Resource = z.looseObject({
18
18
  id: z.string().min(1),
@@ -24,6 +24,8 @@ var Resource = z.looseObject({
24
24
  description: z.string().optional(),
25
25
  icon: z.string().optional(),
26
26
  itemTitle: z.string().optional().meta({ description: "The field used as the title of an item, in lists and pickers." }),
27
+ itemSubtitle: z.string().optional().meta({ description: "The field shown under the title of an item, in search results, cards and pickers." }),
28
+ itemImage: z.string().optional().meta({ description: "The field that holds the image of an item (a media or a URL), in search results, cards and pickers." }),
27
29
  fields: z.array(Field),
28
30
  form: z.looseObject({ layout: z.array(LayoutNode).optional() }).optional(),
29
31
  list: ListDefinition.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"resource.js","names":[],"sources":["../../src/schemas/resource.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { Action } from './action';\nimport { Abilities } from './common';\nimport { BlockType, Field } from './field';\nimport { LayoutNode } from './layout';\nimport { ListDefinition } from './list';\n\nexport const ResourceFeatures = z.looseObject({\n drafts: z.boolean().optional(),\n revisions: z.boolean().optional(),\n preview: z\n .union([\n z.null(),\n z.looseObject({\n mode: z.enum(['component', 'iframe']),\n component: z\n .string()\n .optional()\n .meta({ description: '`component`: the plugin preview.' }),\n }),\n ])\n .optional(),\n search: z\n .boolean()\n .optional()\n .meta({ description: 'Whether the resource is in the global search.' }),\n});\n\nexport const Resource = z\n .looseObject({\n id: z.string().min(1),\n kind: z.string().min(1).optional().meta({\n description:\n \"What the resource is. `media`: a library of media (decision 0026), with a `file` field and a `media` on each item. Without it, an ordinary resource. Clients treat a kind they don't know as an ordinary resource.\",\n }),\n label: z.looseObject({ singular: z.string(), plural: z.string() }),\n description: z.string().optional(),\n icon: z.string().optional(),\n itemTitle: z\n .string()\n .optional()\n .meta({ description: 'The field used as the title of an item, in lists and pickers.' }),\n fields: z.array(Field),\n form: z.looseObject({ layout: z.array(LayoutNode).optional() }).optional(),\n list: ListDefinition.optional(),\n actions: z.array(Action).optional(),\n abilities: Abilities.optional(),\n features: ResourceFeatures.optional(),\n types: z\n .array(BlockType)\n .optional()\n .meta({ description: 'Variants of the resource, each with its own extra fields.' }),\n settings: z.record(z.string(), z.unknown()).optional(),\n })\n .meta({ description: 'A set of items managed in the admin.' });\n\nexport type ResourceFeatures = z.infer<typeof ResourceFeatures>;\nexport type Resource = z.infer<typeof Resource>;\n/** A resource as a backend writes it, before defaults are applied. */\nexport type ResourceInput = z.input<typeof Resource>;\n"],"mappings":";;;;;;;AAQA,IAAa,mBAAmB,EAAE,YAAY;CAC1C,QAAQ,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,QAAQ,CAAC,CAAC,SAAS;CAChC,SAAS,EACJ,MAAM,CACH,EAAE,KAAK,GACP,EAAE,YAAY;EACV,MAAM,EAAE,KAAK,CAAC,aAAa,QAAQ,CAAC;EACpC,WAAW,EACN,OAAO,CAAC,CACR,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,mCAAmC,CAAC;CACjE,CAAC,CACL,CAAC,CAAC,CACD,SAAS;CACd,QAAQ,EACH,QAAQ,CAAC,CACT,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,gDAAgD,CAAC;AAC9E,CAAC;AAED,IAAa,WAAW,EACnB,YAAY;CACT,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACpB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EACpC,aACI,qNACR,CAAC;CACD,OAAO,EAAE,YAAY;EAAE,UAAU,EAAE,OAAO;EAAG,QAAQ,EAAE,OAAO;CAAE,CAAC;CACjE,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,WAAW,EACN,OAAO,CAAC,CACR,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,gEAAgE,CAAC;CAC1F,QAAQ,EAAE,MAAM,KAAK;CACrB,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS;CACzE,MAAM,eAAe,SAAS;CAC9B,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,SAAS;CAClC,WAAW,UAAU,SAAS;CAC9B,UAAU,iBAAiB,SAAS;CACpC,OAAO,EACF,MAAM,SAAS,CAAC,CAChB,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,4DAA4D,CAAC;CACtF,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;AACzD,CAAC,CAAC,CACD,KAAK,EAAE,aAAa,uCAAuC,CAAC"}
1
+ {"version":3,"file":"resource.js","names":[],"sources":["../../src/schemas/resource.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { Action } from './action';\nimport { Abilities } from './common';\nimport { BlockType, Field } from './field';\nimport { LayoutNode } from './layout';\nimport { ListDefinition } from './list';\n\nexport const ResourceFeatures = z.looseObject({\n drafts: z.boolean().optional(),\n revisions: z.boolean().optional(),\n preview: z\n .union([\n z.null(),\n z.looseObject({\n mode: z.enum(['component', 'iframe']),\n component: z\n .string()\n .optional()\n .meta({ description: '`component`: the plugin preview.' }),\n }),\n ])\n .optional(),\n search: z.boolean().optional().meta({\n description:\n 'Whether the resource is in the global search. Defaults to whether its list has a search (`list.search`).',\n }),\n});\n\nexport const Resource = z\n .looseObject({\n id: z.string().min(1),\n kind: z.string().min(1).optional().meta({\n description:\n \"What the resource is. `media`: a library of media (decision 0026), with a `file` field and a `media` on each item. Without it, an ordinary resource. Clients treat a kind they don't know as an ordinary resource.\",\n }),\n label: z.looseObject({ singular: z.string(), plural: z.string() }),\n description: z.string().optional(),\n icon: z.string().optional(),\n itemTitle: z\n .string()\n .optional()\n .meta({ description: 'The field used as the title of an item, in lists and pickers.' }),\n itemSubtitle: z.string().optional().meta({\n description:\n 'The field shown under the title of an item, in search results, cards and pickers.',\n }),\n itemImage: z.string().optional().meta({\n description:\n 'The field that holds the image of an item (a media or a URL), in search results, cards and pickers.',\n }),\n fields: z.array(Field),\n form: z.looseObject({ layout: z.array(LayoutNode).optional() }).optional(),\n list: ListDefinition.optional(),\n actions: z.array(Action).optional(),\n abilities: Abilities.optional(),\n features: ResourceFeatures.optional(),\n types: z\n .array(BlockType)\n .optional()\n .meta({ description: 'Variants of the resource, each with its own extra fields.' }),\n settings: z.record(z.string(), z.unknown()).optional(),\n })\n .meta({ description: 'A set of items managed in the admin.' });\n\nexport type ResourceFeatures = z.infer<typeof ResourceFeatures>;\nexport type Resource = z.infer<typeof Resource>;\n/** A resource as a backend writes it, before defaults are applied. */\nexport type ResourceInput = z.input<typeof Resource>;\n"],"mappings":";;;;;;;AAQA,IAAa,mBAAmB,EAAE,YAAY;CAC1C,QAAQ,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,QAAQ,CAAC,CAAC,SAAS;CAChC,SAAS,EACJ,MAAM,CACH,EAAE,KAAK,GACP,EAAE,YAAY;EACV,MAAM,EAAE,KAAK,CAAC,aAAa,QAAQ,CAAC;EACpC,WAAW,EACN,OAAO,CAAC,CACR,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,mCAAmC,CAAC;CACjE,CAAC,CACL,CAAC,CAAC,CACD,SAAS;CACd,QAAQ,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAChC,aACI,2GACR,CAAC;AACL,CAAC;AAED,IAAa,WAAW,EACnB,YAAY;CACT,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACpB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EACpC,aACI,qNACR,CAAC;CACD,OAAO,EAAE,YAAY;EAAE,UAAU,EAAE,OAAO;EAAG,QAAQ,EAAE,OAAO;CAAE,CAAC;CACjE,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,WAAW,EACN,OAAO,CAAC,CACR,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,gEAAgE,CAAC;CAC1F,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EACrC,aACI,oFACR,CAAC;CACD,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAClC,aACI,sGACR,CAAC;CACD,QAAQ,EAAE,MAAM,KAAK;CACrB,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS;CACzE,MAAM,eAAe,SAAS;CAC9B,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,SAAS;CAClC,WAAW,UAAU,SAAS;CAC9B,UAAU,iBAAiB,SAAS;CACpC,OAAO,EACF,MAAM,SAAS,CAAC,CAChB,SAAS,CAAC,CACV,KAAK,EAAE,aAAa,4DAA4D,CAAC;CACtF,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;AACzD,CAAC,CAAC,CACD,KAAK,EAAE,aAAa,uCAAuC,CAAC"}
@@ -0,0 +1,51 @@
1
+ /** Shortest search, in characters once trimmed: shorter is a `422`. */
2
+ export declare const SEARCH_MIN_LENGTH = 2;
3
+ /** Longest search, in characters. */
4
+ export declare const SEARCH_MAX_LENGTH = 255;
5
+ /** Items per resource without a `limit`. */
6
+ export declare const SEARCH_DEFAULT_LIMIT = 5;
7
+ /** Highest `limit`, and most items of a response, every resource together. */
8
+ export declare const SEARCH_MAX_LIMIT = 20;
9
+ /** What the global search reads of a resource. */
10
+ export interface SearchableResource {
11
+ id: string;
12
+ features?: {
13
+ search?: boolean | undefined;
14
+ } | undefined;
15
+ list?: {
16
+ search?: boolean | undefined;
17
+ } | undefined;
18
+ }
19
+ /** Query of the global search (`GET /search`). */
20
+ export interface SearchQuery {
21
+ q: string;
22
+ /** Items per resource, from 1 to 20. Defaults to 5. */
23
+ limit?: number;
24
+ /** Ids of the searchable resources to search. Defaults to every one. */
25
+ resources?: readonly string[];
26
+ }
27
+ /**
28
+ * Whether a resource is in the global search: its `features.search`, or, without it, whether its
29
+ * list has a search (`list.search`), so one setting serves both (decision 0029).
30
+ */
31
+ export declare function isSearchableResource(resource: Omit<SearchableResource, 'id'>): boolean;
32
+ /** Serializes a search query: `q=été&limit=20&resources=pages,articles`. */
33
+ export declare function toSearchQueryParams(query: SearchQuery): URLSearchParams;
34
+ /** A search query as a backend reads it: valid, or the messages of its invalid parameters. */
35
+ export type ReadSearchQueryResult = {
36
+ query: Required<SearchQuery>;
37
+ errors?: undefined;
38
+ } | {
39
+ query?: undefined;
40
+ errors: Record<string, string[]>;
41
+ };
42
+ /**
43
+ * Reads the query string of `GET /search`: the reference of how a backend validates it. `q` is
44
+ * trimmed and needs 2 characters; `limit` is an integer from 1 to 20; `resources` lists searchable
45
+ * resources of the definition. Invalid parameters are a `422`, with `errors` keyed by parameter.
46
+ * Without `resources`, every searchable resource is searched, in the order of the definition.
47
+ */
48
+ export declare function readSearchQuery(params: URLSearchParams, definition: {
49
+ resources: readonly SearchableResource[];
50
+ }): ReadSearchQueryResult;
51
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,qCAAqC;AACrC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC;IACxD,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAAG,OAAO,CAEtF;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,CASvE;AAED,8FAA8F;AAC9F,MAAM,MAAM,qBAAqB,GAC3B;IAAE,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACpD;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AAE9D;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE;IAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAAE,GACzD,qBAAqB,CAyCvB"}
package/dist/search.js ADDED
@@ -0,0 +1,53 @@
1
+ //#region src/search.ts
2
+ /** Shortest search, in characters once trimmed: shorter is a `422`. */
3
+ var SEARCH_MIN_LENGTH = 2;
4
+ /** Longest search, in characters. */
5
+ var SEARCH_MAX_LENGTH = 255;
6
+ /** Items per resource without a `limit`. */
7
+ var SEARCH_DEFAULT_LIMIT = 5;
8
+ /** Highest `limit`, and most items of a response, every resource together. */
9
+ var SEARCH_MAX_LIMIT = 20;
10
+ /**
11
+ * Whether a resource is in the global search: its `features.search`, or, without it, whether its
12
+ * list has a search (`list.search`), so one setting serves both (decision 0029).
13
+ */
14
+ function isSearchableResource(resource) {
15
+ return resource.features?.search ?? resource.list?.search ?? false;
16
+ }
17
+ /** Serializes a search query: `q=été&limit=20&resources=pages,articles`. */
18
+ function toSearchQueryParams(query) {
19
+ const params = new URLSearchParams({ q: query.q });
20
+ if (query.limit !== void 0) params.set("limit", String(query.limit));
21
+ if (query.resources !== void 0 && query.resources.length > 0) params.set("resources", query.resources.join(","));
22
+ return params;
23
+ }
24
+ /**
25
+ * Reads the query string of `GET /search`: the reference of how a backend validates it. `q` is
26
+ * trimmed and needs 2 characters; `limit` is an integer from 1 to 20; `resources` lists searchable
27
+ * resources of the definition. Invalid parameters are a `422`, with `errors` keyed by parameter.
28
+ * Without `resources`, every searchable resource is searched, in the order of the definition.
29
+ */
30
+ function readSearchQuery(params, definition) {
31
+ const errors = {};
32
+ const q = (params.get("q") ?? "").trim();
33
+ if (q.length < 2) errors.q = [`The search must have at least 2 characters.`];
34
+ else if (q.length > 255) errors.q = [`The search must have at most 255 characters.`];
35
+ const rawLimit = params.get("limit");
36
+ const limit = rawLimit === null ? 5 : Number(rawLimit);
37
+ if (rawLimit !== null && (!/^\d+$/.test(rawLimit) || limit < 1 || limit > 20)) errors.limit = [`The limit must be an integer from 1 to 20.`];
38
+ const searchable = definition.resources.filter(isSearchableResource).map((resource) => resource.id);
39
+ const rawResources = params.get("resources");
40
+ const asked = rawResources === null ? searchable : rawResources.split(",").map((id) => id.trim()).filter((id) => id !== "");
41
+ const unknown = asked.filter((id) => !searchable.includes(id));
42
+ if (rawResources !== null && (asked.length === 0 || unknown.length > 0)) errors.resources = [unknown.length > 0 ? `These resources are not searchable: ${unknown.join(", ")}.` : "List at least one resource."];
43
+ if (Object.keys(errors).length > 0) return { errors };
44
+ return { query: {
45
+ q,
46
+ limit,
47
+ resources: searchable.filter((id) => asked.includes(id))
48
+ } };
49
+ }
50
+ //#endregion
51
+ export { SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LENGTH, SEARCH_MAX_LIMIT, SEARCH_MIN_LENGTH, isSearchableResource, readSearchQuery, toSearchQueryParams };
52
+
53
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","names":[],"sources":["../src/search.ts"],"sourcesContent":["/** Shortest search, in characters once trimmed: shorter is a `422`. */\nexport const SEARCH_MIN_LENGTH = 2;\n/** Longest search, in characters. */\nexport const SEARCH_MAX_LENGTH = 255;\n/** Items per resource without a `limit`. */\nexport const SEARCH_DEFAULT_LIMIT = 5;\n/** Highest `limit`, and most items of a response, every resource together. */\nexport const SEARCH_MAX_LIMIT = 20;\n\n/** What the global search reads of a resource. */\nexport interface SearchableResource {\n id: string;\n features?: { search?: boolean | undefined } | undefined;\n list?: { search?: boolean | undefined } | undefined;\n}\n\n/** Query of the global search (`GET /search`). */\nexport interface SearchQuery {\n q: string;\n /** Items per resource, from 1 to 20. Defaults to 5. */\n limit?: number;\n /** Ids of the searchable resources to search. Defaults to every one. */\n resources?: readonly string[];\n}\n\n/**\n * Whether a resource is in the global search: its `features.search`, or, without it, whether its\n * list has a search (`list.search`), so one setting serves both (decision 0029).\n */\nexport function isSearchableResource(resource: Omit<SearchableResource, 'id'>): boolean {\n return resource.features?.search ?? resource.list?.search ?? false;\n}\n\n/** Serializes a search query: `q=été&limit=20&resources=pages,articles`. */\nexport function toSearchQueryParams(query: SearchQuery): URLSearchParams {\n const params = new URLSearchParams({ q: query.q });\n if (query.limit !== undefined) {\n params.set('limit', String(query.limit));\n }\n if (query.resources !== undefined && query.resources.length > 0) {\n params.set('resources', query.resources.join(','));\n }\n return params;\n}\n\n/** A search query as a backend reads it: valid, or the messages of its invalid parameters. */\nexport type ReadSearchQueryResult =\n | { query: Required<SearchQuery>; errors?: undefined }\n | { query?: undefined; errors: Record<string, string[]> };\n\n/**\n * Reads the query string of `GET /search`: the reference of how a backend validates it. `q` is\n * trimmed and needs 2 characters; `limit` is an integer from 1 to 20; `resources` lists searchable\n * resources of the definition. Invalid parameters are a `422`, with `errors` keyed by parameter.\n * Without `resources`, every searchable resource is searched, in the order of the definition.\n */\nexport function readSearchQuery(\n params: URLSearchParams,\n definition: { resources: readonly SearchableResource[] },\n): ReadSearchQueryResult {\n const errors: Record<string, string[]> = {};\n const q = (params.get('q') ?? '').trim();\n if (q.length < SEARCH_MIN_LENGTH) {\n errors.q = [`The search must have at least ${SEARCH_MIN_LENGTH} characters.`];\n } else if (q.length > SEARCH_MAX_LENGTH) {\n errors.q = [`The search must have at most ${SEARCH_MAX_LENGTH} characters.`];\n }\n\n const rawLimit = params.get('limit');\n const limit = rawLimit === null ? SEARCH_DEFAULT_LIMIT : Number(rawLimit);\n if (rawLimit !== null && (!/^\\d+$/.test(rawLimit) || limit < 1 || limit > SEARCH_MAX_LIMIT)) {\n errors.limit = [`The limit must be an integer from 1 to ${SEARCH_MAX_LIMIT}.`];\n }\n\n const searchable = definition.resources\n .filter(isSearchableResource)\n .map((resource) => resource.id);\n const rawResources = params.get('resources');\n const asked =\n rawResources === null\n ? searchable\n : rawResources\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id !== '');\n const unknown = asked.filter((id) => !searchable.includes(id));\n if (rawResources !== null && (asked.length === 0 || unknown.length > 0)) {\n errors.resources = [\n unknown.length > 0\n ? `These resources are not searchable: ${unknown.join(', ')}.`\n : 'List at least one resource.',\n ];\n }\n\n if (Object.keys(errors).length > 0) {\n return { errors };\n }\n // The order of the definition, without duplicates\n const resources = searchable.filter((id) => asked.includes(id));\n return { query: { q, limit, resources } };\n}\n"],"mappings":";;AACA,IAAa,oBAAoB;;AAEjC,IAAa,oBAAoB;;AAEjC,IAAa,uBAAuB;;AAEpC,IAAa,mBAAmB;;;;;AAsBhC,SAAgB,qBAAqB,UAAmD;CACpF,OAAO,SAAS,UAAU,UAAU,SAAS,MAAM,UAAU;AACjE;;AAGA,SAAgB,oBAAoB,OAAqC;CACrE,MAAM,SAAS,IAAI,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAC;CACjD,IAAI,MAAM,UAAU,KAAA,GAChB,OAAO,IAAI,SAAS,OAAO,MAAM,KAAK,CAAC;CAE3C,IAAI,MAAM,cAAc,KAAA,KAAa,MAAM,UAAU,SAAS,GAC1D,OAAO,IAAI,aAAa,MAAM,UAAU,KAAK,GAAG,CAAC;CAErD,OAAO;AACX;;;;;;;AAaA,SAAgB,gBACZ,QACA,YACqB;CACrB,MAAM,SAAmC,CAAC;CAC1C,MAAM,KAAK,OAAO,IAAI,GAAG,KAAK,GAAA,CAAI,KAAK;CACvC,IAAI,EAAE,SAAA,GACF,OAAO,IAAI,CAAC,6CAAgE;MACzE,IAAI,EAAE,SAAA,KACT,OAAO,IAAI,CAAC,8CAA+D;CAG/E,MAAM,WAAW,OAAO,IAAI,OAAO;CACnC,MAAM,QAAQ,aAAa,OAAA,IAA8B,OAAO,QAAQ;CACxE,IAAI,aAAa,SAAS,CAAC,QAAQ,KAAK,QAAQ,KAAK,QAAQ,KAAK,QAAA,KAC9D,OAAO,QAAQ,CAAC,4CAA6D;CAGjF,MAAM,aAAa,WAAW,UACzB,OAAO,oBAAoB,CAAC,CAC5B,KAAK,aAAa,SAAS,EAAE;CAClC,MAAM,eAAe,OAAO,IAAI,WAAW;CAC3C,MAAM,QACF,iBAAiB,OACX,aACA,aACK,MAAM,GAAG,CAAC,CACV,KAAK,OAAO,GAAG,KAAK,CAAC,CAAC,CACtB,QAAQ,OAAO,OAAO,EAAE;CACvC,MAAM,UAAU,MAAM,QAAQ,OAAO,CAAC,WAAW,SAAS,EAAE,CAAC;CAC7D,IAAI,iBAAiB,SAAS,MAAM,WAAW,KAAK,QAAQ,SAAS,IACjE,OAAO,YAAY,CACf,QAAQ,SAAS,IACX,uCAAuC,QAAQ,KAAK,IAAI,EAAE,KAC1D,6BACV;CAGJ,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,GAC7B,OAAO,EAAE,OAAO;CAIpB,OAAO,EAAE,OAAO;EAAE;EAAG;EAAO,WADV,WAAW,QAAQ,OAAO,MAAM,SAAS,EAAE,CACjC;CAAU,EAAE;AAC5C"}
@@ -0,0 +1,17 @@
1
+ import type { MediaTransform } from './schemas/api';
2
+ /** The ratio of `width:height` (`16:9` gives 1.777…), or null when it isn't one. */
3
+ export declare function parseRatio(ratio: string): number | null;
4
+ /**
5
+ * Whether a size has a ratio (decision 0031): its height is within one pixel of its width divided
6
+ * by the ratio, since crops are in whole pixels.
7
+ */
8
+ export declare function matchesRatio(width: number, height: number, ratio: number | string): boolean;
9
+ /**
10
+ * The size of the crop of a transform as it is rendered: a quarter turn swaps its width and its
11
+ * height. Null without a crop.
12
+ */
13
+ export declare function renderedCropSize(transform: Pick<MediaTransform, 'crop' | 'rotate'>): {
14
+ width: number;
15
+ height: number;
16
+ } | null;
17
+ //# sourceMappingURL=transforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAG3F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,QAAQ,CAAC,GACnD;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQ1C"}
@@ -0,0 +1,34 @@
1
+ import { isRecord } from "./values.js";
2
+ //#region src/transforms.ts
3
+ /** The ratio of `width:height` (`16:9` gives 1.777…), or null when it isn't one. */
4
+ function parseRatio(ratio) {
5
+ const match = /^([1-9][0-9]*):([1-9][0-9]*)$/.exec(ratio);
6
+ return match === null ? null : Number(match[1]) / Number(match[2]);
7
+ }
8
+ /**
9
+ * Whether a size has a ratio (decision 0031): its height is within one pixel of its width divided
10
+ * by the ratio, since crops are in whole pixels.
11
+ */
12
+ function matchesRatio(width, height, ratio) {
13
+ const value = typeof ratio === "number" ? ratio : parseRatio(ratio);
14
+ return value !== null && value > 0 && Math.abs(width / value - height) <= 1;
15
+ }
16
+ /**
17
+ * The size of the crop of a transform as it is rendered: a quarter turn swaps its width and its
18
+ * height. Null without a crop.
19
+ */
20
+ function renderedCropSize(transform) {
21
+ const crop = transform.crop;
22
+ if (!isRecord(crop) || typeof crop.width !== "number" || typeof crop.height !== "number") return null;
23
+ return transform.rotate === 90 || transform.rotate === 270 ? {
24
+ width: crop.height,
25
+ height: crop.width
26
+ } : {
27
+ width: crop.width,
28
+ height: crop.height
29
+ };
30
+ }
31
+ //#endregion
32
+ export { matchesRatio, parseRatio, renderedCropSize };
33
+
34
+ //# sourceMappingURL=transforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.js","names":[],"sources":["../src/transforms.ts"],"sourcesContent":["import type { MediaTransform } from './schemas/api';\nimport { isRecord } from './values';\n\n/** The ratio of `width:height` (`16:9` gives 1.777…), or null when it isn't one. */\nexport function parseRatio(ratio: string): number | null {\n const match = /^([1-9][0-9]*):([1-9][0-9]*)$/.exec(ratio);\n return match === null ? null : Number(match[1]) / Number(match[2]);\n}\n\n/**\n * Whether a size has a ratio (decision 0031): its height is within one pixel of its width divided\n * by the ratio, since crops are in whole pixels.\n */\nexport function matchesRatio(width: number, height: number, ratio: number | string): boolean {\n const value = typeof ratio === 'number' ? ratio : parseRatio(ratio);\n return value !== null && value > 0 && Math.abs(width / value - height) <= 1;\n}\n\n/**\n * The size of the crop of a transform as it is rendered: a quarter turn swaps its width and its\n * height. Null without a crop.\n */\nexport function renderedCropSize(\n transform: Pick<MediaTransform, 'crop' | 'rotate'>,\n): { width: number; height: number } | null {\n const crop = transform.crop;\n if (!isRecord(crop) || typeof crop.width !== 'number' || typeof crop.height !== 'number') {\n return null;\n }\n return transform.rotate === 90 || transform.rotate === 270\n ? { width: crop.height, height: crop.width }\n : { width: crop.width, height: crop.height };\n}\n"],"mappings":";;;AAIA,SAAgB,WAAW,OAA8B;CACrD,MAAM,QAAQ,gCAAgC,KAAK,KAAK;CACxD,OAAO,UAAU,OAAO,OAAO,OAAO,MAAM,EAAE,IAAI,OAAO,MAAM,EAAE;AACrE;;;;;AAMA,SAAgB,aAAa,OAAe,QAAgB,OAAiC;CACzF,MAAM,QAAQ,OAAO,UAAU,WAAW,QAAQ,WAAW,KAAK;CAClE,OAAO,UAAU,QAAQ,QAAQ,KAAK,KAAK,IAAI,QAAQ,QAAQ,MAAM,KAAK;AAC9E;;;;;AAMA,SAAgB,iBACZ,WACwC;CACxC,MAAM,OAAO,UAAU;CACvB,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,WAAW,UAC5E,OAAO;CAEX,OAAO,UAAU,WAAW,MAAM,UAAU,WAAW,MACjD;EAAE,OAAO,KAAK;EAAQ,QAAQ,KAAK;CAAM,IACzC;EAAE,OAAO,KAAK;EAAO,QAAQ,KAAK;CAAO;AACnD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panneau/protocol",
3
- "version": "5.0.0-alpha.4",
3
+ "version": "5.0.0-alpha.6",
4
4
  "description": "The Panneau protocol: definition schema, HTTP API spec and conformance kit",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -200,6 +200,12 @@
200
200
  "itemTitle": {
201
201
  "type": "string"
202
202
  },
203
+ "itemSubtitle": {
204
+ "type": "string"
205
+ },
206
+ "itemImage": {
207
+ "type": "string"
208
+ },
203
209
  "fields": {
204
210
  "type": "array",
205
211
  "items": {
@@ -306,7 +312,20 @@
306
312
  "type": "boolean"
307
313
  },
308
314
  "resource": {
309
- "type": "string"
315
+ "anyOf": [
316
+ {
317
+ "type": "string",
318
+ "minLength": 1
319
+ },
320
+ {
321
+ "minItems": 1,
322
+ "type": "array",
323
+ "items": {
324
+ "type": "string",
325
+ "minLength": 1
326
+ }
327
+ }
328
+ ]
310
329
  },
311
330
  "accept": {
312
331
  "type": "array",
@@ -314,9 +333,33 @@
314
333
  "type": "string"
315
334
  }
316
335
  },
336
+ "ratios": {
337
+ "type": "array",
338
+ "items": {
339
+ "type": "string",
340
+ "pattern": "^[1-9][0-9]*:[1-9][0-9]*$"
341
+ }
342
+ },
343
+ "rotate": {
344
+ "type": "boolean"
345
+ },
346
+ "focalPoint": {
347
+ "type": "boolean"
348
+ },
349
+ "transformBase": {
350
+ "type": "string",
351
+ "enum": [
352
+ "media",
353
+ "original",
354
+ "choose"
355
+ ]
356
+ },
317
357
  "from": {
318
358
  "type": "string"
319
359
  },
360
+ "check": {
361
+ "type": "boolean"
362
+ },
320
363
  "display": {
321
364
  "type": "string"
322
365
  },
@@ -417,7 +460,8 @@
417
460
  "type": "string",
418
461
  "enum": [
419
462
  "email",
420
- "url"
463
+ "url",
464
+ "json"
421
465
  ]
422
466
  },
423
467
  "message": {
@@ -542,6 +586,39 @@
542
586
  ],
543
587
  "additionalProperties": {}
544
588
  },
589
+ {
590
+ "type": "object",
591
+ "properties": {
592
+ "rule": {
593
+ "type": "string",
594
+ "const": "crop"
595
+ },
596
+ "value": {
597
+ "type": "object",
598
+ "properties": {
599
+ "ratios": {
600
+ "type": "array",
601
+ "items": {
602
+ "type": "string",
603
+ "pattern": "^[1-9][0-9]*:[1-9][0-9]*$"
604
+ }
605
+ },
606
+ "required": {
607
+ "type": "boolean"
608
+ }
609
+ },
610
+ "additionalProperties": {}
611
+ },
612
+ "message": {
613
+ "type": "string"
614
+ }
615
+ },
616
+ "required": [
617
+ "rule",
618
+ "value"
619
+ ],
620
+ "additionalProperties": {}
621
+ },
545
622
  {
546
623
  "type": "object",
547
624
  "properties": {
@@ -1463,6 +1540,9 @@
1463
1540
  },
1464
1541
  "abilities": {
1465
1542
  "$ref": "#/$defs/Abilities"
1543
+ },
1544
+ "resource": {
1545
+ "type": "string"
1466
1546
  }
1467
1547
  },
1468
1548
  "required": [
@@ -166,6 +166,12 @@
166
166
  "itemTitle": {
167
167
  "type": "string"
168
168
  },
169
+ "itemSubtitle": {
170
+ "type": "string"
171
+ },
172
+ "itemImage": {
173
+ "type": "string"
174
+ },
169
175
  "fields": {
170
176
  "type": "array",
171
177
  "items": {
@@ -272,7 +278,20 @@
272
278
  "type": "boolean"
273
279
  },
274
280
  "resource": {
275
- "type": "string"
281
+ "anyOf": [
282
+ {
283
+ "type": "string",
284
+ "minLength": 1
285
+ },
286
+ {
287
+ "minItems": 1,
288
+ "type": "array",
289
+ "items": {
290
+ "type": "string",
291
+ "minLength": 1
292
+ }
293
+ }
294
+ ]
276
295
  },
277
296
  "accept": {
278
297
  "type": "array",
@@ -280,9 +299,33 @@
280
299
  "type": "string"
281
300
  }
282
301
  },
302
+ "ratios": {
303
+ "type": "array",
304
+ "items": {
305
+ "type": "string",
306
+ "pattern": "^[1-9][0-9]*:[1-9][0-9]*$"
307
+ }
308
+ },
309
+ "rotate": {
310
+ "type": "boolean"
311
+ },
312
+ "focalPoint": {
313
+ "type": "boolean"
314
+ },
315
+ "transformBase": {
316
+ "type": "string",
317
+ "enum": [
318
+ "media",
319
+ "original",
320
+ "choose"
321
+ ]
322
+ },
283
323
  "from": {
284
324
  "type": "string"
285
325
  },
326
+ "check": {
327
+ "type": "boolean"
328
+ },
286
329
  "display": {
287
330
  "type": "string"
288
331
  },
@@ -383,7 +426,8 @@
383
426
  "type": "string",
384
427
  "enum": [
385
428
  "email",
386
- "url"
429
+ "url",
430
+ "json"
387
431
  ]
388
432
  },
389
433
  "message": {
@@ -508,6 +552,39 @@
508
552
  ],
509
553
  "additionalProperties": {}
510
554
  },
555
+ {
556
+ "type": "object",
557
+ "properties": {
558
+ "rule": {
559
+ "type": "string",
560
+ "const": "crop"
561
+ },
562
+ "value": {
563
+ "type": "object",
564
+ "properties": {
565
+ "ratios": {
566
+ "type": "array",
567
+ "items": {
568
+ "type": "string",
569
+ "pattern": "^[1-9][0-9]*:[1-9][0-9]*$"
570
+ }
571
+ },
572
+ "required": {
573
+ "type": "boolean"
574
+ }
575
+ },
576
+ "additionalProperties": {}
577
+ },
578
+ "message": {
579
+ "type": "string"
580
+ }
581
+ },
582
+ "required": [
583
+ "rule",
584
+ "value"
585
+ ],
586
+ "additionalProperties": {}
587
+ },
511
588
  {
512
589
  "type": "object",
513
590
  "properties": {
@@ -1429,6 +1506,9 @@
1429
1506
  },
1430
1507
  "abilities": {
1431
1508
  "$ref": "#/$defs/Abilities"
1509
+ },
1510
+ "resource": {
1511
+ "type": "string"
1432
1512
  }
1433
1513
  },
1434
1514
  "required": [