Skip to content

meta.saveItem's own docstring tells the caller to echo version back as an If-Match header — the SDK offers no way to send one, on either declaration #11713

Description

@os-zhuang

Measured while implementing #11391. Same defect shape as that card — the SDK prescribing something its own surface cannot do — one parameter over, so filed separately rather than folded: #11391's ruling enumerated exactly three query parameters (force, packageId, mode) and this is a fourth carrier, a request header, which is a contract decision of its own.

What was measured

The docstring directly above meta.saveItem in packages/client/src/index.ts reads:

The resolved version is the ADR-0008 optimistic-concurrency token: echo it back as the If-Match request header on the next write to the same item and a concurrent edit is reported as 409 metadata_conflict instead of silently overwriting.

Neither saveItem declaration accepts a header, an ifMatch option, or anything else that becomes one:

$ grep -n "If-Match\|ifMatch" packages/client/src/index.ts
696: * echo it back as the `If-Match` request header on the next write to the
939: * `If-Match` on the next write to the item. It is nameable here only since
1295: * `version` is the ADR-0008 OCC token (echo as `If-Match`), and the
4689: opts?: { ifMatch?: string },
4698: if (opts?.ifMatch) headers['If-Match'] = String(opts.ifMatch);
4787: opts?: { ifMatch?: string },
4792: if (opts?.ifMatch) headers['If-Match'] = String(opts.ifMatch);

Lines 696 / 939 / 1295 are the three places the SDK tells a caller to use If-Match. Lines 4689–4792 are two other methods that actually implement it — so this is an absent option on saveItem, not a client that cannot send the header.

The server side is live: the REST PUT handler reads it and threads it as parentVersion:

const ifMatchHeader = req.headers?.['if-match'] ?? req.headers?.['If-Match'];
const parentVersion = typeof ifMatchHeader === 'string'
? ifMatchHeader.replace(/^"|"$/g, '')
: undefined;

So the ADR-0008 optimistic-concurrency protection the docstring describes is reachable over raw HTTP and unreachable through @objectstack/client — a first-party SDK caller who follows the instruction has nowhere to put the token, and their concurrent edit silently overwrites instead of answering 409 metadata_conflict. The sibling first-party client MetadataClient in @object-ui/data-objectstack carries ifMatch in its own save options for exactly this reason.

Why it was not folded into #11391

That card's ruling names the three query parameters and the options bag that carries them. If-Match is a header rather than a query parameter, and adding it widens the published SDK surface a fourth time beyond what was ruled — so it is reported rather than taken. The bag landed by #11391 (SaveMetaItemOptions) is the natural place for it if that is ruled in.

Where it would land

packages/client/src/index.ts — both saveItem declarations, which must stay in step — plus pins in packages/client/src/client.test.ts on the headers the methods build (the existing #11391 pins measure the URL only).

Related: #11391 (the query-string half), and ADR-0008 for the OCC contract itself.

Measured on origin/main at 4ceae8ab0.


Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions