@yargote/artifacts-contracts 0.0.0-stage → 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +12 -2
- package/package.json +37 -4
- package/src/http.d.ts +195 -0
- package/src/http.js +33 -0
- package/src/index.d.ts +295 -0
- package/src/index.js +56 -0
- package/src/serialization.d.ts +2 -0
- package/src/serialization.js +11 -0
- package/src/workflow.d.ts +75 -0
- package/src/workflow.js +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yerson Argote
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Artifacts contracts
|
|
2
2
|
|
|
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
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @yargote/artifacts-contracts@0.1.0-beta.1
|
|
7
|
+
```
|
|
8
|
+
|
|
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
|
+
|
|
11
|
+
The existing `@artifacts/artifacts` root, `/contracts`, and `/workflow` exports re-export these implementations. Store and REST packages share one `ArtifactError` runtime.
|
|
12
|
+
|
|
13
|
+
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,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yargote/artifacts-contracts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Shared schemas and contracts for Artifacts clients.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/yersonargotev/artifacts.git",
|
|
11
|
+
"directory": "packages/contracts"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/yersonargotev",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/yersonargotev"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=24"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/index.js",
|
|
22
|
+
"./workflow": "./src/workflow.js",
|
|
23
|
+
"./serialization": "./src/serialization.js",
|
|
24
|
+
"./http": "./src/http.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"zod": "4.6.5"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"tag": "beta",
|
|
37
|
+
"provenance": false
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/http.d.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const identitySchema: z.ZodObject<{
|
|
3
|
+
userId: z.ZodString;
|
|
4
|
+
sessionId: z.ZodString;
|
|
5
|
+
clientId: z.ZodString;
|
|
6
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export type Identity = z.infer<typeof identitySchema>;
|
|
9
|
+
export declare const attachmentSummarySchema: z.ZodObject<{
|
|
10
|
+
path: z.ZodString;
|
|
11
|
+
mediaType: z.ZodString;
|
|
12
|
+
encoding: z.ZodEnum<{
|
|
13
|
+
utf8: "utf8";
|
|
14
|
+
base64: "base64";
|
|
15
|
+
}>;
|
|
16
|
+
decodedBytes: z.ZodNumber;
|
|
17
|
+
sha256: z.ZodString;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export declare const revisionSummarySchema: z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
createdAt: z.ZodISODateTime;
|
|
22
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
23
|
+
sequence: z.ZodNumber;
|
|
24
|
+
authorUserId: z.ZodString;
|
|
25
|
+
authorClientId: z.ZodString;
|
|
26
|
+
sha256: z.ZodString;
|
|
27
|
+
metadata: z.ZodObject<{
|
|
28
|
+
description: z.ZodString;
|
|
29
|
+
title: z.ZodString;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
content: z.ZodObject<{
|
|
32
|
+
kind: z.ZodEnum<{
|
|
33
|
+
text: "text";
|
|
34
|
+
html: "html";
|
|
35
|
+
markdown: "markdown";
|
|
36
|
+
}>;
|
|
37
|
+
utf8Bytes: z.ZodNumber;
|
|
38
|
+
}, z.core.$strict>;
|
|
39
|
+
files: z.ZodArray<z.ZodObject<{
|
|
40
|
+
path: z.ZodString;
|
|
41
|
+
mediaType: z.ZodString;
|
|
42
|
+
encoding: z.ZodEnum<{
|
|
43
|
+
utf8: "utf8";
|
|
44
|
+
base64: "base64";
|
|
45
|
+
}>;
|
|
46
|
+
decodedBytes: z.ZodNumber;
|
|
47
|
+
sha256: z.ZodString;
|
|
48
|
+
}, z.core.$strict>>;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
export declare const compactArtifactReadSchema: z.ZodObject<{
|
|
51
|
+
ownerId: z.ZodString;
|
|
52
|
+
headRevisionId: z.ZodString;
|
|
53
|
+
artifactId: z.ZodString;
|
|
54
|
+
publication: z.ZodNullable<z.ZodObject<{
|
|
55
|
+
revisionId: z.ZodString;
|
|
56
|
+
version: z.ZodNumber;
|
|
57
|
+
url: z.ZodURL;
|
|
58
|
+
}, z.core.$strict>>;
|
|
59
|
+
revision: z.ZodObject<{
|
|
60
|
+
id: z.ZodString;
|
|
61
|
+
createdAt: z.ZodISODateTime;
|
|
62
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
63
|
+
sequence: z.ZodNumber;
|
|
64
|
+
authorUserId: z.ZodString;
|
|
65
|
+
authorClientId: z.ZodString;
|
|
66
|
+
sha256: z.ZodString;
|
|
67
|
+
metadata: z.ZodObject<{
|
|
68
|
+
description: z.ZodString;
|
|
69
|
+
title: z.ZodString;
|
|
70
|
+
}, z.core.$strict>;
|
|
71
|
+
content: z.ZodObject<{
|
|
72
|
+
kind: z.ZodEnum<{
|
|
73
|
+
text: "text";
|
|
74
|
+
html: "html";
|
|
75
|
+
markdown: "markdown";
|
|
76
|
+
}>;
|
|
77
|
+
utf8Bytes: z.ZodNumber;
|
|
78
|
+
}, z.core.$strict>;
|
|
79
|
+
files: z.ZodArray<z.ZodObject<{
|
|
80
|
+
path: z.ZodString;
|
|
81
|
+
mediaType: z.ZodString;
|
|
82
|
+
encoding: z.ZodEnum<{
|
|
83
|
+
utf8: "utf8";
|
|
84
|
+
base64: "base64";
|
|
85
|
+
}>;
|
|
86
|
+
decodedBytes: z.ZodNumber;
|
|
87
|
+
sha256: z.ZodString;
|
|
88
|
+
}, z.core.$strict>>;
|
|
89
|
+
}, z.core.$strict>;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
export declare const artifactSummarySchema: z.ZodObject<{
|
|
92
|
+
artifactId: z.ZodString;
|
|
93
|
+
title: z.ZodString;
|
|
94
|
+
headRevisionId: z.ZodString;
|
|
95
|
+
createdAt: z.ZodISODateTime;
|
|
96
|
+
publication: z.ZodNullable<z.ZodObject<{
|
|
97
|
+
revisionId: z.ZodString;
|
|
98
|
+
version: z.ZodNumber;
|
|
99
|
+
url: z.ZodURL;
|
|
100
|
+
}, z.core.$strict>>;
|
|
101
|
+
}, z.core.$strict>;
|
|
102
|
+
export declare const listResultSchema: z.ZodObject<{
|
|
103
|
+
items: z.ZodArray<z.ZodObject<{
|
|
104
|
+
artifactId: z.ZodString;
|
|
105
|
+
title: z.ZodString;
|
|
106
|
+
headRevisionId: z.ZodString;
|
|
107
|
+
createdAt: z.ZodISODateTime;
|
|
108
|
+
publication: z.ZodNullable<z.ZodObject<{
|
|
109
|
+
revisionId: z.ZodString;
|
|
110
|
+
version: z.ZodNumber;
|
|
111
|
+
url: z.ZodURL;
|
|
112
|
+
}, z.core.$strict>>;
|
|
113
|
+
}, z.core.$strict>>;
|
|
114
|
+
nextCursor: z.ZodNullable<z.ZodString>;
|
|
115
|
+
}, z.core.$strict>;
|
|
116
|
+
export declare const historyResultSchema: z.ZodObject<{
|
|
117
|
+
items: z.ZodArray<z.ZodObject<{
|
|
118
|
+
id: z.ZodString;
|
|
119
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
120
|
+
sequence: z.ZodNumber;
|
|
121
|
+
authorUserId: z.ZodString;
|
|
122
|
+
authorClientId: z.ZodString;
|
|
123
|
+
createdAt: z.ZodISODateTime;
|
|
124
|
+
sha256: z.ZodString;
|
|
125
|
+
snapshot: z.ZodObject<{
|
|
126
|
+
metadata: z.ZodObject<{
|
|
127
|
+
title: z.ZodString;
|
|
128
|
+
description: z.ZodString;
|
|
129
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
130
|
+
}, z.core.$strict>;
|
|
131
|
+
content: z.ZodObject<{
|
|
132
|
+
kind: z.ZodEnum<{
|
|
133
|
+
text: "text";
|
|
134
|
+
html: "html";
|
|
135
|
+
markdown: "markdown";
|
|
136
|
+
}>;
|
|
137
|
+
text: z.ZodString;
|
|
138
|
+
}, z.core.$strict>;
|
|
139
|
+
files: z.ZodArray<z.ZodObject<{
|
|
140
|
+
path: z.ZodString;
|
|
141
|
+
mediaType: z.ZodString;
|
|
142
|
+
encoding: z.ZodEnum<{
|
|
143
|
+
utf8: "utf8";
|
|
144
|
+
base64: "base64";
|
|
145
|
+
}>;
|
|
146
|
+
data: z.ZodString;
|
|
147
|
+
}, z.core.$strict>>;
|
|
148
|
+
}, z.core.$strict>;
|
|
149
|
+
}, z.core.$strict>>;
|
|
150
|
+
nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
|
|
151
|
+
}, z.core.$strict>;
|
|
152
|
+
export declare const summaryHistoryResultSchema: z.ZodObject<{
|
|
153
|
+
nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
|
|
154
|
+
items: z.ZodArray<z.ZodObject<{
|
|
155
|
+
id: z.ZodString;
|
|
156
|
+
createdAt: z.ZodISODateTime;
|
|
157
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
158
|
+
sequence: z.ZodNumber;
|
|
159
|
+
authorUserId: z.ZodString;
|
|
160
|
+
authorClientId: z.ZodString;
|
|
161
|
+
sha256: z.ZodString;
|
|
162
|
+
metadata: z.ZodObject<{
|
|
163
|
+
description: z.ZodString;
|
|
164
|
+
title: z.ZodString;
|
|
165
|
+
}, z.core.$strict>;
|
|
166
|
+
content: z.ZodObject<{
|
|
167
|
+
kind: z.ZodEnum<{
|
|
168
|
+
text: "text";
|
|
169
|
+
html: "html";
|
|
170
|
+
markdown: "markdown";
|
|
171
|
+
}>;
|
|
172
|
+
utf8Bytes: z.ZodNumber;
|
|
173
|
+
}, z.core.$strict>;
|
|
174
|
+
files: z.ZodArray<z.ZodObject<{
|
|
175
|
+
path: z.ZodString;
|
|
176
|
+
mediaType: z.ZodString;
|
|
177
|
+
encoding: z.ZodEnum<{
|
|
178
|
+
utf8: "utf8";
|
|
179
|
+
base64: "base64";
|
|
180
|
+
}>;
|
|
181
|
+
decodedBytes: z.ZodNumber;
|
|
182
|
+
sha256: z.ZodString;
|
|
183
|
+
}, z.core.$strict>>;
|
|
184
|
+
}, z.core.$strict>>;
|
|
185
|
+
}, z.core.$strict>;
|
|
186
|
+
export declare const httpErrorSchema: z.ZodObject<{
|
|
187
|
+
error: z.ZodObject<{
|
|
188
|
+
code: z.ZodString;
|
|
189
|
+
message: z.ZodString;
|
|
190
|
+
details: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
191
|
+
}, z.core.$strict>;
|
|
192
|
+
}, z.core.$strict>;
|
|
193
|
+
export declare function canonicalResource(value: string): string;
|
|
194
|
+
export declare function protectedResourceMetadataUrl(resource: string): URL;
|
|
195
|
+
export declare function restBase(resource: string): string;
|
package/src/http.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { artifactReadSchema, publicationSchema, revisionSchema, snapshotSchema } from "./index.js";
|
|
3
|
+
const identifier = z.string().min(1).max(128);
|
|
4
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
|
+
export const identitySchema = z.strictObject({ userId: identifier, sessionId: identifier, clientId: identifier, scopes: z.array(z.string()) });
|
|
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
|
+
export const revisionSummarySchema = revisionSchema.omit({ snapshot: true }).extend({
|
|
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() }),
|
|
10
|
+
files: z.array(attachmentSummarySchema).max(32),
|
|
11
|
+
});
|
|
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() });
|
|
14
|
+
export const listResultSchema = z.strictObject({ items: z.array(artifactSummarySchema).max(100), nextCursor: identifier.nullable() });
|
|
15
|
+
export const historyResultSchema = z.strictObject({ items: z.array(revisionSchema).max(10), nextBeforeSequence: z.number().int().positive().nullable() });
|
|
16
|
+
export const summaryHistoryResultSchema = historyResultSchema.extend({ items: z.array(revisionSummarySchema).max(10) });
|
|
17
|
+
export const httpErrorSchema = z.strictObject({ error: z.strictObject({ code: z.string().min(1), message: z.string(), details: z.record(z.string(), z.unknown()) }) });
|
|
18
|
+
export function canonicalResource(value) {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
21
|
+
if (url.username || url.password || url.search || url.hash || (url.protocol !== 'https:' && !(loopback && url.protocol === 'http:')))
|
|
22
|
+
throw new TypeError('The server requires HTTPS, or HTTP on an exact loopback host, without credentials, query, or fragment');
|
|
23
|
+
if ((url.href !== value && url.href.replace(/\/$/, '') !== value) || (url.pathname !== '/' && url.pathname.endsWith('/')))
|
|
24
|
+
throw new TypeError('Use the exact canonical resource URL without a trailing slash');
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
export function protectedResourceMetadataUrl(resource) {
|
|
28
|
+
const url = new URL(canonicalResource(resource));
|
|
29
|
+
return new URL(`/.well-known/oauth-protected-resource${url.pathname === '/' ? '' : url.pathname}`, url.origin);
|
|
30
|
+
}
|
|
31
|
+
export function restBase(resource) {
|
|
32
|
+
return `${canonicalResource(resource).replace(/\/$/, '')}/v1`;
|
|
33
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const snapshotSchema: z.ZodObject<{
|
|
3
|
+
metadata: z.ZodObject<{
|
|
4
|
+
title: z.ZodString;
|
|
5
|
+
description: z.ZodString;
|
|
6
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
content: z.ZodObject<{
|
|
9
|
+
kind: z.ZodEnum<{
|
|
10
|
+
text: "text";
|
|
11
|
+
html: "html";
|
|
12
|
+
markdown: "markdown";
|
|
13
|
+
}>;
|
|
14
|
+
text: z.ZodString;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
files: z.ZodArray<z.ZodObject<{
|
|
17
|
+
path: z.ZodString;
|
|
18
|
+
mediaType: z.ZodString;
|
|
19
|
+
encoding: z.ZodEnum<{
|
|
20
|
+
utf8: "utf8";
|
|
21
|
+
base64: "base64";
|
|
22
|
+
}>;
|
|
23
|
+
data: z.ZodString;
|
|
24
|
+
}, z.core.$strict>>;
|
|
25
|
+
}, z.core.$strict>;
|
|
26
|
+
export declare const createSchema: z.ZodObject<{
|
|
27
|
+
requestId: z.ZodString;
|
|
28
|
+
snapshot: z.ZodObject<{
|
|
29
|
+
metadata: z.ZodObject<{
|
|
30
|
+
title: z.ZodString;
|
|
31
|
+
description: z.ZodString;
|
|
32
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
content: z.ZodObject<{
|
|
35
|
+
kind: z.ZodEnum<{
|
|
36
|
+
text: "text";
|
|
37
|
+
html: "html";
|
|
38
|
+
markdown: "markdown";
|
|
39
|
+
}>;
|
|
40
|
+
text: z.ZodString;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
files: z.ZodArray<z.ZodObject<{
|
|
43
|
+
path: z.ZodString;
|
|
44
|
+
mediaType: z.ZodString;
|
|
45
|
+
encoding: z.ZodEnum<{
|
|
46
|
+
utf8: "utf8";
|
|
47
|
+
base64: "base64";
|
|
48
|
+
}>;
|
|
49
|
+
data: z.ZodString;
|
|
50
|
+
}, z.core.$strict>>;
|
|
51
|
+
}, z.core.$strict>;
|
|
52
|
+
response: z.ZodOptional<z.ZodEnum<{
|
|
53
|
+
full: "full";
|
|
54
|
+
compact: "compact";
|
|
55
|
+
}>>;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
export declare const readSchema: z.ZodObject<{
|
|
58
|
+
artifactId: z.ZodString;
|
|
59
|
+
revisionId: z.ZodOptional<z.ZodString>;
|
|
60
|
+
response: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
full: "full";
|
|
62
|
+
compact: "compact";
|
|
63
|
+
}>>;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
export declare const updateSchema: z.ZodObject<{
|
|
66
|
+
requestId: z.ZodString;
|
|
67
|
+
artifactId: z.ZodString;
|
|
68
|
+
expectedRevisionId: z.ZodString;
|
|
69
|
+
snapshot: z.ZodObject<{
|
|
70
|
+
metadata: z.ZodObject<{
|
|
71
|
+
title: z.ZodString;
|
|
72
|
+
description: z.ZodString;
|
|
73
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
74
|
+
}, z.core.$strict>;
|
|
75
|
+
content: z.ZodObject<{
|
|
76
|
+
kind: z.ZodEnum<{
|
|
77
|
+
text: "text";
|
|
78
|
+
html: "html";
|
|
79
|
+
markdown: "markdown";
|
|
80
|
+
}>;
|
|
81
|
+
text: z.ZodString;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
files: z.ZodArray<z.ZodObject<{
|
|
84
|
+
path: z.ZodString;
|
|
85
|
+
mediaType: z.ZodString;
|
|
86
|
+
encoding: z.ZodEnum<{
|
|
87
|
+
utf8: "utf8";
|
|
88
|
+
base64: "base64";
|
|
89
|
+
}>;
|
|
90
|
+
data: z.ZodString;
|
|
91
|
+
}, z.core.$strict>>;
|
|
92
|
+
}, z.core.$strict>;
|
|
93
|
+
response: z.ZodOptional<z.ZodEnum<{
|
|
94
|
+
full: "full";
|
|
95
|
+
compact: "compact";
|
|
96
|
+
}>>;
|
|
97
|
+
}, z.core.$strict>;
|
|
98
|
+
export declare const publishSchema: z.ZodObject<{
|
|
99
|
+
requestId: z.ZodString;
|
|
100
|
+
artifactId: z.ZodString;
|
|
101
|
+
revisionId: z.ZodString;
|
|
102
|
+
expectedPublicationVersion: z.ZodNumber;
|
|
103
|
+
}, z.core.$strict>;
|
|
104
|
+
export declare const exportSchema: z.ZodObject<{
|
|
105
|
+
artifactId: z.ZodString;
|
|
106
|
+
revisionId: z.ZodString;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
export declare const listSchema: z.ZodObject<{
|
|
109
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
110
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
111
|
+
}, z.core.$strict>;
|
|
112
|
+
export declare const libraryQuerySchema: z.ZodObject<{
|
|
113
|
+
q: z.ZodDefault<z.ZodString>;
|
|
114
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
115
|
+
all: "all";
|
|
116
|
+
published: "published";
|
|
117
|
+
unpublished: "unpublished";
|
|
118
|
+
pending: "pending";
|
|
119
|
+
}>>;
|
|
120
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strict>;
|
|
122
|
+
export declare const historySchema: z.ZodObject<{
|
|
123
|
+
artifactId: z.ZodString;
|
|
124
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
125
|
+
beforeSequence: z.ZodOptional<z.ZodNumber>;
|
|
126
|
+
response: z.ZodOptional<z.ZodEnum<{
|
|
127
|
+
full: "full";
|
|
128
|
+
summary: "summary";
|
|
129
|
+
}>>;
|
|
130
|
+
}, z.core.$strict>;
|
|
131
|
+
export declare const issueGrantSchema: z.ZodObject<{
|
|
132
|
+
artifactId: z.ZodString;
|
|
133
|
+
state: z.ZodString;
|
|
134
|
+
}, z.core.$strict>;
|
|
135
|
+
export declare const consumeGrantSchema: z.ZodObject<{
|
|
136
|
+
artifactId: z.ZodString;
|
|
137
|
+
state: z.ZodString;
|
|
138
|
+
code: z.ZodString;
|
|
139
|
+
}, z.core.$strict>;
|
|
140
|
+
export declare const viewerSchema: z.ZodObject<{
|
|
141
|
+
artifactId: z.ZodString;
|
|
142
|
+
userId: z.ZodString;
|
|
143
|
+
sessionId: z.ZodString;
|
|
144
|
+
expiresAt: z.ZodNumber;
|
|
145
|
+
}, z.core.$strict>;
|
|
146
|
+
export type Snapshot = z.infer<typeof snapshotSchema>;
|
|
147
|
+
export type ReadonlyJsonValue = string | number | boolean | null | {
|
|
148
|
+
readonly [key: string]: ReadonlyJsonValue;
|
|
149
|
+
} | readonly ReadonlyJsonValue[];
|
|
150
|
+
export type UpdateInput = {
|
|
151
|
+
readonly requestId: string;
|
|
152
|
+
readonly artifactId: string;
|
|
153
|
+
readonly expectedRevisionId: string;
|
|
154
|
+
readonly snapshot: {
|
|
155
|
+
readonly metadata: {
|
|
156
|
+
readonly title: string;
|
|
157
|
+
readonly description: string;
|
|
158
|
+
readonly custom?: ReadonlyJsonValue;
|
|
159
|
+
};
|
|
160
|
+
readonly content: Readonly<Snapshot['content']>;
|
|
161
|
+
readonly files: readonly Readonly<Snapshot['files'][number]>[];
|
|
162
|
+
};
|
|
163
|
+
readonly response?: 'full' | 'compact';
|
|
164
|
+
};
|
|
165
|
+
export type Actor = Readonly<{
|
|
166
|
+
userId: string;
|
|
167
|
+
sessionId: string;
|
|
168
|
+
clientId: string;
|
|
169
|
+
scopes: readonly string[];
|
|
170
|
+
}>;
|
|
171
|
+
export type Viewer = z.infer<typeof viewerSchema>;
|
|
172
|
+
export type Publication = z.infer<typeof publicationSchema>;
|
|
173
|
+
export type Revision = z.infer<typeof revisionSchema>;
|
|
174
|
+
export type ArtifactRead = z.infer<typeof artifactReadSchema>;
|
|
175
|
+
export type ArtifactSummary = {
|
|
176
|
+
artifactId: string;
|
|
177
|
+
title: string;
|
|
178
|
+
headRevisionId: string;
|
|
179
|
+
createdAt: string;
|
|
180
|
+
publication: Publication | null;
|
|
181
|
+
};
|
|
182
|
+
export type ArtifactLibraryQuery = z.infer<typeof libraryQuerySchema>;
|
|
183
|
+
export type ArtifactLibraryPage = {
|
|
184
|
+
items: ArtifactSummary[];
|
|
185
|
+
nextCursor: string | null;
|
|
186
|
+
counts: {
|
|
187
|
+
all: number;
|
|
188
|
+
published: number;
|
|
189
|
+
unpublished: number;
|
|
190
|
+
pending: number;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
export type Archive = {
|
|
194
|
+
filename: string;
|
|
195
|
+
mediaType: 'application/zip';
|
|
196
|
+
base64: string;
|
|
197
|
+
sha256: string;
|
|
198
|
+
};
|
|
199
|
+
export type ArtifactErrorCode = 'forbidden' | 'not_found' | 'conflict' | 'request_conflict' | 'invalid_grant';
|
|
200
|
+
export declare class ArtifactError extends Error {
|
|
201
|
+
readonly code: ArtifactErrorCode;
|
|
202
|
+
readonly details: Record<string, unknown>;
|
|
203
|
+
constructor(code: ArtifactErrorCode, message: string, details?: Record<string, unknown>);
|
|
204
|
+
}
|
|
205
|
+
export declare function decodeFile(file: {
|
|
206
|
+
encoding: 'utf8' | 'base64';
|
|
207
|
+
data: string;
|
|
208
|
+
}): Uint8Array;
|
|
209
|
+
export declare const publicationSchema: z.ZodObject<{
|
|
210
|
+
revisionId: z.ZodString;
|
|
211
|
+
version: z.ZodNumber;
|
|
212
|
+
url: z.ZodURL;
|
|
213
|
+
}, z.core.$strict>;
|
|
214
|
+
export declare const revisionSchema: z.ZodObject<{
|
|
215
|
+
id: z.ZodString;
|
|
216
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
217
|
+
sequence: z.ZodNumber;
|
|
218
|
+
authorUserId: z.ZodString;
|
|
219
|
+
authorClientId: z.ZodString;
|
|
220
|
+
createdAt: z.ZodISODateTime;
|
|
221
|
+
sha256: z.ZodString;
|
|
222
|
+
snapshot: z.ZodObject<{
|
|
223
|
+
metadata: z.ZodObject<{
|
|
224
|
+
title: z.ZodString;
|
|
225
|
+
description: z.ZodString;
|
|
226
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
227
|
+
}, z.core.$strict>;
|
|
228
|
+
content: z.ZodObject<{
|
|
229
|
+
kind: z.ZodEnum<{
|
|
230
|
+
text: "text";
|
|
231
|
+
html: "html";
|
|
232
|
+
markdown: "markdown";
|
|
233
|
+
}>;
|
|
234
|
+
text: z.ZodString;
|
|
235
|
+
}, z.core.$strict>;
|
|
236
|
+
files: z.ZodArray<z.ZodObject<{
|
|
237
|
+
path: z.ZodString;
|
|
238
|
+
mediaType: z.ZodString;
|
|
239
|
+
encoding: z.ZodEnum<{
|
|
240
|
+
utf8: "utf8";
|
|
241
|
+
base64: "base64";
|
|
242
|
+
}>;
|
|
243
|
+
data: z.ZodString;
|
|
244
|
+
}, z.core.$strict>>;
|
|
245
|
+
}, z.core.$strict>;
|
|
246
|
+
}, z.core.$strict>;
|
|
247
|
+
export declare const artifactReadSchema: z.ZodObject<{
|
|
248
|
+
artifactId: z.ZodString;
|
|
249
|
+
ownerId: z.ZodString;
|
|
250
|
+
headRevisionId: z.ZodString;
|
|
251
|
+
publication: z.ZodNullable<z.ZodObject<{
|
|
252
|
+
revisionId: z.ZodString;
|
|
253
|
+
version: z.ZodNumber;
|
|
254
|
+
url: z.ZodURL;
|
|
255
|
+
}, z.core.$strict>>;
|
|
256
|
+
revision: z.ZodObject<{
|
|
257
|
+
id: z.ZodString;
|
|
258
|
+
parentRevisionId: z.ZodNullable<z.ZodString>;
|
|
259
|
+
sequence: z.ZodNumber;
|
|
260
|
+
authorUserId: z.ZodString;
|
|
261
|
+
authorClientId: z.ZodString;
|
|
262
|
+
createdAt: z.ZodISODateTime;
|
|
263
|
+
sha256: z.ZodString;
|
|
264
|
+
snapshot: z.ZodObject<{
|
|
265
|
+
metadata: z.ZodObject<{
|
|
266
|
+
title: z.ZodString;
|
|
267
|
+
description: z.ZodString;
|
|
268
|
+
custom: z.ZodOptional<z.ZodJSONSchema>;
|
|
269
|
+
}, z.core.$strict>;
|
|
270
|
+
content: z.ZodObject<{
|
|
271
|
+
kind: z.ZodEnum<{
|
|
272
|
+
text: "text";
|
|
273
|
+
html: "html";
|
|
274
|
+
markdown: "markdown";
|
|
275
|
+
}>;
|
|
276
|
+
text: z.ZodString;
|
|
277
|
+
}, z.core.$strict>;
|
|
278
|
+
files: z.ZodArray<z.ZodObject<{
|
|
279
|
+
path: z.ZodString;
|
|
280
|
+
mediaType: z.ZodString;
|
|
281
|
+
encoding: z.ZodEnum<{
|
|
282
|
+
utf8: "utf8";
|
|
283
|
+
base64: "base64";
|
|
284
|
+
}>;
|
|
285
|
+
data: z.ZodString;
|
|
286
|
+
}, z.core.$strict>>;
|
|
287
|
+
}, z.core.$strict>;
|
|
288
|
+
}, z.core.$strict>;
|
|
289
|
+
}, z.core.$strict>;
|
|
290
|
+
export declare const publishResultSchema: z.ZodObject<{
|
|
291
|
+
revisionId: z.ZodString;
|
|
292
|
+
version: z.ZodNumber;
|
|
293
|
+
url: z.ZodURL;
|
|
294
|
+
artifactId: z.ZodString;
|
|
295
|
+
}, z.core.$strict>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const identifier = z.string().min(1).max(128);
|
|
3
|
+
const requestId = z.string().min(8).max(128);
|
|
4
|
+
const fileSchema = z.strictObject({
|
|
5
|
+
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
|
+
mediaType: z.string().max(128).regex(/^[a-zA-Z0-9!#$&^_.+-]+\/[a-zA-Z0-9!#$&^_.+-]+$/),
|
|
7
|
+
encoding: z.enum(['utf8', 'base64']),
|
|
8
|
+
data: z.string().max(1_398_104).refine((data) => data.isWellFormed(), 'File text must contain well-formed Unicode'),
|
|
9
|
+
}).superRefine((file, context) => {
|
|
10
|
+
if (file.encoding === 'base64' && Buffer.from(file.data, 'base64').toString('base64') !== file.data)
|
|
11
|
+
context.addIssue({ code: 'custom', path: ['data'], message: 'Use canonical padded base64' });
|
|
12
|
+
});
|
|
13
|
+
export const snapshotSchema = z.strictObject({
|
|
14
|
+
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') }),
|
|
16
|
+
files: z.array(fileSchema).max(32),
|
|
17
|
+
}).superRefine((snapshot, context) => {
|
|
18
|
+
const paths = snapshot.files.map((file) => file.path);
|
|
19
|
+
if (paths.some((path, index) => paths.some((other, otherIndex) => index !== otherIndex && (path === other || other.startsWith(`${path}/`)))))
|
|
20
|
+
context.addIssue({ code: 'custom', path: ['files'], message: 'File paths must be unique and cannot contain another file' });
|
|
21
|
+
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
|
+
if (bytes > 1_048_576)
|
|
23
|
+
context.addIssue({ code: 'custom', message: 'A revision must fit within 1 MiB of decoded content and metadata' });
|
|
24
|
+
});
|
|
25
|
+
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 });
|
|
28
|
+
export const updateSchema = z.strictObject({ requestId, artifactId: identifier, expectedRevisionId: identifier, snapshot: snapshotSchema, response });
|
|
29
|
+
export const publishSchema = z.strictObject({ requestId, artifactId: identifier, revisionId: identifier, expectedPublicationVersion: z.number().int().nonnegative() });
|
|
30
|
+
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() });
|
|
32
|
+
export const libraryQuerySchema = z.strictObject({
|
|
33
|
+
q: z.string().trim().max(256).default(''),
|
|
34
|
+
status: z.enum(['all', 'published', 'unpublished', 'pending']).default('all'),
|
|
35
|
+
cursor: identifier.optional(),
|
|
36
|
+
});
|
|
37
|
+
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() });
|
|
38
|
+
export const issueGrantSchema = z.strictObject({ artifactId: identifier, state: z.string().min(16).max(256) });
|
|
39
|
+
export const consumeGrantSchema = issueGrantSchema.extend({ code: z.string().min(32).max(128) });
|
|
40
|
+
export const viewerSchema = z.strictObject({ artifactId: identifier, userId: identifier, sessionId: identifier, expiresAt: z.number().int().positive() });
|
|
41
|
+
export class ArtifactError extends Error {
|
|
42
|
+
code;
|
|
43
|
+
details;
|
|
44
|
+
constructor(code, message, details = {}) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.code = code;
|
|
47
|
+
this.details = details;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function decodeFile(file) {
|
|
51
|
+
return Buffer.from(file.data, file.encoding === 'utf8' ? 'utf8' : 'base64');
|
|
52
|
+
}
|
|
53
|
+
export const publicationSchema = z.strictObject({ revisionId: identifier, version: z.number().int().positive(), url: z.url() });
|
|
54
|
+
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 });
|
|
56
|
+
export const publishResultSchema = publicationSchema.extend({ artifactId: identifier });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function canonicalJson(value) {
|
|
2
|
+
return JSON.stringify(value, (_key, item) => {
|
|
3
|
+
if (item && typeof item === 'object' && !Array.isArray(item))
|
|
4
|
+
return Object.fromEntries(Object.entries(item).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
5
|
+
return item;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export async function sha256(value) {
|
|
9
|
+
const bytes = typeof value === 'string' ? new TextEncoder().encode(value) : new Uint8Array(value);
|
|
10
|
+
return Buffer.from(await crypto.subtle.digest('SHA-256', bytes)).toString('hex');
|
|
11
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ArtifactRead, type Revision, type Snapshot, type UpdateInput } from './index.ts';
|
|
2
|
+
export type { UpdateInput } from './index.ts';
|
|
3
|
+
export type AttachmentSummary = {
|
|
4
|
+
path: string;
|
|
5
|
+
mediaType: string;
|
|
6
|
+
encoding: 'utf8' | 'base64';
|
|
7
|
+
decodedBytes: number;
|
|
8
|
+
sha256: string;
|
|
9
|
+
};
|
|
10
|
+
export type RevisionSummary = Omit<Revision, 'snapshot'> & {
|
|
11
|
+
metadata: Pick<Snapshot['metadata'], 'title' | 'description'>;
|
|
12
|
+
content: {
|
|
13
|
+
kind: Snapshot['content']['kind'];
|
|
14
|
+
utf8Bytes: number;
|
|
15
|
+
};
|
|
16
|
+
files: AttachmentSummary[];
|
|
17
|
+
};
|
|
18
|
+
export type CompactArtifactRead = Omit<ArtifactRead, 'revision'> & {
|
|
19
|
+
revision: RevisionSummary;
|
|
20
|
+
};
|
|
21
|
+
export type MetadataChange = {
|
|
22
|
+
field: 'title' | 'description';
|
|
23
|
+
before: string;
|
|
24
|
+
after: string;
|
|
25
|
+
} | {
|
|
26
|
+
field: 'custom';
|
|
27
|
+
before?: Snapshot['metadata']['custom'];
|
|
28
|
+
after?: Snapshot['metadata']['custom'];
|
|
29
|
+
};
|
|
30
|
+
export type AttachmentChange = {
|
|
31
|
+
path: string;
|
|
32
|
+
change: 'added';
|
|
33
|
+
before: null;
|
|
34
|
+
after: AttachmentSummary;
|
|
35
|
+
} | {
|
|
36
|
+
path: string;
|
|
37
|
+
change: 'removed';
|
|
38
|
+
before: AttachmentSummary;
|
|
39
|
+
after: null;
|
|
40
|
+
} | {
|
|
41
|
+
path: string;
|
|
42
|
+
change: 'changed';
|
|
43
|
+
before: AttachmentSummary;
|
|
44
|
+
after: AttachmentSummary;
|
|
45
|
+
};
|
|
46
|
+
export type RevisionComparison = {
|
|
47
|
+
beforeRevisionId: string;
|
|
48
|
+
afterRevisionId: string;
|
|
49
|
+
metadata: MetadataChange[];
|
|
50
|
+
content: {
|
|
51
|
+
before: Snapshot['content'];
|
|
52
|
+
after: Snapshot['content'];
|
|
53
|
+
kindChanged: boolean;
|
|
54
|
+
textChanged: boolean;
|
|
55
|
+
};
|
|
56
|
+
files: AttachmentChange[];
|
|
57
|
+
};
|
|
58
|
+
export declare function summarizeRevision(revision: Revision): Promise<RevisionSummary>;
|
|
59
|
+
export declare function compactArtifactRead(document: ArtifactRead): Promise<CompactArtifactRead>;
|
|
60
|
+
export declare function compareRevisions(before: Revision, after: Revision): Promise<RevisionComparison>;
|
|
61
|
+
export type ArtifactEditClient = {
|
|
62
|
+
read(input: {
|
|
63
|
+
artifactId: string;
|
|
64
|
+
}): Promise<ArtifactRead>;
|
|
65
|
+
};
|
|
66
|
+
export type PrepareArtifactEditInput = {
|
|
67
|
+
artifactId: string;
|
|
68
|
+
requestId: string;
|
|
69
|
+
changes: {
|
|
70
|
+
metadata?: Partial<Snapshot['metadata']>;
|
|
71
|
+
content?: Partial<Snapshot['content']>;
|
|
72
|
+
files?: Snapshot['files'];
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
export declare function prepareArtifactEdit(client: ArtifactEditClient, value: PrepareArtifactEditInput): Promise<UpdateInput>;
|
package/src/workflow.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ArtifactError, artifactReadSchema, createSchema, decodeFile, readSchema, snapshotSchema, updateSchema } from "./index.js";
|
|
3
|
+
import { canonicalJson, sha256 } from "./serialization.js";
|
|
4
|
+
async function summarizeFiles(files) {
|
|
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 }) => {
|
|
6
|
+
const bytes = decodeFile({ encoding, data });
|
|
7
|
+
return { path, mediaType, encoding, decodedBytes: bytes.length, sha256: await sha256(bytes) };
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
export async function summarizeRevision(revision) {
|
|
11
|
+
const { snapshot, ...identity } = revision;
|
|
12
|
+
return {
|
|
13
|
+
...identity,
|
|
14
|
+
metadata: { title: snapshot.metadata.title, description: snapshot.metadata.description },
|
|
15
|
+
content: { kind: snapshot.content.kind, utf8Bytes: Buffer.byteLength(snapshot.content.text) },
|
|
16
|
+
files: await summarizeFiles(snapshot.files),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function compactArtifactRead(document) {
|
|
20
|
+
const { revision, ...identity } = document;
|
|
21
|
+
return { ...identity, revision: await summarizeRevision(revision) };
|
|
22
|
+
}
|
|
23
|
+
export async function compareRevisions(before, after) {
|
|
24
|
+
const metadata = [];
|
|
25
|
+
for (const field of ['title', 'description']) {
|
|
26
|
+
if (before.snapshot.metadata[field] !== after.snapshot.metadata[field])
|
|
27
|
+
metadata.push({ field, before: before.snapshot.metadata[field], after: after.snapshot.metadata[field] });
|
|
28
|
+
}
|
|
29
|
+
if (canonicalJson(before.snapshot.metadata.custom) !== canonicalJson(after.snapshot.metadata.custom)) {
|
|
30
|
+
metadata.push({ field: 'custom', before: structuredClone(before.snapshot.metadata.custom), after: structuredClone(after.snapshot.metadata.custom) });
|
|
31
|
+
}
|
|
32
|
+
const [beforeFiles, afterFiles] = await Promise.all([summarizeFiles(before.snapshot.files), summarizeFiles(after.snapshot.files)]);
|
|
33
|
+
const beforeByPath = new Map(beforeFiles.map((file) => [file.path, file]));
|
|
34
|
+
const afterByPath = new Map(afterFiles.map((file) => [file.path, file]));
|
|
35
|
+
const files = [];
|
|
36
|
+
for (const path of [...new Set([...beforeByPath.keys(), ...afterByPath.keys()])].sort()) {
|
|
37
|
+
const previous = beforeByPath.get(path);
|
|
38
|
+
const next = afterByPath.get(path);
|
|
39
|
+
if (!previous && next)
|
|
40
|
+
files.push({ path, change: 'added', before: null, after: next });
|
|
41
|
+
else if (previous && !next)
|
|
42
|
+
files.push({ path, change: 'removed', before: previous, after: null });
|
|
43
|
+
else if (previous && next && (previous.sha256 !== next.sha256 || previous.mediaType !== next.mediaType || previous.encoding !== next.encoding))
|
|
44
|
+
files.push({ path, change: 'changed', before: previous, after: next });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
beforeRevisionId: before.id,
|
|
48
|
+
afterRevisionId: after.id,
|
|
49
|
+
metadata,
|
|
50
|
+
content: {
|
|
51
|
+
before: { ...before.snapshot.content },
|
|
52
|
+
after: { ...after.snapshot.content },
|
|
53
|
+
kindChanged: before.snapshot.content.kind !== after.snapshot.content.kind,
|
|
54
|
+
textChanged: before.snapshot.content.text !== after.snapshot.content.text,
|
|
55
|
+
},
|
|
56
|
+
files,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const prepareEditSchema = z.strictObject({
|
|
60
|
+
artifactId: readSchema.shape.artifactId,
|
|
61
|
+
requestId: createSchema.shape.requestId,
|
|
62
|
+
changes: z.strictObject({
|
|
63
|
+
metadata: snapshotSchema.shape.metadata.partial().optional(),
|
|
64
|
+
content: snapshotSchema.shape.content.partial().optional(),
|
|
65
|
+
files: snapshotSchema.shape.files.optional(),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
function freezeJson(value) {
|
|
69
|
+
if (value && typeof value === 'object') {
|
|
70
|
+
for (const child of Object.values(value))
|
|
71
|
+
freezeJson(child);
|
|
72
|
+
Object.freeze(value);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
export async function prepareArtifactEdit(client, value) {
|
|
77
|
+
const input = prepareEditSchema.parse(structuredClone(value));
|
|
78
|
+
const current = artifactReadSchema.parse(structuredClone(await client.read({ artifactId: input.artifactId })));
|
|
79
|
+
if (current.artifactId !== input.artifactId)
|
|
80
|
+
throw new TypeError('Artifact reader returned a different artifact');
|
|
81
|
+
if (current.revision.id !== current.headRevisionId)
|
|
82
|
+
throw new ArtifactError('conflict', 'Artifact reader did not return the current head', { currentRevisionId: current.headRevisionId });
|
|
83
|
+
const snapshot = current.revision.snapshot;
|
|
84
|
+
const metadata = { ...snapshot.metadata, ...input.changes.metadata };
|
|
85
|
+
if (Object.hasOwn(input.changes.metadata ?? {}, 'custom') && input.changes.metadata?.custom === undefined)
|
|
86
|
+
delete metadata.custom;
|
|
87
|
+
return freezeJson(updateSchema.parse({
|
|
88
|
+
artifactId: input.artifactId,
|
|
89
|
+
requestId: input.requestId,
|
|
90
|
+
expectedRevisionId: current.headRevisionId,
|
|
91
|
+
snapshot: {
|
|
92
|
+
metadata,
|
|
93
|
+
content: { ...snapshot.content, ...input.changes.content },
|
|
94
|
+
files: input.changes.files ?? snapshot.files,
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|