Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ permissions:

jobs:
verify:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [20, 22, 24]
# Windows too: tools that read paths as globs (tsup's entries) break
# on backslashes, and people develop the SDK there.
include:
- os: windows-latest
node: 24
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -24,10 +31,10 @@ jobs:
# Unit tier only: the integration tier needs a testing instance and writes fixtures.
- run: npm run verify
- name: Pack
if: matrix.node == 24
if: matrix.node == 24 && matrix.os == 'ubuntu-latest'
run: npm pack
- uses: actions/upload-artifact@v4
if: matrix.node == 24
if: matrix.node == 24 && matrix.os == 'ubuntu-latest'
with:
name: versionone-sdk-tarball
path: versionone-sdk-*.tgz
name: versionone-agility-sdk-tarball
path: versionone-agility-sdk-*.tgz
60 changes: 60 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Publish

# Publishes @versionone/agility-sdk to npmjs when a version tag is pushed:
# git tag v1.0.0-alpha.0 && git push origin v1.0.0-alpha.0
#
# A prerelease (1.0.0-alpha.0, -beta.1, -rc.2) goes out on its own dist-tag
# (alpha, beta, rc), so `npm install @versionone/agility-sdk` never picks it
# up; only a plain version moves `latest`.
#
# Authentication is npm trusted publishing (OIDC): link this repository and
# this workflow file on the package's npmjs settings page, and no token is
# stored anywhere. The package must exist before it can be linked, so the
# very first version may need one manual publish (see the README's
# "Releasing" section); an NPM_TOKEN secret, if set, is used as a fallback.

on:
push:
tags: ["v*"]

permissions:
contents: read
id-token: write # OIDC for trusted publishing and provenance

concurrency:
group: publish
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
# Trusted publishing needs npm 11.5.1 or later.
- run: npm install -g npm@^11.5.1
- run: npm ci
- name: The tag names the version in package.json
run: |
version="$(node -p "require('./package.json').version")"
if [ "v${version}" != "${GITHUB_REF_NAME}" ]; then
echo "Tag ${GITHUB_REF_NAME} does not match package.json version ${version}" >&2
exit 1
fi
echo "VERSION=${version}" >> "$GITHUB_ENV"
- run: npm run verify
- name: Choose the dist-tag
run: |
case "${VERSION}" in
*-*) tag="$(echo "${VERSION#*-}" | sed 's/[.0-9]*$//')" ;;
*) tag=latest ;;
esac
if [ -z "${tag}" ]; then tag=next; fi
echo "DIST_TAG=${tag}" >> "$GITHUB_ENV"
echo "Publishing ${VERSION} with dist-tag ${tag}"
- run: npm publish --provenance --access public --tag "${DIST_TAG}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2012-2026 Digital.ai Software, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 39 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
# VersionOne SDK (TypeScript starter)
# @versionone/agility-sdk

Minimal TypeScript SDK starter for building JavaScript bindings to external APIs.
TypeScript SDK for the Digital.ai Agility (formerly VersionOne) API. It works in Node (ESM or CommonJS) and the browser, ships its own types, and has no runtime dependencies.

## Quick start
Not to be confused with `v1sdk`, the 2.x JavaScript SDK: this is a new API, not a compatible successor.

## Install

Alpha releases are for feedback; the API may change between them.

```bash
npm install @versionone/agility-sdk@alpha
```

```ts
import { AgilityApiClient } from "@versionone/agility-sdk";

const client = new AgilityApiClient({ baseUrl: "https://www14.v1host.com/v1sdktesting", accessToken: process.env.V1_ACCESS_TOKEN });
const stories = await client.queryV1.query({ from: "Story", select: ["Name", "Estimate"], page: { size: 10, start: 0 } });
```

## Working on the SDK

