@yargote/artifacts-contracts 0.1.0-beta.1 → 0.1.0-beta.10

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,19 @@
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.1
6
+ npm install @yargote/artifacts-contracts@0.1.0-beta.10
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. `moveSchema` takes `project`, `slug`, or both, each optional and nullable. `moveResultSchema` is unchanged. `ArtifactRead` and the HTTP summary schema include `slug`, and `ArtifactErrorCode` includes `slug_conflict`.
14
+
15
+ `searchSchema` takes a trimmed `query` of 1 to 256 characters, optional `project`, `limit` from 1 to 25 with default 10, and an opaque decimal `cursor`. `searchResultSchema` holds `items` of `searchHitSchema`, `{ artifactId, project, slug, title, snippet, rank }`, and a nullable `nextCursor`.
16
+
11
17
  The existing `@artifacts/artifacts` root, `/contracts`, and `/workflow` exports re-export these implementations. Store and REST packages share one `ArtifactError` runtime.
12
18
 
13
19
  This package is MIT licensed. The license applies to this package and does not license the private Artifacts application repository.
20
+
21
+ `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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yargote/artifacts-contracts",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.10",
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>;
@@ -50,6 +51,8 @@ export declare const revisionSummarySchema: z.ZodObject<{
50
51
  export declare const compactArtifactReadSchema: z.ZodObject<{
51
52
  ownerId: z.ZodString;
52
53
  headRevisionId: z.ZodString;
54
+ project: z.ZodNullable<z.ZodString>;
55
+ slug: z.ZodNullable<z.ZodString>;
53
56
  artifactId: z.ZodString;
54
57
  publication: z.ZodNullable<z.ZodObject<{
55
58
  revisionId: z.ZodString;
@@ -73,6 +76,7 @@ export declare const compactArtifactReadSchema: z.ZodObject<{
73
76
  text: "text";
74
77
  html: "html";
75
78
  markdown: "markdown";
79
+ design: "design";
76
80
  }>;
77
81
  utf8Bytes: z.ZodNumber;
78
82
  }, z.core.$strict>;
@@ -93,6 +97,8 @@ export declare const artifactSummarySchema: z.ZodObject<{
93
97
  title: z.ZodString;
94
98
  headRevisionId: z.ZodString;
95
99
  createdAt: z.ZodISODateTime;
100
+ project: z.ZodNullable<z.ZodString>;
101
+ slug: z.ZodNullable<z.ZodString>;
96
102
  publication: z.ZodNullable<z.ZodObject<{
97
103
  revisionId: z.ZodString;
98
104
  version: z.ZodNumber;
@@ -105,6 +111,8 @@ export declare const listResultSchema: z.ZodObject<{
105
111
  title: z.ZodString;
106
112
  headRevisionId: z.ZodString;
107
113
  createdAt: z.ZodISODateTime;
114
+ project: z.ZodNullable<z.ZodString>;
115
+ slug: z.ZodNullable<z.ZodString>;
108
116
  publication: z.ZodNullable<z.ZodObject<{
109
117
  revisionId: z.ZodString;
110
118
  version: z.ZodNumber;
@@ -113,6 +121,16 @@ export declare const listResultSchema: z.ZodObject<{
113
121
  }, z.core.$strict>>;
114
122
  nextCursor: z.ZodNullable<z.ZodString>;
115
123
  }, z.core.$strict>;
124
+ export declare const projectSummarySchema: z.ZodObject<{
125
+ name: z.ZodString;
126
+ artifactCount: z.ZodNumber;
127
+ }, z.core.$strict>;
128
+ export declare const projectsResultSchema: z.ZodObject<{
129
+ items: z.ZodArray<z.ZodObject<{
130
+ name: z.ZodString;
131
+ artifactCount: z.ZodNumber;
132
+ }, z.core.$strict>>;
133
+ }, z.core.$strict>;
116
134
  export declare const historyResultSchema: z.ZodObject<{
117
135
  items: z.ZodArray<z.ZodObject<{
118
136
  id: z.ZodString;
@@ -133,6 +151,7 @@ export declare const historyResultSchema: z.ZodObject<{
133
151
  text: "text";
134
152
  html: "html";
135
153
  markdown: "markdown";
154
+ design: "design";
136
155
  }>;
137
156
  text: z.ZodString;
138
157
  }, z.core.$strict>;
@@ -168,6 +187,7 @@ export declare const summaryHistoryResultSchema: z.ZodObject<{
168
187
  text: "text";
169
188
  html: "html";
170
189
  markdown: "markdown";
190
+ design: "design";
171
191
  }>;
172
192
  utf8Bytes: z.ZodNumber;
173
193
  }, z.core.$strict>;
@@ -191,5 +211,10 @@ export declare const httpErrorSchema: z.ZodObject<{
191
211
  }, z.core.$strict>;
192
212
  }, z.core.$strict>;
193
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>;
194
219
  export declare function protectedResourceMetadataUrl(resource: string): URL;
195
220
  export declare function restBase(resource: string): string;
package/src/http.js CHANGED
@@ -1,17 +1,19 @@
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(), 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
+ export const projectSummarySchema = z.strictObject({ name: z.string(), artifactCount: z.number().int().positive() });
16
+ export const projectsResultSchema = z.strictObject({ items: z.array(projectSummarySchema) });
15
17
  export const historyResultSchema = z.strictObject({ items: z.array(revisionSchema).max(10), nextBeforeSequence: z.number().int().positive().nullable() });
16
18
  export const summaryHistoryResultSchema = historyResultSchema.extend({ items: z.array(revisionSummarySchema).max(10) });
17
19
  export const httpErrorSchema = z.strictObject({ error: z.strictObject({ code: z.string().min(1), message: z.string(), details: z.record(z.string(), z.unknown()) }) });
@@ -24,6 +26,19 @@ export function canonicalResource(value) {
24
26
  throw new TypeError('Use the exact canonical resource URL without a trailing slash');
25
27
  return value;
26
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
+ });
27
42
  export function protectedResourceMetadataUrl(resource) {
28
43
  const url = new URL(canonicalResource(resource));
29
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,6 +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;
61
+ export declare const projectNameSchema: z.ZodString;
62
+ export declare const slugSchema: z.ZodString;
26
63
  export declare const createSchema: z.ZodObject<{
27
64
  requestId: z.ZodString;
28
65
  snapshot: z.ZodObject<{
@@ -36,6 +73,7 @@ export declare const createSchema: z.ZodObject<{
36
73
  text: "text";
37
74
  html: "html";
38
75
  markdown: "markdown";
76
+ design: "design";
39
77
  }>;
40
78
  text: z.ZodString;
41
79
  }, z.core.$strict>;
@@ -49,12 +87,14 @@ export declare const createSchema: z.ZodObject<{
49
87
  data: z.ZodString;
50
88
  }, z.core.$strict>>;
51
89
  }, z.core.$strict>;
90
+ project: z.ZodOptional<z.ZodString>;
91
+ slug: z.ZodOptional<z.ZodString>;
52
92
  response: z.ZodOptional<z.ZodEnum<{
53
93
  full: "full";
54
94
  compact: "compact";
55
95
  }>>;
56
96
  }, z.core.$strict>;
57
- export declare const readSchema: z.ZodObject<{
97
+ export declare const readByIdSchema: z.ZodObject<{
58
98
  artifactId: z.ZodString;
59
99
  revisionId: z.ZodOptional<z.ZodString>;
60
100
  response: z.ZodOptional<z.ZodEnum<{
@@ -62,6 +102,16 @@ export declare const readSchema: z.ZodObject<{
62
102
  compact: "compact";
63
103
  }>>;
64
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>;
65
115
  export declare const updateSchema: z.ZodObject<{
66
116
  requestId: z.ZodString;
67
117
  artifactId: z.ZodString;
@@ -77,6 +127,7 @@ export declare const updateSchema: z.ZodObject<{
77
127
  text: "text";
78
128
  html: "html";
79
129
  markdown: "markdown";
130
+ design: "design";
80
131
  }>;
81
132
  text: z.ZodString;
82
133
  }, z.core.$strict>;
@@ -101,6 +152,85 @@ export declare const publishSchema: z.ZodObject<{
101
152
  revisionId: z.ZodString;
102
153
  expectedPublicationVersion: z.ZodNumber;
103
154
  }, z.core.$strict>;
155
+ export declare const deleteSchema: z.ZodObject<{
156
+ requestId: z.ZodString;
157
+ artifactId: z.ZodString;
158
+ expectedRevisionId: z.ZodString;
159
+ expectedPublicationVersion: z.ZodNumber;
160
+ }, z.core.$strict>;
161
+ export declare const deleteResultSchema: z.ZodObject<{
162
+ artifactId: z.ZodString;
163
+ }, z.core.$strict>;
164
+ export type DeleteInput = z.infer<typeof deleteSchema>;
165
+ export type DeleteResult = z.infer<typeof deleteResultSchema>;
166
+ export declare const moveSchema: z.ZodObject<{
167
+ requestId: z.ZodString;
168
+ artifactId: z.ZodString;
169
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
170
+ slug: z.ZodOptional<z.ZodNullable<z.ZodString>>;
171
+ }, z.core.$strict>;
172
+ export declare const moveResultSchema: z.ZodObject<{
173
+ artifactId: z.ZodString;
174
+ project: z.ZodNullable<z.ZodString>;
175
+ }, z.core.$strict>;
176
+ export type MoveResult = z.infer<typeof moveResultSchema>;
177
+ export declare const shareSchema: z.ZodObject<{
178
+ requestId: z.ZodString;
179
+ artifactId: z.ZodString;
180
+ email: z.ZodPipe<z.ZodString, z.ZodEmail>;
181
+ notify: z.ZodOptional<z.ZodBoolean>;
182
+ requireNew: z.ZodOptional<z.ZodLiteral<true>>;
183
+ }, z.core.$strict>;
184
+ export declare const unshareSchema: z.ZodObject<{
185
+ requestId: z.ZodString;
186
+ artifactId: z.ZodString;
187
+ shareId: z.ZodString;
188
+ }, z.core.$strict>;
189
+ export declare const listSharesSchema: z.ZodObject<{
190
+ artifactId: z.ZodString;
191
+ }, z.core.$strict>;
192
+ export declare const shareResultSchema: z.ZodObject<{
193
+ shareId: z.ZodString;
194
+ artifactId: z.ZodString;
195
+ kind: z.ZodLiteral<"email">;
196
+ value: z.ZodEmail;
197
+ createdAt: z.ZodISODateTime;
198
+ }, z.core.$strict>;
199
+ export declare const notificationSchema: z.ZodUnion<readonly [z.ZodObject<{
200
+ status: z.ZodLiteral<"requested">;
201
+ }, z.core.$strict>, z.ZodObject<{
202
+ status: z.ZodLiteral<"skipped">;
203
+ reason: z.ZodLiteral<"disabled">;
204
+ }, z.core.$strict>, z.ZodObject<{
205
+ status: z.ZodLiteral<"skipped">;
206
+ reason: z.ZodLiteral<"rate_limited">;
207
+ retryAt: z.ZodISODateTime;
208
+ }, z.core.$strict>]>;
209
+ export declare const shareCommandResultSchema: z.ZodObject<{
210
+ shareId: z.ZodString;
211
+ artifactId: z.ZodString;
212
+ kind: z.ZodLiteral<"email">;
213
+ value: z.ZodEmail;
214
+ createdAt: z.ZodISODateTime;
215
+ notification: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
216
+ status: z.ZodLiteral<"requested">;
217
+ }, z.core.$strict>, z.ZodObject<{
218
+ status: z.ZodLiteral<"skipped">;
219
+ reason: z.ZodLiteral<"disabled">;
220
+ }, z.core.$strict>, z.ZodObject<{
221
+ status: z.ZodLiteral<"skipped">;
222
+ reason: z.ZodLiteral<"rate_limited">;
223
+ retryAt: z.ZodISODateTime;
224
+ }, z.core.$strict>]>>;
225
+ }, z.core.$strict>;
226
+ export declare const unshareResultSchema: z.ZodObject<{
227
+ artifactId: z.ZodString;
228
+ shareId: z.ZodString;
229
+ }, z.core.$strict>;
230
+ export type Share = z.infer<typeof shareResultSchema>;
231
+ export type ShareResult = z.infer<typeof shareCommandResultSchema>;
232
+ export type Notification = z.infer<typeof notificationSchema>;
233
+ export type UnshareResult = z.infer<typeof unshareResultSchema>;
104
234
  export declare const exportSchema: z.ZodObject<{
105
235
  artifactId: z.ZodString;
106
236
  revisionId: z.ZodString;
@@ -108,7 +238,35 @@ export declare const exportSchema: z.ZodObject<{
108
238
  export declare const listSchema: z.ZodObject<{
109
239
  limit: z.ZodDefault<z.ZodNumber>;
110
240
  cursor: z.ZodOptional<z.ZodString>;
241
+ project: z.ZodOptional<z.ZodString>;
242
+ }, z.core.$strict>;
243
+ export declare const searchSchema: z.ZodObject<{
244
+ query: z.ZodString;
245
+ project: z.ZodOptional<z.ZodString>;
246
+ limit: z.ZodDefault<z.ZodNumber>;
247
+ cursor: z.ZodOptional<z.ZodString>;
111
248
  }, z.core.$strict>;
249
+ export declare const searchHitSchema: z.ZodObject<{
250
+ artifactId: z.ZodString;
251
+ project: z.ZodNullable<z.ZodString>;
252
+ slug: z.ZodNullable<z.ZodString>;
253
+ title: z.ZodString;
254
+ snippet: z.ZodString;
255
+ rank: z.ZodNumber;
256
+ }, z.core.$strict>;
257
+ export declare const searchResultSchema: z.ZodObject<{
258
+ items: z.ZodArray<z.ZodObject<{
259
+ artifactId: z.ZodString;
260
+ project: z.ZodNullable<z.ZodString>;
261
+ slug: z.ZodNullable<z.ZodString>;
262
+ title: z.ZodString;
263
+ snippet: z.ZodString;
264
+ rank: z.ZodNumber;
265
+ }, z.core.$strict>>;
266
+ nextCursor: z.ZodNullable<z.ZodString>;
267
+ }, z.core.$strict>;
268
+ export type SearchHit = z.infer<typeof searchHitSchema>;
269
+ export type SearchResult = z.infer<typeof searchResultSchema>;
112
270
  export declare const libraryQuerySchema: z.ZodObject<{
113
271
  q: z.ZodDefault<z.ZodString>;
114
272
  status: z.ZodDefault<z.ZodEnum<{
@@ -117,6 +275,7 @@ export declare const libraryQuerySchema: z.ZodObject<{
117
275
  unpublished: "unpublished";
118
276
  pending: "pending";
119
277
  }>>;
278
+ project: z.ZodOptional<z.ZodString>;
120
279
  cursor: z.ZodOptional<z.ZodString>;
121
280
  }, z.core.$strict>;
122
281
  export declare const historySchema: z.ZodObject<{
@@ -172,13 +331,26 @@ export type Viewer = z.infer<typeof viewerSchema>;
172
331
  export type Publication = z.infer<typeof publicationSchema>;
173
332
  export type Revision = z.infer<typeof revisionSchema>;
174
333
  export type ArtifactRead = z.infer<typeof artifactReadSchema>;
334
+ export type SharedRead = z.infer<typeof sharedReadSchema>;
335
+ export type SharedArtifactSummary = Readonly<{
336
+ artifactId: string;
337
+ title: string;
338
+ ownerName: string;
339
+ url: string;
340
+ }>;
175
341
  export type ArtifactSummary = {
176
342
  artifactId: string;
177
343
  title: string;
178
344
  headRevisionId: string;
179
345
  createdAt: string;
346
+ project: string | null;
347
+ slug: string | null;
180
348
  publication: Publication | null;
181
349
  };
350
+ export type ProjectSummary = {
351
+ name: string;
352
+ artifactCount: number;
353
+ };
182
354
  export type ArtifactLibraryQuery = z.infer<typeof libraryQuerySchema>;
183
355
  export type ArtifactLibraryPage = {
184
356
  items: ArtifactSummary[];
@@ -196,7 +368,7 @@ export type Archive = {
196
368
  base64: string;
197
369
  sha256: string;
198
370
  };
199
- export type ArtifactErrorCode = 'forbidden' | 'not_found' | 'conflict' | 'request_conflict' | 'invalid_grant';
371
+ export type ArtifactErrorCode = 'forbidden' | 'not_found' | 'conflict' | 'request_conflict' | 'slug_conflict' | 'invalid_grant';
200
372
  export declare class ArtifactError extends Error {
201
373
  readonly code: ArtifactErrorCode;
202
374
  readonly details: Record<string, unknown>;
@@ -230,6 +402,7 @@ export declare const revisionSchema: z.ZodObject<{
230
402
  text: "text";
231
403
  html: "html";
232
404
  markdown: "markdown";
405
+ design: "design";
233
406
  }>;
234
407
  text: z.ZodString;
235
408
  }, z.core.$strict>;
@@ -248,6 +421,8 @@ export declare const artifactReadSchema: z.ZodObject<{
248
421
  artifactId: z.ZodString;
249
422
  ownerId: z.ZodString;
250
423
  headRevisionId: z.ZodString;
424
+ project: z.ZodNullable<z.ZodString>;
425
+ slug: z.ZodNullable<z.ZodString>;
251
426
  publication: z.ZodNullable<z.ZodObject<{
252
427
  revisionId: z.ZodString;
253
428
  version: z.ZodNumber;
@@ -272,6 +447,46 @@ export declare const artifactReadSchema: z.ZodObject<{
272
447
  text: "text";
273
448
  html: "html";
274
449
  markdown: "markdown";
450
+ design: "design";
451
+ }>;
452
+ text: z.ZodString;
453
+ }, z.core.$strict>;
454
+ files: z.ZodArray<z.ZodObject<{
455
+ path: z.ZodString;
456
+ mediaType: z.ZodString;
457
+ encoding: z.ZodEnum<{
458
+ utf8: "utf8";
459
+ base64: "base64";
460
+ }>;
461
+ data: z.ZodString;
462
+ }, z.core.$strict>>;
463
+ }, z.core.$strict>;
464
+ }, z.core.$strict>;
465
+ }, z.core.$strict>;
466
+ export declare const sharedReadSchema: z.ZodObject<{
467
+ artifactId: z.ZodString;
468
+ ownerName: z.ZodDefault<z.ZodString>;
469
+ publication: z.ZodObject<{
470
+ revisionId: z.ZodString;
471
+ version: z.ZodNumber;
472
+ url: z.ZodURL;
473
+ }, z.core.$strict>;
474
+ revision: z.ZodObject<{
475
+ id: z.ZodString;
476
+ createdAt: z.ZodISODateTime;
477
+ sha256: z.ZodString;
478
+ snapshot: z.ZodObject<{
479
+ metadata: z.ZodObject<{
480
+ title: z.ZodString;
481
+ description: z.ZodString;
482
+ custom: z.ZodOptional<z.ZodJSONSchema>;
483
+ }, z.core.$strict>;
484
+ content: z.ZodObject<{
485
+ kind: z.ZodEnum<{
486
+ text: "text";
487
+ html: "html";
488
+ markdown: "markdown";
489
+ design: "design";
275
490
  }>;
276
491
  text: z.ZodString;
277
492
  }, 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,17 +76,92 @@ 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
+ }
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');
25
124
  const response = z.enum(['full', 'compact']).optional();
26
- export const createSchema = z.strictObject({ requestId, snapshot: snapshotSchema, response });
27
- 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');
28
133
  export const updateSchema = z.strictObject({ requestId, artifactId: identifier, expectedRevisionId: identifier, snapshot: snapshotSchema, response });
29
134
  export const publishSchema = z.strictObject({ requestId, artifactId: identifier, revisionId: identifier, expectedPublicationVersion: z.number().int().nonnegative() });
135
+ export const deleteSchema = z.strictObject({ requestId, artifactId: identifier, expectedRevisionId: identifier, expectedPublicationVersion: z.number().int().nonnegative() });
136
+ export const deleteResultSchema = z.strictObject({ artifactId: identifier });
137
+ export const moveSchema = z.strictObject({ requestId, artifactId: identifier, project: projectNameSchema.nullable().optional(), slug: slugSchema.nullable().optional() })
138
+ .refine((input) => input.project !== undefined || input.slug !== undefined, 'Specify project, slug, or both; null clears either');
139
+ export const moveResultSchema = z.strictObject({ artifactId: identifier, project: z.string().nullable() });
140
+ 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() });
141
+ export const unshareSchema = z.strictObject({ requestId, artifactId: identifier, shareId: identifier });
142
+ export const listSharesSchema = z.strictObject({ artifactId: identifier });
143
+ export const shareResultSchema = z.strictObject({ shareId: identifier, artifactId: identifier, kind: z.literal('email'), value: z.email(), createdAt: z.iso.datetime() });
144
+ export const notificationSchema = z.union([
145
+ z.strictObject({ status: z.literal('requested') }),
146
+ z.strictObject({ status: z.literal('skipped'), reason: z.literal('disabled') }),
147
+ z.strictObject({ status: z.literal('skipped'), reason: z.literal('rate_limited'), retryAt: z.iso.datetime() }),
148
+ ]);
149
+ export const shareCommandResultSchema = shareResultSchema.extend({ notification: notificationSchema.optional() });
150
+ export const unshareResultSchema = z.strictObject({ artifactId: identifier, shareId: identifier });
30
151
  export const exportSchema = z.strictObject({ artifactId: identifier, revisionId: identifier });
31
- export const listSchema = z.strictObject({ limit: z.number().int().min(1).max(100).default(25), cursor: identifier.optional() });
152
+ export const listSchema = z.strictObject({ limit: z.number().int().min(1).max(100).default(25), cursor: identifier.optional(), project: projectNameSchema.optional() });
153
+ export const searchSchema = z.strictObject({
154
+ query: z.string().trim().min(1).max(256),
155
+ project: projectNameSchema.optional(),
156
+ limit: z.number().int().min(1).max(25).default(10),
157
+ cursor: z.string().regex(/^\d{1,6}$/, 'Use the nextCursor from a previous search').optional(),
158
+ });
159
+ export const searchHitSchema = z.strictObject({ artifactId: identifier, project: z.string().nullable(), slug: z.string().nullable(), title: z.string(), snippet: z.string(), rank: z.number() });
160
+ export const searchResultSchema = z.strictObject({ items: z.array(searchHitSchema).max(25), nextCursor: z.string().nullable() });
32
161
  export const libraryQuerySchema = z.strictObject({
33
162
  q: z.string().trim().max(256).default(''),
34
163
  status: z.enum(['all', 'published', 'unpublished', 'pending']).default('all'),
164
+ project: projectNameSchema.optional(),
35
165
  cursor: identifier.optional(),
36
166
  });
37
167
  export const historySchema = z.strictObject({ artifactId: identifier, limit: z.number().int().min(1).max(10).default(5), beforeSequence: z.number().int().positive().optional(), response: z.enum(['full', 'summary']).optional() });
@@ -52,5 +182,6 @@ export function decodeFile(file) {
52
182
  }
53
183
  export const publicationSchema = z.strictObject({ revisionId: identifier, version: z.number().int().positive(), url: z.url() });
54
184
  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 });
55
- export const artifactReadSchema = z.strictObject({ artifactId: identifier, ownerId: identifier, headRevisionId: identifier, publication: publicationSchema.nullable(), revision: revisionSchema });
185
+ export const artifactReadSchema = z.strictObject({ artifactId: identifier, ownerId: identifier, headRevisionId: identifier, project: z.string().nullable(), slug: z.string().nullable(), publication: publicationSchema.nullable(), revision: revisionSchema });
186
+ 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 }) });
56
187
  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(),