Skip to content

Commit c731e43

Browse files
committed
A5 done: batch service — receipts and reverse batches
services/batch ports gh-v1's preview-snapshot-send-receipt pipeline as data, leaving file I/O, console output, permalinks and the prompt to the CLI (confirm is a callback between the snapshot and the send). runBatch: refuse what cannot be guarded (unless guard: false; a bare update/execute from an alias always), preview, describe, name the touched assets (each type asked only for the Name/Number it has, per the schema), snapshot exactly what the batch overwrites plus ChangeDate, send guarded on those ChangeDates, then a receipt: outcome, created with aliases and numbers, touched, skipped, after-stamps and the reverse batch. dryRun stops after the snapshot. A send that fails outright throws BatchSendError carrying the receipt so far: whether it applied is unknown. reversible() drops from a receipt's reverse the assets changed since (their ChangeDate is no longer the after-stamp) unless forced. Services now read results through assetApi.outcomeOf (created, modified, operated, failures, query rows) and never the envelope; the layers test enforces that for services, as spec 000 §1 already said. Live: a guarded batch updating two Stories (scalar and multi-relation) and creating a Task, its receipt, and its reverse sent back through runBatch restore both Stories and delete the Task; a reverse skips an asset another writer changed unless forced. Plan C3 now maps gh-v1's resolve.js/batch.js onto these. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016NSWj65GY2KaXwFjZ9Jzyb
1 parent 2046128 commit c731e43

11 files changed

Lines changed: 546 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ npm test
2121

2222
## Public API
2323

24-
- `AgilityApiClient` - top-level SDK client (`meta`, `data` for rest-1, `assets` for api/asset, `localize` for loc-2, `schema`)
24+
- `AgilityApiClient` - top-level SDK client (`meta`, `data` for rest-1, `assets` for api/asset, `localize` for loc-2, `schema`), and the services on it:
25+
- `resolve.numbers` / `resolve.ref` - asset numbers and OID tokens to assets, in one request
26+
- `assets.sendGuarded` - a batch as a compare-and-set on each asset's ChangeDate; changed assets come back `skipped`
27+
- `batch.run` / `batch.reversible` - preview, snapshot, (confirm,) send guarded, and a receipt with a reverse batch
2528
- `createFetchTransport` - configurable HTTP transport
2629
- `ApiError` - typed API error; `CommandFailureError` - an api/asset batch in which commands failed (`failures` by index, plus the `response`)
2730

‎plans/001-sdk-adoption-campaign.md‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,16 @@ instance: same requests out, same rows in.
230230
- **C2 Delete the decodes.** With rows translated by the schema, `isTrue`
231231
and `oidOf` drop out of `meta.js`, `resolve.js`, `board.js`; `momentless`
232232
comes from the SDK.
233-
- **C3 Invert the services.** Once A5 lands, `resolve.js` and `batch.js`
234-
keep only prompting, rendering and receipt files; the logic is imported.
233+
- **C3 Invert the services.** A5 is done, so `resolve.js` and `batch.js`
234+
keep only prompting, console rendering, permalinks and receipt files:
235+
`resolveAssets` → `client.resolve.numbers` (its `missing` drives the
236+
"not found — skipped" lines); `run()` → `client.batch.run` with
237+
`confirm` as the prompt and the receipt written as returned;
238+
`--reverse` → `client.batch.reversible` (+ `--force`). Two deliberate
239+
differences to carry over: the SDK refuses an unguardable batch unless
240+
`guard: false` (gh-v1 sent it as written with a note — keep that UX by
241+
checking `unguardable()` first), and its number roots are wider than
242+
gh-v1's list.
235243
- **C4 Passthrough.** `raw`/`blob` stay CLI-local unless an `attachment`
236244
wire module (gap 5) is built for rooms — then `gh v1 attach` uses it.
237245