```bash
npm install
Expand All @@ -25,7 +42,7 @@ npm test
- `queryV1.query` / `queryV1.queries` - reads through query.v1: the same query bodies as `assets.query`, rows translated the same way, but the endpoint is read-only and answers with rows alone, so it is the safe place to explore a query
- `resolve.numbers` / `resolve.ref` - asset numbers and OID tokens to assets, in one request
- `assets.sendGuarded` - a batch as a compare-and-set on each asset's ChangeDate; changed assets come back `skipped`
- `attachments.list` / `info` / `get` / `add` / `put` / `remove` / `url` - files attached to an asset: metadata on the Attachment asset, bytes through attachment.img; `add` sends a small file inline (Base64, one request) and a large one in two steps (create, then upload), and a failed upload throws `AttachmentUploadError` naming the empty attachment it left
- `attachments.list` / `page` / `info` / `get` / `add` / `put` / `remove` / `url` - files attached to an asset: metadata on the Attachment asset, bytes through attachment.img; `page` adds the total and whether more pages follow; `get`'s `contentType` is the type the server sent the bytes with; `add` sends a small file inline (Base64, one request) and a large one in two steps (create, then upload), and a failed upload throws `AttachmentUploadError` naming the empty attachment it left
- `embeddedImages.upload` / `url` / `normalize` - images in rich text: upload returns the `<img src>` URL; `normalize` rewrites `embedded.img` references to this instance's URL. They cannot be deleted
- `batch.run` / `batch.reversible` - preview, snapshot, (confirm,) send guarded, and a receipt with a reverse batch. Undo (`batch.reversible`) reactivates a closed asset before deleting it, and a second command on one asset is sent as a second guarded pass. `confirm` sees the touched assets' names; aliased updates/executes are sent as written with `guard: false`; a failure after the send throws `BatchRecordError` carrying the receipt
- `TypeCache` - one instance's loaded type definitions, shared by many clients (`new AgilityApiClient(config, { types })`): a server that builds a client per user fetches each type once
Expand All @@ -46,6 +63,24 @@ API reference: https://docs.digital.ai/agility-api/

No registry package yet. Pin a commit: `npm install github:versionone/VersionOne.SDK.JavaScript#<sha>` builds `dist` on install (the `prepare` script). CI also uploads an `npm pack` tarball on every run.

## Releasing

Versions are published to npmjs by `.github/workflows/publish.yml` when a
tag `v<version>` is pushed; the tag must equal `package.json`'s version. A
prerelease (`1.0.0-alpha.0`, `-beta.N`, `-rc.N`) goes out on its own
dist-tag (`alpha`, `beta`, `rc`), so `latest` is never a prerelease.

```bash
npm version 1.0.0-alpha.1 --no-git-tag-version # bump, commit, merge
git tag v1.0.0-alpha.1 && git push origin v1.0.0-alpha.1
```

Authentication is npm trusted publishing: on npmjs, the package's settings
link this repository and `publish.yml`, and no token is stored. A package
must exist before it can be linked, so the very first version is published
once by hand by an `@versionone` org member (`npm publish --access public
--tag alpha` from a clean checkout of the tagged commit), then linked.

## Before PR

