# @bounded-sh/client

> Web and React Native client SDK for Bounded, the full-stack app builder and runtime for AI-built apps: hosted auth, policy-enforced data, files and search, functions, realtime subscriptions, and atomic writes.

Latest version **0.0.100** (published 2026-09-19) · MIT license · 3.2K weekly downloads

## Install

```sh
npm install @bounded-sh/client
pnpm add @bounded-sh/client
yarn add @bounded-sh/client
bun add @bounded-sh/client
```

## Health

**Score 85/100 (A)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score; growing popularity.

Warnings: pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.0.100 |
| Published | 2026-09-19 |
| First published | 2026-06-22 |
| Weekly downloads | 3.2K |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 13 |
| Unpacked size | 6.7 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Bounded |
| Maintainers | amitpoofdotnew, bilalpoof, prpatel05, athar-poof |
| Keywords | bounded, full-stack, app-builder, app-runtime, ai-app-builder, realtime-backend, ai-apps, policy, auth, database, subscriptions, react-native, sdk |

## Links

- npm: https://www.npmjs.com/package/@bounded-sh/client
- Homepage: https://bounded.sh/docs/sdk
- npm.io page: https://npm.io/package/@bounded-sh/client

## Dependencies (13)

- [ws](https://npm.io/package/ws.md) ^8.21.0
- [bs58](https://npm.io/package/bs58.md) 6.0.0
- [buffer](https://npm.io/package/buffer.md) 6.0.3
- [@solana/kit](https://npm.io/package/@solana/kit.md) 8.2.0
- [@noble/curves](https://npm.io/package/@noble/curves.md) ^1.9.7
- [rpc-websockets](https://npm.io/package/rpc-websockets.md) 9.3.9
- [@solana/web3.js](https://npm.io/package/@solana/web3.js.md) 1.98.4
- [@bounded-sh/core](https://npm.io/package/@bounded-sh/core.md) 0.0.100
- [@coral-xyz/anchor](https://npm.io/package/@coral-xyz/anchor.md) 0.31.1
- [@wallet-standard/app](https://npm.io/package/@wallet-standard/app.md) 1.1.1
- [@wallet-standard/base](https://npm.io/package/@wallet-standard/base.md) 1.1.1
- [@wallet-standard/features](https://npm.io/package/@wallet-standard/features.md) 1.1.1
- [@solana/wallet-standard-features](https://npm.io/package/@solana/wallet-standard-features.md) 1.4.0

## Alternatives

- [@clerk/clerk-expo](https://npm.io/package/@clerk/clerk-expo.md) — 133.6K weekly downloads
- [@pothos/plugin-authz](https://npm.io/package/@pothos/plugin-authz.md) — 12.4K weekly downloads
- [@luigi-project/plugin-auth-oauth2](https://npm.io/package/@luigi-project/plugin-auth-oauth2.md) — 2.3K weekly downloads
- [@nocobase/plugin-verification](https://npm.io/package/@nocobase/plugin-verification.md) — 2.0K weekly downloads
- [escher-request](https://npm.io/package/escher-request.md) — 545 weekly downloads

## Recent versions

- 0.0.100 (latest) — 2026-09-19
- 0.0.100-performance.1 (candidate-0.0.100-performance.1) — 2026-09-13
- 0.0.100-performance.0 (candidate-0.0.100-performance.0) — 2026-09-13
- 0.0.72 (candidate-0.0.72) — 2026-08-18
- 0.0.71 (candidate-0.0.71) — 2026-08-18
- 0.0.68 (candidate-0.0.68) — 2026-08-12
- 0.0.53-next.1 (next) — 2026-07-21
- 0.0.99 — 2026-09-15
- 0.0.98 — 2026-09-11
- 0.0.97 — 2026-09-10
- 0.0.96 — 2026-09-09
- 0.0.95 — 2026-09-08
- 0.0.94 — 2026-09-07
- 0.0.93 — 2026-09-06
- 0.0.92 — 2026-09-05
- … 83 more at https://npm.io/package/@bounded-sh/client/versions

## README

# Bounded JS SDK
Bounded is a provable realtime backend for web and React Native applications.

Welcome to the **Bounded JavaScript SDK**. This SDK lets JavaScript and
TypeScript applications authenticate users, read and write policy-governed data,
subscribe to realtime updates, and call Bounded services.


## Table of Contents
- [Installation](#installation)

- [Getting Started](#getting-started)

	- [Initialization](#initialization)

	- [Authentication](#authentication)

- [Usage Examples](#usage-examples)

	- [Initialize the SDK](#initialize-the-sdk)

	- [Authenticate a User](#authenticate-a-user)

	- [Get Current User](#get-current-user)

	- [Set Data](#set-data)

		- [On-Chain and Off-Chain Data](#on-chain-and-off-chain-data)

	- [Subscribe](#subscribe)

	- [Get Data](#get-data)

	- [Counting, Aggregation, Search, and Joins](#counting-aggregation-search-and-joins)

	- [Get Multiple Documents](#get-multiple-documents)

- [Additional Resources](#additional-resources)

- [React Native Support](#react-native-support)

- [Contributing](#contributing)

- [License](#license)

  

## Installation
  
Install the SDK via npm:

  
```bash
npm install @bounded-sh/client
```
  

Or with Yarn:
```bash
yarn add @bounded-sh/client
```

### `npm audit` reports a moderate `uuid` advisory

`npm audit --omit=dev` reports GHSA-w5hq-g745-h8pq through this production path:

```
@bounded-sh/client -> @solana/web3.js -> jayson -> uuid@8.3.2
```

**The vulnerable code path is not reachable.** The advisory covers `uuid`'s
v3/v5/v6 buffer-writing path when a `buf` argument is supplied; jayson only ever
calls `uuid.v4()` with no arguments, so nothing in this dependency graph can
reach it. There is no upstream fix today: `@solana/web3.js@1.98.4` is the latest
1.x, and every published `jayson` release pins `uuid: ^8.3.2`.

If your release process requires a clean production audit, add a scoped override
in **your own** `package.json` (npm honors `overrides` only in the root project,
so the SDK cannot apply this for you):

```json
  "overrides": { "jayson": { "uuid": "^11.1.1" } }
```

Yarn users use the equivalent `resolutions` entry. **Remove the override
manually** once jayson ships a fixed `uuid` - it does not retire itself.

  

## Getting Started
### Initialization

  

Before using the SDK, initialize it with your **Application ID**. Hosted Bounded
Auth email login is the default human-login provider.

  
```typescript
import { init } from '@bounded-sh/client';

await init({ appId: 'YOUR_APP_ID' });
```  

### Authentication

Human login runs through the hosted redirect or popup helpers, which bind the
returned token to the app id and registered redirect URI. Inline app-origin OTP
helpers are retired.

```typescript
import { loginWithRedirect, completeLoginFromRedirect } from '@bounded-sh/client';

// Start hosted login:
await loginWithRedirect({ redirectUri: 'https://yourapp.com/auth/callback', methods: ['email'] });

// On your callback page, on load:
const user = await completeLoginFromRedirect();
```

You can also listen for authentication state changes:
  
```typescript
import { onAuthStateChanged } from '@bounded-sh/client';

const unsubscribe = onAuthStateChanged((user) => {
	if (user) {
		console.log('User logged in:', user.address);
	} else {
		console.log('User logged out');
	}
});

// Later:
unsubscribe();
```

### Embedded wallets — signing

When an app enables embedded wallets in `policy.json` (`"auth": { "wallets": true }`),
every email login also gets a non-custodial Solana wallet (`@user.address`). That user
can approve transactions with it — signing runs in a Bounded-hosted popup that does a
one-time email verification; Bounded never holds the key.

```typescript
import { signAndSubmitTransaction } from '@bounded-sh/client';
import { VersionedTransaction } from '@solana/web3.js';

// Call from a user gesture (click/tap) — signing opens a popup.
async function pay(tx: VersionedTransaction) {
  const signature = await signAndSubmitTransaction(tx); // signs + submits, returns the hash
  console.log('submitted', signature);
}
```

`signMessage` and `signTransaction` (sign without submit) throw for embedded smart
wallets — they sign and submit atomically, so use `signAndSubmitTransaction`.

### Platform app and build control

The headless `apps` and `builds` namespaces provision apps and control unified
build runs from registered first-party product surfaces.
Developer-api CORS blocks browser calls from generated-app origins.
Server-side callers are unaffected by that CORS boundary.

```typescript
import { apps, builds } from '@bounded-sh/client';

// Parentless genesis: the app (slug + routable URL + proven starter policy)
// exists the moment create returns. idempotencyKey is the create identity —
// retries with the same key converge on the same app.
const app = await apps.create({ prompt, idempotencyKey, intent: 'create-flow' });

const submitted = await builds.submit(app.appId, {
  prompt,
  operation: 'create',
  idempotencyKey,
});
const watcher = builds.watch(app.appId, submitted.runId, ({ run, events }) => {
  console.log(run.state, events);
});
// watcher.stop();
```

#### Media builds

An image or video is a build too: pass a `deliverable`. Each design round serves
candidates; a decision names the one it approves or revises. Publication lands a
release on the app's media asset through the same landing policy as an app change,
and a release can be edited, forked or rolled back.

```typescript
const image = await builds.submit(appId, {
  operation: 'create',
  prompt: 'A launch poster for a late-night jazz club',
  deliverable: { kind: 'image', spec: { size: '1024x1536', candidates: 2 } },
  idempotencyKey,
});

// While a round is under review (run.design.gate):
await builds.decideGate(appId, image.runId, gateId, {
  decision: 'request_changes',
  feedback: { text: 'Add a full moon above the club' },
  selectedArtifactId: 'c1',
  region: { x: 200, y: 100, width: 600, height: 300 }, // only this rectangle changes
  idempotencyKey: 'moon',
});
await builds.decideGate(appId, image.runId, gateId, { decision: 'approve', selectedArtifactId: 'c1', idempotencyKey: 'pick' });

// After publication: the verified release, a signed download, an edit of that exact release.
const { asset } = await builds.media(appId, image.runId);
const { url } = await builds.mediaDownloadUrl(appId, image.runId, 'c1');
await builds.submit(appId, {
  operation: 'edit',
  prompt: 'Make the lettering gold',
  deliverable: { kind: 'image', assetId: asset.assetId, expectedReleaseId: asset.currentRelease!.releaseId },
  idempotencyKey: 'gold',
});
```

An application build can use released images of its app, pinned by digest:
`mediaSlots` places each image byte for byte at `/media/<slot>.<ext>` in the app,
so a later edit of the image never changes the app until an app edit pins the new
release.

```typescript
await builds.submit(appId, {
  operation: 'edit',
  prompt: 'Use the launch poster as the hero image',
  mediaSlots: [{ slot: 'hero-image', assetId: asset.assetId }],
  idempotencyKey: 'hero',
});
```

A multi-scene video names its scenes and captions; a `request_changes` with
`sceneIds` regenerates only those scenes, an edit with `replaceScenes` replaces
one scene and keeps every other clip byte for byte, and a captions-only edit
makes no generation call.

Every release stays retained, with its alternatives and scene clips, for edits,
forks and rollback. A build that ends without a release is collected a week
after it ends, and an app that retains 20 GiB of media builds is refused new
ones with `media_storage_quota`.

#### Agent tools for media builds

`mediaAgentTools(appId)` returns the builds client as tool declarations for an
agent: `media_create`, `media_edit`, `media_inspect`, `media_revise`,
`media_select`, `media_export` and `media_propose`. Each has a name, a
description and a JSON Schema `inputSchema` to offer a model, and an `execute`
that returns `{ ok, data, next }` or `{ ok: false, code, message, retryable, next }`,
where `next` names the operations that make sense from the run's state and `code`
is Build's own reason when the API relays one (`base_moved`, `candidate_unknown`). A request
the run cannot take (no open review, an unknown candidate, a region outside a PNG
candidate) is refused with its reason before any call is made. No tool publishes:
`media_propose` reports whether the chosen candidate awaits the owner's landing
decision or is already released.

```typescript
import { mediaAgentTools } from '@bounded-sh/client';

const tools = mediaAgentTools(appId);
const declarations = tools.map(({ name, description, inputSchema }) => ({ name, description, input_schema: inputSchema }));
// When the model calls a tool:
const result = await tools.find((tool) => tool.name === call.name)!.execute(call.input);
```

## Usage

### Get Current User


```typescript
import { getCurrentUser } from '@bounded-sh/client';

const user = await getCurrentUser();
if (user) {
	console.log('Current user:', user.address);
} else {
	console.log('No user is currently logged in.');
}
```

  

### Set Data
```typescript
import { set } from '@bounded-sh/client';

const path = 'path/to/data';
const data = { key: 'value' };

await set(path, data);

console.log(`Data set at ${path}`);
```
  

#### On-Chain and Off-Chain Data

The `set` and `get` functions automatically handle whether data is stored **on-chain** or **off-chain** based on your application's policy configuration in the [Tarobase Console](https://console.tarobase.com).
  

**On-Chain Data:**

- If the data path corresponds to on-chain storage defined in your application policy, the `set` function will store data on-chain.

- The data must adhere to the `fields` schema defined in your Tarobase policy.

- Transactions are handled automatically, and users may be prompted to approve blockchain transactions.

- On-chain transactions require an explicit Solana RPC URL in `init({ rpcUrl })`; the SDK does not choose a bundled mainnet/devnet RPC when this is omitted.

**Off-Chain Data:**

- If the data path corresponds to off-chain storage, the `set` function will store data off-chain.

  

**Important:**

  

- Manage your data storage preferences and define policies at the [Tarobase Console](https://console.tarobase.com).

- Ensure that for on-chain data, the parameters you provide to `set` match the `fields` specified in your Tarobase policy.

- The policy also controls what can be `set` or `get` in your application.

- For information on how to form your policy specifically for your app's use-cases, view the policy docs [here](https://docs.tarobase.com/policies).

  
### Subscribe

```typescript
import { subscribe } from '@bounded-sh/client';

// Subscribe to a single document
const unsubscribe = await subscribe('users/123/profile', {
	onData: (data) => console.log('Data updated:', data),
	onError: (e) => console.error(e.message),
});

// Subscribe to a filtered, live collection feed
await subscribe('orders', {
	filter: { status: 'open', amount: { $gt: 100 } },
	limit: 50,
	onData: (orders) => console.log('Live open orders:', orders),
});

// Later, to stop receiving updates:
await unsubscribe();
```


### Get Data

```typescript
import { get } from '@bounded-sh/client';

// Read a single document
const message = await get('messages/abc123');

// Query a collection with a structured filter, sort, limit, and cursor pagination
const recent = await get('messages', {
	filter: { text: { $regex: '^f', $options: 'i' } }, // text starting with "f"
	sort: { _createdAt: -1 },
	limit: 20,
	// cursor: previousPage.nextCursor,  // opaque keyset cursor (no numeric offset)
});

console.log('Messages:', recent);
```

`filter` uses MongoDB-style operators (`$gt`, `$gte`, `$lt`, `$lte`, `$ne`,
`$in`, `$nin`, `$all`, `$size`, `$elemMatch`, `$exists`, `$type`, `$regex`,
`$and`, `$or`, `$nor`) evaluated server-side. See the
[Querying reference](https://docs.tarobase.com/querying) for the full table.

> **Note:** The older natural-language `prompt` option is legacy and no longer
> filters results — always use the structured `filter` option above.

**Note:** Similar to `set`, the `get` and `subscribe` functions will automatically retrieve data from on-chain or off-chain storage based on your application's configuration.

### Counting, Aggregation, Search, and Joins

```typescript
import { count, aggregate, queryAggregate, search, get } from '@bounded-sh/client';

// Count matching documents
const { value: openCount } = await count('orders', { filter: { status: 'open' } });

// Single scalar aggregate: count | uniqueCount | sum | avg | min | max
const { value: total } = await aggregate('orders', 'sum', { field: 'amount' });

// Grouped aggregation → one row per group
const byCategory = await queryAggregate('spend', { groupBy: ['category'], count: true, sum: ['amount'] });

// Full-text search (collection must declare `search: { fields: [...] }` in policy)
const docs = await search('orgs/o1/docs', 'quarterly report', { fields: ['title', 'body'] });

// Relationship join via `shape` (collection must declare `links` in policy)
const projects = await get('projects', { shape: { owner: {} } });
```

### Get Multiple Documents

Use `getMany` to efficiently fetch multiple documents in a single request:

```typescript
import { getMany } from '@bounded-sh/client';

// Fetch multiple documents at once (max 30 paths)
const results = await getMany([
  'users/123/profile',
  'users/456/profile',
  'posts/789'
]);

// Each result contains: { path, data, error? }
results.forEach(result => {
  if (result.error) {
    console.log(`Error for ${result.path}: ${result.error.message}`);
  } else {
    console.log(`Data for ${result.path}:`, result.data);
  }
});
```

The `getMany` function:
- Accepts up to 30 document paths per request
- Returns results in the same order as the input paths
- Includes per-document error handling (NOT_FOUND, UNAUTHORIZED)
- Automatically caches successful fetches


  

### Additional Resources
For more comprehensive examples, detailed guides, and API references, please visit our documentation site:

- [Tarobase Documentation](https://docs.tarobase.com)

- Quickstart Guide

- [API Reference](https://docs.tarobase.com/api)


### React Native Support
React Native and Expo are supported through the same `@bounded-sh/client`
package. See [REACT_NATIVE.md](./REACT_NATIVE.md) for platform setup, hosted
login, and guest login examples.

### Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.

### License
This Bounded JS SDK project is licensed under the MIT License.

* * * * *
  

Thank you for using the Bounded JS SDK. If you have questions or need help,
please create an issue.

---
_Source: https://npm.io/package/@bounded-sh/client · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
