@panneau/protocol 5.0.0-alpha.3 → 5.0.0-alpha.4

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 (40) hide show
  1. package/dist/conformance/index.d.ts.map +1 -1
  2. package/dist/conformance/index.js +31 -1
  3. package/dist/conformance/index.js.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/lint.d.ts +2 -1
  6. package/dist/lint.d.ts.map +1 -1
  7. package/dist/lint.js +27 -1
  8. package/dist/lint.js.map +1 -1
  9. package/dist/mock/example.d.ts +2 -1
  10. package/dist/mock/example.d.ts.map +1 -1
  11. package/dist/mock/example.js +219 -143
  12. package/dist/mock/example.js.map +1 -1
  13. package/dist/mock/index.d.ts.map +1 -1
  14. package/dist/mock/index.js +145 -5
  15. package/dist/mock/index.js.map +1 -1
  16. package/dist/openapi.d.ts.map +1 -1
  17. package/dist/openapi.js +18 -94
  18. package/dist/openapi.js.map +1 -1
  19. package/dist/schemas/api.d.ts +113 -82
  20. package/dist/schemas/api.d.ts.map +1 -1
  21. package/dist/schemas/api.js +68 -26
  22. package/dist/schemas/api.js.map +1 -1
  23. package/dist/schemas/definition.d.ts +1 -0
  24. package/dist/schemas/definition.d.ts.map +1 -1
  25. package/dist/schemas/definition.js +2 -2
  26. package/dist/schemas/definition.js.map +1 -1
  27. package/dist/schemas/field.js +3 -3
  28. package/dist/schemas/field.js.map +1 -1
  29. package/dist/schemas/index.d.ts +106 -79
  30. package/dist/schemas/index.d.ts.map +1 -1
  31. package/dist/schemas/index.js +11 -5
  32. package/dist/schemas/index.js.map +1 -1
  33. package/dist/schemas/resource.d.ts +1 -0
  34. package/dist/schemas/resource.d.ts.map +1 -1
  35. package/dist/schemas/resource.js +1 -0
  36. package/dist/schemas/resource.js.map +1 -1
  37. package/package.json +1 -1
  38. package/schema/boot.schema.json +4 -0
  39. package/schema/definition.schema.json +4 -0
  40. package/schema/openapi.json +302 -465
@@ -1,7 +1,8 @@
1
1
  //#region src/mock/example.ts
2
2
  /**
3
3
  * A small but complete definition: pages with localized titles, a status, a publication date shown
4
- * for published pages, an author and typed blocks, in tabs and a sidebar, plus authors. For tests
4
+ * for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus
5
+ * authors with a photo, and a media library. For tests
5
6
  * and stories, with `createMockBackend()`.
6
7
  */
