@panneau/protocol 5.0.0-alpha.2 → 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 (44) hide show
  1. package/dist/conformance/index.d.ts.map +1 -1
  2. package/dist/conformance/index.js +64 -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 +218 -141
  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 +165 -6
  15. package/dist/mock/index.js.map +1 -1
  16. package/dist/openapi.d.ts.map +1 -1
  17. package/dist/openapi.js +20 -95
  18. package/dist/openapi.js.map +1 -1
  19. package/dist/schemas/action.d.ts +1 -0
  20. package/dist/schemas/action.d.ts.map +1 -1
  21. package/dist/schemas/action.js +1 -0
  22. package/dist/schemas/action.js.map +1 -1
  23. package/dist/schemas/api.d.ts +114 -82
  24. package/dist/schemas/api.d.ts.map +1 -1
  25. package/dist/schemas/api.js +70 -27
  26. package/dist/schemas/api.js.map +1 -1
  27. package/dist/schemas/definition.d.ts +2 -0
  28. package/dist/schemas/definition.d.ts.map +1 -1
  29. package/dist/schemas/definition.js +2 -2
  30. package/dist/schemas/definition.js.map +1 -1
  31. package/dist/schemas/field.js +3 -3
  32. package/dist/schemas/field.js.map +1 -1
  33. package/dist/schemas/index.d.ts +110 -79
  34. package/dist/schemas/index.d.ts.map +1 -1
  35. package/dist/schemas/index.js +11 -5
  36. package/dist/schemas/index.js.map +1 -1
  37. package/dist/schemas/resource.d.ts +2 -0
  38. package/dist/schemas/resource.d.ts.map +1 -1
  39. package/dist/schemas/resource.js +1 -0
  40. package/dist/schemas/resource.js.map +1 -1
  41. package/package.json +1 -1
  42. package/schema/boot.schema.json +7 -0
  43. package/schema/definition.schema.json +7 -0
  44. package/schema/openapi.json +317 -462
@@ -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,164 +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
- }]
31
- },
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
- }]
15
+ resources: [
16
+ {
17
+ id: "pages",
18
+ label: {
19
+ singular: "page",
20
+ plural: "pages"
46
21
  },
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"
183
+ name: "name",
184
+ type: "text",
185
+ rules: [{ rule: "required" }]
186
+ }, {
187
+ name: "photo",
188
+ type: "media",
189
+ accept: ["image/*"]
148
190
  }]
149
- }, {
150
- id: "edit",
151
- label: "Modifier",
152
- kind: "link",
153
- url: "/pages/{id}/edit",
154
- scope: ["item"]
155
- }],
156
- abilities: {
157
- viewAny: true,
158
- create: true
159
- }
160
- }, {
161
- id: "authors",
162
- label: {
163
- singular: "auteur",
164
- plural: "auteurs"
165
191
  },
166
- fields: [{
167
- name: "name",
168
- type: "text",
169
- rules: [{ rule: "required" }]
170
- }]
171
- }],
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
+ ],
172
248
  menus: { main: [{ type: "resources" }, {
173
249
  type: "link",
174
250
  label: "Site",
@@ -181,7 +257,8 @@ function createExampleDefinition() {
181
257
  id: "google",
182
258
  label: "Google"
183
259
  }]
184
- }
260
+ },
261
+ uploads: { mode: "presigned" }
185
262
  };
186
263
  }
187
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 },\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,CAyN7F;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
@@ -196,8 +334,27 @@ function createMockBackend({ definition, items = {} }) {
196
334
  }
197
335
  if (segments[2] === "actions" && segments[3] !== void 0 && segments.length === 4) {
198
336
  if (method !== "POST") return problem(405, "Method not allowed.");
199
- if (segments[3] !== "delete") return problem(404, "Action not found.");
337
+ const actionId = segments[3];
338
+ const action = (resource.actions ?? []).find((candidate) => candidate.id === actionId);
200
339
  const body = await readBody();
340
+ if (body.dryRun === true) {
341
+ if (action?.dryRun !== true) return problem(400, "This action does not support dry runs.");
342
+ const selected = selectItems(list, body.selection);
343
+ const allowed = selected.filter((item) => item.abilities?.[actionId] !== false);
344
+ return json(200, {
345
+ summary: {
346
+ succeeded: allowed.length,
347
+ failed: 0,
348
+ skipped: selected.length - allowed.length
349
+ },
350
+ results: selected.filter((item) => !allowed.includes(item)).slice(0, 20).map((item) => ({
351
+ id: item.id,
352
+ status: "skipped",
353
+ reason: "forbidden"
354
+ }))
355
+ });
356
+ }
357
+ if (actionId !== "delete") return problem(404, "Action not found.");
201
358
  const selected = selectItems(list, body.selection);
202
359
  store.set(resource.id, list.filter((item) => !selected.includes(item)));
203
360
  return json(200, {
@@ -222,9 +379,11 @@ function createMockBackend({ definition, items = {} }) {
222
379
  type: "urn:panneau:problem:version-conflict",
223
380
  current
224
381
  });
225
- const values = await readBody();
226
- const invalid = validationProblem(resource, values, "update");
382
+ const body = await readBody();
383
+ const invalid = validationProblem(resource, body, "update");
227
384
  if (invalid !== null) return invalid;
385
+ const { values, problem: unresolved } = resolveValues(resource, body);
386
+ if (unresolved !== null) return unresolved;
228
387
  const updates = Object.fromEntries(Object.entries(values).filter(([key]) => !RESERVED_KEYS.has(key)));
229
388
  const updated = {
230
389
  ...current,