Add @weftsh/sdk: the TypeScript SDK for Weft - #1
Merged
Merged
Conversation
A small, dependency-free client for the Weft Repos API, shaped after the code.storage SDK: a `Weft` client, a `Repo` handle, and one method per thing you want to do. - Repositories: create (with a generated name by default), find, hydrate without a request, list and iterate, batch create/delete, update, delete, fork; mirrors. - Commits through a builder (`createCommit().put().delete().send()`): strings go as `put`, bytes as `put_base64`, `expectedParent` maps to the server's three cases (omitted / SHA / null), and a 409 surfaces as `WeftConflictError` with `currentTip` from either conflict shape the server uses (`current_tip` on commits, `current` on reset/revert). - Reads: files with ETag round trips and the X-Weft-* headers, trees, flat listings, paginated and path-filtered history, diffs. - Refs, reset/revert, tokens, webhooks, bundle export. - `getRemoteURL()` mints a short-lived token bound to one repository and embeds it, so a sandbox can clone and push without the client's token. - `verifyWebhook()` on Web Crypto, for X-Weft-Signature-256. Tested two ways. Unit tests pin what goes on the wire. The e2e suite runs against a real Weft server: it clones and pushes with the real git CLI through getRemoteURL, runs `git fsck --full --strict`, proves a read-only URL cannot push and a repo-scoped credential cannot reach a sibling repository, and verifies a webhook delivery the server itself signed. Breaking the `expected_parent` mapping turns four e2e cases red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYuyBuroUvxh32x9SCbx13
… null
getFile and readFile mapped every 404 to `null`. But the server answers
three different misses with a 404: a path not at that revision and an
unknown revision both as JSON `{ "error": … }`, and a repository that
does not exist — or that the caller cannot see — as a bare `not found`.
So `weft.repo('tpyo').readFile('README.md')` resolved to `null`, which
reads as "the file is not there" when the truth is "you named the wrong
repository", and an agent would go on to write that file into a
repository that does not exist.
Only a 404 with a JSON error body is now treated as a miss inside the
repository; anything else is rethrown. Pinned by a unit test on the
wire shape and by an e2e case against the real server, which also
checks that an unknown branch still reads as null.
Also says in the types and README that `branch` defaults to "main"
rather than the repository's default branch — the server's default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYuyBuroUvxh32x9SCbx13
CI failed with "Weft: `token` must be a non-empty string" on every Node version. `describe.skipIf` skips the tests, but vitest still runs the describe body to collect them, and the body built a `Weft` from the unset WEFT_E2E_TOKEN — so the constructor's own validation threw and failed the file. The local "skips cleanly" check only unset the URL, so the token was still in the environment and the failure never showed. The client is now built only when the suite is enabled. Checked with all three variables unset: 26 passed, 17 skipped, and every CI step passes from a fresh `npm ci`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYuyBuroUvxh32x9SCbx13
…oteURL The README opened with a snippet and went straight into reference. It now has a Quickstart: get a token (with the scopes it actually needs), install, save and run examples/quickstart.mts, see the output. The script creates a repository, commits over HTTP, reads the file back and clones it with the real git CLI through getRemoteURL(). The README embeds the script verbatim between markers; a unit test fails if the two differ, and the e2e suite runs the script as a subprocess against a live server, importing '@weftsh/sdk' by name from the build. tsconfig maps '@weftsh/sdk' to src so the example typechecks before a build. Two things the quickstart surfaced: - The README said getRemoteURL() needs "a personal token whose owner can write to the repository". Not enough: minting also needs org:read on the calling token. Checked against the server with real personal tokens: org:read + repo:write runs the whole quickstart; repo:write alone fails at step 4. - That failure surfaced as "POST /v1/orgs/acme/tokens answered 404", because the server masks what the caller cannot see. getRemoteURL now rethrows a 400/403/404 from the mint as a WeftError that names the scopes needed, keeping the status, body and the original as cause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYuyBuroUvxh32x9SCbx13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first release (0.1.0) of
@weftsh/sdk, a small TypeScript client for the Weft Repos API. It's shaped after the code.storage SDK: aWeftclient, aRepohandle, and one method per action.createCommit().put().delete().send()). Strings are sent asputand bytes asput_base64.expectedParentcovers the server's three cases: omitted, a SHA, ornull(the branch must not exist yet). A 409 throwsWeftConflictError, and itscurrentTipis read from either conflict shape the server returns (current_tipfor commits,currentfor reset/revert).X-Weft-*headers; trees; flat file lists; paginated history, optionally filtered by path; diffs.reset,revert.getRemoteURL(): each call creates a short-lived token bound to that one repository and puts it in the URL. A sandbox can clone and push without the client's own token. If the mint is refused (400/403/404), it throws aWeftErrorthat names the scopes needed rather than passing on the server's masked 404.verifyWebhook(): checksX-Weft-Signature-256using Web Crypto.examples/quickstart.mtsandexamples/agent-session.ts; and a CI workflow that runs typecheck, tests and build on Node 20/22/24 and checks that both module formats load.readFileandgetFilereturnnullonly for a missing path or revision. A missing (or invisible) repository throws a 404, because the server answers that case with a barenot foundrather than a JSON error. Without this, a typo in a repository name looked like a missing file.Quickstart
The README opens with a Quickstart:
examples/quickstart.mts, which creates a repository, commits, reads the file back and clones it with the realgitCLI.The README embeds the script verbatim. A unit test fails if the two differ, and the e2e suite runs the script as a subprocess against a live server.
Writing it surfaced a wrong claim. The README said a personal token "whose owner can write" can call
getRemoteURL(), but minting also needsorg:read. I checked with real personal tokens:org:read+repo:writeruns the whole quickstart;repo:writealone fails at the clone step. The README is corrected, and that failure now explains itself.Testing
fetch. They also check that the README quickstart matches the example file; a one-character drift turns that test red.stratum-core. They are skipped unlessWEFT_E2E_URL,WEFT_E2E_TOKENandWEFT_E2E_ORGare set. Among other things they:gitCLI throughgetRemoteURL, and rungit fsck --full --stricton the clone;null;expected_parentmapping turned 4 end-to-end tests and 2 unit tests red.npm run check(typecheck, tests, build) passes. All 50 tests pass against a local server, and with no server set, 32 pass and 18 skip.Before publishing
@weftshnpm scope needs to belong to us.🤖 Generated with Claude Code
https://claude.ai/code/session_01SYuyBuroUvxh32x9SCbx13