@yargote/artifacts-contracts 0.1.0-beta.6 → 0.1.0-beta.8

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 CHANGED
@@ -3,11 +3,15 @@
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
6
+ npm install @yargote/artifacts-contracts@0.1.0-beta.8
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.
10
10
 
11
+ `protectedResourceMetadataSchema` accepts standard protected-resource metadata and an optional `artifacts_rest_resource`. The relation names the exact absolute canonical REST OAuth resource URL. It may name the resource itself or one distinct REST resource. Metadata for the distinct resource may omit the relation or name itself, but cannot name another resource.
12
+
13
+ `slugSchema` validates page slugs such as `concepts/rag` without normalizing them. `readSchema` takes exactly one of `artifactId` or a `{ project, slug }` locator, and `readByIdSchema` takes only an ID. `ArtifactRead` and the HTTP summary schema include `slug`, and `ArtifactErrorCode` includes `slug_conflict`.
14
+
11
15
  The existing `@artifacts/artifacts` root, `/contracts`, and `/workflow` exports re-export these implementations. Store and REST packages share one `ArtifactError` runtime.
12
16
 
13
17
  This package is MIT licensed. The license applies to this package and does not license the private Artifacts application repository.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yargote/artifacts-contracts",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.0-beta.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Shared schemas and contracts for Artifacts clients.",
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>;
@@ -51,6 +52,7 @@ export declare const compactArtifactReadSchema: z.ZodObject<{
51
52
  ownerId: z.ZodString;
52
53
  headRevisionId: z.ZodString;
53
54
  project: z.ZodNullable<z.ZodString>;
55
+ slug: z.ZodNullable<z.ZodString>;
54
56
  artifactId: z.ZodString;
55
57
  publication: z.ZodNullable<z.ZodObject<{
56
58
  revisionId: z.ZodString;
@@ -74,6 +76,7 @@ export declare const compactArtifactReadSchema: z.ZodObject<{
74
76
  text: "text";
75
77
  html: "html";
76
78
  markdown: "markdown";
79
+ design: "design";
77
80
  }>;
78
81
  utf8Bytes: z.ZodNumber;
79
82
  }, z.core.$strict>;
@@ -95,6 +98,7 @@ export declare const artifactSummarySchema: z.ZodObject<{
95
98
  headRevisionId: z.ZodString;
96
99
  createdAt: z.ZodISODateTime;
97
100
  project: z.ZodNullable<z.ZodString>;
101
+ slug: z.ZodNullable<z.ZodString>;
98
102
  publication: z.ZodNullable<z.ZodObject<{
99
103
  revisionId: z.ZodString;
100
104
  version: z.ZodNumber;
@@ -108,6 +112,7 @@ export declare const listResultSchema: z.ZodObject<{
108
112
  headRevisionId: z.ZodString;
109
113
  createdAt: z.ZodISODateTime;
110
114
  project: z.ZodNullable<z.ZodString>;
115
+ slug: z.ZodNullable<z.ZodString>;
111
116
  publication: z.ZodNullable<z.ZodObject<{
112
117
  revisionId: z.ZodString;
113
118
  version: z.ZodNumber;
@@ -146,6 +151,7 @@ export declare const historyResultSchema: z.ZodObject<{
146
151
  text: "text";
147
152
  html: "html";
148
153
  markdown: "markdown";
154
+ design: "design";
149
155
  }>;
150
156
  text: z.ZodString;
151
157
  }, z.core.$strict>;
@@ -181,6 +187,7 @@ export declare const summaryHistoryResultSchema: z.ZodObject<{
181
187
  text: "text";
182
188
  html: "html";
183
189
  markdown: "markdown";
190
+ design: "design";
184
191
  }>;
185
192
  utf8Bytes: z.ZodNumber;
186
193
  }, z.core.$strict>;
@@ -204,5 +211,10 @@ export declare const httpErrorSchema: z.ZodObject<{
204
211
  }, z.core.$strict>;
205
212
  }, z.core.$strict>;
206
213
  export declare function canonicalResource(value: string): string;
214
+ export declare const protectedResourceMetadataSchema: z.ZodObject<{
215
+ resource: z.ZodString;
216
+ authorization_servers: z.ZodArray<z.ZodString>;
217
+ artifacts_rest_resource: z.ZodOptional<z.ZodString>;
218
+ }, z.core.$strip>;
207
219
  export declare function protectedResourceMetadataUrl(resource: string): URL;
208
220
  export declare function restBase(resource: string): string;
package/src/http.js CHANGED
@@ -1,16 +1,16 @@
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: snapshotSchema.shape.content.shape.kind, utf8Bytes: z.number().int().nonnegative() }),
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 });
13
- export const artifactSummarySchema = z.strictObject({ artifactId: identifier, title: z.string(), headRevisionId: identifier, createdAt: z.iso.datetime(), project: z.string().nullable(), publication: publicationSchema.nullable() });
13
+ export const artifactSummarySchema = z.strictObject({ artifactId: identifier, title: z.string(), headRevisionId: identifier, createdAt: z.iso.datetime(), project: z.string().nullable(), slug: z.string().nullable(), publication: publicationSchema.nullable() });
14
14
  export const listResultSchema = z.strictObject({ items: z.array(artifactSummarySchema).max(100), nextCursor: identifier.nullable() });
