@yargote/artifacts-cli 0.1.0-beta.10 → 0.1.0-beta.11

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
@@ -11,9 +11,9 @@ Use Node 24 or later. The beta targets macOS and Linux. Windows has not been ver
11
11
  Choose one persistent installation:
12
12
 
13
13
  ```sh
14
- npm install --global @yargote/artifacts-cli@0.1.0-beta.10
14
+ npm install --global @yargote/artifacts-cli@0.1.0-beta.11
15
15
  # Or, with pnpm and an existing global bin directory on PATH:
16
- pnpm add --global @yargote/artifacts-cli@0.1.0-beta.10
16
+ pnpm add --global @yargote/artifacts-cli@0.1.0-beta.11
17
17
  artifacts --version
18
18
  ```
19
19
 
@@ -22,8 +22,8 @@ If pnpm reports that no global bin directory exists, run `pnpm setup` and reopen
22
22
  For one-off execution, pin the same version:
23
23
 
24
24
  ```sh
25
- npx --yes @yargote/artifacts-cli@0.1.0-beta.10 --help
26
- pnpm dlx @yargote/artifacts-cli@0.1.0-beta.10 --help
25
+ npx --yes @yargote/artifacts-cli@0.1.0-beta.11 --help
26
+ pnpm dlx @yargote/artifacts-cli@0.1.0-beta.11 --help
27
27
  ```
28
28
 
29
29
  `@beta` follows future prereleases. Exact versions remain reproducible. No stable `latest` release is part of this beta. An organization's pnpm minimum release age or trust policy can delay a new release. Follow that policy rather than disabling it globally.
@@ -131,6 +131,7 @@ artifacts create --command FILE
131
131
  artifacts list [--limit N] [--cursor CURSOR] [--project NAME]
132
132
  artifacts projects
133
133
  artifacts search QUERY [--project NAME] [--limit N] [--cursor CURSOR]
134
+ artifacts batch --command FILE
134
135
  artifacts read ID [--revision REV] [--response full|compact] [--out FILE [--quiet]]
135
136
  artifacts read --slug SLUG [--project NAME] [--revision REV] [--response full|compact]
136
137
  [--out FILE [--quiet]]
@@ -237,7 +238,7 @@ A slug is unique within the owner's project, matched by the project's stored spe
237
238
 
238
239
  ## Search pages
239
240
 
240
- `search QUERY` finds owned Markdown and text artifacts whose title, description, or content at the head revision contains every term of `QUERY`. Quote a query that contains spaces.
241
+ `search QUERY` finds owned artifacts whose head revision contains every term of `QUERY`. Markdown and text match by title, description, and content. HTML matches by title, description, and visible text, without tags, attributes, scripts, or styles. A design matches by title and description only. Quote a query that contains spaces.
241
242
 
242
243
  ```sh
243
244
  artifacts search envigado
@@ -245,7 +246,23 @@ artifacts search "tarde envigado" --project mi-wiki
245
246
  artifacts search envigado --limit 5 --cursor 5
246
247
  ```
247
248
 
