A Serverless, Reactive tRPC replacement for AWS DynamoDB
npm install dynamodb-reactiveTagline: A Serverless, Reactive tRPC replacement for AWS - unified package.
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.
``bash`
npm install dynamodb-reactive zodor
pnpm add dynamodb-reactive zod
Peer Dependencies (install as needed):
| Dependency | Required For |
| :--- | :--- |
| zod | Schema definitions (required) |react
| | React hooks |aws-cdk-lib
| | Infrastructure |constructs
| | Infrastructure |
| 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 |
`typescript
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',
});
`
`typescript
import { z } from 'zod';
import { initReactive } from 'dynamodb-reactive/server';
import { TodoTable } from './schema';
type AppContext = Record
const t = initReactive
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;
`
`typescript
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);
}
`
`typescript
// app/layout.tsx or providers.tsx
'use client';
import { ReactiveClientProvider } from 'dynamodb-reactive/client';
export function Providers({ children }: { children: React.ReactNode }) {
return (
url: process.env.NEXT_PUBLIC_WS_URL!,
httpUrl: '/api/reactive',
}}
>
{children}
);
}
`
`typescript
'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
// 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
return (
WebSocket: {connectionState}
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:
mutations auto-invalidate subscriptions.Example - Instant Toggle:
`typescript
// 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:
`typescript
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 |* Node.js >= 18.0.0
* TypeScript >= 5.3.0
MIT