15
15
  export const projectSummarySchema = z.strictObject({ name: z.string(), artifactCount: z.number().int().positive() });
16
16
  export const projectsResultSchema = z.strictObject({ items: z.array(projectSummarySchema) });
@@ -26,6 +26,19 @@ export function canonicalResource(value) {
26
26
  throw new TypeError('Use the exact canonical resource URL without a trailing slash');
27
27
  return value;
28
28
  }
29
+ export const protectedResourceMetadataSchema = z.object({
30
+ resource: z.string(),
31
+ authorization_servers: z.array(z.string()).min(1),
32
+ artifacts_rest_resource: z.string().refine((value) => {
33
+ try {
34
+ canonicalResource(value);
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }).optional(),
41
+ });
29
42
  export function protectedResourceMetadataUrl(resource) {
30
43
  const url = new URL(canonicalResource(resource));
31
44
  return new URL(`/.well-known/oauth-protected-resource${url.pathname === '/' ? '' : url.pathname}`, url.origin);
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,7 +48,18 @@ 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;
62
+ export declare const slugSchema: z.ZodString;
27
63
  export declare const createSchema: z.ZodObject<{
28
64
  requestId: z.ZodString;
29
65
  snapshot: z.ZodObject<{
@@ -37,6 +73,7 @@ export declare const createSchema: z.ZodObject<{
37
73
  text: "text";
38
74
  html: "html";
39
75
  markdown: "markdown";
76
+ design: "design";
40
77
  }>;
41
78
  text: z.ZodString;
42
79
  }, z.core.$strict>;
@@ -51,12 +88,13 @@ export declare const createSchema: z.ZodObject<{
51
88
  }, z.core.$strict>>;
52
89
  }, z.core.$strict>;
53
90
  project: z.ZodOptional<z.ZodString>;
91
+ slug: z.ZodOptional<z.ZodString>;
54
92
  response: z.ZodOptional<z.ZodEnum<{
55
93
  full: "full";
56
94
  compact: "compact";
57
95
  }>>;
58
96
  }, z.core.$strict>;
59
- export declare const readSchema: z.ZodObject<{
97
+ export declare const readByIdSchema: z.ZodObject<{
60
98
  artifactId: z.ZodString;
61
99
  revisionId: z.ZodOptional<z.ZodString>;
62
100
  response: z.ZodOptional<z.ZodEnum<{
@@ -64,6 +102,16 @@ export declare const readSchema: z.ZodObject<{
64
102
  compact: "compact";
65
103
  }>>;
66
104
  }, z.core.$strict>;
105
+ export declare const readSchema: z.ZodObject<{
106
+ artifactId: z.ZodOptional<z.ZodString>;
107
+ project: z.ZodOptional<z.ZodString>;
108
+ slug: z.ZodOptional<z.ZodString>;
109
+ revisionId: z.ZodOptional<z.ZodString>;
110
+ response: z.ZodOptional<z.ZodEnum<{
111
+ full: "full";
112
+ compact: "compact";
113
+ }>>;
114
+ }, z.core.$strict>;
67
115
  export declare const updateSchema: z.ZodObject<{
68
116
  requestId: z.ZodString;
69
117
  artifactId: z.ZodString;
@@ -79,6 +127,7 @@ export declare const updateSchema: z.ZodObject<{
79
127
  text: "text";
80
128
  html: "html";
81
129
  markdown: "markdown";
130
+ design: "design";
82
131
  }>;
83
132
  text: z.ZodString;
84
133
  }, z.core.$strict>;
@@ -267,6 +316,7 @@ export type ArtifactSummary = {
267
316
  headRevisionId: string;
268
317
  createdAt: string;
269
318
  project: string | null;
319
+ slug: string | null;
270
320
  publication: Publication | null;
271
321
  };
272
322
  export type ProjectSummary = {
@@ -290,7 +340,7 @@ export type Archive = {
290
340
  base64: string;
291
341
  sha256: string;
292
342
  };
293
- export type ArtifactErrorCode = 'forbidden' | 'not_found' | 'conflict' | 'request_conflict' | 'invalid_grant';
343
+ export type ArtifactErrorCode = 'forbidden' | 'not_found' | 'conflict' | 'request_conflict' | 'slug_conflict' | 'invalid_grant';
294
344
  export declare class ArtifactError extends Error {
295
345
  readonly code: ArtifactErrorCode;
296
346
  readonly details: Record<string, unknown>;
@@ -324,6 +374,7 @@ export declare const revisionSchema: z.ZodObject<{
324
374
  text: "text";
325
375
  html: "html";
326
376
  markdown: "markdown";
377
+ design: "design";
327
378
  }>;
328
379
  text: z.ZodString;
329
380
  }, z.core.$strict>;
@@ -343,6 +394,7 @@ export declare const artifactReadSchema: z.ZodObject<{
343
394
  ownerId: z.ZodString;
344
395
  headRevisionId: z.ZodString;
345
396
  project: z.ZodNullable<z.ZodString>;
397
+ slug: z.ZodNullable<z.ZodString>;
346
398
  publication: z.ZodNullable<z.ZodObject<{
347
399
  revisionId: z.ZodString;
348
400
  version: z.ZodNumber;
@@ -367,6 +419,7 @@ export declare const artifactReadSchema: z.ZodObject<{
367
419
  text: "text";
368
420
  html: "html";
369
421
  markdown: "markdown";
422
+ design: "design";
370
423
  }>;
371
424
  text: z.ZodString;
372
425
  }, z.core.$strict>;
@@ -405,6 +458,7 @@ export declare const sharedReadSchema: z.ZodObject<{
405
458
  text: "text";
406
459
  html: "html";
407
460
  markdown: "markdown";
461
+ design: "design";
408
462
  }>;
409
463
  text: z.ZodString;
410
464
  }, 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(['html', 'markdown', 'text']), text: z.string().max(1_048_576).refine((text) => text.isWellFormed(), 'Content must contain well-formed Unicode') }),
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,11 +76,60 @@ 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
- export const createSchema = z.strictObject({ requestId, snapshot: snapshotSchema, project: projectNameSchema.optional(), response });
28
- export const readSchema = z.strictObject({ artifactId: identifier, revisionId: identifier.optional(), response });
125
+ // The .md check aborts so a file-style name gets the suffix hint instead of the generic pattern message.
126
+ export const slugSchema = z.string().min(1).max(160)
127
+ .refine((slug) => !slug.endsWith('.md'), { message: 'Remove the .md suffix: a slug names a page, not a file', abort: true })
128
+ .regex(/^[a-z0-9]+(-[a-z0-9]+)*(\/[a-z0-9]+(-[a-z0-9]+)*)*$/, 'Use lowercase kebab-case segments separated by /, such as concepts/rag');
129
+ export const createSchema = z.strictObject({ requestId, snapshot: snapshotSchema, project: projectNameSchema.optional(), slug: slugSchema.optional(), response });
130
+ export const readByIdSchema = z.strictObject({ artifactId: identifier, revisionId: identifier.optional(), response });
131
+ export const readSchema = z.strictObject({ artifactId: identifier.optional(), project: projectNameSchema.optional(), slug: slugSchema.optional(), revisionId: identifier.optional(), response })
132
+ .refine((input) => (input.artifactId === undefined) !== (input.slug === undefined) && (input.project === undefined || input.slug !== undefined), 'Specify exactly one of artifactId or slug; project applies only with slug');
29
133
  export const updateSchema = z.strictObject({ requestId, artifactId: identifier, expectedRevisionId: identifier, snapshot: snapshotSchema, response });
30
134
  export const publishSchema = z.strictObject({ requestId, artifactId: identifier, revisionId: identifier, expectedPublicationVersion: z.number().int().nonnegative() });
31
135
  export const deleteSchema = z.strictObject({ requestId, artifactId: identifier, expectedRevisionId: identifier, expectedPublicationVersion: z.number().int().nonnegative() });
@@ -69,6 +173,6 @@ export function decodeFile(file) {
69
173
  }
70
174
  export const publicationSchema = z.strictObject({ revisionId: identifier, version: z.number().int().positive(), url: z.url() });
71
175
  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 });