7
8
  function createExampleDefinition() {
@@ -11,165 +12,239 @@ function createExampleDefinition() {
11
12
  locale: "fr",
12
13
  locales: ["fr", "en"],
13
14
  api: { baseUrl: "/panneau/api" },
14
- resources: [{
15
- id: "pages",
16
- label: {
17
- singular: "page",
18
- plural: "pages"
19
- },
20
- itemTitle: "title",
21
- fields: [
22
- {
23
- name: "title",
24
- type: "text",
25
- label: "Titre",
26
- localized: true,
27
- rules: [{ rule: "required" }, {
28
- rule: "max",
29
- value: 120
30
- }]
15
+ resources: [
16
+ {
17
+ id: "pages",
18
+ label: {
19
+ singular: "page",
20
+ plural: "pages"
31
21
  },
32
- {
33
- name: "status",
34
- type: "select",
35
- options: [{
36
- value: "draft",
37
- label: "Brouillon"
38
- }, {
39
- value: "published",
40
- label: "Publiée"
41
- }],
42
- rules: [{ rule: "required" }, {
43
- rule: "in",
44
- value: ["draft", "published"]
45
- }]
46
- },
47
- {
48
- name: "published_at",
49
- type: "datetime",
50
- visibleWhen: {
51
- field: "status",
52
- equals: "published"
22
+ itemTitle: "title",
23
+ fields: [
24
+ {
25
+ name: "title",
26
+ type: "text",
27
+ label: "Titre",
28
+ localized: true,
29
+ rules: [{ rule: "required" }, {
30
+ rule: "max",
31
+ value: 120
32
+ }]
53
33
  },
54
- rules: [{ rule: "required" }]
55
- },
56
- {
57
- name: "author",
58
- type: "relation",
59
- resource: "authors"
60
- },
61
- {
62
- name: "blocks",
63
- type: "repeater",
64
- types: [{
65
- id: "text",
66
- label: "Texte",
67
- fields: [{
68
- name: "body",
69
- type: "richtext",
70
- rules: [{ rule: "required" }]
34
+ {
35
+ name: "status",
36
+ type: "select",
37
+ options: [{
38
+ value: "draft",
39
+ label: "Brouillon"
40
+ }, {
41
+ value: "published",
42
+ label: "Publiée"
43
+ }],
44
+ rules: [{ rule: "required" }, {
45
+ rule: "in",
46
+ value: ["draft", "published"]
71
47
  }]
72
- }, {
73
- id: "image",
74
- label: "Image",
75
- fields: [{
76
- name: "image",
77
- type: "media",
78
- rules: [{
79
- rule: "accept",
80
- value: ["image/*"]
81
- }, {
82
- rule: "maxSize",
83
- value: 1e3
48
+ },
49
+ {
50
+ name: "published_at",
51
+ type: "datetime",
52
+ visibleWhen: {
53
+ field: "status",
54
+ equals: "published"
55
+ },
56
+ rules: [{ rule: "required" }]
57
+ },
58
+ {
59
+ name: "author",
60
+ type: "relation",
61
+ resource: "authors"
62
+ },
63
+ {
64
+ name: "blocks",
65
+ type: "repeater",
66
+ types: [{
67
+ id: "text",
68
+ label: "Texte",
69
+ fields: [{
70
+ name: "body",
71
+ type: "richtext",
72
+ rules: [{ rule: "required" }]
73
+ }]
74
+ }, {
75
+ id: "image",
76
+ label: "Image",
77
+ fields: [{
78
+ name: "image",
79
+ type: "media",
80
+ rules: [{
81
+ rule: "accept",
82
+ value: ["image/*"]
83
+ }, {
84
+ rule: "maxSize",
85
+ value: 1e3
86
+ }]
84
87
  }]
85
88
  }]
86
- }]
87
- }
88
- ],
89
- form: { layout: [{
90
- type: "tabs",
91
- children: [{
92
- type: "tab",
93
- label: "Contenu",
89
+ },
90
+ {
91
+ name: "attachment",
92
+ type: "file",
93
+ label: "Pièce jointe",
94
+ rules: [{
95
+ rule: "accept",
96
+ value: ["application/pdf"]
97
+ }]
98
+ }
99
+ ],
100
+ form: { layout: [{
101
+ type: "tabs",
94
102
  children: [{
95
- type: "row",
103
+ type: "tab",
104
+ label: "Contenu",
96
105
  children: [{
97
- type: "field",
98
- name: "title",
99
- width: 8
106
+ type: "row",
107
+ children: [{
108
+ type: "field",
109
+ name: "title",
110
+ width: 8
111
+ }, {
112
+ type: "field",
113
+ name: "status",
114
+ width: 4
115
+ }]
100
116
  }, {
101
117
  type: "field",
102
- name: "status",
103
- width: 4
118
+ name: "blocks"
104
119
  }]
120
+ }]
121
+ }, {
122
+ type: "sidebar",
123
+ children: [{
124
+ type: "field",
125
+ name: "published_at"
105
126
  }, {
106
127
  type: "field",
107
- name: "blocks"
128
+ name: "attachment"
108
129
  }]
109
- }]
110
- }, {
111
- type: "sidebar",
112
- children: [{
113
- type: "field",
114
- name: "published_at"
115
- }]
116
- }] },
117
- list: {
118
- columns: [
119
- {
120
- field: "title",
121
- sortable: true
122
- },
123
- { field: "status" },
124
- { id: "updated" }
125
- ],
126
- filters: [{
127
- id: "status",
128
- type: "select"
130
+ }] },
131
+ list: {
132
+ columns: [
133
+ {
134
+ field: "title",
135
+ sortable: true
136
+ },
137
+ { field: "status" },
138
+ { id: "updated" }
139
+ ],
140
+ filters: [{
141
+ id: "status",
142
+ type: "select"
143
+ }, {
144
+ id: "published_at",
145
+ type: "date-range",
146
+ label: "Publication"
147
+ }],
148
+ sorts: ["title", "published_at"],
149
+ defaultSort: "title",
150
+ perPage: 20,
151
+ search: true
152
+ },
153
+ actions: [{
154
+ id: "publish",
155
+ label: "Publier",
156
+ scope: ["item", "selection"],
157
+ placement: ["row", "toolbar"],
158
+ confirm: { message: "Publier {count} pages ?" },
159
+ fields: [{
160
+ name: "notify",
161
+ type: "boolean"
162
+ }],
163
+ dryRun: true
129
164
  }, {
130
- id: "published_at",
131
- type: "date-range",
132
- label: "Publication"
165
+ id: "edit",
166
+ label: "Modifier",
167
+ kind: "link",
168
+ url: "/pages/{id}/edit",
169
+ scope: ["item"]
133
170
  }],
134
- sorts: ["title", "published_at"],
135
- defaultSort: "title",
136
- perPage: 20,
137
- search: true
171
+ abilities: {
172
+ viewAny: true,
173
+ create: true
174
+ }
138
175
  },
139
- actions: [{
140
- id: "publish",
141
- label: "Publier",
142
- scope: ["item", "selection"],
143
- placement: ["row", "toolbar"],
144
- confirm: { message: "Publier {count} pages ?" },
176
+ {
177
+ id: "authors",
178
+ label: {
179
+ singular: "auteur",
180
+ plural: "auteurs"
181
+ },
145
182
  fields: [{
146
- name: "notify",
147
- type: "boolean"
148
- }],
149
- dryRun: true
150
- }, {
151
- id: "edit",
152
- label: "Modifier",
153
- kind: "link",
154
- url: "/pages/{id}/edit",
155
- scope: ["item"]
156
- }],
157
- abilities: {
158
- viewAny: true,
159
- create: true
160
- }
161
- }, {
162
- id: "authors",
163
- label: {
164
- singular: "auteur",
165
- plural: "auteurs"
183
+ name: "name",
184
+ type: "text",
185
+ rules: [{ rule: "required" }]
186
+ }, {
187
+ name: "photo",
188
+ type: "media",
189
+ accept: ["image/*"]
190
+ }]
166
191
  },
167
- fields: [{
168
- name: "name",
169
- type: "text",
170
- rules: [{ rule: "required" }]
171
- }]
172
- }],
192
+ {
193
+ id: "media",
194
+ kind: "media",
195
+ label: {
196
+ singular: "média",
197
+ plural: "médias"
198
+ },
199
+ itemTitle: "name",
200
+ fields: [
201
+ {
202
+ name: "file",
203
+ type: "file",
204
+ accept: [
205
+ "image/*",
206
+ "video/*",
207
+ "application/pdf"
208
+ ],
209
+ rules: [{ rule: "required" }]
210
+ },
211
+ {
212
+ name: "name",
213
+ type: "text"
214
+ },
215
+ {
216
+ name: "alt",
217
+ type: "text",
218
+ localized: true
219
+ }
220
+ ],
221
+ list: {
222
+ filters: [{
223
+ id: "type",
224
+ type: "select",
225
+ options: [
226
+ {
227
+ value: "image",
228
+ label: "Images"
229
+ },
230
+ {
231
+ value: "video",
232
+ label: "Vidéos"
233
+ },
234
+ {
235
+ value: "document",
236
+ label: "Documents"
237
+ }
238
+ ]
239
+ }],
240
+ search: true
241
+ },
242
+ abilities: {
243
+ viewAny: true,
244
+ create: true
245
+ }
246
+ }
247
+ ],
173
248
  menus: { main: [{ type: "resources" }, {
174
249
  type: "link",
175
250
  label: "Site",
@@ -182,7 +257,8 @@ function createExampleDefinition() {
182
257
  id: "google",
183
258
  label: "Google"
184
259
  }]
185
- }
260
+ },
261
+ uploads: { mode: "presigned" }
186
262
  };
187
263
  }
188
264
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author and typed blocks, in tabs and a sidebar, plus authors. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n { type: 'sidebar', children: [{ type: 'field', name: 'published_at' }] },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n dryRun: true,\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [{ name: 'name', type: 'text', rules: [{ rule: 'required' }] }],\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"example.js","names":[],"sources":["../../src/mock/example.ts"],"sourcesContent":["import type { DefinitionInput } from '../schemas';\n\n/**\n * A small but complete definition: pages with localized titles, a status, a publication date shown\n * for published pages, an author, an attachment and typed blocks, in tabs and a sidebar, plus\n * authors with a photo, and a media library. For tests\n * and stories, with `createMockBackend()`.\n */\nexport function createExampleDefinition(): DefinitionInput {\n return {\n protocol: 1,\n name: 'Test site',\n locale: 'fr',\n locales: ['fr', 'en'],\n api: { baseUrl: '/panneau/api' },\n resources: [\n {\n id: 'pages',\n label: { singular: 'page', plural: 'pages' },\n itemTitle: 'title',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Titre',\n localized: true,\n rules: [{ rule: 'required' }, { rule: 'max', value: 120 }],\n },\n {\n name: 'status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Brouillon' },\n { value: 'published', label: 'Publiée' },\n ],\n rules: [\n { rule: 'required' },\n { rule: 'in', value: ['draft', 'published'] },\n ],\n },\n {\n name: 'published_at',\n type: 'datetime',\n visibleWhen: { field: 'status', equals: 'published' },\n rules: [{ rule: 'required' }],\n },\n { name: 'author', type: 'relation', resource: 'authors' },\n {\n name: 'blocks',\n type: 'repeater',\n types: [\n {\n id: 'text',\n label: 'Texte',\n fields: [\n {\n name: 'body',\n type: 'richtext',\n rules: [{ rule: 'required' }],\n },\n ],\n },\n {\n id: 'image',\n label: 'Image',\n fields: [\n {\n name: 'image',\n type: 'media',\n rules: [\n { rule: 'accept', value: ['image/*'] },\n { rule: 'maxSize', value: 1000 },\n ],\n },\n ],\n },\n ],\n },\n {\n name: 'attachment',\n type: 'file',\n label: 'Pièce jointe',\n rules: [{ rule: 'accept', value: ['application/pdf'] }],\n },\n ],\n form: {\n layout: [\n {\n type: 'tabs',\n children: [\n {\n type: 'tab',\n label: 'Contenu',\n children: [\n {\n type: 'row',\n children: [\n { type: 'field', name: 'title', width: 8 },\n { type: 'field', name: 'status', width: 4 },\n ],\n },\n { type: 'field', name: 'blocks' },\n ],\n },\n ],\n },\n {\n type: 'sidebar',\n children: [\n { type: 'field', name: 'published_at' },\n { type: 'field', name: 'attachment' },\n ],\n },\n ],\n },\n list: {\n columns: [\n { field: 'title', sortable: true },\n { field: 'status' },\n { id: 'updated' },\n ],\n filters: [\n { id: 'status', type: 'select' },\n { id: 'published_at', type: 'date-range', label: 'Publication' },\n ],\n sorts: ['title', 'published_at'],\n defaultSort: 'title',\n perPage: 20,\n search: true,\n },\n actions: [\n {\n id: 'publish',\n label: 'Publier',\n scope: ['item', 'selection'],\n placement: ['row', 'toolbar'],\n confirm: { message: 'Publier {count} pages ?' },\n fields: [{ name: 'notify', type: 'boolean' }],\n dryRun: true,\n },\n {\n id: 'edit',\n label: 'Modifier',\n kind: 'link',\n url: '/pages/{id}/edit',\n scope: ['item'],\n },\n ],\n abilities: { viewAny: true, create: true },\n },\n {\n id: 'authors',\n label: { singular: 'auteur', plural: 'auteurs' },\n fields: [\n { name: 'name', type: 'text', rules: [{ rule: 'required' }] },\n { name: 'photo', type: 'media', accept: ['image/*'] },\n ],\n },\n {\n id: 'media',\n kind: 'media',\n label: { singular: 'média', plural: 'médias' },\n itemTitle: 'name',\n fields: [\n {\n name: 'file',\n type: 'file',\n accept: ['image/*', 'video/*', 'application/pdf'],\n rules: [{ rule: 'required' }],\n },\n { name: 'name', type: 'text' },\n { name: 'alt', type: 'text', localized: true },\n ],\n list: {\n filters: [\n {\n id: 'type',\n type: 'select',\n options: [\n { value: 'image', label: 'Images' },\n { value: 'video', label: 'Vidéos' },\n { value: 'document', label: 'Documents' },\n ],\n },\n ],\n search: true,\n },\n abilities: { viewAny: true, create: true },\n },\n ],\n menus: { main: [{ type: 'resources' }, { type: 'link', label: 'Site', url: '/' }] },\n auth: { mode: 'panneau', password: true, providers: [{ id: 'google', label: 'Google' }] },\n uploads: { mode: 'presigned' },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAAgB,IAAI,EAAsB,MAAM,gBAAgB,CAAC;AAI7E,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;CACpC;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;AA6GD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,kBAAkB,GAAG,WAAW,CA+O7F;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mock/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAER,IAAI,EAQP,MAAM,gBAAgB,CAAC;AAIxB,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;CACpC;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;AAqID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,kBAAkB,GAAG,WAAW,CAwa7F;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
@@ -74,7 +74,7 @@ function applyQuery(items, query) {
74
74
  }
75
75
  const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);
76
76
  result = result.filter((item) => {
77
- const value = item[key];
77
+ const value = key in item ? item[key] : isRecord(item.media) ? item.media[key] : void 0;
78
78
  return (Array.isArray(value) ? value : [value]).some((candidate) => accepted.includes(String(candidate)));
79
79
  });
80
80
  });
@@ -94,6 +94,24 @@ function applyQuery(items, query) {
94
94
  });
95
95
  return result;
96
96
  }
97
+ /** The type of a media, from its MIME type. */
98
+ function mediaType(mime) {
99
+ const family = (mime ?? "").split("/")[0] ?? "";
100
+ if (family === "image" || family === "video" || family === "audio") return family;
101
+ return mime === "application/pdf" || family === "text" ? "document" : "other";
102
+ }
103
+ /** What the mock computes about a stored file: no renditions, ready at once. */
104
+ function mediaOf(file) {
105
+ const type = mediaType(file.mime);
106
+ return {
107
+ type,
108
+ mime: file.mime ?? null,
109
+ size: file.size ?? null,
110
+ url: file.url ?? null,
111
+ thumbnailUrl: type === "image" ? file.url ?? null : null,
112
+ status: "ready"
113
+ };
114
+ }
97
115
  /**
98
116
  * An in-memory backend that implements the protocol's resource routes: definition, list, show,
99
117
  * create, update (with `If-Match`), delete and the built-in `delete` action. Values are checked
@@ -147,6 +165,123 @@ function createMockBackend({ definition, items = {} }) {
147
165
  if (q !== void 0) query.q = q;
148
166
  return applyQuery(list, query).filter((item) => !except.includes(item.id));
149
167
  };
168
+ const uploads = /* @__PURE__ */ new Map();
169
+ let nextUpload = 1;
170
+ const handleUploads = async (segments, method, readBody) => {
171
+ if (definition.uploads?.mode !== "presigned") return problem(404, "Not found.");
172
+ if (method !== "POST") return problem(405, "Method not allowed.");
173
+ if (segments.length === 2 && segments[1] === "sign") {
174
+ const body = await readBody();
175
+ if (body.uploadId === void 0 && (body.method === "POST" || body.method === "PUT")) {
176
+ const id = `upl_${nextUpload++}`;
177
+ const upload = {
178
+ id,
179
+ key: `uploads/${id}`
180
+ };
181
+ uploads.set(id, upload);
182
+ return json(200, {
183
+ url: `https://storage.test/${upload.key}?signature=mock`,
184
+ key: upload.key,
185
+ upload: id
186
+ });
187
+ }
188
+ const upload = [...uploads.values()].find((candidate) => candidate.key === body.key);
189
+ if (upload === void 0) return problem(404, "Upload not found.");
190
+ return json(200, { url: `https://storage.test/${upload.key}?signature=mock` });
191
+ }
192
+ if (segments.length === 3 && segments[2] === "complete" && segments[1] !== void 0) {
193
+ const upload = uploads.get(segments[1]);
194
+ if (upload === void 0) return problem(404, "Upload not found.");
195
+ const { name } = await readBody();
196
+ if (typeof name === "string") upload.name = name;
197
+ return json(200, { data: upload });
198
+ }
199
+ return problem(404, "Not found.");
200
+ };
201
+ const resolveFiles = (resource, values) => {
202
+ const missing = [];
203
+ const resolved = { ...values };
204
+ const toFile = (value, path) => {
205
+ if (!isRecord(value) || typeof value.upload !== "string") return value;
206
+ const upload = uploads.get(value.upload);
207
+ if (upload === void 0) {
208
+ missing.push(path);
209
+ return value;
210
+ }
211
+ return {
212
+ name: upload.name ?? (typeof value.name === "string" ? value.name : upload.id),
213
+ url: `https://storage.test/${upload.key ?? upload.id}`,
214
+ mime: upload.mime ?? (typeof value.mime === "string" ? value.mime : null),
215
+ size: upload.size ?? (typeof value.size === "number" ? value.size : null)
216
+ };
217
+ };
218
+ resource.fields.filter((field) => field.type === "file" && field.name in values).forEach((field) => {
219
+ const value = values[field.name];
220
+ resolved[field.name] = Array.isArray(value) ? value.map((entry, index) => toFile(entry, `${field.name}.${String(index)}`)) : toFile(value, field.name);
221
+ });
222
+ return {
223
+ values: resolved,
224
+ missing
225
+ };
226
+ };
227
+ const missingMedia = (paths) => problem(422, "The given data was invalid.", {
228
+ type: "urn:panneau:problem:validation",
229
+ errors: Object.fromEntries(paths.map((path) => [path, ["This media does not exist."]]))
230
+ });
231
+ const missingUploads = (paths) => problem(422, "The given data was invalid.", {
232
+ type: "urn:panneau:problem:validation",
233
+ errors: Object.fromEntries(paths.map((path) => [path, ["This upload does not exist."]]))
234
+ });
235
+ const resolveMedia = (resource, values) => {
236
+ const missing = [];
237
+ const resolved = { ...values };
238
+ const libraries = definition.resources.filter((candidate) => candidate.kind === "media");
239
+ resource.fields.filter((field) => field.type === "media" && field.name in values).forEach((field) => {
240
+ const library = libraries.find((candidate) => field.resource !== void 0 ? candidate.id === field.resource : true);
241
+ const toReference = (value, path) => {
242
+ const id = isRecord(value) ? value.id : value;
243
+ if (typeof id !== "string" && typeof id !== "number") return value;
244
+ const media = (store.get(library?.id ?? "") ?? []).find((candidate) => candidate.id === String(id));
245
+ if (media === void 0 || library === void 0) {
246
+ missing.push(path);
247
+ return value;
248
+ }
249
+ const title = media[library.itemTitle ?? "name"];
250
+ return {
251
+ id: media.id,
252
+ ...typeof title === "string" ? { label: title } : {},
253
+ ...isRecord(media.media) ? { media: media.media } : {},
254
+ ...isRecord(value) && isRecord(value.transform) ? { transform: value.transform } : {}
255
+ };
256
+ };
257
+ const value = values[field.name];
258
+ resolved[field.name] = Array.isArray(value) ? value.map((entry, index) => toReference(entry, `${field.name}.${String(index)}`)) : toReference(value, field.name);
259
+ });
260
+ return {
261
+ values: resolved,
262
+ missing
263
+ };
264
+ };
265
+ const resolveValues = (resource, body) => {
266
+ const sent = { ...body };
267
+ if (resource.kind === "media") delete sent.media;
268
+ const files = resolveFiles(resource, sent);
269
+ if (files.missing.length > 0) return {
270
+ values: body,
271
+ problem: missingUploads(files.missing)
272
+ };
273
+ const media = resolveMedia(resource, files.values);
274
+ if (media.missing.length > 0) return {
275
+ values: body,
276
+ problem: missingMedia(media.missing)
277
+ };
278
+ const values = media.values;
279
+ if (resource.kind === "media" && isRecord(values.file)) values.media = mediaOf(values.file);
280
+ return {
281
+ values,
282
+ problem: null
283
+ };
284
+ };
150
285
  const handle = async (request) => {
151
286
  const url = new URL(request.url, "http://panneau.test");
152
287
  if (!url.pathname.startsWith(basePath)) return problem(404, "Not found.");
@@ -157,6 +292,7 @@ function createMockBackend({ definition, items = {} }) {
157
292
  return isRecord(body) ? body : {};
158
293
  };
159
294
  if (segments.length === 1 && segments[0] === "definition" && method === "GET") return json(200, definition);
295
+ if (segments[0] === "uploads") return handleUploads(segments, method, readBody);
160
296
  if (segments[0] !== "resources" || segments[1] === void 0) return problem(404, "Not found.");
161
297
  const resource = definition.resources.find((candidate) => candidate.id === segments[1]);
162
298
  const list = resource !== void 0 ? store.get(resource.id) : void 0;
@@ -182,9 +318,11 @@ function createMockBackend({ definition, items = {} }) {
182
318
  });
183
319
  }
184
320
  if (method === "POST") {
185
- const values = await readBody();
186
- const invalid = validationProblem(resource, values, "create");
321
+ const body = await readBody();
322
+ const invalid = validationProblem(resource, body, "create");
187
323
  if (invalid !== null) return invalid;
324
+ const { values, problem: unresolved } = resolveValues(resource, body);
325
+ if (unresolved !== null) return unresolved;
188
326
  const item = makeItem(resource, {
189
327
  ...values,
190
328
  id: void 0
@@ -241,9 +379,11 @@ function createMockBackend({ definition, items = {} }) {
241
379
  type: "urn:panneau:problem:version-conflict",
242
380
  current
243
381
  });
244
- const values = await readBody();
245
- const invalid = validationProblem(resource, values, "update");
382
+ const body = await readBody();
383
+ const invalid = validationProblem(resource, body, "update");
246
384
  if (invalid !== null) return invalid;
385
+ const { values, problem: unresolved } = resolveValues(resource, body);
386
+ if (unresolved !== null) return unresolved;
247
387
  const updates = Object.fromEntries(Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)));
248
388
  const updated = {
249
389
  ...current,