248
- The query is plain text of 1 to 256 characters. 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. `--project NAME` limits results to one project, matched regardless of letter case. `--limit` is 1 to 25 and defaults to 10. The command prints `{ "items": [...], "nextCursor": "10" }`; pass `nextCursor` as `--cursor` for the next page, and `null` means there are no more results. 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 `**`. Search requires `artifacts:read`.
249
+ The query is plain text of 1 to 256 characters. 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. Attachments, older revisions, and artifacts shared with the caller are not searched. `--project NAME` limits results to one project, matched regardless of letter case. `--limit` is 1 to 25 and defaults to 10. The command prints `{ "items": [...], "nextCursor": "10" }`; pass `nextCursor` as `--cursor` for the next page, and `null` means there are no more results. 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 `**`. Search requires `artifacts:read`.
250
+
251
+ ## Apply a batch
252
+
253
+ `batch --command FILE` applies 1 to 25 create, update, and move operations as one all-or-nothing command. The file holds the complete command with its `requestId`, and the CLI retains it before submission like the other `--command` mutations, so `artifacts retry REQUEST_ID` replays a lost batch.
254
+
255
+ ```json
256
+ {
257
+ "requestId": "ingest-2026-11-07",
258
+ "operations": [
259
+ { "op": "create", "project": "mi-wiki", "slug": "concepts/rag", "snapshot": { "metadata": { "title": "RAG", "description": "" }, "content": { "kind": "markdown", "text": "# RAG" }, "files": [] } },
260
+ { "op": "update", "project": "mi-wiki", "slug": "index", "expectedRevisionId": "REV", "snapshot": { "metadata": { "title": "Index", "description": "" }, "content": { "kind": "markdown", "text": "- [RAG](concepts/rag.md)" }, "files": [] } }
261
+ ]
262
+ }
263
+ ```
264
+
265
+ Each operation is `{ op: 'create', snapshot, project?, slug? }`, `{ op: 'update', snapshot, expectedRevisionId? }` addressed by exactly one of `artifactId` or `slug`, with `project` only alongside `slug`, or `{ op: 'move', project?, slug? }` addressed by exactly one of `artifactId` or `from: { project?, slug }`. A move takes the `move` fields: it needs `project`, `slug`, or both, an absent field keeps its value, and `null` clears it. A slug may name a page created earlier in the same batch, and later operations address a moved page by its new slug. `expectedRevisionId` must equal the head at that point in the batch, and it may be omitted only for a page that an earlier operation in the batch created or updated, whose revision ID the caller cannot know. Operations run in order, and the first failure commits nothing: its error, such as `conflict` with `currentRevisionId`, `slug_conflict`, or `not_found`, carries `index`, the 0-based position of the failing operation. Each snapshot keeps the 1 MiB and 32-file limits, and the whole batch has a 4 MiB ceiling of decoded content and metadata. The result is `{ results: [{ index, artifactId, revisionId }] }`, one entry per operation. A move creates no revision, so its `revisionId` is the head it left in place. The batch writes one receipt: the same `requestId` returns it, and a changed batch fails with `request_conflict`. Search reflects the batch when it commits. A batch never publishes. Batch requires `artifacts:write`.
249
266
 
250
267
  ## Share publication access
251
268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yargote/artifacts-cli",
3
- "version": "0.1.0-beta.10",
3
+ "version": "0.1.0-beta.11",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Command-line client for the Artifacts REST API.",
@@ -27,8 +27,8 @@
27
27
  "skills"
28
28
  ],
29
29
  "dependencies": {
30
- "@yargote/artifacts-contracts": "0.1.0-beta.10",
31
- "@yargote/artifacts-client": "0.1.0-beta.10",
30
+ "@yargote/artifacts-contracts": "0.1.0-beta.11",
31
+ "@yargote/artifacts-client": "0.1.0-beta.11",
32
32
  "openid-client": "6.8.8",
33
33
  "zod": "4.6.5"
34
34
  },
@@ -37,7 +37,9 @@ When the user names a project for new content, add `--project NAME` to `create`.
37
37
 
38
38
  When the user names a page, add `--slug SLUG` such as `concepts/rag` to `create`, and read it later with `read --project NAME --slug SLUG`, or `read --slug SLUG` for an unfiled page, instead of listing for its ID. Omit the `.md` suffix. A `slug_conflict` error names the `artifactId` that already holds the slug in that project. Rename a page with `move ID --slug SLUG` and clear its slug with `move ID --no-slug`. A move to another project keeps the slug, and a move into a used slug fails with `slug_conflict` without changing anything.
39
39
 
40
- To find a page by what it says, run `artifacts search QUERY`, optionally with `--project NAME`, instead of listing and reading every artifact. Every term must match, and letter case and accents are ignored. Read a hit by its `artifactId`, or by `--project` and `--slug` when it has a slug. Search covers only owned Markdown and text artifacts at their head revision.
40
+ To find a page by what it says, run `artifacts search QUERY`, optionally with `--project NAME`, instead of listing and reading every artifact. Every term must match, and letter case and accents are ignored. Read a hit by its `artifactId`, or by `--project` and `--slug` when it has a slug. Search covers owned artifacts at their head revision. Markdown and text match by title, description, and content. HTML matches by title, description, and visible text, without tags, attributes, scripts, or styles. A design matches by title and description only. Attachments, older revisions, and shared artifacts are not searched.
41
+
42
+ To change several pages together, such as an ingest that creates pages and updates the index and log, or a reorganization that moves pages and renames their slugs, write one batch command file and run `artifacts batch --command FILE` instead of one command per page. To patch a page that already exists, such as appending to the log, use an `edit` operation with only the changed fields instead of restating its full snapshot. All operations commit or none do, and a failure names the 0-based `index` of the failing operation. Fix that operation and send the corrected batch in a file with a new `requestId`, because the CLI already retained the failed one. A batch never publishes.
41
43
 