72
- export const artifactReadSchema = z.strictObject({ artifactId: identifier, ownerId: identifier, headRevisionId: identifier, project: z.string().nullable(), publication: publicationSchema.nullable(), revision: revisionSchema });
176
+ export const artifactReadSchema = z.strictObject({ artifactId: identifier, ownerId: identifier, headRevisionId: identifier, project: z.string().nullable(), slug: z.string().nullable(), publication: publicationSchema.nullable(), revision: revisionSchema });
73
177
  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 }) });
74
178
  export const publishResultSchema = publicationSchema.extend({ artifactId: identifier });
package/src/workflow.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { ArtifactError, artifactReadSchema, createSchema, decodeFile, readSchema, snapshotSchema, updateSchema } from "./index.js";
2
+ import { ArtifactError, artifactReadSchema, createSchema, decodeFile, readByIdSchema, snapshotSchema, updateSchema } from "./index.js";
3
3
  import { canonicalJson, sha256 } from "./serialization.js";
4
4
  async function summarizeFiles(files) {
5
5
  return Promise.all([...files].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0).map(async ({ path, mediaType, encoding, data }) => {
@@ -57,7 +57,7 @@ export async function compareRevisions(before, after) {
57
57
  };
58
58
  }
59
59
  const prepareEditSchema = z.strictObject({
60
- artifactId: readSchema.shape.artifactId,
60
+ artifactId: readByIdSchema.shape.artifactId,
61
61
  requestId: createSchema.shape.requestId,
62
62
  changes: z.strictObject({
63
63
  metadata: snapshotSchema.shape.metadata.partial().optional(),