@yargote/artifacts-client 0.0.0-stage → 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/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,85 @@
1
- # Temporary Holding Version
1
+ # Artifacts HTTP client reference
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@yargote/artifacts-client` is a typed Fetch client for the Artifacts REST API. It depends on `@yargote/artifacts-contracts` and Zod. It does not install the artifact store or a database driver. Node 24 or later is required.
4
+
5
+ ```sh
6
+ npm install @yargote/artifacts-client@0.1.0-beta.10
7
+ ```
8
+
9
+ ```ts
10
+ import { createArtifactHttpClient, HttpError } from '@yargote/artifacts-client';
11
+
12
+ const client = createArtifactHttpClient({
13
+ resource: process.env.ARTIFACTS_SERVER!,
14
+ accessToken: async () => obtainAccessToken(),
15
+ timeoutMs: 30_000,
16
+ });
17
+
18
+ const identity = await client.identity();
19
+ const document = await client.read({ artifactId: 'artifact-id' });
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ `createArtifactHttpClient({ resource, accessToken, fetch?, timeoutMs? })` returns an `ArtifactHttpClient`.
25
+
26
+ `resource` is the exact canonical REST OAuth resource. Set it to the deployment's `API_URL` origin, such as `https://artifacts.example`, to use routes under `/v1`. Request access tokens for that same origin. MCP uses a separate `/mcp` audience. The client appends `/v1` to the supplied resource and does not remove or rewrite paths. Older deployments without `API_URL` retain their complete MCP resource and `/mcp/v1` routes.
27
+
28
+ HTTPS is required except for HTTP on `localhost`, `127.0.0.1`, or `[::1]`. Credentials, queries, fragments, and noncanonical URLs are rejected. The client has no default resource.
29
+
30
+ `accessToken` is a bearer token or an asynchronous function that returns one. The function runs once per method call. The caller owns credential storage and refresh.
31
+
32
+ `fetch` defaults to `globalThis.fetch`. `timeoutMs` defaults to 30 seconds and covers the request and response body. It must be a positive safe integer no greater than 2147483647.
33
+
34
+ ## Methods
35
+
36
+ Every method makes one HTTP request. The client does not retry, follow redirects, rebase conflicts, or change expected versions.
37
+
38
+ | Method | Input | Result |
39
+ | --- | --- | --- |
40
+ | `identity()` | None | Verified user, session, client, and scopes |
41
+ | `create(command)` | `requestId`, complete `snapshot`, optional `project`, `slug`, and `response` | Artifact receipt |
42
+ | `list(query?)` | Optional `limit`, `cursor`, and `project` | Artifact summaries and `nextCursor` |
43
+ | `projects()` | None | `{ items: [{ name, artifactCount }] }` |
44
+ | `search(query)` | `query`, optional `project`, `limit`, and `cursor` | `{ items: SearchHit[], nextCursor }` |
45
+ | `read(query)` | Exactly one of `artifactId` or `slug` with optional `project`, optional `revisionId` and `response` | Artifact revision |
46
+ | `update(command)` | `requestId`, `artifactId`, `expectedRevisionId`, complete `snapshot`, optional `response` | Artifact receipt |
47
+ | `history(query)` | `artifactId`, optional `limit`, `beforeSequence`, and `response` | Revisions and `nextBeforeSequence` |
48
+ | `publish(command)` | `requestId`, `artifactId`, `revisionId`, `expectedPublicationVersion` | Publication receipt |
49
+ | `share(command)` | `requestId`, `artifactId`, `email`, optional `notify` and `requireNew: true` | Share receipt with optional notification disposition |
50
+ | `listShares(query)` | `artifactId` | `{ items: Share[] }` |
51
+ | `unshare(command)` | `requestId`, `artifactId`, `shareId` | `void` after an empty HTTP 204 |
52
+ | `move(command)` | `requestId`, `artifactId`, and at least one of `project` (name or null) and `slug` (slug or null) | `{ artifactId, project }` |
53
+ | `export(query)` | `artifactId`, `revisionId` | `{ filename, mediaType, bytes, sha256 }` |
54
+
55
+ Create, read, and update accept `response: 'compact'`. The returned revision contains metadata and file summaries. Omitted response and `response: 'full'` return the complete snapshot. History accepts `response: 'summary'` or `response: 'full'`. Its page limit is ten revisions. Overloads reflect these projections in the return type.
56
+
57
+ All commands and JSON responses pass the strict domain schemas. An omitted mutation `response` remains omitted in the transmitted command. JSON responses are bounded at 64 MiB, which accommodates ten maximum-size revisions with JSON escaping. Archives are bounded at 8 MiB. Export verifies the binary bytes against `X-Artifact-SHA256` and returns the bytes without rebuilding the ZIP.
58
+
59
+ All sharing methods require `artifacts:publish` and artifact ownership. `share` trims and lowercases the email. The client places identifiers in encoded URL segments and sends `{ requestId, email }` plus any explicitly supplied `notify` or `requireNew` for share or `{ requestId }` for unshare. `unshare` resolves with `undefined` and requires no JSON content type on its empty response.
60
+
61
+ Artifact reads and list items include `project`, a name or null. A project name matches an existing project regardless of letter case and keeps its stored spelling. `move` never creates a revision or changes the publication. `projects()` lists only projects that contain artifacts, ordered by name. Because response schemas are strict, this client requires a server that returns `project`. Version `0.1.0-beta.4` and earlier reject responses from such a server with `invalid_response`.
62
+
63
+ Artifact reads and list items also include `slug`, a page name or null. `read({ project, slug })` calls `GET /lookup` and `read({ artifactId })` calls `GET /artifacts/:artifactId`. An omitted `project` reads the unfiled namespace. A used slug fails `create` with an `HttpError` whose code is `slug_conflict`, status 409, and whose details name the holder's `artifactId`. Version `0.1.0-beta.7` and earlier reject responses that include `slug` with `invalid_response`. `move` sets, changes, or clears the slug: an omitted field keeps its value and `null` clears it. A move into a used slug fails with the same `slug_conflict`. The move result is unchanged, so published clients keep parsing it. Read the artifact to see its new slug.
64
+
65
+ `search({ query, project?, limit?, cursor? })` calls `GET /search` and searches the caller's owned Markdown and text artifacts at their head revision. Every whitespace-separated term must match, letter case and accents are ignored, and operators or punctuation in the query are treated as text, so input such as `"foo" OR NEAR(` never fails with a syntax error. HTML and design artifacts, older revisions, and artifacts shared with the caller are not searched. Items are `{ artifactId, project, slug, title, snippet, rank }`, ordered by `rank` and then `artifactId`. `rank` is the FTS5 `bm25` score, where lower is more relevant, and `snippet` is an excerpt with each match wrapped in `**`. Pass `nextCursor` as `cursor` for the next page. `limit` is 1 to 25 and defaults to 10.
66
+
67
+ `prepareArtifactEdit(client, { artifactId, requestId, changes })` reads the current full snapshot and prepares an immutable update command. Metadata and content changes merge into that snapshot. An explicit `files` array replaces the attachments. The helper does not submit the update. Retain its exact output before sending it.
68
+
69
+ ## Errors and retries
70
+
71
+ `HttpError` extends `Error` and contains `status`, `code`, and `details`. Server conflicts preserve the server's details. Transport failures use status `0` and code `network_error` or `timeout`. Rejected redirects use `redirect_refused`. Invalid response schemas use `invalid_response`. Oversized responses use `response_too_large`. A mismatched archive digest uses `archive_digest_mismatch`.
72
+
73
+ Invalid local commands throw a Zod validation error before any HTTP request. Invalid configuration throws `TypeError`.
74
+
75
+ A caller that needs recovery retains the complete create, update, publish, move, share, or unshare command before its first request. While the artifact exists, retrying that exact command returns the original receipt or confirms unshare with another empty 204. Changing `response`, the snapshot, an expected version, or a sharing argument changes the command. A conflict requires a new deliberate command with a new request ID. A replayed receipt describes the original operation and can differ from a fresh read. Replaying a share after revocation returns its historical receipt without restoring access. A new unshare command for a removed share returns `not_found`.
76
+
77
+ ## Compiled installation
78
+
79
+ The registry package contains compiled ESM JavaScript and declarations. Consumers need no TypeScript loader or database package. For repository development, `pnpm build:packages` produces local tarballs and a `dist/packages/manifest.json` inventory. `pnpm test:packages` verifies an ordinary Node consumer and its types.
80
+
81
+ This package is MIT licensed. The license applies to this package and does not license the private Artifacts application repository.
82
+
83
+ Permanent deletion from the panel invalidates historical content-bearing receipts. An exact create, update, publish or move retry for a deleted artifact returns `not_found`; request identity remains reserved, so a changed command still returns `request_conflict` and an old create cannot recreate it. Live-session and scope checks still precede replay. This package exposes no deletion operation.
84
+
85
+ `notify: true` requests one invitation attempt. The result adds `notification` with `status: "requested"`, or `status: "skipped"` and `reason: "disabled"` or `"rate_limited"`. A rate-limited result includes the next UTC day in `retryAt`. Access succeeds in each case. `requested` does not confirm delivery. Exact retries never send again. A fresh request can notify existing access. `requireNew: true` atomically rejects existing access with `conflict` and `details.reason: "share_exists"`. Plain and explicit-false results keep the five grant fields.
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@yargote/artifacts-client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-beta.10",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Typed HTTP client for the Artifacts REST API.",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/yersonargotev/artifacts.git",
11
+ "directory": "packages/client"
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
+ },
23
+ "files": [
24
+ "src",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "dependencies": {
29
+ "@yargote/artifacts-contracts": "0.1.0-beta.10",
30
+ "zod": "4.6.5"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "tag": "beta",
35
+ "provenance": false
36
+ }
37
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { z } from 'zod';
2
+ import { createSchema, exportSchema, historySchema, listSchema, listSharesSchema, moveResultSchema, moveSchema, publishResultSchema, publishSchema, readSchema, searchSchema, shareSchema, unshareSchema, type ArtifactRead, type SearchResult, type Share, type ShareResult, type UpdateInput } from '@yargote/artifacts-contracts';
3
+ import { historyResultSchema, listResultSchema, projectsResultSchema, summaryHistoryResultSchema, type Identity } from '@yargote/artifacts-contracts/http';
4
+ import type { CompactArtifactRead } from '@yargote/artifacts-contracts/workflow';
5
+ export type { ArtifactRead, Share, Snapshot, UpdateInput } from '@yargote/artifacts-contracts';
6
+ export type { Identity } from '@yargote/artifacts-contracts/http';
7
+ export type { CompactArtifactRead, RevisionSummary } from '@yargote/artifacts-contracts/workflow';
8
+ export { prepareArtifactEdit } from '@yargote/artifacts-contracts/workflow';
9
+ export type CreateInput = z.input<typeof createSchema>;
10
+ export type ReadInput = z.input<typeof readSchema>;
11
+ export type PublishInput = z.input<typeof publishSchema>;
12
+ export type ShareInput = z.input<typeof shareSchema>;
13
+ export type UnshareInput = z.input<typeof unshareSchema>;
14
+ export type ListSharesInput = z.input<typeof listSharesSchema>;
15
+ export type ListSharesResult = {
16
+ items: Share[];
17
+ };
18
+ export type ListInput = z.input<typeof listSchema>;
19
+ export type SearchInput = z.input<typeof searchSchema>;
20
+ export type { SearchResult };
21
+ export type MoveInput = z.input<typeof moveSchema>;
22
+ export type MoveResult = z.infer<typeof moveResultSchema>;
23
+ export type ProjectsResult = z.infer<typeof projectsResultSchema>;
24
+ export type HistoryInput = z.input<typeof historySchema>;
25
+ export type ExportInput = z.input<typeof exportSchema>;
26
+ export type ArtifactArchive = {
27
+ filename: string;
28
+ mediaType: 'application/zip';
29
+ bytes: Uint8Array;
30
+ sha256: string;
31
+ };
32
+ export type HistoryResult = z.infer<typeof historyResultSchema>;
33
+ export type SummaryHistoryResult = z.infer<typeof summaryHistoryResultSchema>;
34
+ export declare class HttpError extends Error {
35
+ readonly status: number;
36
+ readonly code: string;
37
+ readonly details: Record<string, unknown>;
38
+ constructor(status: number, code: string, message: string, details?: Record<string, unknown>, options?: ErrorOptions);
39
+ }
40
+ export interface ArtifactHttpClient {
41
+ identity(): Promise<Identity>;
42
+ create(input: CreateInput & {
43
+ response: 'compact';
44
+ }): Promise<CompactArtifactRead>;
45
+ create(input: CreateInput & {
46
+ response?: 'full';
47
+ }): Promise<ArtifactRead>;
48
+ create(input: CreateInput): Promise<ArtifactRead | CompactArtifactRead>;
49
+ list(input?: ListInput): Promise<z.infer<typeof listResultSchema>>;
50
+ projects(): Promise<ProjectsResult>;
51
+ search(input: SearchInput): Promise<SearchResult>;
52
+ read(input: ReadInput & {
53
+ response: 'compact';
54
+ }): Promise<CompactArtifactRead>;
55
+ read(input: ReadInput & {
56
+ response?: 'full';
57
+ }): Promise<ArtifactRead>;
58
+ read(input: ReadInput): Promise<ArtifactRead | CompactArtifactRead>;
59
+ update(input: UpdateInput & {
60
+ response: 'compact';
61
+ }): Promise<CompactArtifactRead>;
62
+ update(input: UpdateInput & {
63
+ response?: 'full';
64
+ }): Promise<ArtifactRead>;
65
+ update(input: UpdateInput): Promise<ArtifactRead | CompactArtifactRead>;
66
+ history(input: HistoryInput & {
67
+ response: 'summary';
68
+ }): Promise<SummaryHistoryResult>;
69
+ history(input: HistoryInput & {
70
+ response?: 'full';
71
+ }): Promise<HistoryResult>;
72
+ history(input: HistoryInput): Promise<HistoryResult | SummaryHistoryResult>;
73
+ publish(input: PublishInput): Promise<z.infer<typeof publishResultSchema>>;
74
+ share(input: ShareInput): Promise<ShareResult>;
75
+ unshare(input: UnshareInput): Promise<void>;
76
+ listShares(input: ListSharesInput): Promise<ListSharesResult>;
77
+ move(input: MoveInput): Promise<MoveResult>;
78
+ export(input: ExportInput): Promise<ArtifactArchive>;
79
+ }
80
+ export declare function createArtifactHttpClient(options: {
81
+ resource: string;
82
+ accessToken: string | (() => Promise<string>);
83
+ fetch?: typeof globalThis.fetch;
84
+ timeoutMs?: number;
85
+ }): ArtifactHttpClient;
package/src/index.js ADDED
@@ -0,0 +1,198 @@
1
+ import { z } from 'zod';
2
+ import { artifactReadSchema, createSchema, exportSchema, historySchema, listSchema, listSharesSchema, moveResultSchema, moveSchema, publishResultSchema, publishSchema, readSchema, searchResultSchema, searchSchema, shareCommandResultSchema, shareResultSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
3
+ import { compactArtifactReadSchema, historyResultSchema, httpErrorSchema, identitySchema, listResultSchema, projectsResultSchema, restBase, summaryHistoryResultSchema } from '@yargote/artifacts-contracts/http';
4
+ export { prepareArtifactEdit } from '@yargote/artifacts-contracts/workflow';
5
+ export class HttpError extends Error {
6
+ status;
7
+ code;
8
+ details;
9
+ constructor(status, code, message, details = {}, options) {
10
+ super(message, options);
11
+ this.name = 'HttpError';
12
+ this.status = status;
13
+ this.code = code;
14
+ this.details = details;
15
+ }
16
+ }
17
+ const maxJsonBytes = 64 * 1024 * 1024;
18
+ const maxArchiveBytes = 8 * 1024 * 1024;
19
+ const listSharesResultSchema = z.strictObject({ items: z.array(shareResultSchema) });
20
+ async function boundedBytes(response, maximum) {
21
+ const length = response.headers.get('content-length');
22
+ if (length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) {
23
+ await response.body?.cancel();
24
+ throw new HttpError(response.status, 'response_too_large', 'The server response exceeds the size limit');
25
+ }
26
+ const reader = response.body?.getReader();
27
+ if (!reader)
28
+ return new Uint8Array();
29
+ const chunks = [];
30
+ let size = 0;
31
+ try {
32
+ while (true) {
33
+ const next = await reader.read();
34
+ if (next.done)
35
+ break;
36
+ size += next.value.byteLength;
37
+ if (size > maximum) {
38
+ await reader.cancel();
39
+ throw new HttpError(response.status, 'response_too_large', 'The server response exceeds the size limit');
40
+ }
41
+ chunks.push(next.value);
42
+ }
43
+ }
44
+ finally {
45
+ reader.releaseLock();
46
+ }
47
+ const bytes = new Uint8Array(size);
48
+ let offset = 0;
49
+ for (const chunk of chunks) {
50
+ bytes.set(chunk, offset);
51
+ offset += chunk.byteLength;
52
+ }
53
+ return bytes;
54
+ }
55
+ function query(values) {
56
+ const parameters = new URLSearchParams();
57
+ for (const [key, value] of Object.entries(values))
58
+ if (value !== undefined)
59
+ parameters.set(key, String(value));
60
+ const encoded = parameters.toString();
61
+ return encoded ? `?${encoded}` : '';
62
+ }
63
+ function parseResponse(schema, value, status) {
64
+ const result = schema.safeParse(value);
65
+ if (!result.success)
66
+ throw new HttpError(status, 'invalid_response', 'The server returned an invalid response');
67
+ return result.data;
68
+ }
69
+ export function createArtifactHttpClient(options) {
70
+ const base = restBase(options.resource);
71
+ const fetcher = options.fetch ?? globalThis.fetch;
72
+ const timeoutMs = options.timeoutMs ?? 30_000;
73
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
74
+ throw new TypeError('timeoutMs must be a positive integer no greater than 2147483647');
75
+ async function request(path, method, expectedStatus, parse, input, maximum = maxJsonBytes) {
76
+ const token = typeof options.accessToken === 'string' ? options.accessToken : await options.accessToken();
77
+ if (!token || /[\r\n]/.test(token))
78
+ throw new TypeError('A nonempty access token is required');
79
+ const controller = new AbortController();
80
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
81
+ try {
82
+ const response = await fetcher(`${base}${path}`, {
83
+ method,
84
+ redirect: 'manual',
85
+ signal: controller.signal,
86
+ headers: { authorization: `Bearer ${token}`, accept: maximum === maxArchiveBytes ? 'application/zip, application/json' : 'application/json', ...(input === undefined ? {} : { 'content-type': 'application/json' }) },
87
+ ...(input === undefined ? {} : { body: JSON.stringify(input) }),
88
+ });
89
+ if (response.status >= 300 && response.status < 400) {
90
+ await response.body?.cancel();
91
+ throw new HttpError(response.status, 'redirect_refused', 'The server attempted to redirect an authenticated request');
92
+ }
93
+ const bytes = await boundedBytes(response, response.ok ? maximum : Math.min(maximum, 1024 * 1024));
94
+ if (!response.ok) {
95
+ let value;
96
+ try {
97
+ value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
98
+ }
99
+ catch {
100
+ throw new HttpError(response.status, 'invalid_response', 'The server returned an invalid error response');
101
+ }
102
+ const { error } = parseResponse(httpErrorSchema, value, response.status);
103
+ throw new HttpError(response.status, error.code, error.message, error.details);
104
+ }
105
+ if (response.status !== expectedStatus)
106
+ throw new HttpError(response.status, 'invalid_response', `The server returned status ${response.status}, expected ${expectedStatus}`);
107
+ return await parse(response, bytes);
108
+ }
109
+ catch (error) {
110
+ if (error instanceof HttpError)
111
+ throw error;
112
+ if (controller.signal.aborted)
113
+ throw new HttpError(0, 'timeout', 'The server request timed out', {}, { cause: error });
114
+ throw new HttpError(0, 'network_error', 'The server request failed', {}, { cause: error });
115
+ }
116
+ finally {
117
+ clearTimeout(timer);
118
+ }
119
+ }
120
+ function json(path, method, schema, input) {
121
+ return request(path, method, method === 'POST' ? 201 : 200, (response, bytes) => {
122
+ if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json')
123
+ throw new HttpError(response.status, 'invalid_response', 'The server did not return JSON');
124
+ let value;
125
+ try {
126
+ value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
127
+ }
128
+ catch {
129
+ throw new HttpError(response.status, 'invalid_response', 'The server returned invalid JSON');
130
+ }
131
+ return parseResponse(schema, value, response.status);
132
+ }, input);
133
+ }
134
+ const client = {
135
+ identity: () => json('/identity', 'GET', identitySchema),
136
+ create(value) {
137
+ const input = createSchema.parse(value);
138
+ return json('/artifacts', 'POST', input.response === 'compact' ? compactArtifactReadSchema : artifactReadSchema, input);
139
+ },
140
+ list(value = {}) {
141
+ return json(`/artifacts${query(listSchema.parse(value))}`, 'GET', listResultSchema);
142
+ },
143
+ projects: () => json('/projects', 'GET', projectsResultSchema),
144
+ search(value) {
145
+ return json(`/search${query(searchSchema.parse(value))}`, 'GET', searchResultSchema);
146
+ },
147
+ read(value) {
148
+ const { artifactId, ...parameters } = readSchema.parse(value);
149
+ return json(`${artifactId === undefined ? '/lookup' : `/artifacts/${encodeURIComponent(artifactId)}`}${query(parameters)}`, 'GET', parameters.response === 'compact' ? compactArtifactReadSchema : artifactReadSchema);
150
+ },
151
+ update(value) {
152
+ const input = updateSchema.parse(value);
153
+ return json(`/artifacts/${encodeURIComponent(input.artifactId)}`, 'PUT', input.response === 'compact' ? compactArtifactReadSchema : artifactReadSchema, input);
154
+ },
155
+ history(value) {
156
+ const { artifactId, ...parameters } = historySchema.parse(value);
157
+ return json(`/artifacts/${encodeURIComponent(artifactId)}/history${query(parameters)}`, 'GET', parameters.response === 'summary' ? summaryHistoryResultSchema : historyResultSchema);
158
+ },
159
+ publish(value) {
160
+ const input = publishSchema.parse(value);
161
+ return json(`/artifacts/${encodeURIComponent(input.artifactId)}/publication`, 'PUT', publishResultSchema, input);
162
+ },
163
+ share(value) {
164
+ const { artifactId, ...input } = shareSchema.parse(value);
165
+ return json(`/artifacts/${encodeURIComponent(artifactId)}/shares`, 'POST', shareCommandResultSchema, input);
166
+ },
167
+ listShares(value) {
168
+ const { artifactId } = listSharesSchema.parse(value);
169
+ return json(`/artifacts/${encodeURIComponent(artifactId)}/shares`, 'GET', listSharesResultSchema);
170
+ },
171
+ unshare(value) {
172
+ const { artifactId, shareId, ...input } = unshareSchema.parse(value);
173
+ return request(`/artifacts/${encodeURIComponent(artifactId)}/shares/${encodeURIComponent(shareId)}`, 'DELETE', 204, (response, bytes) => {
174
+ if (bytes.byteLength !== 0)
175
+ throw new HttpError(response.status, 'invalid_response', 'The server returned a nonempty response');
176
+ }, input);
177
+ },
178
+ move(value) {
179
+ const input = moveSchema.parse(value);
180
+ return json(`/artifacts/${encodeURIComponent(input.artifactId)}/project`, 'PUT', moveResultSchema, input);
181
+ },
182
+ export(value) {
183
+ const input = exportSchema.parse(value);
184
+ return request(`/artifacts/${encodeURIComponent(input.artifactId)}/revisions/${encodeURIComponent(input.revisionId)}/export`, 'GET', 200, async (response, bytes) => {
185
+ const disposition = response.headers.get('content-disposition');
186
+ const filename = /^attachment; filename="([A-Za-z0-9._-]+\.zip)"$/.exec(disposition ?? '')?.[1];
187
+ const digest = response.headers.get('x-artifact-sha256');
188
+ if (!filename || !digest || !/^[a-f0-9]{64}$/.test(digest) || response.headers.get('content-type')?.split(';')[0].trim() !== 'application/zip')
189
+ throw new HttpError(response.status, 'invalid_response', 'The server returned invalid archive headers');
190
+ const actualDigest = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', new Uint8Array(bytes))), (byte) => byte.toString(16).padStart(2, '0')).join('');
191
+ if (actualDigest !== digest)
192
+ throw new HttpError(response.status, 'archive_digest_mismatch', 'The archive SHA-256 does not match its response header');
193
+ return { filename, mediaType: 'application/zip', bytes, sha256: digest };
194
+ }, undefined, maxArchiveBytes);
195
+ },
196
+ };
197
+ return client;
198
+ }