# snoozestack-js — JavaScript & TypeScript

> The JavaScript/TypeScript client SDK for snoozestack.

[HTML version](https://snoozestack.com/docs/sdk-js/) · [Docs index](https://snoozestack.com/llms.txt)

snoozestack-js v1.14.0 · [Install or upgrade](https://snoozestack.com/docs/installation.md)

`snoozestack-js` is a dependency-free client for a project's identity, functions, queues, and Signals — plus the `defineTable()` schema vocabulary the runtime builds your database from. Install it from [snoozestack.com/js](https://snoozestack.com/docs/installation.md).

```bash title="setup"
import { createClient } from 'snoozestack-js';

const snoozestack = createClient(
  'https://<project-ref>.snzzz.com/api',   // Project Overview
  SNOOZESTACK_ANON_KEY,                          // the public project key
);
```

Every service hangs off that `/api` base — `/api/auth`, `/api/functions/v1`, `/api/storage/v1` and the rest — and a [custom domain](https://snoozestack.com/docs/domains.md) serves the identical paths under its own name. In a browser app you can pass the base on its own:

```bash title="browser app served by snoozestack"
// Same-origin: resolves against whichever host serves the page, so the same
// build works on <project-ref>.snzzz.com and on a custom domain.
const snoozestack = createClient('/api', SNOOZESTACK_ANON_KEY);
```

A relative base needs a browser to resolve against, so on a server pass the absolute`/api` URL instead. See [Websites](https://snoozestack.com/docs/websites.md) for publishing the app itself.

### Database

There's no query builder on this client, deliberately. A table is only reachable from inside a [function](https://snoozestack.com/docs/functions.md), through its declared `db` capability — the client calls the function instead:

```bash
const { data: clubs, error } = await snoozestack.functions.invoke('list-clubs');

await snoozestack.functions.invoke('add-club', {
  body: { name: '7 iron', distance: 165 },
});
```

```ts title="snoozestack/functions/list-clubs/index.ts"
export default async function handler(req: Request, capabilities) {
  const { rows } = capabilities.db.query(
    "select id, name, distance from clubs where in_bag = 1 order by distance desc limit 20",
  );
  return Response.json(rows);
}
```

The schema itself is also this package's job — `defineTable()` and `schema.*` in your committed `schema.mjs` are imported from `snoozestack-js`. See [Schema](https://snoozestack.com/docs/schema.md) and [Permissions](https://snoozestack.com/docs/permissions.md).

### Application identity

Projects on the runtime use `SnoozeStackIdentity` instead: a small state machine (`anonymous` → `pending` → `authenticated` → `expired`) over the project's own `/auth` endpoints, with sessions verified locally by the runtime. All the sign-in methods share it — magic link, Google, Apple, email/password, and native id_token for apps that never see a browser redirect.

```bash
import { SnoozeStackIdentity } from 'snoozestack-js';

const identity = new SnoozeStackIdentity(PROJECT_URL, ANON_KEY);

// Magic link: request, then complete on the emailed link's landing page
await identity.requestMagicLink({ email, redirectTo: 'https://app.example.com/' });
const { user } = await identity.completeMagicLink(); // reads ?token=… itself

// Google / Apple: one call each — the provider name is the only difference.
// Both navigate the browser to the provider and come back to redirectTo.
identity.startOAuth({ provider: 'google', redirectTo: 'https://app.example.com/' });
identity.startOAuth({ provider: 'apple', redirectTo: 'https://app.example.com/' });
const oauth = await identity.completeOAuthCallback(); // on the redirectTo page

// Email/password: sign-up stores the password pending until the email is
// proven (the standard verification email goes out) — so nobody can squat
// an address they don't own. Sign-in is immediate once a password exists.
await identity.signUpWithPassword({ email, password, redirectTo: 'https://app.example.com/' });
const signedIn = await identity.signInWithPassword({ email, password });

// A magic-link/OAuth-only user sets a first password by omitting currentPassword.
await identity.changePassword({ currentPassword, newPassword });

// Self-serve deletion: remove your own data about the user first, then this
// deletes the identity and signs out locally.
await identity.deleteAccount();

// Native apps skip the redirect entirely: Google's SDK and Sign in with
// Apple hand you a signed id_token, so there is no callback to complete.
// nonce is the RAW value you generated, never its SHA-256 — Apple puts the
// hash in the token and the server compares the two.
await identity.signInWithIdToken({ provider: 'apple', idToken, nonce });
await identity.signInWithIdToken({ provider: 'google', idToken });

identity.onStateChange((state, session, user) => { /* re-render */ });
await identity.signOut();
```

Sessions refresh themselves before expiry and persist in `localStorage`, namespaced per project and per namespace. Failures reject with a stable `code` (`invalid_credentials`, `rate_limited`, `password_too_weak`, …) on `SnoozeStackIdentityError`. Google and Apple need the project's own provider credentials declared in `project.toml` — see [Authentication](https://snoozestack.com/docs/authentication.md) for the per-provider setup, including the Apple Services ID walkthrough.

Against `snoozestack dev` there is a built-in account so you can reach a signed-in screen with no provider set up and no email sent: sign in as `user@test.com` with the password `1234` (a preview has it too) — and any other email can sign up, sign in with a link from the dashboard's Emails page, or use the Google and Apple buttons, which open a local account chooser instead of the provider and then complete the real callback. The client presents the dev server's local anon key (printed at startup) the way it presents the hosted one; the code you write is the code that runs in production. `GET /_dev/auth` and `POST /_dev/auth/reset` let a test inspect and reset the local plane. The seeded account and the `/_dev` routes exist only in the local dev server; nothing hosted — project host, custom domain, or preview — has them.

### Storage

The SDK ships no storage client. Uploads and downloads are mediated by your own functions, which keeps the authorization decision in code you wrote rather than in a policy evaluated by a service the client talks to directly.

```bash
// Ask one of your functions to authorize a path — it returns a presigned URL.
const { data: signed, error } = await snoozestack.functions.invoke('avatar-upload-url', {
  body: { contentType: file.type },
});

// A presigned URL carries its own signature, so this is a plain PUT.
await fetch(signed.url, { method: 'PUT', body: file, headers: { 'content-type': file.type } });

// A public bucket needs no call at all — the object URL is stable:
const src = `${SNOOZESTACK_URL}/storage/v1/object/public/avatars/${path}`;
```

See [Storage](https://snoozestack.com/docs/storage.md) for buckets, sharing files, and access control.

### Functions

```bash
// POST by default. The signed-in session is attached automatically.
const { data, error } = await snoozestack.functions.invoke('chat', {
  body: { messages },
});

// A reading function is a GET.
const { data: wallets } = await snoozestack.functions.invoke('wallets', { method: 'GET' });
```

An error is *returned*, not thrown — check `error` before using `data`. `error.message` carries the function's own message when it sent one.

### Signals

```bash
snoozestack.signals.identify(user.id);            // merges their anonymous history in
snoozestack.signals.track('signup', { plan: 'pro' });   // batched; never blocks, never throws
snoozestack.signals.reset();                      // on sign-out
```

Also usable standalone as `SnoozeStackSignals`, with no other client needed. See [Signals](https://snoozestack.com/docs/signals.md) for batching, identity, and the export API.

### Queues

```bash title="server-side only"
await snoozestack.queues.send('jobs', { kind: 'booking-confirmed', id: 42 });
```

Enqueueing is trusted — see [Queues](https://snoozestack.com/docs/queues.md) for why a browser never does it directly.

### Environment variable conventions

```bash title=".env"
SNOOZESTACK_URL=https://<project-ref>.snzzz.com/api
SNOOZESTACK_ANON_KEY=eyJ…            # the public project key — safe in browsers
```

In Vite use the `VITE_` prefix (`import.meta.env.VITE_SNOOZESTACK_ANON_KEY`); in Next.js, `NEXT_PUBLIC_` for browser-side values. Realtime subscriptions are not supported.
