@panneau/protocol 0.0.0-stage → 5.0.0-alpha.1

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 (103) hide show
  1. package/README.md +35 -2
  2. package/dist/conditions.d.ts +7 -0
  3. package/dist/conditions.d.ts.map +1 -0
  4. package/dist/conditions.js +21 -0
  5. package/dist/conditions.js.map +1 -0
  6. package/dist/conformance/index.d.ts +42 -0
  7. package/dist/conformance/index.d.ts.map +1 -0
  8. package/dist/conformance/index.js +270 -0
  9. package/dist/conformance/index.js.map +1 -0
  10. package/dist/constants.d.ts +21 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +73 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +21 -0
  17. package/dist/lint.d.ts +8 -0
  18. package/dist/lint.d.ts.map +1 -0
  19. package/dist/lint.js +73 -0
  20. package/dist/lint.js.map +1 -0
  21. package/dist/mock/example.d.ts +8 -0
  22. package/dist/mock/example.d.ts.map +1 -0
  23. package/dist/mock/example.js +190 -0
  24. package/dist/mock/example.js.map +1 -0
  25. package/dist/mock/index.d.ts +24 -0
  26. package/dist/mock/index.d.ts.map +1 -0
  27. package/dist/mock/index.js +254 -0
  28. package/dist/mock/index.js.map +1 -0
  29. package/dist/openapi.d.ts +11 -0
  30. package/dist/openapi.d.ts.map +1 -0
  31. package/dist/openapi.js +540 -0
  32. package/dist/openapi.js.map +1 -0
  33. package/dist/problem.d.ts +7 -0
  34. package/dist/problem.d.ts.map +1 -0
  35. package/dist/problem.js +22 -0
  36. package/dist/problem.js.map +1 -0
  37. package/dist/query.d.ts +41 -0
  38. package/dist/query.d.ts.map +1 -0
  39. package/dist/query.js +80 -0
  40. package/dist/query.js.map +1 -0
  41. package/dist/rules.d.ts +29 -0
  42. package/dist/rules.d.ts.map +1 -0
  43. package/dist/rules.js +143 -0
  44. package/dist/rules.js.map +1 -0
  45. package/dist/runtime/index.d.ts +10 -0
  46. package/dist/runtime/index.d.ts.map +1 -0
  47. package/dist/runtime/index.js +9 -0
  48. package/dist/schemas/action.d.ts +163 -0
  49. package/dist/schemas/action.d.ts.map +1 -0
  50. package/dist/schemas/action.js +56 -0
  51. package/dist/schemas/action.js.map +1 -0
  52. package/dist/schemas/api.d.ts +371 -0
  53. package/dist/schemas/api.d.ts.map +1 -0
  54. package/dist/schemas/api.js +140 -0
  55. package/dist/schemas/api.js.map +1 -0
  56. package/dist/schemas/boot.d.ts +14 -0
  57. package/dist/schemas/boot.d.ts.map +1 -0
  58. package/dist/schemas/boot.js +16 -0
  59. package/dist/schemas/boot.js.map +1 -0
  60. package/dist/schemas/common.d.ts +28 -0
  61. package/dist/schemas/common.d.ts.map +1 -0
  62. package/dist/schemas/common.js +35 -0
  63. package/dist/schemas/common.js.map +1 -0
  64. package/dist/schemas/definition.d.ts +462 -0
  65. package/dist/schemas/definition.d.ts.map +1 -0
  66. package/dist/schemas/definition.js +101 -0
  67. package/dist/schemas/definition.js.map +1 -0
  68. package/dist/schemas/field.d.ts +139 -0
  69. package/dist/schemas/field.d.ts.map +1 -0
  70. package/dist/schemas/field.js +94 -0
  71. package/dist/schemas/field.js.map +1 -0
  72. package/dist/schemas/index.d.ts +1536 -0
  73. package/dist/schemas/index.d.ts.map +1 -0
  74. package/dist/schemas/index.js +68 -0
  75. package/dist/schemas/index.js.map +1 -0
  76. package/dist/schemas/layout.d.ts +26 -0
  77. package/dist/schemas/layout.d.ts.map +1 -0
  78. package/dist/schemas/layout.js +23 -0
  79. package/dist/schemas/layout.js.map +1 -0
  80. package/dist/schemas/list.d.ts +69 -0
  81. package/dist/schemas/list.d.ts.map +1 -0
  82. package/dist/schemas/list.js +41 -0
  83. package/dist/schemas/list.js.map +1 -0
  84. package/dist/schemas/resource.d.ts +328 -0
  85. package/dist/schemas/resource.d.ts.map +1 -0
  86. package/dist/schemas/resource.js +38 -0
  87. package/dist/schemas/resource.js.map +1 -0
  88. package/dist/validate.d.ts +28 -0
  89. package/dist/validate.d.ts.map +1 -0
  90. package/dist/validate.js +52 -0
  91. package/dist/validate.js.map +1 -0
  92. package/dist/values.d.ts +7 -0
  93. package/dist/values.d.ts.map +1 -0
  94. package/dist/values.js +29 -0
  95. package/dist/values.js.map +1 -0
  96. package/dist/version.d.ts +5 -0
  97. package/dist/version.d.ts.map +1 -0
  98. package/dist/version.js +9 -0
  99. package/dist/version.js.map +1 -0
  100. package/package.json +53 -5
  101. package/schema/boot.schema.json +1229 -0
  102. package/schema/definition.schema.json +1195 -0
  103. package/schema/openapi.json +3839 -0
