Skip to content

Usage Examples

End-to-End: User CRUD

1. Define the Interface

ts
interface User {
  @primkey id: string;
  name: string;
  @perm(perms.R__) email: string;
  age: number;
}

2. Write Backend Functions

ts
class UserService {
  @backend({ egroup: 'api-v1', perm: perms.RWX })
  async createUser(name: string, email: string, age: number) {
    const id = crypto.randomUUID();
    await env.storage.users.set(id, { id, name, email, age });
    return HttpResp.created({ id, name, email, age });
  }

  @backend({ egroup: 'api-v1', perm: perms.R__ })
  async getUser(id: string) {
    const user = await env.storage.users.get(id);
    if (!user) throw new NotFoundError();
    return user;
  }

  @backend({ egroup: 'api-v1', perm: perms.RW_ })
  async updateUser(id: string, data: Partial<User>) {
    await env.storage.users.update(id, data);
    return HttpResp.ok({ updated: true });
  }

  @backend({ egroup: 'admin', perm: perms.RWX })
  async deleteUser(id: string) {
    await env.storage.users.delete(id);
    return HttpResp.noContent();
  }
}

3. Compiler Output

Client stub (auto-generated):

ts
export async function createUser(name: string, email: string, age: number) {
  return await __kontract_rpc('createUser', [name, email, age], { egroup: 'api-v1', perm: 7 });
}

Server route (auto-generated):

ts
__kontract_routes.set('createUser', {
  handler: async (ctx, args) => new UserService()['createUser'](...args),
  meta: { egroup: 'api-v1', perm: 7 }
});

4. Client Usage

ts
const user = await createUser('Alice', 'alice@example.com', 30);
const fetched = await getUser(user.id);
await updateUser(user.id, { name: 'Bob' });

Storage Proxy Operations

ts
const proxy = new TableProxy(pg, 'tasks', ctx);

// Key-value operations
await proxy.set('task-1', { title: 'Write docs', done: false });
const task = await proxy.get('task-1');

// List operations (ordered by _order)
const id = await proxy.push({ title: 'Review PR', done: false });
const last = await proxy.pop();
const first = await proxy.shift();

// Query with JSONB containment
for await (const t of proxy.query({ done: false })) {
  console.log(t.title);
}

// Raw SQL (restricted to current table)
const results = await proxy.exec(
  "SELECT data FROM tasks WHERE data->>'done' = $1",
  ['false']
);

Middleware Chain

ts
const middleware = [
  // Rate limiting for all API routes
  {
    fn: async (ctx, next) => {
      const key = `rate:${ctx.owner}`;
      const count = await env.shared.get(key) ?? 0;
      if (count > 100) throw new HttpError('Rate limited', 429);
      await env.shared.set(key, count + 1, { ttl: 60 });
      await next();
    },
    filter: { prefixurl: '/api' }
  },

  // Admin guard for admin endpoints
  {
    fn: async (ctx, next) => {
      if (ctx.perm < perms.RWX) throw new ForbiddenError();
      await next();
    },
    filter: { egroup: 'admin' }
  }
];

const chain = inlineMiddlewareChain(filterApplicable(middleware, '/api/users', 'api-v1'));
await chain(ctx, handler);

Encryption

ts
import { encrypt, decrypt, hkdf } from 'kontract';

// Derive session key
const sessionKey = hkdf(sharedSecret, 'raystream-v1', 32);

// Encrypt
const payload = new TextEncoder().encode(JSON.stringify({ method: 'getUser', args: ['123'] }));
const { nonce, data, tag } = encrypt(payload, sessionKey);

// Decrypt
const decrypted = decrypt({ nonce, data, tag }, sessionKey);
const message = JSON.parse(new TextDecoder().decode(decrypted));

Event Subscription

ts
const bus = new EventBus();

// Subscribe
const unsubscribe = bus.subscribe('users', (event) => {
  console.log(`${event.type}: ${event.id}`);
  if (event.type === 'insert') {
    console.log('New user data:', event.data);
  }
});

// Emit (from server-side handler)
bus.emit('users', {
  type: 'insert',
  id: 'user-123',
  data: { name: 'Alice', email: 'alice@example.com' }
});

// SSE format for HTTP streaming
const ssePayload = formatSSE({ type: 'insert', id: 'user-123', data: { name: 'Alice' } });
// → "data: {\"type\":\"insert\",\"id\":\"user-123\",\"data\":{\"name\":\"Alice\"}}\n\n"

// Cleanup
unsubscribe();