- Run `npm run verify` to validate typecheck, build, and tests before opening a pull request.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 21 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"name": "@versionone/sdk",
"version": "0.1.0",
"description": "Minimal TypeScript SDK starter for digital.ai Agility APIs",
"name": "@versionone/agility-sdk",
"version": "1.0.0-alpha.0",
"description": "TypeScript SDK for the Digital.ai Agility (VersionOne) API: queries, batches, schema-translated rows, guarded writes, attachments",
"keywords": [
"agility",
"digital.ai",
"versionone",
"v1",
"sdk",
"typescript",
"api-client"
"api-client",
"typescript"
],
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -52,5 +56,17 @@
},
"volta": {
"node": "24.14.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/versionone/VersionOne.SDK.JavaScript.git"
},
"homepage": "https://github.com/versionone/VersionOne.SDK.JavaScript#readme",
"bugs": {
"url": "https://github.com/versionone/VersionOne.SDK.JavaScript/issues"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
14 changes: 8 additions & 6 deletions plans/001-sdk-adoption-campaign.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Plan 001: TypeScript SDK campaign and adoption

A parallel campaign, run from a cloud agent session on this repo, alongside
daily work. Goal: take `@versionone/sdk` from a verified starter (layers,
daily work. Goal: take `@versionone/agility-sdk` from a verified starter (layers,
api/asset + rest-1 + meta.v1 wire modules, schema-driven translation, 49 unit
tests green) to a package that three surfaces depend on:

Expand Down Expand Up @@ -83,7 +83,7 @@ Findings from the testing instance worth carrying into later milestones:
the `Attributes` envelope via `lib/transformDataToAsset.js`; `findAssets`
reads with `find`/`findin`/`page`), and `loc-2.v1`. Its `AGENTS.md` already
plans for this: the Bolt migration's Phase 2 rewrites the seam as
`services/v1.js` on native `fetch` "so it can migrate into `@versionone/sdk`".
`services/v1.js` on native `fetch` "so it can migrate into `@versionone/agility-sdk`".
It hand-decodes string scalars in 3 places (`"Team.HasProcess" === "True"`,
`IsKanban.toLowerCase() === "true"`) — exactly what spec 000 §2a removes.

Expand Down Expand Up @@ -196,7 +196,7 @@ Found by reading the code above against `src/`:
8. **Distribution.** No CI in this repo, no publish target, version 0.1.0.
Source is browser-safe already (no `node:` imports, no `Buffer`).
9. **CommonJS consumers — done.** `test/unit/cjs.test.ts` builds the CJS
bundle, installs it as `node_modules/@versionone/sdk`, and has a plain
bundle, installs it as `node_modules/@versionone/agility-sdk`, and has a plain
`.cjs` script `require` and use it. The exports map now gives each
condition its own types (`.d.cts` for require), which a CJS caller
type-checking with NodeNext (gh-v1 after G2) needs.
Expand Down Expand Up @@ -449,7 +449,9 @@ ESM" here means finishing the edges:
## Decisions needed

- ~~Publish target~~ — decided 2026-11-07: public npmjs, as
`@versionone/sdk` (the `@versionone` org already publishes there), starting
`@versionone/agility-sdk` (renamed from `@versionone/sdk` on 2026-11-07,
before any publish: the scope already has `ctm-pipeline-builder-sdk`, and
the product is Agility; the `@versionone` org already publishes there), starting
at `1.0.0-alpha.0` on the `alpha` dist-tag so `latest` is never a
prerelease. Not `v1sdk` 3.x: the 2.x package (`v1sdk`, last published
2022) stays as it is, because a new major of the same name would imply a
Expand All @@ -473,7 +475,7 @@ ESM" here means finishing the edges:
- [x] A4 slack-shaped reads — `queryV1` wire module, `client.queryV1.query`/`queries` (read-only, idempotent, same row translation as api/asset)
- [x] A5 services port from gh-v1 — `resolve` (`client.resolve.numbers`/`.ref`), `guard` (`client.assets.sendGuarded`), `batch` (`client.batch.run`/`.reversible`)
- [x] A7 attachments and embedded images — `attachment` wire module (`attachment.img` bytes, `EmbeddedImage.mvc/Upload`, URL helpers, browser-safe Base64), `client.attachments` (list/info/get/add/put/remove/url; inline up to `INLINE_LIMIT`, two-step above) and `client.embeddedImages` (upload/url/normalize)
- [ ] A6 CI + prerelease publish — CI (`.github/workflows/ci.yml`: verify on Node 20/22/24, `npm pack` artifact), git-SHA installs (`prepare`) and the dependency audit done; publish `@versionone/sdk@1.0.0-alpha.0` to npmjs with `--tag alpha` next (needs an org owner's token or trusted publisher)
- [ ] A6 CI + prerelease publish — CI, git-SHA installs, the dependency audit and the publish workflow (`.github/workflows/publish.yml`: on a `v*` tag, verify, dist-tag from the prerelease id, `npm publish --provenance`) done; version `1.0.0-alpha.0`. License decided 2026-11-07: MIT (`LICENSE`; `master`'s BSD 3-clause `LICENSE.md` stays with the 2.x code). Left: the first publish by hand and the trusted-publisher link on npmjs
- [x] G0–G3 gh-v1 migrations — versionone/gh-v1#29–#32
- [x] S1–S4 slack-bot migrations — digital-ai/k6i-agility-slack-bot#21–#24 (on `bolt-migration`)
- [x] B1 slack bot client swap — digital-ai/k6i-agility-slack-bot#29
Expand All @@ -484,5 +486,5 @@ ESM" here means finishing the edges:
- [x] C1 gh-v1 seam swap — versionone/gh-v1#33, with the vendored SDK's hashes, CI and Node 22 floor in #35
- [x] C2 gh-v1 rows translated through the schema; `isTrue`/`oidOf` gone — versionone/gh-v1#36
- [x] C3 gh-v1 resolve and batch on `client.resolve`/`client.batch` — versionone/gh-v1#37 (with the SDK fixes from #43)
- [ ] C4 gh-v1 attachments on `client.attachments` (unblocked: A7 done); `raw` stays CLI-local
- [x] C4 gh-v1 `gh v1 attach` on `client.attachments`, `blob` removed — versionone/gh-v1#38 (re-vendored at `d51fb16`; small files now upload inline); `raw` stays CLI-local
- [ ] D1–D4 agility-rooms
6 changes: 3 additions & 3 deletions plans/002-adopt-sdk-gh-v1.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Plan 002: gh-v1 adopts `@versionone/sdk` (Track C)
# Plan 002: gh-v1 adopts `@versionone/agility-sdk` (Track C)

Brief for a session on `versionone/gh-v1`. Start once the SDK PR that adds
this file has merged into `proposed_type_script_api_version`. Source plan:
`plans/001-sdk-adoption-campaign.md`, Track C.

## Goal

gh-v1 talks to Agility only through `@versionone/sdk`. CLI behaviour and
gh-v1 talks to Agility only through `@versionone/agility-sdk`. CLI behaviour and
output stay the same; the code that duplicates the SDK goes.

## Where things stand (checked 2026-11-06, gh-v1 `2c58c7e`)
Expand Down Expand Up @@ -34,7 +34,7 @@ runtime dependency, but its code ships inside the repo:
- `vendor/versionone-sdk/` holds the SDK's ESM build (`index.js`, which
imports nothing) and types (`index.d.ts`), plus `SOURCE.json` naming
the commit they came from. `lib/api.js` imports from there.
- `package.json` lists `@versionone/sdk` under `devDependencies`, pinned
- `package.json` lists `@versionone/agility-sdk` under `devDependencies`, pinned
to a full commit, so the lockfile records exactly what was vendored.
- To move the pin: change it in `package.json`, then
`npm install && npm run vendor:sdk`, and commit the copy.
Expand Down
8 changes: 4 additions & 4 deletions plans/003-adopt-sdk-k6i-agility-slack-bot.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Plan 003: k6i-agility-slack-bot adopts `@versionone/sdk` (Track B)
# Plan 003: k6i-agility-slack-bot adopts `@versionone/agility-sdk` (Track B)

Brief for a session on `digital-ai/k6i-agility-slack-bot`. Start once the
SDK PR that adds this file has merged into `proposed_type_script_api_version`.
Expand All @@ -7,7 +7,7 @@ Source plan: `plans/001-sdk-adoption-campaign.md`, Track B. Work on

## Goal

`services/v1.js`'s `createV1Client` runs on `@versionone/sdk`, with the same
`services/v1.js`'s `createV1Client` runs on `@versionone/agility-sdk`, with the same
exported surface, so listeners and features do not change. The bot's
hand-decoding and rest-1 write path go.

Expand All @@ -27,9 +27,9 @@ hand-decoding and rest-1 write path go.
## Dependency

No registry package yet. Pin a commit:
`"@versionone/sdk": "github:versionone/VersionOne.SDK.JavaScript#<merge sha>"`.
`"@versionone/agility-sdk": "github:versionone/VersionOne.SDK.JavaScript#<merge sha>"`.
The SDK's `prepare` script builds `dist` on install. The bot is ESM, so
`import { AgilityApiClient } from "@versionone/sdk"`.
`import { AgilityApiClient } from "@versionone/agility-sdk"`.

## Steps, one PR each

Expand Down
2 changes: 1 addition & 1 deletion plans/handoffs/gh-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Paste everything below the line into the session working on

---

Context: gh-v1 will become the first consumer of `@versionone/sdk` (the
Context: gh-v1 will become the first consumer of `@versionone/agility-sdk` (the
TypeScript rewrite of the Agility JS SDK). Before that swap, gh-v1 should
adopt these migrations so the swap later touches only `lib/api.js`. Do them
as separate PRs, in order, each with `npm test` green (99 tests
Expand Down
4 changes: 2 additions & 2 deletions plans/handoffs/k6i-agility-slack-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ in `versionone/VersionOne.SDK.JavaScript` (branch
---

Context: after the Bolt migration, `services/v1.js` (`createV1Client`) will
be re-implemented on `@versionone/sdk`, the TypeScript rewrite of the
be re-implemented on `@versionone/agility-sdk`, the TypeScript rewrite of the
Agility JS SDK, keeping its exported surface. These four migrations come
first, so that swap is one file. Work on (or branch from) `bolt-migration`,
not `main`. One PR each, `npm test` green, and follow AGENTS.md and
Expand Down Expand Up @@ -51,5 +51,5 @@ https://docs.digital.ai/agility-api/apis/asset-bulk/updating-assets
API reference for all of this: https://docs.digital.ai/agility-api/ (the
whole site as text: https://docs.digital.ai/agility-api/llms-full.txt).

Do not add `@versionone/sdk` yet (milestone B1, run from the SDK session
Do not add `@versionone/agility-sdk` yet (milestone B1, run from the SDK session
after S2 and S3 merge). Report back with the PR links.
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class AgilityApiClient {
*/
readonly attachments = {
list: (asset: string, options?: { page?: { size: number; start: number } }) => attachments.list(this.transport, asset, options),
page: (asset: string, options?: { page?: { size: number; start: number } }) => attachments.page(this.transport, asset, options),
info: (id: string | number) => attachments.info(this.transport, id),
get: (id: string | number) => attachments.get(this.transport, id),
add: (asset: string, data: attachment.Bytes, options: attachments.AddOptions) => attachments.add(this.transport, asset, data, options),
Expand Down
5 changes: 4 additions & 1 deletion src/core/fetchTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ export function createFetchTransport(config: FetchTransportConfig): Transport {
}

try {
if (response.ok) return (await readBody(response, responseType)) as T;
if (response.ok) {
options.onResponse?.({ status: response.status, headers: response.headers });
return (await readBody(response, responseType)) as T;
}

// An answer, not a failure to get one. 5xx may be retried when
// idempotent; anything else is final.
Expand Down
Loading
Loading