@@ -0,0 +1,540 @@
1
+ import { PROBLEM_CONTENT_TYPE } from "./version.js";
2
+ import { Definition } from "./schemas/definition.js";
3
+ import { BootConfig } from "./schemas/boot.js";
4
+ import { createSchemaRegistry } from "./schemas/index.js";
5
+ import { z } from "zod";
6
+ //#region src/openapi.ts
7
+ var ref = (id) => ({ $ref: `#/components/schemas/${id}` });
8
+ var json = (schema) => ({ "application/json": { schema } });
9
+ var ok = (description, schema, headers) => ({
10
+ description,
11
+ ...schema !== void 0 ? { content: json(schema) } : {},
12
+ ...headers !== void 0 ? { headers } : {}
13
+ });
14
+ var PROBLEMS = {
15
+ 401: "Not authenticated.",
16
+ 403: "Not allowed.",
17
+ 404: "Not found.",
18
+ 409: "Conflict with a business rule.",
19
+ 412: "Version conflict: `If-Match` does not match the current version, which is in `current`.",
20
+ 422: "Invalid values: messages are in `errors`, keyed by dot path."
21
+ };
22
+ function problems(...statuses) {
23
+ return Object.fromEntries(statuses.map((status) => [String(status), {
24
+ description: PROBLEMS[status],
25
+ content: { [PROBLEM_CONTENT_TYPE]: { schema: ref("Problem") } }
26
+ }]));
27
+ }
28
+ var etag = { ETag: {
29
+ description: "The version of the item.",
30
+ schema: { type: "string" }
31
+ } };
32
+ var pathParameter = (name, description) => ({
33
+ name,
34
+ in: "path",
35
+ required: true,
36
+ description,
37
+ schema: { type: "string" }
38
+ });
39
+ var queryParameter = (name, description, schema) => ({
40
+ name,
41
+ in: "query",
42
+ required: false,
43
+ description,
44
+ schema
45
+ });
46
+ var listParameters = [
47
+ queryParameter("page", "Page number, from 1.", {
48
+ type: "integer",
49
+ minimum: 1
50
+ }),
51
+ queryParameter("perPage", "Items per page.", {
52
+ type: "integer",
53
+ minimum: 1
54
+ }),
55
+ queryParameter("sort", "Comma-separated fields; a `-` prefix sorts descending.", { type: "string" }),
56
+ {
57
+ name: "filter",
58
+ in: "query",
59
+ required: false,
60
+ style: "deepObject",
61
+ explode: true,
62
+ description: "`filter[status]=published`; `filter[tags][]=a&filter[tags][]=b` for any of several values; `filter[published_at][from]=2026-01-01&filter[published_at][to]=2026-01-31` for the inclusive bounds of a range filter (`date-range`), without the open end.",
63
+ schema: {
64
+ type: "object",
65
+ additionalProperties: true
66
+ }
67
+ },
68
+ queryParameter("q", "Search.", { type: "string" }),
69
+ queryParameter("fields", "Comma-separated fields to include in each item.", { type: "string" })
70
+ ];
71
+ var resource = pathParameter("resource", "Resource id.");
72
+ var id = pathParameter("id", "Item id.");
73
+ var values = {
74
+ type: "object",
75
+ additionalProperties: true
76
+ };
77
+ /** Component schemas, generated from the protocol's Zod schemas. */
78
+ function createComponentSchemas() {
79
+ const { schemas } = z.toJSONSchema(createSchemaRegistry(), {
80
+ io: "input",
81
+ uri: (schemaId) => `#/components/schemas/${schemaId}`
82
+ });
83
+ return Object.fromEntries(Object.entries(schemas).map(([schemaId, schema]) => {
84
+ const { $schema: _schema, $id: _id, ...rest } = schema;
85
+ return [schemaId, rest];
86
+ }));
87
+ }
88
+ /** The JSON Schema of the definition (`GET /definition`). */
89
+ function createDefinitionJsonSchema() {
90
+ return {
91
+ $id: "https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/definition.schema.json",
92
+ ...z.toJSONSchema(Definition, {
93
+ io: "input",
94
+ metadata: createSchemaRegistry()
95
+ })
96
+ };
97
+ }
98
+ /** The JSON Schema of the boot config, which a server writes in the page of the app. */
99
+ function createBootConfigJsonSchema() {
100
+ return {
101
+ $id: "https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/boot.schema.json",
102
+ ...z.toJSONSchema(BootConfig, {
103
+ io: "input",
104
+ metadata: createSchemaRegistry()
105
+ })
106
+ };
107
+ }
108
+ /** The OpenAPI 3.1 document of the Panneau HTTP API. */
109
+ function createOpenApiDocument() {
110
+ return {
111
+ openapi: "3.1.0",
112
+ info: {
113
+ title: "Panneau protocol",
114
+ version: String(1),
115
+ description: "The HTTP API between a Panneau frontend and its backend. Paths are relative to `api.baseUrl` of the definition. Authentication is up to the backend (session cookie or bearer token). Errors use `application/problem+json`."
116
+ },
117
+ paths: {
118
+ "/definition": { get: {
119
+ operationId: "getDefinition",
120
+ summary: "The definition for the current user, in the current locale.",
121
+ responses: {
122
+ 200: ok("The definition.", ref("Definition")),
123
+ ...problems(401, 403)
124
+ }
125
+ } },
126
+ "/resources/{resource}": {
127
+ parameters: [resource],
128
+ get: {
129
+ operationId: "listItems",
130
+ parameters: listParameters,
131
+ responses: {
132
+ 200: ok("A page of items.", ref("ListResponse")),
133
+ ...problems(401, 403, 404)
134
+ }
135
+ },
136
+ post: {
137
+ operationId: "createItem",
138
+ requestBody: {
139
+ required: true,
140
+ content: json(values)
141
+ },
142
+ responses: {
143
+ 201: ok("The created item.", ref("ItemResponse"), etag),
144
+ ...problems(401, 403, 404, 409, 422)
145
+ }
146
+ }
147
+ },
148
+ "/resources/{resource}/{id}": {
149
+ parameters: [resource, id],
150
+ get: {
151
+ operationId: "getItem",
152
+ responses: {
153
+ 200: ok("The item.", ref("ItemResponse"), etag),
154
+ ...problems(401, 403, 404)
155
+ }
156
+ },
157
+ patch: {
158
+ operationId: "updateItem",
159
+ description: "Partial update: only the values sent are changed.",
160
+ parameters: [{
161
+ name: "If-Match",
162
+ in: "header",
163
+ required: false,
164
+ description: "The version the client edited.",
165
+ schema: { type: "string" }
166
+ }],
167
+ requestBody: {
168
+ required: true,
169
+ content: json(values)
170
+ },
171
+ responses: {
172
+ 200: ok("The updated item.", ref("ItemResponse"), etag),
173
+ ...problems(401, 403, 404, 409, 412, 422)
174
+ }
175
+ },
176
+ delete: {
177
+ operationId: "deleteItem",
178
+ responses: {
179
+ 204: ok("Deleted."),
180
+ ...problems(401, 403, 404, 409)
181
+ }
182
+ }
183
+ },
184
+ "/resources/{resource}/{id}/preview": {
185
+ parameters: [resource, id],
186
+ post: {
187
+ operationId: "previewItem",
188
+ requestBody: {
189
+ required: true,
190
+ content: json(values)
191
+ },
192
+ responses: {
193
+ 200: ok("A short-lived preview URL.", ref("PreviewResponse")),
194
+ ...problems(401, 403, 404, 422)
195
+ }
196
+ }
197
+ },
198
+ "/resources/{resource}/actions/{action}": {
199
+ parameters: [resource, pathParameter("action", "Action id.")],
200
+ post: {
201
+ operationId: "runAction",
202
+ requestBody: {
203
+ required: true,
204
+ content: {
205
+ ...json(ref("ActionRequest")),
206
+ "multipart/form-data": { schema: ref("ActionRequest") }
207
+ }
208
+ },
209
+ responses: {
210
+ 200: ok("The result.", ref("ActionResult")),
211
+ 202: ok("The action runs in the background.", ref("ActionRunResponse")),
212
+ ...problems(401, 403, 404, 409, 422)
213
+ }
214
+ }
215
+ },
216
+ "/actions/runs": { get: {
217
+ operationId: "listActionRuns",
218
+ summary: "The current user's recent runs.",
219
+ responses: {
220
+ 200: ok("Runs, newest first.", {
221
+ type: "object",
222
+ required: ["data"],
223
+ properties: { data: {
224
+ type: "array",
225
+ items: ref("ActionRun")
226
+ } }
227
+ }),
228
+ ...problems(401)
229
+ }
230
+ } },
231
+ "/actions/runs/{run}": {
232
+ parameters: [pathParameter("run", "Run id.")],
233
+ get: {
234
+ operationId: "getActionRun",
235
+ responses: {
236
+ 200: ok("The run.", ref("ActionRunResponse")),
237
+ ...problems(401, 403, 404)
238
+ }
239
+ },
240
+ delete: {
241
+ operationId: "cancelActionRun",
242
+ responses: {
243
+ 204: ok("Cancelled."),
244
+ ...problems(401, 403, 404, 409)
245
+ }
246
+ }
247
+ },
248
+ "/media": {
249
+ get: {
250
+ operationId: "listMedia",
251
+ parameters: listParameters,
252
+ responses: {
253
+ 200: ok("A page of media.", ref("MediaListResponse")),
254
+ ...problems(401, 403)
255
+ }
256
+ },
257
+ post: {
258
+ operationId: "uploadMedia",
259
+ requestBody: {
260
+ required: true,
261
+ content: { "multipart/form-data": { schema: {
262
+ type: "object",
263
+ required: ["file"],
264
+ properties: {
265
+ file: {
266
+ type: "string",
267
+ contentMediaType: "application/octet-stream"
268
+ },
269
+ name: { type: "string" },
270
+ tags: {
271
+ type: "array",
272
+ items: { type: "string" }
273
+ }
274
+ }
275
+ } } }
276
+ },
277
+ responses: {
278
+ 201: ok("The created media.", ref("MediaResponse")),
279
+ ...problems(401, 403, 422)
280
+ }
281
+ }
282
+ },
283
+ "/media/tags": { get: {
284
+ operationId: "listMediaTags",
285
+ responses: {
286
+ 200: ok("Tags in use.", {
287
+ type: "object",
288
+ required: ["data"],
289
+ properties: { data: {
290
+ type: "array",
291
+ items: { type: "string" }
292
+ } }
293
+ }),
294
+ ...problems(401, 403)
295
+ }
296
+ } },
297
+ "/media/{id}": {
298
+ parameters: [pathParameter("id", "Media id.")],
299
+ get: {
300
+ operationId: "getMedia",
301
+ responses: {
302
+ 200: ok("The media.", ref("MediaResponse")),
303
+ ...problems(401, 403, 404)
304
+ }
305
+ },
306
+ patch: {
307
+ operationId: "updateMedia",
308
+ description: "Updates metadata, tags or the image `transform`.",
309
+ requestBody: {
310
+ required: true,
311
+ content: json(values)
312
+ },
313
+ responses: {
314
+ 200: ok("The updated media.", ref("MediaResponse")),
315
+ ...problems(401, 403, 404, 422)
316
+ }
317
+ },
318
+ delete: {
319
+ operationId: "deleteMedia",
320
+ responses: {
321
+ 204: ok("Moved to the trash."),
322
+ ...problems(401, 403, 404, 409)
323
+ }
324
+ }
325
+ },
326
+ "/media/{id}/file": {
327
+ parameters: [pathParameter("id", "Media id.")],
328
+ post: {
329
+ operationId: "replaceMediaFile",
330
+ requestBody: {
331
+ required: true,
332
+ content: { "multipart/form-data": { schema: {
333
+ type: "object",
334
+ required: ["file"],
335
+ properties: { file: {
336
+ type: "string",
337
+ contentMediaType: "application/octet-stream"
338
+ } }
339
+ } } }
340
+ },
341
+ responses: {
342
+ 200: ok("The media.", ref("MediaResponse")),
343
+ ...problems(401, 403, 404, 422)
344
+ }
345
+ }
346
+ },
347
+ "/media/{id}/restore": {
348
+ parameters: [pathParameter("id", "Media id.")],
349
+ post: {
350
+ operationId: "restoreMedia",
351
+ responses: {
352
+ 200: ok("The restored media.", ref("MediaResponse")),
353
+ ...problems(401, 403, 404)
354
+ }
355
+ }
356
+ },
357
+ "/auth/session": { get: {
358
+ operationId: "getSession",
359
+ responses: {
360
+ 200: ok("The current user.", ref("Session")),
361
+ ...problems(401)
362
+ }
363
+ } },
364
+ "/auth/login": { post: {
365
+ operationId: "login",
366
+ requestBody: {
367
+ required: true,
368
+ content: json({
369
+ type: "object",
370
+ required: ["email", "password"],
371
+ properties: {
372
+ email: { type: "string" },
373
+ password: { type: "string" },
374
+ remember: { type: "boolean" }
375
+ }
376
+ })
377
+ },
378
+ responses: {
379
+ 200: ok("Logged in.", ref("Session")),
380
+ 202: ok("A second factor is required: answer `/auth/two-factor/challenge`.", {
381
+ type: "object",
382
+ properties: { twoFactor: {
383
+ type: "boolean",
384
+ const: true
385
+ } }
386
+ }),
387
+ ...problems(422)
388
+ }
389
+ } },
390
+ "/auth/logout": { post: {
391
+ operationId: "logout",
392
+ responses: { 204: ok("Logged out.") }
393
+ } },
394
+ "/auth/providers/{provider}/redirect": {
395
+ parameters: [pathParameter("provider", "Provider id.")],
396
+ get: {
397
+ operationId: "redirectToProvider",
398
+ parameters: [queryParameter("next", "Local path to go to after login.", { type: "string" })],
399
+ responses: {
400
+ 302: { description: "Redirect to the provider." },
401
+ ...problems(404)
402
+ }
403
+ }
404
+ },
405
+ "/auth/password/forgot": { post: {
406
+ operationId: "forgotPassword",
407
+ requestBody: {
408
+ required: true,
409
+ content: json({
410
+ type: "object",
411
+ required: ["email"],
412
+ properties: { email: { type: "string" } }
413
+ })
414
+ },
415
+ responses: {
416
+ 204: ok("A reset link was sent, if the account exists."),
417
+ ...problems(422)
418
+ }
419
+ } },
420
+ "/auth/password/reset": { post: {
421
+ operationId: "resetPassword",
422
+ requestBody: {
423
+ required: true,
424
+ content: json({
425
+ type: "object",
426
+ required: [
427
+ "token",
428
+ "email",
429
+ "password"
430
+ ],
431
+ properties: {
432
+ token: { type: "string" },
433
+ email: { type: "string" },
434
+ password: { type: "string" }
435
+ }
436
+ })
437
+ },
438
+ responses: {
439
+ 204: ok("The password was reset."),
440
+ ...problems(422)
441
+ }
442
+ } },
443
+ "/auth/two-factor/challenge": { post: {
444
+ operationId: "twoFactorChallenge",
445
+ requestBody: {
446
+ required: true,
447
+ content: json({
448
+ type: "object",
449
+ properties: {
450
+ code: { type: "string" },
451
+ recoveryCode: { type: "string" }
452
+ }
453
+ })
454
+ },
455
+ responses: {
456
+ 200: ok("Logged in.", ref("Session")),
457
+ ...problems(422)
458
+ }
459
+ } },
460
+ "/auth/passkeys/options": { post: {
461
+ operationId: "passkeyOptions",
462
+ responses: { 200: ok("WebAuthn request options.", {
463
+ type: "object",
464
+ additionalProperties: true
465
+ }) }
466
+ } },
467
+ "/auth/passkeys/login": { post: {
468
+ operationId: "passkeyLogin",
469
+ requestBody: {
470
+ required: true,
471
+ content: json({
472
+ type: "object",
473
+ additionalProperties: true
474
+ })
475
+ },
476
+ responses: {
477
+ 200: ok("Logged in.", ref("Session")),
478
+ ...problems(422)
479
+ }
480
+ } },
481
+ "/account": {
482
+ get: {
483
+ operationId: "getAccount",
484
+ responses: {
485
+ 200: ok("The current user.", {
486
+ type: "object",
487
+ required: ["data"],
488
+ properties: { data: ref("User") }
489
+ }),
490
+ ...problems(401)
491
+ }
492
+ },
493
+ patch: {
494
+ operationId: "updateAccount",
495
+ requestBody: {
496
+ required: true,
497
+ content: json(values)
498
+ },
499
+ responses: {
500
+ 200: ok("The updated user.", {
501
+ type: "object",
502
+ required: ["data"],
503
+ properties: { data: ref("User") }
504
+ }),
505
+ ...problems(401, 422)
506
+ }
507
+ }
508
+ },
509
+ "/search": { get: {
510
+ operationId: "search",
511
+ parameters: [{
512
+ ...queryParameter("q", "Search terms.", { type: "string" }),
513
+ required: true
514
+ }],
515
+ responses: {
516
+ 200: ok("Results grouped by resource.", ref("SearchResponse")),
517
+ ...problems(401)
518
+ }
519
+ } },
520
+ "/dashboard/widgets/{widget}": {
521
+ parameters: [pathParameter("widget", "Widget id.")],
522
+ get: {
523
+ operationId: "getWidgetData",
524
+ responses: {
525
+ 200: ok("The widget's data. Its shape depends on the widget type.", {
526
+ type: "object",
527
+ additionalProperties: true
528
+ }),
529
+ ...problems(401, 403, 404)
530
+ }
531
+ }
532
+ }
533
+ },
534
+ components: { schemas: createComponentSchemas() }
535
+ };
536
+ }
537
+ //#endregion
538
+ export { createBootConfigJsonSchema, createComponentSchemas, createDefinitionJsonSchema, createOpenApiDocument };
539
+
540
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","names":[],"sources":["../src/openapi.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { createSchemaRegistry } from './schemas';\nimport { BootConfig } from './schemas/boot';\nimport { Definition } from './schemas/definition';\nimport { PROBLEM_CONTENT_TYPE, PROTOCOL_VERSION } from './version';\n\ntype JsonObject = Record<string, unknown>;\n\nconst ref = (id: string): JsonObject => ({ $ref: `#/components/schemas/${id}` });\n\nconst json = (schema: JsonObject): JsonObject => ({ 'application/json': { schema } });\n\nconst ok = (description: string, schema?: JsonObject, headers?: JsonObject): JsonObject => ({\n description,\n ...(schema !== undefined ? { content: json(schema) } : {}),\n ...(headers !== undefined ? { headers } : {}),\n});\n\nconst PROBLEMS: Record<number, string> = {\n 401: 'Not authenticated.',\n 403: 'Not allowed.',\n 404: 'Not found.',\n 409: 'Conflict with a business rule.',\n 412: 'Version conflict: `If-Match` does not match the current version, which is in `current`.',\n 422: 'Invalid values: messages are in `errors`, keyed by dot path.',\n};\n\nfunction problems(...statuses: number[]): JsonObject {\n return Object.fromEntries(\n statuses.map((status) => [\n String(status),\n {\n description: PROBLEMS[status],\n content: { [PROBLEM_CONTENT_TYPE]: { schema: ref('Problem') } },\n },\n ]),\n );\n}\n\nconst etag = { ETag: { description: 'The version of the item.', schema: { type: 'string' } } };\n\nconst pathParameter = (name: string, description: string): JsonObject => ({\n name,\n in: 'path',\n required: true,\n description,\n schema: { type: 'string' },\n});\n\nconst queryParameter = (name: string, description: string, schema: JsonObject): JsonObject => ({\n name,\n in: 'query',\n required: false,\n description,\n schema,\n});\n\nconst listParameters = [\n queryParameter('page', 'Page number, from 1.', { type: 'integer', minimum: 1 }),\n queryParameter('perPage', 'Items per page.', { type: 'integer', minimum: 1 }),\n queryParameter('sort', 'Comma-separated fields; a `-` prefix sorts descending.', {\n type: 'string',\n }),\n {\n name: 'filter',\n in: 'query',\n required: false,\n style: 'deepObject',\n explode: true,\n description:\n '`filter[status]=published`; `filter[tags][]=a&filter[tags][]=b` for any of several values; `filter[published_at][from]=2026-01-01&filter[published_at][to]=2026-01-31` for the inclusive bounds of a range filter (`date-range`), without the open end.',\n schema: { type: 'object', additionalProperties: true },\n },\n queryParameter('q', 'Search.', { type: 'string' }),\n queryParameter('fields', 'Comma-separated fields to include in each item.', { type: 'string' }),\n];\n\nconst resource = pathParameter('resource', 'Resource id.');\nconst id = pathParameter('id', 'Item id.');\nconst values = { type: 'object', additionalProperties: true };\n\n/** Component schemas, generated from the protocol's Zod schemas. */\nexport function createComponentSchemas(): Record<string, JsonObject> {\n const { schemas } = z.toJSONSchema(createSchemaRegistry(), {\n io: 'input',\n uri: (schemaId) => `#/components/schemas/${schemaId}`,\n });\n return Object.fromEntries(\n Object.entries(schemas).map(([schemaId, schema]) => {\n const { $schema: _schema, $id: _id, ...rest } = schema as JsonObject;\n return [schemaId, rest];\n }),\n );\n}\n\n/** The JSON Schema of the definition (`GET /definition`). */\nexport function createDefinitionJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/definition.schema.json',\n ...(z.toJSONSchema(Definition, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The JSON Schema of the boot config, which a server writes in the page of the app. */\nexport function createBootConfigJsonSchema(): JsonObject {\n return {\n $id: 'https://github.com/folkloreinc/panneau-js/blob/v5.x/packages/protocol/schema/boot.schema.json',\n ...(z.toJSONSchema(BootConfig, {\n io: 'input',\n metadata: createSchemaRegistry(),\n }) as JsonObject),\n };\n}\n\n/** The OpenAPI 3.1 document of the Panneau HTTP API. */\nexport function createOpenApiDocument(): JsonObject {\n return {\n openapi: '3.1.0',\n info: {\n title: 'Panneau protocol',\n version: String(PROTOCOL_VERSION),\n description:\n 'The HTTP API between a Panneau frontend and its backend. Paths are relative to `api.baseUrl` of the definition. Authentication is up to the backend (session cookie or bearer token). Errors use `application/problem+json`.',\n },\n paths: {\n '/definition': {\n get: {\n operationId: 'getDefinition',\n summary: 'The definition for the current user, in the current locale.',\n responses: {\n 200: ok('The definition.', ref('Definition')),\n ...problems(401, 403),\n },\n },\n },\n '/resources/{resource}': {\n parameters: [resource],\n get: {\n operationId: 'listItems',\n parameters: listParameters,\n responses: {\n 200: ok('A page of items.', ref('ListResponse')),\n ...problems(401, 403, 404),\n },\n },\n post: {\n operationId: 'createItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 201: ok('The created item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 422),\n },\n },\n },\n '/resources/{resource}/{id}': {\n parameters: [resource, id],\n get: {\n operationId: 'getItem',\n responses: {\n 200: ok('The item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404),\n },\n },\n patch: {\n operationId: 'updateItem',\n description: 'Partial update: only the values sent are changed.',\n parameters: [\n {\n name: 'If-Match',\n in: 'header',\n required: false,\n description: 'The version the client edited.',\n schema: { type: 'string' },\n },\n ],\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated item.', ref('ItemResponse'), etag),\n ...problems(401, 403, 404, 409, 412, 422),\n },\n },\n delete: {\n operationId: 'deleteItem',\n responses: { 204: ok('Deleted.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/resources/{resource}/{id}/preview': {\n parameters: [resource, id],\n post: {\n operationId: 'previewItem',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('A short-lived preview URL.', ref('PreviewResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/resources/{resource}/actions/{action}': {\n parameters: [resource, pathParameter('action', 'Action id.')],\n post: {\n operationId: 'runAction',\n requestBody: {\n required: true,\n content: {\n ...json(ref('ActionRequest')),\n 'multipart/form-data': { schema: ref('ActionRequest') },\n },\n },\n responses: {\n 200: ok('The result.', ref('ActionResult')),\n 202: ok('The action runs in the background.', ref('ActionRunResponse')),\n ...problems(401, 403, 404, 409, 422),\n },\n },\n },\n '/actions/runs': {\n get: {\n operationId: 'listActionRuns',\n summary: \"The current user's recent runs.\",\n responses: {\n 200: ok('Runs, newest first.', {\n type: 'object',\n required: ['data'],\n properties: { data: { type: 'array', items: ref('ActionRun') } },\n }),\n ...problems(401),\n },\n },\n },\n '/actions/runs/{run}': {\n parameters: [pathParameter('run', 'Run id.')],\n get: {\n operationId: 'getActionRun',\n responses: {\n 200: ok('The run.', ref('ActionRunResponse')),\n ...problems(401, 403, 404),\n },\n },\n delete: {\n operationId: 'cancelActionRun',\n responses: { 204: ok('Cancelled.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/media': {\n get: {\n operationId: 'listMedia',\n parameters: listParameters,\n responses: {\n 200: ok('A page of media.', ref('MediaListResponse')),\n ...problems(401, 403),\n },\n },\n post: {\n operationId: 'uploadMedia',\n requestBody: {\n required: true,\n content: {\n 'multipart/form-data': {\n schema: {\n type: 'object',\n required: ['file'],\n properties: {\n file: {\n type: 'string',\n contentMediaType: 'application/octet-stream',\n },\n name: { type: 'string' },\n tags: { type: 'array', items: { type: 'string' } },\n },\n },\n },\n },\n },\n responses: {\n 201: ok('The created media.', ref('MediaResponse')),\n ...problems(401, 403, 422),\n },\n },\n },\n '/media/tags': {\n get: {\n operationId: 'listMediaTags',\n responses: {\n 200: ok('Tags in use.', {\n type: 'object',\n required: ['data'],\n properties: { data: { type: 'array', items: { type: 'string' } } },\n }),\n ...problems(401, 403),\n },\n },\n },\n '/media/{id}': {\n parameters: [pathParameter('id', 'Media id.')],\n get: {\n operationId: 'getMedia',\n responses: {\n 200: ok('The media.', ref('MediaResponse')),\n ...problems(401, 403, 404),\n },\n },\n patch: {\n operationId: 'updateMedia',\n description: 'Updates metadata, tags or the image `transform`.',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated media.', ref('MediaResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n delete: {\n operationId: 'deleteMedia',\n responses: { 204: ok('Moved to the trash.'), ...problems(401, 403, 404, 409) },\n },\n },\n '/media/{id}/file': {\n parameters: [pathParameter('id', 'Media id.')],\n post: {\n operationId: 'replaceMediaFile',\n requestBody: {\n required: true,\n content: {\n 'multipart/form-data': {\n schema: {\n type: 'object',\n required: ['file'],\n properties: {\n file: {\n type: 'string',\n contentMediaType: 'application/octet-stream',\n },\n },\n },\n },\n },\n },\n responses: {\n 200: ok('The media.', ref('MediaResponse')),\n ...problems(401, 403, 404, 422),\n },\n },\n },\n '/media/{id}/restore': {\n parameters: [pathParameter('id', 'Media id.')],\n post: {\n operationId: 'restoreMedia',\n responses: {\n 200: ok('The restored media.', ref('MediaResponse')),\n ...problems(401, 403, 404),\n },\n },\n },\n '/auth/session': {\n get: {\n operationId: 'getSession',\n responses: { 200: ok('The current user.', ref('Session')), ...problems(401) },\n },\n },\n '/auth/login': {\n post: {\n operationId: 'login',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email', 'password'],\n properties: {\n email: { type: 'string' },\n password: { type: 'string' },\n remember: { type: 'boolean' },\n },\n }),\n },\n responses: {\n 200: ok('Logged in.', ref('Session')),\n 202: ok(\n 'A second factor is required: answer `/auth/two-factor/challenge`.',\n {\n type: 'object',\n properties: { twoFactor: { type: 'boolean', const: true } },\n },\n ),\n ...problems(422),\n },\n },\n },\n '/auth/logout': {\n post: { operationId: 'logout', responses: { 204: ok('Logged out.') } },\n },\n '/auth/providers/{provider}/redirect': {\n parameters: [pathParameter('provider', 'Provider id.')],\n get: {\n operationId: 'redirectToProvider',\n parameters: [\n queryParameter('next', 'Local path to go to after login.', {\n type: 'string',\n }),\n ],\n responses: {\n 302: { description: 'Redirect to the provider.' },\n ...problems(404),\n },\n },\n },\n '/auth/password/forgot': {\n post: {\n operationId: 'forgotPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['email'],\n properties: { email: { type: 'string' } },\n }),\n },\n responses: {\n 204: ok('A reset link was sent, if the account exists.'),\n ...problems(422),\n },\n },\n },\n '/auth/password/reset': {\n post: {\n operationId: 'resetPassword',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n required: ['token', 'email', 'password'],\n properties: {\n token: { type: 'string' },\n email: { type: 'string' },\n password: { type: 'string' },\n },\n }),\n },\n responses: { 204: ok('The password was reset.'), ...problems(422) },\n },\n },\n '/auth/two-factor/challenge': {\n post: {\n operationId: 'twoFactorChallenge',\n requestBody: {\n required: true,\n content: json({\n type: 'object',\n properties: {\n code: { type: 'string' },\n recoveryCode: { type: 'string' },\n },\n }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/auth/passkeys/options': {\n post: {\n operationId: 'passkeyOptions',\n responses: {\n 200: ok('WebAuthn request options.', {\n type: 'object',\n additionalProperties: true,\n }),\n },\n },\n },\n '/auth/passkeys/login': {\n post: {\n operationId: 'passkeyLogin',\n requestBody: {\n required: true,\n content: json({ type: 'object', additionalProperties: true }),\n },\n responses: { 200: ok('Logged in.', ref('Session')), ...problems(422) },\n },\n },\n '/account': {\n get: {\n operationId: 'getAccount',\n responses: {\n 200: ok('The current user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401),\n },\n },\n patch: {\n operationId: 'updateAccount',\n requestBody: { required: true, content: json(values) },\n responses: {\n 200: ok('The updated user.', {\n type: 'object',\n required: ['data'],\n properties: { data: ref('User') },\n }),\n ...problems(401, 422),\n },\n },\n },\n '/search': {\n get: {\n operationId: 'search',\n parameters: [\n {\n ...queryParameter('q', 'Search terms.', { type: 'string' }),\n required: true,\n },\n ],\n responses: {\n 200: ok('Results grouped by resource.', ref('SearchResponse')),\n ...problems(401),\n },\n },\n },\n '/dashboard/widgets/{widget}': {\n parameters: [pathParameter('widget', 'Widget id.')],\n get: {\n operationId: 'getWidgetData',\n responses: {\n 200: ok(\"The widget's data. Its shape depends on the widget type.\", {\n type: 'object',\n additionalProperties: true,\n }),\n ...problems(401, 403, 404),\n },\n },\n },\n },\n components: { schemas: createComponentSchemas() },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Problem } from './schemas/api';
2
+ /**
3
+ * Whether a value is a problem details object (an API error). It checks the same shape as the
4
+ * `Problem` schema, without zod, so that UI packages can detect errors without loading the schemas.
5
+ */
6
+ export declare function isProblem(value: unknown): value is Problem;
7
+ //# sourceMappingURL=problem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../src/problem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAO7C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAuB1D"}
@@ -0,0 +1,22 @@
1
+ import { isRecord } from "./values.js";
2
+ //#region src/problem.ts
3
+ var isOptional = (value, check) => value === void 0 || check(value);
4
+ var isString = (value) => typeof value === "string";
5
+ /**
6
+ * Whether a value is a problem details object (an API error). It checks the same shape as the
7
+ * `Problem` schema, without zod, so that UI packages can detect errors without loading the schemas.
8
+ */
9
+ function isProblem(value) {
10
+ if (!isRecord(value)) return false;
11
+ const { type, title, status, detail, instance, errors, current } = value;
12
+ return isString(type) && isString(title) && Number.isInteger(status) && status >= 400 && status <= 599 && isOptional(detail, isString) && isOptional(instance, isString) && isOptional(errors, (messages) => isRecord(messages) && Object.values(messages).every((list) => Array.isArray(list) && list.every(isString))) && isOptional(current, isItem);
13
+ }
14
+ function isItem(value) {
15
+ if (!isRecord(value)) return false;
16
+ const { id, version, abilities } = value;
17
+ return isString(id) && id !== "" && (version === null || isString(version)) && isRecord(abilities) && Object.values(abilities).every((ability) => typeof ability === "boolean");
18
+ }
19
+ //#endregion
20
+ export { isProblem };
21
+
22
+ //# sourceMappingURL=problem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problem.js","names":[],"sources":["../src/problem.ts"],"sourcesContent":["import type { Problem } from './schemas/api';\nimport { isRecord } from './values';\n\nconst isOptional = (value: unknown, check: (value: unknown) => boolean) =>\n value === undefined || check(value);\nconst isString = (value: unknown) => typeof value === 'string';\n\n/**\n * Whether a value is a problem details object (an API error). It checks the same shape as the\n * `Problem` schema, without zod, so that UI packages can detect errors without loading the schemas.\n */\nexport function isProblem(value: unknown): value is Problem {\n if (!isRecord(value)) {\n return false;\n }\n const { type, title, status, detail, instance, errors, current } = value;\n return (\n isString(type) &&\n isString(title) &&\n Number.isInteger(status) &&\n (status as number) >= 400 &&\n (status as number) <= 599 &&\n isOptional(detail, isString) &&\n isOptional(instance, isString) &&\n isOptional(\n errors,\n (messages) =>\n isRecord(messages) &&\n Object.values(messages).every(\n (list) => Array.isArray(list) && list.every(isString),\n ),\n ) &&\n isOptional(current, isItem)\n );\n}\n\nfunction isItem(value: unknown): boolean {\n if (!isRecord(value)) {\n return false;\n }\n const { id, version, abilities } = value;\n return (\n isString(id) &&\n id !== '' &&\n (version === null || isString(version)) &&\n isRecord(abilities) &&\n Object.values(abilities).every((ability) => typeof ability === 'boolean')\n );\n}\n"],"mappings":";;AAGA,IAAM,cAAc,OAAgB,UAChC,UAAU,KAAA,KAAa,MAAM,KAAK;AACtC,IAAM,YAAY,UAAmB,OAAO,UAAU;;;;;AAMtD,SAAgB,UAAU,OAAkC;CACxD,IAAI,CAAC,SAAS,KAAK,GACf,OAAO;CAEX,MAAM,EAAE,MAAM,OAAO,QAAQ,QAAQ,UAAU,QAAQ,YAAY;CACnE,OACI,SAAS,IAAI,KACb,SAAS,KAAK,KACd,OAAO,UAAU,MAAM,KACtB,UAAqB,OACrB,UAAqB,OACtB,WAAW,QAAQ,QAAQ,KAC3B,WAAW,UAAU,QAAQ,KAC7B,WACI,SACC,aACG,SAAS,QAAQ,KACjB,OAAO,OAAO,QAAQ,CAAC,CAAC,OACnB,SAAS,MAAM,QAAQ,IAAI,KAAK,KAAK,MAAM,QAAQ,CACxD,CACR,KACA,WAAW,SAAS,MAAM;AAElC;AAEA,SAAS,OAAO,OAAyB;CACrC,IAAI,CAAC,SAAS,KAAK,GACf,OAAO;CAEX,MAAM,EAAE,IAAI,SAAS,cAAc;CACnC,OACI,SAAS,EAAE,KACX,OAAO,OACN,YAAY,QAAQ,SAAS,OAAO,MACrC,SAAS,SAAS,KAClB,OAAO,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY,OAAO,YAAY,SAAS;AAEhF"}