@yargote/artifacts-client 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 +26 -6
- package/package.json +2 -2
- package/src/index.d.ts +20 -3
- package/src/index.js +30 -7
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
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
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm install @yargote/artifacts-client@0.1.0-beta.
|
|
6
|
+
npm install @yargote/artifacts-client@0.1.0-beta.10
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
```ts
|
|
@@ -23,7 +23,9 @@ const document = await client.read({ artifactId: 'artifact-id' });
|
|
|
23
23
|
|
|
24
24
|
`createArtifactHttpClient({ resource, accessToken, fetch?, timeoutMs? })` returns an `ArtifactHttpClient`.
|
|
25
25
|
|
|
26
|
-
`resource` is the exact canonical OAuth resource.
|
|
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.
|
|
27
29
|
|
|
28
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.
|
|
29
31
|
|
|
@@ -36,18 +38,32 @@ Every method makes one HTTP request. The client does not retry, follow redirects
|
|
|
36
38
|
| Method | Input | Result |
|
|
37
39
|
| --- | --- | --- |
|
|
38
40
|
| `identity()` | None | Verified user, session, client, and scopes |
|
|
39
|
-
| `create(command)` | `requestId`, complete `snapshot`, optional `response` | Artifact receipt |
|
|
40
|
-
| `list(query?)` | Optional `limit` and `
|
|
41
|
-
| `
|
|
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 |
|
|
42
46
|
| `update(command)` | `requestId`, `artifactId`, `expectedRevisionId`, complete `snapshot`, optional `response` | Artifact receipt |
|
|
43
47
|
| `history(query)` | `artifactId`, optional `limit`, `beforeSequence`, and `response` | Revisions and `nextBeforeSequence` |
|
|
44
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 }` |
|
|
45
53
|
| `export(query)` | `artifactId`, `revisionId` | `{ filename, mediaType, bytes, sha256 }` |
|
|
46
54
|
|
|
47
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.
|
|
48
56
|
|
|
49
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.
|
|
50
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
|
+
|
|
51
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.
|
|
52
68
|
|
|
53
69
|
## Errors and retries
|
|
@@ -56,10 +72,14 @@ All commands and JSON responses pass the strict domain schemas. An omitted mutat
|
|
|
56
72
|
|
|
57
73
|
Invalid local commands throw a Zod validation error before any HTTP request. Invalid configuration throws `TypeError`.
|
|
58
74
|
|
|
59
|
-
A caller that needs recovery retains the complete create, update, or
|
|
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`.
|
|
60
76
|
|
|
61
77
|
## Compiled installation
|
|
62
78
|
|
|
63
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.
|
|
64
80
|
|
|
65
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,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yargote/artifacts-client",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Typed HTTP client for the Artifacts REST API.",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"LICENSE"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@yargote/artifacts-contracts": "0.1.0-beta.
|
|
29
|
+
"@yargote/artifacts-contracts": "0.1.0-beta.10",
|
|
30
30
|
"zod": "4.6.5"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
package/src/index.d.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { createSchema, exportSchema, historySchema, listSchema, publishResultSchema, publishSchema, readSchema, type ArtifactRead, type UpdateInput } from '@yargote/artifacts-contracts';
|
|
3
|
-
import { historyResultSchema, listResultSchema, summaryHistoryResultSchema, type Identity } from '@yargote/artifacts-contracts/http';
|
|
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
4
|
import type { CompactArtifactRead } from '@yargote/artifacts-contracts/workflow';
|
|
5
|
-
export type { ArtifactRead, Snapshot, UpdateInput } from '@yargote/artifacts-contracts';
|
|
5
|
+
export type { ArtifactRead, Share, Snapshot, UpdateInput } from '@yargote/artifacts-contracts';
|
|
6
6
|
export type { Identity } from '@yargote/artifacts-contracts/http';
|
|
7
7
|
export type { CompactArtifactRead, RevisionSummary } from '@yargote/artifacts-contracts/workflow';
|
|
8
8
|
export { prepareArtifactEdit } from '@yargote/artifacts-contracts/workflow';
|
|
9
9
|
export type CreateInput = z.input<typeof createSchema>;
|
|
10
10
|
export type ReadInput = z.input<typeof readSchema>;
|
|
11
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
|
+
};
|
|
12
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>;
|
|
13
24
|
export type HistoryInput = z.input<typeof historySchema>;
|
|
14
25
|
export type ExportInput = z.input<typeof exportSchema>;
|
|
15
26
|
export type ArtifactArchive = {
|
|
@@ -36,6 +47,8 @@ export interface ArtifactHttpClient {
|
|
|
36
47
|
}): Promise<ArtifactRead>;
|
|
37
48
|
create(input: CreateInput): Promise<ArtifactRead | CompactArtifactRead>;
|
|
38
49
|
list(input?: ListInput): Promise<z.infer<typeof listResultSchema>>;
|
|
50
|
+
projects(): Promise<ProjectsResult>;
|
|
51
|
+
search(input: SearchInput): Promise<SearchResult>;
|
|
39
52
|
read(input: ReadInput & {
|
|
40
53
|
response: 'compact';
|
|
41
54
|
}): Promise<CompactArtifactRead>;
|
|
@@ -58,6 +71,10 @@ export interface ArtifactHttpClient {
|
|
|
58
71
|
}): Promise<HistoryResult>;
|
|
59
72
|
history(input: HistoryInput): Promise<HistoryResult | SummaryHistoryResult>;
|
|
60
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>;
|
|
61
78
|
export(input: ExportInput): Promise<ArtifactArchive>;
|
|
62
79
|
}
|
|
63
80
|
export declare function createArtifactHttpClient(options: {
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { artifactReadSchema, createSchema, exportSchema, historySchema, listSchema, publishResultSchema, publishSchema, readSchema, updateSchema } from '@yargote/artifacts-contracts';
|
|
3
|
-
import { compactArtifactReadSchema, historyResultSchema, httpErrorSchema, identitySchema, listResultSchema, restBase, summaryHistoryResultSchema } from '@yargote/artifacts-contracts/http';
|
|
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
4
|
export { prepareArtifactEdit } from '@yargote/artifacts-contracts/workflow';
|
|
5
5
|
export class HttpError extends Error {
|
|
6
6
|
status;
|
|
@@ -16,6 +16,7 @@ export class HttpError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
const maxJsonBytes = 64 * 1024 * 1024;
|
|
18
18
|
const maxArchiveBytes = 8 * 1024 * 1024;
|
|
19
|
+
const listSharesResultSchema = z.strictObject({ items: z.array(shareResultSchema) });
|
|
19
20
|
async function boundedBytes(response, maximum) {
|
|
20
21
|
const length = response.headers.get('content-length');
|
|
21
22
|
if (length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) {
|
|
@@ -71,7 +72,7 @@ export function createArtifactHttpClient(options) {
|
|
|
71
72
|
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
72
73
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
|
|
73
74
|
throw new TypeError('timeoutMs must be a positive integer no greater than 2147483647');
|
|
74
|
-
async function request(path, method, parse, input, maximum = maxJsonBytes) {
|
|
75
|
+
async function request(path, method, expectedStatus, parse, input, maximum = maxJsonBytes) {
|
|
75
76
|
const token = typeof options.accessToken === 'string' ? options.accessToken : await options.accessToken();
|
|
76
77
|
if (!token || /[\r\n]/.test(token))
|
|
77
78
|
throw new TypeError('A nonempty access token is required');
|
|
@@ -101,7 +102,6 @@ export function createArtifactHttpClient(options) {
|
|
|
101
102
|
const { error } = parseResponse(httpErrorSchema, value, response.status);
|
|
102
103
|
throw new HttpError(response.status, error.code, error.message, error.details);
|
|
103
104
|
}
|
|
104
|
-
const expectedStatus = method === 'POST' ? 201 : 200;
|
|
105
105
|
if (response.status !== expectedStatus)
|
|
106
106
|
throw new HttpError(response.status, 'invalid_response', `The server returned status ${response.status}, expected ${expectedStatus}`);
|
|
107
107
|
return await parse(response, bytes);
|
|
@@ -118,7 +118,7 @@ export function createArtifactHttpClient(options) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
function json(path, method, schema, input) {
|
|
121
|
-
return request(path, method, (response, bytes) => {
|
|
121
|
+
return request(path, method, method === 'POST' ? 201 : 200, (response, bytes) => {
|
|
122
122
|
if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json')
|
|
123
123
|
throw new HttpError(response.status, 'invalid_response', 'The server did not return JSON');
|
|
124
124
|
let value;
|
|
@@ -140,9 +140,13 @@ export function createArtifactHttpClient(options) {
|
|
|
140
140
|
list(value = {}) {
|
|
141
141
|
return json(`/artifacts${query(listSchema.parse(value))}`, 'GET', listResultSchema);
|
|
142
142
|
},
|
|
143
|
+
projects: () => json('/projects', 'GET', projectsResultSchema),
|
|
144
|
+
search(value) {
|
|
145
|
+
return json(`/search${query(searchSchema.parse(value))}`, 'GET', searchResultSchema);
|
|
146
|
+
},
|
|
143
147
|
read(value) {
|
|
144
148
|
const { artifactId, ...parameters } = readSchema.parse(value);
|
|
145
|
-
return json(`/artifacts/${encodeURIComponent(artifactId)}${query(parameters)}`, 'GET', parameters.response === 'compact' ? compactArtifactReadSchema : artifactReadSchema);
|
|
149
|
+
return json(`${artifactId === undefined ? '/lookup' : `/artifacts/${encodeURIComponent(artifactId)}`}${query(parameters)}`, 'GET', parameters.response === 'compact' ? compactArtifactReadSchema : artifactReadSchema);
|
|
146
150
|
},
|
|
147
151
|
update(value) {
|
|
148
152
|
const input = updateSchema.parse(value);
|
|
@@ -156,9 +160,28 @@ export function createArtifactHttpClient(options) {
|
|
|
156
160
|
const input = publishSchema.parse(value);
|
|
157
161
|
return json(`/artifacts/${encodeURIComponent(input.artifactId)}/publication`, 'PUT', publishResultSchema, input);
|
|
158
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
|
+
},
|
|
159
182
|
export(value) {
|
|
160
183
|
const input = exportSchema.parse(value);
|
|
161
|
-
return request(`/artifacts/${encodeURIComponent(input.artifactId)}/revisions/${encodeURIComponent(input.revisionId)}/export`, 'GET', async (response, bytes) => {
|
|
184
|
+
return request(`/artifacts/${encodeURIComponent(input.artifactId)}/revisions/${encodeURIComponent(input.revisionId)}/export`, 'GET', 200, async (response, bytes) => {
|
|
162
185
|
const disposition = response.headers.get('content-disposition');
|
|
163
186
|
const filename = /^attachment; filename="([A-Za-z0-9._-]+\.zip)"$/.exec(disposition ?? '')?.[1];
|
|
164
187
|
const digest = response.headers.get('x-artifact-sha256');
|