Amba
SDKsFeatures

Collections

Typed typed CRUD with auto-scoped reads. Same expressive filter DSL on every SDK.

Collections are typed tables you define once per project and read/write from any SDK. Every read auto-scopes to the signed-in user — you never see another user's rows even if you forget to add a filter. Writes stamp the user from the session token; client-supplied user_id fields are ignored.

Define a collection's schema via the CLI (amba collections create <name> --field title:text --field body:text) or the admin API before reading or writing from a client SDK.

Quick start

import { Amba } from '@layers/amba-web';
 
await Amba.auth.signInAnonymously();
 
const post = await Amba.collections.insert<{ id: string; title: string; body: string }>('posts', {
  title: 'Hello amba',
  body: 'My first post.',
});
 
const refetched = await Amba.collections.findOne<typeof post>('posts', post.id);

Operations

Insert

The server stamps user_id from your session. Any user_id in the payload is rejected.

const post = await Amba.collections.insert<{ id: string; title: string }>('posts', {
  title: 'Hello',
});

Find one

const post = await Amba.collections.findOne<Post>('posts', 'post_123');

Returns null (or throws NotFound, depending on platform) if the row doesn't exist or doesn't belong to the signed-in user.

Find many

find returns { data, next_cursor, has_more }. The filter DSL composes expressive operators server-side.

const { where } = Amba.collections;
 
// Equality
const { data: published } = await Amba.collections.find('posts', {
  filter: where.eq('status', 'published'),
  order: [{ column: 'created_at', direction: 'desc' }],
  limit: 50,
});
 
// Composition
const { data: hot } = await Amba.collections.find('posts', {
  filter: where.and(
    where.eq('status', 'published'),
    where.gte('score', 100),
    where.or(where.like('title', 'Hello%'), where.in('tag', ['featured', 'trending'])),
  ),
  order: [{ column: 'score', direction: 'desc' }],
  limit: 20,
});

Filter operators

OperatorWeb / Node / RNServer semantics
eqwhere.eq('col', value)=
newhere.ne('col', value)<>
gtwhere.gt('col', value)>
gtewhere.gte('col', value)>=
ltwhere.lt('col', value)<
ltewhere.lte('col', value)<=
inwhere.in('col', [a, b, c])IN (...)
notInwhere.notIn('col', [a, b, c])NOT IN (...)
likewhere.like('col', 'Hello%')LIKE (case-sensitive)
ilikewhere.ilike('col', 'hello%')ILIKE (case-insens.)
isNullwhere.isNull('col')IS NULL
isNotNullwhere.isNotNull('col')IS NOT NULL
andwhere.and(f1, f2, ...)... AND ...
orwhere.or(f1, f2, ...)... OR ...
notwhere.not(f)NOT (...)

Update

const updated = await Amba.collections.update('posts', 'post_123', {
  title: 'New title',
  body: 'Updated body.',
});

Update by id is the canonical shape. Bulk update (update many rows matching a filter) is exposed via the admin API; on the client SDK, iterate and update individually.

Delete

await Amba.collections.delete('posts', 'post_123');

Soft delete — rows are marked deleted_at and excluded from subsequent reads. Hard delete is admin-only.

Patterns

Cursor pagination

For large collections, prefer cursor pagination over offset (it's stable under concurrent inserts and constant-cost per page):

const page1 = await Amba.collections.find('posts', {
  order: [{ column: 'created_at', direction: 'desc' }],
  limit: 50,
});
 
if (page1.has_more) {
  const page2 = await Amba.collections.find('posts', {
    order: [{ column: 'created_at', direction: 'desc' }],
    limit: 50,
    cursor: page1.next_cursor!,
  });
}

Typed reads

Pass a generic to insert / findOne / find so the response is typed end-to-end:

type Post = { id: string; title: string; body: string; created_at: string };
 
const post = await Amba.collections.insert<Post>('posts', { title: 'Hi', body: '' });
// `post.title` is typed string.
 
const { data } = await Amba.collections.find<Post>('posts', { limit: 20 });
// `data` is typed Post[].

React hook reactivity

useCollection re-fetches on mount, on options change, and on auth state change. Use refetch for manual refresh:

const { data, loading, error, refetch } = useCollection<Post>('posts', {
  order: [{ column: 'created_at', direction: 'desc' }],
  limit: 50,
});

Limits

  • Max page size: 500 rows per find call. Use cursor pagination for larger result sets.
  • Filter depth: and / or / not can nest up to 8 levels deep.
  • Row payload size: up to 1 MB per insert / update (JSON-encoded).
  • Auto-scoping: client SDKs cannot read or write another user's rows. To act across users from a server context, use amba.asUser(uid) (Node SDK) or the admin API.
  • Schema is define-once: schemas are managed via the CLI / admin API, not from client SDKs.

Reference

On this page