42
44
  Use `move ID --project NAME` to file an existing artifact under a project and `move ID --no-project` to remove it from one. A move changes only the project. It creates no revision and leaves the publication unchanged. It is a retained mutation, so recover an uncertain move with `retry`. Verify it with `read` or `list --project NAME`. A project disappears when its last artifact leaves it.
43
45
 
@@ -132,7 +132,17 @@ artifacts search "tarde envigado" --project "mi wiki"
132
132
  artifacts search envigado --limit 5 --cursor 5
133
133
  ```
134
134
 
135
- `search QUERY` finds owned Markdown and text artifacts whose title, description, or content at the head revision contains every term. Quote a query with spaces. The query is plain text of 1 to 256 characters. 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. `--project` matches regardless of letter case. `--limit` is 1 to 25 and defaults to 10. 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; `null` means there are no more results.
135
+ `search QUERY` finds owned artifacts whose head revision contains every term. Markdown and text match by title, description, and content. HTML matches by title, description, and visible text, without tags, attributes, scripts, or styles. A design matches by title and description only. Quote a query with spaces. The query is plain text of 1 to 256 characters. 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. Attachments, older revisions, and artifacts shared with the caller are not searched. `--project` matches regardless of letter case. `--limit` is 1 to 25 and defaults to 10. 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; `null` means there are no more results.
136
+
137
+ ## Apply a batch
138
+
139
+ Check that installed help lists `batch` before using it.
140
+
141
+ ```sh
142
+ artifacts batch --command ingest.json
143
+ ```
144
+
145
+ The file holds `{ requestId, operations }`. Each operation is `{ op: 'create', snapshot, project?, slug? }`, `{ op: 'update', snapshot, expectedRevisionId? }` addressed by exactly one of `artifactId` or `slug`, with `project` only alongside `slug`, `{ op: 'edit', changes }` with the same address as `update`, or `{ op: 'move', project?, slug? }` addressed by exactly one of `artifactId` or `from: { project?, slug }`. An edit takes the `changes` of `edit --changes`: metadata and content fields merge into the head snapshot, and an explicit `files` array replaces the attachments. The CLI reads the head of each edited page before submission and sends the edit as an `update` with that head as `expectedRevisionId`. An edit therefore addresses a page that exists before the batch: an edit of a page that does not exist yet fails with `not_found` and its `index` before anything is retained or sent. If the page changed after the CLI read it, or an earlier operation in the same batch writes it, the batch fails with `conflict` at the edit's `index`. Use `update` for a page that the batch already creates or writes. A move takes the `move` fields: it needs `project`, `slug`, or both, an absent field keeps its value, and `null` clears it. A slug may name a page created earlier in the same batch, and later operations address a moved page by its new slug. `expectedRevisionId` must equal the head at that point in the batch, and it may be omitted only for a page that an earlier operation in the batch created or updated, whose revision ID the caller cannot know. Operations run in order, and the first failure commits nothing: its error, such as `conflict` with `currentRevisionId`, `slug_conflict`, or `not_found`, carries `index`, the 0-based position of the failing operation. Each snapshot keeps the 1 MiB and 32-file limits, and the whole batch has a 4 MiB ceiling of decoded content and metadata. The result is `{ results: [{ index, artifactId, revisionId }] }`, one entry per operation. A move creates no revision, so its `revisionId` is the head it left in place. The batch writes one receipt: the same `requestId` returns it, and a changed batch fails with `request_conflict`. Search reflects the batch when it commits. A batch never publishes. The CLI retains the prepared command, with each edit already turned into its `update`, before submission, so `artifacts retry REQUEST_ID` replays a lost batch exactly as it was prepared.
136
146
 
137
147
  ## Edit without losing retained data
138
148
 
package/src/index.js CHANGED
@@ -8,7 +8,7 @@ import { parseArgs } from 'node:util';
8
8
  import { z } from 'zod';
9
9
  import { createSchema, listSharesSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
10
10
  import { canonicalResource } from '@yargote/artifacts-contracts/http';
11
- import { createArtifactHttpClient, HttpError, prepareArtifactEdit } from '@yargote/artifacts-client';
11
+ import { BatchOperationError, createArtifactHttpClient, HttpError, prepareArtifactEdit, prepareBatch } from '@yargote/artifacts-client';
12
12
  import { accessToken, login, logout } from "./auth.js";
13
13
  import { CredentialStore } from "./credentials.js";
14
14
  import { loadOperation, retainOperation, submitOperation } from "./operations.js";
@@ -45,6 +45,7 @@ Commands:
45
45
  [--response full|compact]
46
46
  update [ID] --command FILE
47
47
  edit ID --changes FILE [--request-id ID]
48
+ batch --command FILE
48
49
  history ID [--limit N] [--before-sequence N] [--summary]
49
50
  publish ID --revision REV --expected-version N [--request-id ID]
50
51
  publish [ID] --command FILE
@@ -56,7 +57,8 @@ Commands:
56
57
  unshare ID SHARE_ID [--request-id ID]
57
58
  retry REQUEST_ID
58
59
 
59
- Mutation command files contain the complete create, update, or publish JSON.
60
+ Mutation command files contain the complete create, update, publish, or batch
61
+ JSON.
60
62
  Mutation request IDs default to a random UUID. Commands are retained before
61
63
  submission. Retry sends the exact retained command for the same server and owner.
62
64
  Projects group artifacts by name without regard to letter case. A project
@@ -68,9 +70,21 @@ project keeps the slug.
68
70
  A slug such as concepts/rag names an artifact within its project, or within
69
71
  the unfiled artifacts without --project. Read --slug finds it without its ID.
70
72
  Create or move fails with slug_conflict when the project already uses the slug.
71
- Search finds owned Markdown and text artifacts whose title, description, or
72
- content contains every term of QUERY, ignoring letter case and accents. Quote a
73
- QUERY with spaces. Results are ordered by relevance with a **bold** snippet.
73
+ Search finds owned artifacts whose head contains every term of QUERY, ignoring
74
+ letter case and accents. It reads Markdown and text content, the visible text
75
+ of HTML, and the title and description of every kind. Quote a QUERY with
76
+ spaces. Results are ordered by relevance with a **bold** snippet.
77
+ Batch applies 1 to 25 create, update, edit, and move operations from one command
78
+ file with a requestId and operations. All succeed or none do. A failure names the
79
+ 0-based index of its operation. An update addresses its artifact by artifactId or
80
+ by project and slug, including a page created earlier in the batch, and omits
81
+ expectedRevisionId only for such a page. An edit takes the same address and the
82
+ changes of edit --changes for a page that exists before the batch. The CLI reads
83
+ its head and sends an update with that expectedRevisionId, so a page changed
84
+ meanwhile, or written earlier in the batch, fails with conflict; use update for
85
+ such a page. Retry sends the prepared batch. A move addresses its artifact by
86
+ artifactId or by from { project, slug } and takes the move project and slug.
87
+ Later operations address a moved page by its new slug. A batch never publishes.
74
88
  Read --out saves the returned artifact JSON. Export --out saves the ZIP.
75
89
  Read --quiet requires --out and suppresses successful JSON stdout.
76
90
  Output files are created exclusively and never overwrite an existing path.
@@ -101,7 +115,7 @@ const commandOptions = {
101
115
  create: ['snapshot', 'project', 'slug', 'request-id', 'response', 'command'],
102
116
  list: ['limit', 'cursor', 'project'], projects: [], search: ['project', 'limit', 'cursor'], read: ['slug', 'project', 'revision', 'response', 'out', 'quiet'],
103
117
  update: ['snapshot', 'expected-revision', 'request-id', 'response', 'command'],
104
- edit: ['changes', 'request-id'], history: ['limit', 'before-sequence', 'summary'],
118
+ edit: ['changes', 'request-id'], batch: ['command'], history: ['limit', 'before-sequence', 'summary'],
105
119
  publish: ['revision', 'expected-version', 'request-id', 'command'],
106
120
  share: ['email', 'notify', 'request-id'], shares: [], unshare: ['request-id'],
107
121
  export: ['revision', 'out'], move: ['project', 'no-project', 'slug', 'no-slug', 'request-id'], retry: [],
@@ -230,9 +244,9 @@ async function main() {
230
244
  if (extra.length)
231
245
  throw new UsageError('--command cannot be combined with mutation options');
232
246
  const value = await jsonFile(required('command'));
233
- if (kind === 'create') {
247
+ if (kind === 'create' || kind === 'batch') {
234
248
  none();
235
- return { kind, command: createSchema.parse(value) };
249
+ return kind === 'create' ? { kind, command: createSchema.parse(value) } : { kind, command: await prepareBatch(client, value) };
236
250
  }
237
251
  const mutation = kind === 'update' ? { kind, command: updateSchema.parse(value) } : { kind, command: publishSchema.parse(value) };
238
252
  const id = argument(false);
@@ -290,6 +304,7 @@ async function main() {
290
304
  const input = await prepareArtifactEdit(client, { artifactId, requestId: values['request-id'] ?? randomUUID(), changes: changes });
291
305
  return mutate({ kind: 'update', command: updateSchema.parse(input) });
292
306
  }
307
+ case 'batch': return mutate(await commandFile('batch'));
293
308
  case 'history': return client.history({ artifactId: argument(), ...(values.limit === undefined ? {} : { limit: integer('limit') }), ...(values['before-sequence'] === undefined ? {} : { beforeSequence: integer('before-sequence') }), ...(values.summary ? { response: 'summary' } : {}) });
294
309
  case 'publish': {
295
310
  if (values.command)
@@ -327,17 +342,23 @@ async function main() {
327
342
  }
328
343
  }
329
344
  }
345
+ function errorBody(error) {
346
+ if (error instanceof BatchOperationError) {
347
+ const body = errorBody(error.cause);
348
+ return { ...body, details: { ...body.details, index: error.index } };
349
+ }
350
+ return error instanceof HttpError
351
+ ? { code: error.code, message: error.message, status: error.status, details: error.details }
352
+ : error instanceof z.ZodError
353
+ ? { code: 'invalid_input', message: 'Input does not match the artifact command schema', details: { issues: error.issues } }
354
+ : { code: error instanceof UsageError || error instanceof TypeError || error instanceof SyntaxError ? 'invalid_arguments' : 'local_error', message: error instanceof Error ? error.message : 'Command failed', details: {} };
355
+ }
330
356
  try {
331
357
  const result = await main();
332
358
  if (result !== undefined)
333
359
  process.stdout.write(`${JSON.stringify(result)}\n`);
334
360
  }
335
361
  catch (error) {
336
- const body = error instanceof HttpError
337
- ? { code: error.code, message: error.message, status: error.status, details: error.details }
338
- : error instanceof z.ZodError
339
- ? { code: 'invalid_input', message: 'Input does not match the artifact command schema', details: { issues: error.issues } }
340
- : { code: error instanceof UsageError || error instanceof TypeError || error instanceof SyntaxError ? 'invalid_arguments' : 'local_error', message: error instanceof Error ? error.message : 'Command failed', details: {} };
341
- process.stderr.write(`${JSON.stringify({ error: body })}\n`);
362
+ process.stderr.write(`${JSON.stringify({ error: errorBody(error) })}\n`);
342
363
  process.exitCode = 1;
343
364
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
2
+ import { batchSchema, createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
3
3
  import type { ArtifactHttpClient } from '@yargote/artifacts-client';
4
4
  export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5
5
  kind: z.ZodLiteral<"create">;
@@ -123,6 +123,84 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
123
123
  version: z.ZodLiteral<1>;
124
124
  resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
125
125
  ownerId: z.ZodString;
126
+ }, z.core.$strict>, z.ZodObject<{
127
+ kind: z.ZodLiteral<"batch">;
128
+ command: z.ZodObject<{
129
+ requestId: z.ZodString;
130
+ operations: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
131
+ op: z.ZodLiteral<"create">;
132
+ snapshot: z.ZodObject<{
133
+ metadata: z.ZodObject<{
134
+ title: z.ZodString;
135
+ description: z.ZodString;
136
+ custom: z.ZodOptional<z.ZodJSONSchema>;
137
+ }, z.core.$strict>;
138
+ content: z.ZodObject<{
139
+ kind: z.ZodEnum<{
140
+ text: "text";
141
+ html: "html";
142
+ markdown: "markdown";
143
+ design: "design";
144
+ }>;
145
+ text: z.ZodString;
146
+ }, z.core.$strict>;
147
+ files: z.ZodArray<z.ZodObject<{
148
+ path: z.ZodString;
149
+ mediaType: z.ZodString;
150
+ encoding: z.ZodEnum<{
151
+ utf8: "utf8";
152
+ base64: "base64";
153
+ }>;
154
+ data: z.ZodString;
155
+ }, z.core.$strict>>;
156
+ }, z.core.$strict>;
157
+ project: z.ZodOptional<z.ZodString>;
158
+ slug: z.ZodOptional<z.ZodString>;
159
+ }, z.core.$strict>, z.ZodObject<{
160
+ expectedRevisionId: z.ZodOptional<z.ZodString>;
161
+ snapshot: z.ZodObject<{
162
+ metadata: z.ZodObject<{
163
+ title: z.ZodString;
164
+ description: z.ZodString;
165
+ custom: z.ZodOptional<z.ZodJSONSchema>;
166
+ }, z.core.$strict>;
167
+ content: z.ZodObject<{
168
+ kind: z.ZodEnum<{
169
+ text: "text";
170
+ html: "html";
171
+ markdown: "markdown";
172
+ design: "design";
173
+ }>;
174
+ text: z.ZodString;
175
+ }, z.core.$strict>;
176
+ files: z.ZodArray<z.ZodObject<{
177
+ path: z.ZodString;
178
+ mediaType: z.ZodString;
179
+ encoding: z.ZodEnum<{
180
+ utf8: "utf8";
181
+ base64: "base64";
182
+ }>;
183
+ data: z.ZodString;
184
+ }, z.core.$strict>>;
185
+ }, z.core.$strict>;
186
+ artifactId: z.ZodOptional<z.ZodString>;
187
+ project: z.ZodOptional<z.ZodString>;
188
+ slug: z.ZodOptional<z.ZodString>;
189
+ op: z.ZodLiteral<"update">;
190
+ }, z.core.$strict>, z.ZodObject<{
191
+ op: z.ZodLiteral<"move">;
192
+ artifactId: z.ZodOptional<z.ZodString>;
193
+ from: z.ZodOptional<z.ZodObject<{
194
+ project: z.ZodOptional<z.ZodString>;
195
+ slug: z.ZodString;
196
+ }, z.core.$strict>>;
197
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
198
+ slug: z.ZodOptional<z.ZodNullable<z.ZodString>>;
199
+ }, z.core.$strict>], "op">>;
200
+ }, z.core.$strict>;
201
+ version: z.ZodLiteral<1>;
202
+ resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
203
+ ownerId: z.ZodString;
126
204
  }, z.core.$strict>], "kind">;
127
205
  export type PreparedOperation = z.infer<typeof operationSchema>;
128
206
  export type Mutation = {
@@ -143,11 +221,20 @@ export type Mutation = {
143
221
  } | {
144
222
  kind: 'unshare';
145
223
  command: z.input<typeof unshareSchema>;
224
+ } | {
225
+ kind: 'batch';
226
+ command: z.input<typeof batchSchema>;
146
227
  };
147
228
  export declare function operationPath(stateDir: string, resource: string, requestId: string): string;
148
229
  export declare function retainOperation(stateDir: string, resource: string, ownerId: string, mutation: Mutation): Promise<PreparedOperation>;
149
230
  export declare function loadOperation(stateDir: string, requestId: string, resource: string, ownerId: string): Promise<PreparedOperation>;
150
231
  export declare function submitOperation(client: ArtifactHttpClient, operation: PreparedOperation): Promise<void> | Promise<{
232
+ results: {
233
+ index: number;
234
+ artifactId: string;
235
+ revisionId: string;
236
+ }[];
237
+ }> | Promise<{
151
238
  shareId: string;
152
239
  artifactId: string;
153
240
  kind: "email";
package/src/operations.js CHANGED
@@ -3,7 +3,7 @@ import { lstat, mkdir, open } from 'node:fs/promises';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
5
  import { z } from 'zod';
6
- import { createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
6
+ import { batchSchema, createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
7
7
  import { canonicalResource } from '@yargote/artifacts-contracts/http';
8
8
  const binding = {
9
9
  version: z.literal(1),
@@ -17,6 +17,7 @@ export const operationSchema = z.discriminatedUnion('kind', [
17
17
  z.strictObject({ ...binding, kind: z.literal('move'), command: moveSchema }),
18
18
  z.strictObject({ ...binding, kind: z.literal('share'), command: shareSchema }),
19
19
  z.strictObject({ ...binding, kind: z.literal('unshare'), command: unshareSchema }),
20
+ z.strictObject({ ...binding, kind: z.literal('batch'), command: batchSchema }),
20
21
  ]);
21
22
  export function operationPath(stateDir, resource, requestId) {
22
23
  createSchema.shape.requestId.parse(requestId);
@@ -92,6 +93,7 @@ export function submitOperation(client, operation) {
92
93
  case 'move': return client.move(operation.command);
93
94
  case 'share': return client.share(operation.command);
94
95
  case 'unshare': return client.unshare(operation.command);
96
+ case 'batch': return client.batch(operation.command);
95
97
  }
96
98
  const exhaustive = operation;
97
99
  throw new Error(`Unsupported retained operation: ${exhaustive}`);