dynamodb-reactive
v0.1.22
Published
A Serverless, Reactive tRPC replacement for AWS DynamoDB
Downloads
1,953
Maintainers
Readme
dynamodb-reactive
Tagline: A Serverless, Reactive tRPC replacement for AWS - unified package.
1. Overview
dynamodb-reactive provides type-safe, real-time DynamoDB subscriptions with automatic JSON Patch diffing over WebSockets.
Key Features:
- Full TypeScript Support: End-to-end type safety from schema definition to React hooks.
2. Installation
npm install dynamodb-reactive zod
# or
pnpm add dynamodb-reactive zodPeer Dependencies (install as needed):
| Dependency | Required For |
| :--- | :--- |
| zod | Schema definitions (required) |
| react | React hooks |
| aws-cdk-lib | Infrastructure |
| constructs | Infrastructure |
3. Package Exports
| Import Path | Purpose |
| :--- | :--- |
| dynamodb-reactive | Core exports (DynamoTable, schemas) |
| dynamodb-reactive/core | Core table definitions |
| dynamodb-reactive/server | Server runtime (Router, handlers) |
| dynamodb-reactive/client | Frontend client |
| dynamodb-reactive/react | React hooks |
| dynamodb-reactive/infra | CDK constructs |
4. Quick Start
Step 1: Define Your Schema
import { z } from 'zod';
import { DynamoTable } from 'dynamodb-reactive/core';
export const TodoTable = new DynamoTable({
tableName: 'my-table',
schema: z.object({
PK: z.string(),
SK: z.string(),
id: z.string(),
text: z.string(),
completed: z.boolean(),
createdAt: z.number(),
}),
pk: 'PK',
sk: 'SK',
});Step 2: Create the Router
import { z } from 'zod';
import { initReactive } from 'dynamodb-reactive/server';
import { TodoTable } from './schema';
type AppContext = Record<string, unknown>;
const t = initReactive<AppContext>();
export const appRouter = t.router({
todos: {
// Query procedure
list: t.procedure
.input(z.object({}).optional())
.query(async ({ ctx }) => {
return ctx.db
.query(TodoTable)
.filter((q) => q.eq(TodoTable.field.PK, 'TODO'))
.execute();
}),
// Mutation procedure
create: t.procedure
.input(z.object({ text: z.string() }))
.mutation(async ({ ctx, input }) => {
const item = {
PK: 'TODO',
SK: Date.now().toString(),
id: Date.now().toString(),
text: input.text,
completed: false,
createdAt: Date.now(),
};
await ctx.db.put(TodoTable, item);
return item;
}),
},
});
export type AppRouter = typeof appRouter;Step 3: Create API Handler
import { createReactiveHandler } from 'dynamodb-reactive/server';
import { appRouter } from './router';
export const handler = createReactiveHandler({
router: appRouter,
dbConfig: { region: 'us-east-1' },
getContext: async () => ({}),
});
// In Next.js API route:
export async function POST(request: Request) {
const body = await request.json();
const response = await handler.handleRequest('client-id', body);
return Response.json(response);
}Step 4: Set Up React Provider
// app/layout.tsx or providers.tsx
'use client';
import { ReactiveClientProvider } from 'dynamodb-reactive/client';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ReactiveClientProvider
config={{
url: process.env.NEXT_PUBLIC_WS_URL!,
httpUrl: '/api/reactive',
}}
>
{children}
</ReactiveClientProvider>
);
}Step 5: Use React Hooks
'use client';
import type { Todo } from './types';
import type { AppRouter } from './router';
import { useClient, useConnectionState } from 'dynamodb-reactive/client';
export function TodoList() {
const connectionState = useConnectionState();
const client = useClient<AppRouter>();
// Subscribe to todos - receives real-time updates via WebSocket
const { data: todos, loading, error } = client.todos.list.useQuery({});
// Mutations with automatic optimistic updates
const createMutation = client.todos.create.useMutation();
const toggleMutation = client.todos.toggle.useMutation();
const deleteMutation = client.todos.delete.useMutation();
const handleCreate = async (text: string) => {
await createMutation.mutate({ text });
};
const handleToggle = async (id: string) => {
await toggleMutation.mutate({ id });
};
const handleDelete = async (id: string) => {
await deleteMutation.mutate({ id }); // Removes instantly from UI
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<p>WebSocket: {connectionState}</p>
{todos?.map((todo) => (
<div key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</div>
))}
</div>
);
}5. Optimistic Updates
All mutations apply instant optimistic updates (before HTTP request):
| Update Type | Behavior | Example Input |
| :--- | :--- | :--- |
| 'merge' | Find item by input.id, merge input fields | { id: '123', completed: true } |
| 'remove' | Remove item by input.id | { id: '123' } |
| Custom function | Apply custom logic | (data, input) => [...] |
Convention: <namespace>.<action> mutations auto-invalidate <namespace>.list subscriptions.
Example - Instant Toggle:
// For instant optimistic updates, include the fields you want to change
const handleToggle = async (todo: Todo) => {
await updateMutation.mutate({
id: todo.id,
completed: !todo.completed // Include new value for instant UI update
});
};Custom options:
const mutation = client.todos.update.useMutation({
invalidates: 'todos.list', // Override auto-detection
optimisticUpdate: 'merge', // 'merge' | 'remove' | custom function
});
// For creates (no id), provide optimisticData
const createMutation = client.todos.create.useMutation({
optimisticData: (input) => ({
id: `temp-${Date.now()}`,
...input,
completed: false,
createdAt: Date.now(),
}),
});6. Database Context Methods
The ctx.db object provides these methods:
| Method | Description |
| :--- | :--- |
| query(table).filter(...).execute() | Query with filters |
| get(table, key) | Get single item by key |
| put(table, item) | Create/replace item |
| update(table, key, updates) | Update item fields |
| delete(table, key) | Delete item |
7. Requirements
- Node.js >= 18.0.0
- TypeScript >= 5.3.0
8. License
MIT
