@omirion/orbit-sdk 0.1.0 → 0.2.0

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
@@ -1,9 +1,6 @@
1
1
  # @omirion/orbit-sdk
2
2
 
3
- Official TypeScript SDK for the [Omirion Orbit](https://orbit.omirion.com)
4
- public API (`/api/v1`). Works in Node 18+, Deno, Bun, Cloudflare Workers, and
5
- browsers. ESM + CJS, fully typed, zero runtime dependencies (the fetch client is
6
- bundled).
3
+ Official TypeScript SDK for the [Omirion Orbit](https://orbit.omirion.com) public API (`/api/v1`). Works in Node 18+, Deno, Bun, Cloudflare Workers, and browsers. ESM + CJS, fully typed, zero runtime dependencies (the fetch client is bundled).
7
4
 
8
5
  ## Install
9
6
 
@@ -33,15 +30,18 @@ for await (const device of orbit.devices.iterate({ status: 'online' })) {
33
30
  }
34
31
  ```
35
32
 
36
- `OrbitClient` wraps the generated transport client with bearer auth, automatic
37
- retries (429/502/503/504 + network) with backoff, response unwrapping, cursor
38
- auto-pagination, and **typed errors** you can `catch`:
33
+ `OrbitClient` wraps the generated transport client with bearer auth, automatic retries (429/502/503/504 + network) with backoff, response unwrapping, cursor auto-pagination, and **typed errors** you can `catch`:
39
34
 
40
35
  ```ts
41
36
  import { InsufficientScopeError, DeviceRpcError } from '@omirion/orbit-sdk'
42
37
 
43
38
  try {
44
- await orbit.commands.exec(deviceId, { argv: ['systemctl', 'status', 'orbit-agent'] })
39
+ // Commands are declared per device type, a name plus typed key/value args, no shell.
40
+ // `orbit.commands.manifest(deviceId)` lists what this device accepts.
41
+ const result = await orbit.commands.exec(deviceId, { name: 'state-request', timeoutMs: 10_000 })
42
+ if (result.queued) {
43
+ // Store-and-forward device: poll orbit.commands.get(deviceId, result.invocationId)
44
+ }
45
45
  } catch (err) {
46
46
  if (err instanceof InsufficientScopeError) {/* token missing api:command:exec */}
47
47
  else if (err instanceof DeviceRpcError) {/* device offline / transport failed */}
@@ -51,32 +51,29 @@ try {
51
51
 
52
52
  ### Surface
53
53
 
54
- - `orbit.me()`. Who the token authenticates as, its abilities and project
55
- scope, and the projects it reaches. Requires no ability: the cheapest way to
56
- verify a token works and to discover valid `projectId` values.
57
- - `orbit.devices`. `list(params?)`, `get(id)`, `iterate(params?)` (async generator)
58
- - `orbit.telemetry`. `latest(deviceId)`, `history(deviceId, { hours? })`, `manifest(deviceId)`
59
- - `orbit.firmware`. `upload({ file, projectId, version, compatible, description? })`
60
- - `orbit.commands`. `exec(deviceId, { argv, elevate?, timeoutMs? })`
61
- - `decodeSample(sample, manifest)` / `decodeSamples(...)`, labelled telemetry rows
62
- - Errors: `OrbitError` + `AuthenticationError`, `InsufficientScopeError`,
63
- `ResourceNotFoundError`, `BadRequestError`, `ValidationError`,
64
- `BundleExistsError`, `DeviceRpcError`, `RateLimitError`, `ServerError`, `NetworkError`
54
+ Every collection has a cursor-paginated `list(params?)` and a lazy `iterate(params?)` async-generator twin that walks all pages.
55
+
56
+ - `orbit.me()`. Who the token authenticates as, its abilities and project scope, and the projects it reaches. Requires no ability. The cheapest way to verify a token works and to discover valid `projectId` values.
57
+ - `orbit.projects`. `list` / `iterate` / `get(id)`, plus `appToken.create(projectId)` / `appToken.rotate(projectId)` (the provisioning token is returned once and never again).
58
+ - `orbit.deviceTypes`. `list` / `iterate` / `get(projectId, slug)` / `create` / `update` / `delete`, the models a fleet identifies as, with their telemetry and command manifests.
59
+ - `orbit.devices`. `list` / `iterate` / `get(id)` / `delete(id)` / `resetToken(id)`.
60
+ - `orbit.admissions`. `list` / `iterate` / `get(id)` / `accept` / `reject` / `dismiss`, the fleet-join requests.
61
+ - `orbit.telemetry`. `latest(deviceId)`, `history(deviceId, { hours? })`, `manifest(deviceId)`.
62
+ - `orbit.firmware`. `upload({ file, projectId, kind?, version?, compatible?, description? })` (version and target are decoded from the binary where it carries them), `deleteBundle(id)`, `bundles.list` / `iterate` / `get`, `deployments.trigger` / `list` / `iterate` / `get` / `cancel` / `rollback`.
63
+ - `orbit.commands`. `exec(deviceId, { name, args?, timeoutMs? })`, `get(deviceId, invocationId)` (the pull path for a queued dispatch), `manifest(deviceId)`.
64
+ - `orbit.webhooks`. `list` / `iterate` / `create` / `get` / `update` / `delete` / `deliveries` / `iterateDeliveries`. Receiver-side helpers `verifyWebhookSignature` and `parseWebhookEvent` validate incoming deliveries (HMAC + replay window).
65
+ - `orbit.usb` / `orbit.terminal`. Single-use ticket minting for the USB/IP and device-shell WebSocket planes (`api:usb:attach` / `api:terminal:open`).
66
+ - `decodeSample(sample, manifest)` / `decodeSamples(...)`, labelled telemetry rows.
67
+ - Errors: `OrbitError` + `AuthenticationError`, `InsufficientScopeError`, `ResourceNotFoundError`, `BadRequestError`, `ValidationError`, `ConflictError`, `BundleExistsError`, `DeviceRpcError`, `RateLimitError`, `ServerError`, `NetworkError`
65
68
 
66
69
  The raw generated transport client is still available under `import { raw } from '@omirion/orbit-sdk'`.
67
70
 
68
- ## Develop
71
+ ## Examples
69
72
 
70
- ```bash
71
- pnpm install --ignore-workspace
72
- pnpm generate # regenerate src/generated/ from ../spec/openapi.json
73
- pnpm typecheck
74
- pnpm test # vitest (ergonomics layer)
75
- pnpm build # tsup → dist/ (ESM + CJS + .d.ts)
76
- ```
73
+ Two runnable examples ship in this package under `examples/`. Copy a folder anywhere, `pnpm install`, and follow its README.
77
74
 
78
- `src/generated/` is committed and must never be hand-edited, change the server
79
- spec and regenerate (`pnpm sdk:gen` from the repo root).
75
+ - `examples/device-rpc-flow`. An Ink terminal UI walking the full read-then-act loop, list devices, decode their latest telemetry via the type manifest, then drive a device through a sequence of declared commands.
76
+ - `examples/webhook-listener`. The smallest useful webhook receiver, verifies every delivery's signature with `parseWebhookEvent`, dedupes on the event id, and prints each event.
80
77
 
81
78
  ## License
82
79
 
package/dist/index.cjs CHANGED
@@ -1109,7 +1109,9 @@ var ValidationError = class extends OrbitError {
1109
1109
  this.fieldErrors = options.fieldErrors ?? [];
1110
1110
  }
1111
1111
  };
1112
- var BundleExistsError = class extends OrbitError {
1112
+ var ConflictError = class extends OrbitError {
1113
+ };
1114
+ var BundleExistsError = class extends ConflictError {
1113
1115
  };
1114
1116
  var DeviceRpcError = class extends OrbitError {
1115
1117
  };
@@ -1127,12 +1129,6 @@ var RateLimitError = class extends OrbitError {
1127
1129
  function extractErrorFields(body) {
1128
1130
  if (!body || typeof body !== "object") return {};
1129
1131
  const record = body;
1130
- if (typeof record.error === "string") {
1131
- return {
1132
- code: record.error,
1133
- message: typeof record.message === "string" ? record.message : record.error
1134
- };
1135
- }
1136
1132
  if (Array.isArray(record.errors) && record.errors.length > 0) {
1137
1133
  const first = record.errors[0];
1138
1134
  return {
@@ -1184,7 +1180,7 @@ function normalizeError(status, body, response) {
1184
1180
  case 404:
1185
1181
  return new ResourceNotFoundError(msg, base);
1186
1182
  case 409:
1187
- return new BundleExistsError(msg, base);
1183
+ return code === "E_BUNDLE_ALREADY_EXISTS" ? new BundleExistsError(msg, base) : new ConflictError(msg, base);
1188
1184
  case 422:
1189
1185
  return new ValidationError(msg, { ...base, fieldErrors: extractFieldErrors(body) });
1190
1186
  case 429:
@@ -1661,6 +1657,7 @@ __export(generated_exports, {
1661
1657
  exports.AuthenticationError = AuthenticationError;
1662
1658
  exports.BadRequestError = BadRequestError;
1663
1659
  exports.BundleExistsError = BundleExistsError;
1660
+ exports.ConflictError = ConflictError;
1664
1661
  exports.DeviceRpcError = DeviceRpcError;
1665
1662
  exports.InsufficientScopeError = InsufficientScopeError;
1666
1663
  exports.NetworkError = NetworkError;