@@ -347,7 +355,7 @@ ESM" here means finishing the edges:
347355
- [x] A2 command-failure error (`CommandFailureError`, `throwOnFailure`, reads always throw)
348356
- [x] A3 loc2 (`loc2.localize`, `client.localize`; GET, POST past 2000 encoded chars, culture via Accept-Language)
349357
- [ ] A4 slack-shaped reads (queryV1 or api/asset)
350-
- [~] A5 services port from gh-v1 — `resolve` done (`client.resolve.numbers`/`.ref`); `guard` done (`client.assets.sendGuarded`); `batch` to come
358+
- [x] A5 services port from gh-v1 — `resolve` (`client.resolve.numbers`/`.ref`), `guard` (`client.assets.sendGuarded`), `batch` (`client.batch.run`/`.reversible`)
351359
- [ ] A6 CI + prerelease publish
352360
- [ ] G0–G3 gh-v1 migrations (gh-v1 session)
353361
- [ ] S1–S4 slack-bot migrations (slack-bot session, `bolt-migration`)

‎src/client.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ClientConfig } from "./core/clientConfig";
22
import { createFetchTransport } from "./core/fetchTransport";
33
import type { Transport } from "./core/transport";
4+
import * as batch from "./services/batch";
45
import * as guard from "./services/guard";
56
import * as resolve from "./services/resolve";
67
import { Schema } from "./services/schema";
@@ -28,7 +29,7 @@ export interface ClientOptions {
2829
* them. Every api/asset read goes through the schema so its rows arrive in
2930
* the model's shape (numbers as numbers, a single relation as one Ref).
3031
* Services ported from gh-v1 are exposed here the same way: resolve, the
31-
* guard (assets.sendGuarded); batch to come.
32+
* guard (assets.sendGuarded), batch.
3233
*/
3334
export class AgilityApiClient {
3435
readonly transport: Transport;
@@ -74,6 +75,17 @@ export class AgilityApiClient {
7475
ref: (spec: string) => resolve.resolveRef(this.transport, spec)
7576
};
7677

78+
/**
79+
* A batch with a record: preview, snapshot what it overwrites, (confirm,)
80+
* send guarded, and a receipt with created/touched/skipped assets and a
81+
* reverse batch. `reversible` drops from a receipt's reverse the assets
82+
* changed since, unless forced.
83+
*/
84+
readonly batch = {
85+
run: (commands: assetApi.Command[], options?: batch.BatchOptions) => batch.runBatch(this.transport, this.schema, commands, options),
86+
reversible: (receipt: Pick<batch.Receipt, "reverse" | "after">, options?: { force?: boolean }) => batch.reversible(this.transport, receipt, options)
87+
};
88+
7789
/** rest-1.v1: bounded reads, one asset, history. */
7890
readonly data = {
7991
list: <Row extends Asset = Asset>(type: string, params: rest1.ReadParams) => rest1.list<Row>(this.transport, type, params),

‎src/index.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type * from "./types/assets/index";
1616
// The wire layer: one module per endpoint.
1717
export { assetApi, rest1, meta, loc2 } from "./wire";
1818
export * as select from "./wire/select";
19-
export type { AssetApiResponse, Command, CommandBody, CommandFailureDetail, CommandValue, CreateBody, ExecuteBody, QueryBody, UpdateBody, ReadOptions, RowContext, SendOptions } from "./wire/assetApi";
19+
export type { AssetApiResponse, Command, CommandBody, CommandFailureDetail, Outcome, CommandValue, CreateBody, ExecuteBody, QueryBody, UpdateBody, ReadOptions, RowContext, SendOptions } from "./wire/assetApi";
2020
export { CommandFailureError } from "./wire/assetApi";
2121
export type { AssetDocument, AssetsDocument, ReadParams, ReadResult } from "./wire/rest1";
2222
export type { Localizations, LocalizeOptions } from "./wire/loc2";
@@ -27,3 +27,5 @@ export { CHUNK as RESOLVE_CHUNK, NUMBER_ROOTS, isAssetNumber, resolveNumbers, re
2727
export type { Resolution, ResolvedAsset } from "./services/resolve";
2828
export { UNGUARDABLE_TYPES, UnguardableError, guard, sendGuarded, stampsOf, touchedBy, unguardable } from "./services/guard";
2929
export type { GuardedResult, Unguardable } from "./services/guard";
30+
export { BatchSendError, INVERSE, describe as describeBatch, namesOf, reverseOf, reversible, runBatch, snapshot } from "./services/batch";
31+
export type { BatchOptions, Described, Named, Receipt, Reverse } from "./services/batch";

0 commit comments

Comments
 (0)