@yargote/artifacts-contracts 0.1.0-beta.5 → 0.1.0-beta.7
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.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/http.d.ts +4 -0
- package/src/http.js +2 -2
- package/src/index.d.ts +142 -0
- package/src/index.js +111 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`@yargote/artifacts-contracts` contains strict domain schemas, types, `ArtifactError`, snapshot serialization, and pure retained-edit helpers. It has no database, ZIP, server, or CLI dependency. Node 24 or later is required.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm install @yargote/artifacts-contracts@0.1.0-beta.
|
|
6
|
+
npm install @yargote/artifacts-contracts@0.1.0-beta.7
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
The root export contains domain schemas and types. `/workflow` contains `prepareArtifactEdit`, revision comparison, and compact projections. `/serialization` contains canonical JSON and SHA-256. `/http` contains HTTP response schemas, identity, canonical resource validation, and route URL helpers.
|
|
@@ -11,3 +11,5 @@ The root export contains domain schemas and types. `/workflow` contains `prepare
|
|
|
11
11
|
The existing `@artifacts/artifacts` root, `/contracts`, and `/workflow` exports re-export these implementations. Store and REST packages share one `ArtifactError` runtime.
|
|
12
12
|
|
|
13
13
|
This package is MIT licensed. The license applies to this package and does not license the private Artifacts application repository.
|
|
14
|
+
|
|
15
|
+
`shareSchema` accepts optional `notify` and `requireNew: true` without defaults. `shareResultSchema` remains the strict five-field grant shape used by lists. `shareCommandResultSchema` adds an optional immutable notification disposition for commands. Plain and explicit-false requests return the original grant shape. An absent flag and an explicit false remain distinct receipt arguments.
|
package/package.json
CHANGED
package/src/http.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export declare const revisionSummarySchema: z.ZodObject<{
|
|
|
33
33
|
text: "text";
|
|
34
34
|
html: "html";
|
|
35
35
|
markdown: "markdown";
|
|
36
|
+
design: "design";
|
|
36
37
|
}>;
|
|
37
38
|
utf8Bytes: z.ZodNumber;
|
|
38
39
|
}, z.core.$strict>;
|
|
@@ -74,6 +75,7 @@ export declare const compactArtifactReadSchema: z.ZodObject<{
|
|
|
74
75
|
text: "text";
|
|
75
76
|
html: "html";
|
|
76
77
|
markdown: "markdown";
|
|
78
|
+
design: "design";
|
|
77
79
|
}>;
|
|
78
80
|
utf8Bytes: z.ZodNumber;
|
|
79
81
|
}, z.core.$strict>;
|
|
@@ -146,6 +148,7 @@ export declare const historyResultSchema: z.ZodObject<{
|
|
|
146
148
|
text: "text";
|
|
147
149
|
html: "html";
|
|
148
150
|
markdown: "markdown";
|
|
151
|
+
design: "design";
|
|
149
152
|
}>;
|
|
150
153
|
text: z.ZodString;
|
|
151
154
|
}, z.core.$strict>;
|
|
@@ -181,6 +184,7 @@ export declare const summaryHistoryResultSchema: z.ZodObject<{
|
|
|
181
184
|
text: "text";
|
|
182
185
|
html: "html";
|
|
183
186
|
markdown: "markdown";
|
|
187
|
+
design: "design";
|
|
184
188
|
}>;
|
|
185
189
|
utf8Bytes: z.ZodNumber;
|
|
186
190
|
}, z.core.$strict>;
|
package/src/http.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { artifactReadSchema, publicationSchema, revisionSchema, snapshotSchema } from "./index.js";
|
|
2
|
+
import { artifactReadSchema, contentKinds, publicationSchema, revisionSchema, snapshotSchema } from "./index.js";
|
|
3
3
|
const identifier = z.string().min(1).max(128);
|
|
4
4
|
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
5
|
export const identitySchema = z.strictObject({ userId: identifier, sessionId: identifier, clientId: identifier, scopes: z.array(z.string()) });
|
|
6
6
|
export const attachmentSummarySchema = z.strictObject({ path: z.string(), mediaType: z.string(), encoding: z.enum(['utf8', 'base64']), decodedBytes: z.number().int().nonnegative(), sha256: digest });
|
|
7
7
|
export const revisionSummarySchema = revisionSchema.omit({ snapshot: true }).extend({
|
|
8
8
|
metadata: snapshotSchema.shape.metadata.pick({ title: true, description: true }),
|
|
9
|
-
content: z.strictObject({ kind:
|
|
9
|
+
content: z.strictObject({ kind: z.enum(contentKinds), utf8Bytes: z.number().int().nonnegative() }),
|
|
10
10
|
files: z.array(attachmentSummarySchema).max(32),
|
|
11
11
|
});
|
|
12
12
|
export const compactArtifactReadSchema = artifactReadSchema.omit({ revision: true }).extend({ revision: revisionSummarySchema });
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export declare const contentKinds: readonly ["html", "markdown", "text", "design"];
|
|
3
|
+
export declare const canvasIndexSchema: z.ZodObject<{
|
|
4
|
+
v: z.ZodLiteral<1>;
|
|
5
|
+
boards: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
6
|
+
x: z.ZodNumber;
|
|
7
|
+
y: z.ZodNumber;
|
|
8
|
+
w: z.ZodNumber;
|
|
9
|
+
h: z.ZodNumber;
|
|
10
|
+
title: z.ZodOptional<z.ZodString>;
|
|
11
|
+
page: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strict>>>;
|
|
13
|
+
order: z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodArray<z.ZodString>>;
|
|
14
|
+
notes: z.ZodOptional<z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
+
x: z.ZodNumber;
|
|
16
|
+
y: z.ZodNumber;
|
|
17
|
+
text: z.ZodString;
|
|
18
|
+
w: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
page: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strict>>>>;
|
|
21
|
+
pages: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodArray<z.ZodObject<{
|
|
22
|
+
id: z.ZodString;
|
|
23
|
+
name: z.ZodString;
|
|
24
|
+
}, z.core.$strict>>>>;
|
|
25
|
+
}, z.core.$strict>;
|
|
2
26
|
export declare const snapshotSchema: z.ZodObject<{
|
|
3
27
|
metadata: z.ZodObject<{
|
|
4
28
|
title: z.ZodString;
|
|
@@ -10,6 +34,7 @@ export declare const snapshotSchema: z.ZodObject<{
|
|
|
10
34
|
text: "text";
|
|
11
35
|
html: "html";
|
|
12
36
|
markdown: "markdown";
|
|
37
|
+
design: "design";
|
|
13
38
|
}>;
|
|
14
39
|
text: z.ZodString;
|
|
15
40
|
}, z.core.$strict>;
|
|
@@ -23,6 +48,16 @@ export declare const snapshotSchema: z.ZodObject<{
|
|
|
23
48
|
data: z.ZodString;
|
|
24
49
|
}, z.core.$strict>>;
|
|
25
50
|
}, z.core.$strict>;
|
|
51
|
+
export type CanvasIndex = z.infer<typeof canvasIndexSchema>;
|
|
52
|
+
export type CanvasBoard = CanvasIndex['boards'][string] & {
|
|
53
|
+
path: string;
|
|
54
|
+
html: string;
|
|
55
|
+
};
|
|
56
|
+
export type Canvas = {
|
|
57
|
+
index: CanvasIndex;
|
|
58
|
+
boards: CanvasBoard[];
|
|
59
|
+
};
|
|
60
|
+
export declare function parseCanvas(snapshot: Pick<Snapshot, 'content' | 'files'>): Canvas;
|
|
26
61
|
export declare const projectNameSchema: z.ZodString;
|
|
27
62
|
export declare const createSchema: z.ZodObject<{
|
|
28
63
|
requestId: z.ZodString;
|
|
@@ -37,6 +72,7 @@ export declare const createSchema: z.ZodObject<{
|
|
|
37
72
|
text: "text";
|
|
38
73
|
html: "html";
|
|
39
74
|
markdown: "markdown";
|
|
75
|
+
design: "design";
|
|
40
76
|
}>;
|
|
41
77
|
text: z.ZodString;
|
|
42
78
|
}, z.core.$strict>;
|
|
@@ -79,6 +115,7 @@ export declare const updateSchema: z.ZodObject<{
|
|
|
79
115
|
text: "text";
|
|
80
116
|
html: "html";
|
|
81
117
|
markdown: "markdown";
|
|
118
|
+
design: "design";
|
|
82
119
|
}>;
|
|
83
120
|
text: z.ZodString;
|
|
84
121
|
}, z.core.$strict>;
|
|
@@ -124,6 +161,63 @@ export declare const moveResultSchema: z.ZodObject<{
|
|
|
124
161
|
project: z.ZodNullable<z.ZodString>;
|
|
125
162
|
}, z.core.$strict>;
|
|
126
163
|
export type MoveResult = z.infer<typeof moveResultSchema>;
|
|
164
|
+
export declare const shareSchema: z.ZodObject<{
|
|
165
|
+
requestId: z.ZodString;
|
|
166
|
+
artifactId: z.ZodString;
|
|
167
|
+
email: z.ZodPipe<z.ZodString, z.ZodEmail>;
|
|
168
|
+
notify: z.ZodOptional<z.ZodBoolean>;
|
|
169
|
+
requireNew: z.ZodOptional<z.ZodLiteral<true>>;
|
|
170
|
+
}, z.core.$strict>;
|
|
171
|
+
export declare const unshareSchema: z.ZodObject<{
|
|
172
|
+
requestId: z.ZodString;
|
|
173
|
+
artifactId: z.ZodString;
|
|
174
|
+
shareId: z.ZodString;
|
|
175
|
+
}, z.core.$strict>;
|
|
176
|
+
export declare const listSharesSchema: z.ZodObject<{
|
|
177
|
+
artifactId: z.ZodString;
|
|
178
|
+
}, z.core.$strict>;
|
|
179
|
+
export declare const shareResultSchema: z.ZodObject<{
|
|
180
|
+
shareId: z.ZodString;
|
|
181
|
+
artifactId: z.ZodString;
|
|
182
|
+
kind: z.ZodLiteral<"email">;
|
|
183
|
+
value: z.ZodEmail;
|
|
184
|
+
createdAt: z.ZodISODateTime;
|
|
185
|
+
}, z.core.$strict>;
|
|
186
|
+
export declare const notificationSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
187
|
+
status: z.ZodLiteral<"requested">;
|
|
188
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
189
|
+
status: z.ZodLiteral<"skipped">;
|
|
190
|
+
reason: z.ZodLiteral<"disabled">;
|
|
191
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
192
|
+
status: z.ZodLiteral<"skipped">;
|
|
193
|
+
reason: z.ZodLiteral<"rate_limited">;
|
|
194
|
+
retryAt: z.ZodISODateTime;
|
|
195
|
+
}, z.core.$strict>]>;
|
|
196
|
+
export declare const shareCommandResultSchema: z.ZodObject<{
|
|
197
|
+
shareId: z.ZodString;
|
|
198
|
+
artifactId: z.ZodString;
|
|
199
|
+
kind: z.ZodLiteral<"email">;
|
|
200
|
+
value: z.ZodEmail;
|
|
201
|
+
createdAt: z.ZodISODateTime;
|
|
202
|
+
notification: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
203
|
+
status: z.ZodLiteral<"requested">;
|
|
204
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
205
|
+
status: z.ZodLiteral<"skipped">;
|
|
206
|
+
reason: z.ZodLiteral<"disabled">;
|
|
207
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
208
|
+
status: z.ZodLiteral<"skipped">;
|
|
209
|
+
reason: z.ZodLiteral<"rate_limited">;
|
|
210
|
+
retryAt: z.ZodISODateTime;
|
|
211
|
+
}, z.core.$strict>]>>;
|
|
212
|
+
}, z.core.$strict>;
|
|
213
|
+
export declare const unshareResultSchema: z.ZodObject<{
|
|
214
|
+
artifactId: z.ZodString;
|
|
215
|
+
shareId: z.ZodString;
|
|
216
|
+
}, z.core.$strict>;
|
|
217
|
+
export type Share = z.infer<typeof shareResultSchema>;
|
|
218
|
+
export type ShareResult = z.infer<typeof shareCommandResultSchema>;
|
|
219
|
+
export type Notification = z.infer<typeof notificationSchema>;
|
|
220
|
+
export type UnshareResult = z.infer<typeof unshareResultSchema>;
|
|
127
221
|
export declare const exportSchema: z.ZodObject<{
|
|
128
222
|
artifactId: z.ZodString;
|
|
129
223
|
revisionId: z.ZodString;
|
|
@@ -197,6 +291,13 @@ export type Viewer = z.infer<typeof viewerSchema>;
|
|
|
197
291
|
export type Publication = z.infer<typeof publicationSchema>;
|
|
198
292
|
export type Revision = z.infer<typeof revisionSchema>;
|
|
199
293
|
export type ArtifactRead = z.infer<typeof artifactReadSchema>;
|
|
294
|
+
export type SharedRead = z.infer<typeof sharedReadSchema>;
|
|
295
|
+
export type SharedArtifactSummary = Readonly<{
|
|
296
|
+
artifactId: string;
|
|
297
|
+
title: string;
|
|
298
|
+
ownerName: string;
|
|
299
|
+
url: string;
|
|
300
|
+
}>;
|
|
200
301
|
export type ArtifactSummary = {
|
|
201
302
|
artifactId: string;
|
|
202
303
|
title: string;
|
|
@@ -260,6 +361,7 @@ export declare const revisionSchema: z.ZodObject<{
|
|
|
260
361
|
text: "text";
|
|
261
362
|
html: "html";
|
|
262
363
|
markdown: "markdown";
|
|
364
|
+
design: "design";
|
|
263
365
|
}>;
|
|
264
366
|
text: z.ZodString;
|
|
265
367
|
}, z.core.$strict>;
|
|
@@ -303,6 +405,46 @@ export declare const artifactReadSchema: z.ZodObject<{
|
|
|
303
405
|
text: "text";
|
|
304
406
|
html: "html";
|
|
305
407
|
markdown: "markdown";
|
|
408
|
+
design: "design";
|
|
409
|
+
}>;
|
|
410
|
+
text: z.ZodString;
|
|
411
|
+
}, z.core.$strict>;
|
|
412
|
+
files: z.ZodArray<z.ZodObject<{
|
|
413
|
+
path: z.ZodString;
|
|
414
|
+
mediaType: z.ZodString;
|
|
415
|
+
encoding: z.ZodEnum<{
|
|
416
|
+
utf8: "utf8";
|
|
417
|
+
base64: "base64";
|
|
418
|
+
}>;
|
|
419
|
+
data: z.ZodString;
|
|
420
|
+
}, z.core.$strict>>;
|
|
421
|
+
}, z.core.$strict>;
|
|
422
|
+
}, z.core.$strict>;
|
|
423
|
+
}, z.core.$strict>;
|
|
424
|
+
export declare const sharedReadSchema: z.ZodObject<{
|
|
425
|
+
artifactId: z.ZodString;
|
|
426
|
+
ownerName: z.ZodDefault<z.ZodString>;
|
|
427
|
+
publication: z.ZodObject<{
|
|
428
|
+
revisionId: z.ZodString;
|
|
429
|
+
version: z.ZodNumber;
|
|
430
|
+
url: z.ZodURL;
|
|
431
|
+
}, z.core.$strict>;
|
|
432
|
+
revision: z.ZodObject<{
|
|
433
|
+
id: z.ZodString;
|
|
434
|
+
createdAt: z.ZodISODateTime;
|
|
435
|
+
sha256: z.ZodString;
|
|
436
|
+
snapshot: z.ZodObject<{
|
|
437
|
+
metadata: z.ZodObject<{
|
|
438
|
+
title: z.ZodString;
|
|
439
|
+
description: z.ZodString;
|
|
440
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
441
|
+
}, z.core.$strict>;
|
|
442
|
+
content: z.ZodObject<{
|
|
443
|
+
kind: z.ZodEnum<{
|
|
444
|
+
text: "text";
|
|
445
|
+
html: "html";
|
|
446
|
+
markdown: "markdown";
|
|
447
|
+
design: "design";
|
|
306
448
|
}>;
|
|
307
449
|
text: z.ZodString;
|
|
308
450
|
}, z.core.$strict>;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const identifier = z.string().min(1).max(128);
|
|
3
3
|
const requestId = z.string().min(8).max(128);
|
|
4
|
+
export const contentKinds = ['html', 'markdown', 'text', 'design'];
|
|
4
5
|
const fileSchema = z.strictObject({
|
|
5
6
|
path: z.string().min(1).max(240).refine((path) => path === path.normalize('NFC') && !/[:\\%\x00-\x1f\x7f]/.test(path) && path.split('/').every((part) => part !== '' && part !== '.' && part !== '..'), 'Use a normalized relative file path'),
|
|
6
7
|
mediaType: z.string().max(128).regex(/^[a-zA-Z0-9!#$&^_.+-]+\/[a-zA-Z0-9!#$&^_.+-]+$/),
|
|
@@ -10,9 +11,63 @@ const fileSchema = z.strictObject({
|
|
|
10
11
|
if (file.encoding === 'base64' && Buffer.from(file.data, 'base64').toString('base64') !== file.data)
|
|
11
12
|
context.addIssue({ code: 'custom', path: ['data'], message: 'Use canonical padded base64' });
|
|
12
13
|
});
|
|
14
|
+
const canvasId = z.string().regex(/^[A-Za-z0-9_-]{1,40}$/, 'Use 1 to 40 letters, digits, underscores, or hyphens').refine((id) => id !== '__proto__', 'Use an id other than __proto__');
|
|
15
|
+
const boardPath = z.string().regex(/^boards\/[A-Za-z0-9_][A-Za-z0-9_.-]*\.html$/, 'Use a board path such as boards/Home.html');
|
|
16
|
+
const coordinate = z.number().min(-1_000_000).max(1_000_000);
|
|
17
|
+
const extent = z.number().min(40).max(8000);
|
|
18
|
+
const label = z.string().min(1).max(256);
|
|
19
|
+
// An oversized count or a bad key aborts before any entry is parsed, so an oversized collection reports one issue.
|
|
20
|
+
// Keys are read from the raw object because a zod record skips a __proto__ key without checking it.
|
|
21
|
+
function keyed(key, value, max, noun) {
|
|
22
|
+
return z.unknown().superRefine((input, context) => {
|
|
23
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
24
|
+
return;
|
|
25
|
+
const keys = Object.keys(input);
|
|
26
|
+
if (keys.length > max) {
|
|
27
|
+
context.addIssue({ code: 'too_big', origin: 'object', maximum: max, inclusive: true, input, continue: false, message: `A canvas holds at most ${max} ${noun}` });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const name of keys) {
|
|
31
|
+
const result = key.safeParse(name);
|
|
32
|
+
if (!result.success)
|
|
33
|
+
context.addIssue({ code: 'invalid_key', origin: 'record', issues: result.error.issues, input: name, path: [name], continue: false, message: result.error.issues[0].message });
|
|
34
|
+
}
|
|
35
|
+
}).pipe(z.record(z.string(), value));
|
|
36
|
+
}
|
|
37
|
+
export const canvasIndexSchema = z.strictObject({
|
|
38
|
+
v: z.literal(1),
|
|
39
|
+
boards: keyed(boardPath, z.strictObject({ x: coordinate, y: coordinate, w: extent, h: extent, title: label.optional(), page: canvasId.optional() }), 32, 'boards'),
|
|
40
|
+
order: z.array(z.unknown()).min(1, 'A canvas needs at least one board').max(32, { message: 'A canvas holds at most 32 boards', abort: true }).pipe(z.array(boardPath)),
|
|
41
|
+
notes: keyed(canvasId, z.strictObject({ x: coordinate, y: coordinate, text: z.string(), w: extent.optional(), page: canvasId.optional() }), 200, 'notes').optional(),
|
|
42
|
+
pages: z.array(z.unknown()).max(32, { message: 'A canvas holds at most 32 pages', abort: true }).pipe(z.array(z.strictObject({ id: canvasId, name: label }))).optional(),
|
|
43
|
+
}).superRefine((index, context) => {
|
|
44
|
+
const ordered = new Set();
|
|
45
|
+
index.order.forEach((path, position) => {
|
|
46
|
+
if (!Object.hasOwn(index.boards, path))
|
|
47
|
+
context.addIssue({ code: 'custom', path: ['order', position], message: `Canvas order names ${path}, which is not a board` });
|
|
48
|
+
else if (ordered.has(path))
|
|
49
|
+
context.addIssue({ code: 'custom', path: ['order', position], message: `Canvas order lists ${path} more than once` });
|
|
50
|
+
ordered.add(path);
|
|
51
|
+
});
|
|
52
|
+
for (const path of Object.keys(index.boards))
|
|
53
|
+
if (!ordered.has(path))
|
|
54
|
+
context.addIssue({ code: 'custom', path: ['order'], message: `Canvas order omits board ${path}` });
|
|
55
|
+
const pages = new Set();
|
|
56
|
+
index.pages?.forEach(({ id }, position) => {
|
|
57
|
+
if (pages.has(id))
|
|
58
|
+
context.addIssue({ code: 'custom', path: ['pages', position, 'id'], message: `Canvas page ${id} is declared more than once` });
|
|
59
|
+
pages.add(id);
|
|
60
|
+
});
|
|
61
|
+
for (const [path, board] of Object.entries(index.boards))
|
|
62
|
+
if (board.page !== undefined && !pages.has(board.page))
|
|
63
|
+
context.addIssue({ code: 'custom', path: ['boards', path, 'page'], message: `Canvas board ${path} names unknown page ${board.page}` });
|
|
64
|
+
for (const [id, note] of Object.entries(index.notes ?? {}))
|
|
65
|
+
if (note.page !== undefined && !pages.has(note.page))
|
|
66
|
+
context.addIssue({ code: 'custom', path: ['notes', id, 'page'], message: `Canvas note ${id} names unknown page ${note.page}` });
|
|
67
|
+
});
|
|
13
68
|
export const snapshotSchema = z.strictObject({
|
|
14
69
|
metadata: z.strictObject({ title: z.string().min(1).max(256), description: z.string().max(8192), custom: z.json().optional() }),
|
|
15
|
-
content: z.strictObject({ kind: z.enum(
|
|
70
|
+
content: z.strictObject({ kind: z.enum(contentKinds), text: z.string().max(1_048_576).refine((text) => text.isWellFormed(), 'Content must contain well-formed Unicode') }),
|
|
16
71
|
files: z.array(fileSchema).max(32),
|
|
17
72
|
}).superRefine((snapshot, context) => {
|
|
18
73
|
const paths = snapshot.files.map((file) => file.path);
|
|
@@ -21,7 +76,50 @@ export const snapshotSchema = z.strictObject({
|
|
|
21
76
|
const bytes = Buffer.byteLength(JSON.stringify(snapshot.metadata)) + Buffer.byteLength(snapshot.content.text) + snapshot.files.reduce((total, file) => total + Buffer.byteLength(file.path) + Buffer.byteLength(file.mediaType) + decodeFile(file).length, 0);
|
|
22
77
|
if (bytes > 1_048_576)
|
|
23
78
|
context.addIssue({ code: 'custom', message: 'A revision must fit within 1 MiB of decoded content and metadata' });
|
|
79
|
+
// A request body can reach 8 MiB, so the index of an oversized design is not parsed.
|
|
80
|
+
if (snapshot.content.kind !== 'design' || bytes > 1_048_576)
|
|
81
|
+
return;
|
|
82
|
+
const canvas = readCanvas(snapshot);
|
|
83
|
+
if (!canvas.success)
|
|
84
|
+
for (const issue of canvas.issues)
|
|
85
|
+
context.addIssue({ ...issue });
|
|
24
86
|
});
|
|
87
|
+
function readCanvas({ content, files }) {
|
|
88
|
+
let value;
|
|
89
|
+
try {
|
|
90
|
+
value = JSON.parse(content.text);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { success: false, issues: [{ code: 'custom', path: ['content', 'text'], message: 'Design content must be a canvas index in JSON' }] };
|
|
94
|
+
}
|
|
95
|
+
const index = canvasIndexSchema.safeParse(value);
|
|
96
|
+
if (!index.success)
|
|
97
|
+
return { success: false, issues: index.error.issues.map((issue) => ({ ...issue, path: ['content', 'text', ...issue.path] })) };
|
|
98
|
+
const positions = new Map(files.map((file, position) => [file.path, position]));
|
|
99
|
+
const boards = [];
|
|
100
|
+
const issues = [];
|
|
101
|
+
for (const path of index.data.order) {
|
|
102
|
+
const position = positions.get(path);
|
|
103
|
+
if (position === undefined) {
|
|
104
|
+
issues.push({ code: 'custom', path: ['content', 'text', 'boards', path], message: `Canvas board ${path} has no matching file` });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const file = files[position];
|
|
108
|
+
if (file.mediaType !== 'text/html' || file.encoding !== 'utf8')
|
|
109
|
+
issues.push({ code: 'custom', path: ['files', position], message: `Canvas board ${path} must be a text/html file with utf8 encoding` });
|
|
110
|
+
else
|
|
111
|
+
boards.push({ ...index.data.boards[path], path, html: file.data });
|
|
112
|
+
}
|
|
113
|
+
return issues.length ? { success: false, issues } : { success: true, data: { index: index.data, boards } };
|
|
114
|
+
}
|
|
115
|
+
export function parseCanvas(snapshot) {
|
|
116
|
+
if (snapshot.content.kind !== 'design')
|
|
117
|
+
throw new TypeError('Only a design snapshot has a canvas');
|
|
118
|
+
const canvas = readCanvas(snapshot);
|
|
119
|
+
if (!canvas.success)
|
|
120
|
+
throw new z.ZodError(canvas.issues);
|
|
121
|
+
return canvas.data;
|
|
122
|
+
}
|
|
25
123
|
export const projectNameSchema = z.string().normalize('NFC').trim().min(1).max(80).refine((name) => name.isWellFormed() && !/\p{Cc}/u.test(name), 'Use a project name without control characters');
|
|
26
124
|
const response = z.enum(['full', 'compact']).optional();
|
|
27
125
|
export const createSchema = z.strictObject({ requestId, snapshot: snapshotSchema, project: projectNameSchema.optional(), response });
|
|
@@ -32,6 +130,17 @@ export const deleteSchema = z.strictObject({ requestId, artifactId: identifier,
|
|
|
32
130
|
export const deleteResultSchema = z.strictObject({ artifactId: identifier });
|
|
33
131
|
export const moveSchema = z.strictObject({ requestId, artifactId: identifier, project: projectNameSchema.nullable() });
|
|
34
132
|
export const moveResultSchema = z.strictObject({ artifactId: identifier, project: z.string().nullable() });
|
|
133
|
+
export const shareSchema = z.strictObject({ requestId, artifactId: identifier, email: z.string().trim().toLowerCase().pipe(z.email().max(254)).meta({ format: 'email', description: 'Email address, trimmed and lowercased; at most 254 characters after trimming' }), notify: z.boolean().optional(), requireNew: z.literal(true).optional() });
|
|
134
|
+
export const unshareSchema = z.strictObject({ requestId, artifactId: identifier, shareId: identifier });
|
|
135
|
+
export const listSharesSchema = z.strictObject({ artifactId: identifier });
|
|
136
|
+
export const shareResultSchema = z.strictObject({ shareId: identifier, artifactId: identifier, kind: z.literal('email'), value: z.email(), createdAt: z.iso.datetime() });
|
|
137
|
+
export const notificationSchema = z.union([
|
|
138
|
+
z.strictObject({ status: z.literal('requested') }),
|
|
139
|
+
z.strictObject({ status: z.literal('skipped'), reason: z.literal('disabled') }),
|
|
140
|
+
z.strictObject({ status: z.literal('skipped'), reason: z.literal('rate_limited'), retryAt: z.iso.datetime() }),
|
|
141
|
+
]);
|
|
142
|
+
export const shareCommandResultSchema = shareResultSchema.extend({ notification: notificationSchema.optional() });
|
|
143
|
+
export const unshareResultSchema = z.strictObject({ artifactId: identifier, shareId: identifier });
|
|
35
144
|
export const exportSchema = z.strictObject({ artifactId: identifier, revisionId: identifier });
|
|
36
145
|
export const listSchema = z.strictObject({ limit: z.number().int().min(1).max(100).default(25), cursor: identifier.optional(), project: projectNameSchema.optional() });
|
|
37
146
|
export const libraryQuerySchema = z.strictObject({
|
|
@@ -59,4 +168,5 @@ export function decodeFile(file) {
|
|
|
59
168
|
export const publicationSchema = z.strictObject({ revisionId: identifier, version: z.number().int().positive(), url: z.url() });
|
|
60
169
|
export const revisionSchema = z.strictObject({ id: identifier, parentRevisionId: identifier.nullable(), sequence: z.number().int().positive(), authorUserId: identifier, authorClientId: identifier, createdAt: z.iso.datetime(), sha256: z.string().regex(/^[a-f0-9]{64}$/), snapshot: snapshotSchema });
|
|
61
170
|
export const artifactReadSchema = z.strictObject({ artifactId: identifier, ownerId: identifier, headRevisionId: identifier, project: z.string().nullable(), publication: publicationSchema.nullable(), revision: revisionSchema });
|
|
171
|
+
export const sharedReadSchema = z.strictObject({ artifactId: identifier, ownerName: z.string().default('Usuario de Artifacts'), publication: publicationSchema, revision: revisionSchema.pick({ id: true, createdAt: true, sha256: true, snapshot: true }) });
|
|
62
172
|
export const publishResultSchema = publicationSchema.extend({ artifactId: identifier });
|