diff --git a/docs/design/API-DESIGN.md b/docs/design/API-DESIGN.md index cfa606555..f14950d1d 100644 --- a/docs/design/API-DESIGN.md +++ b/docs/design/API-DESIGN.md @@ -510,7 +510,7 @@ The Transferable class is functional on both sides, but the implementations diff | Side | Class | Methods | Source | |------|-------|---------|--------| -| Server (Lambda) | `RealtimeChannel` | Real AWS SDK calls (AppSync, IoT, etc.) | `aws-runtime` export | +| Server (Lambda) | `RealtimeChannel` | API Gateway Management API + DynamoDB connection registry | `aws-runtime` export | | Server (Mock) | `RealtimeChannel` | In-process event emitter | `default` export | | Client (Browser) | `RealtimeChannelClient` | WebSocket connection | Client plugin | diff --git a/docs/reference/ARCHITECTURE-LAYERS.md b/docs/reference/ARCHITECTURE-LAYERS.md index 8a378cb08..16bcacc98 100644 --- a/docs/reference/ARCHITECTURE-LAYERS.md +++ b/docs/reference/ARCHITECTURE-LAYERS.md @@ -288,7 +288,7 @@ export {}; realtime/ ├── client-hook.ts # WebSocket client setup ├── index.ts # Server-side pub/sub -├── infra.ts # AppSync Event API +├── infra.ts # API Gateway WebSocket API + DynamoDB connection registry └── mock.ts # In-memory pub/sub ``` @@ -317,28 +317,30 @@ export class RealtimeClientHook { **index.ts:** ```typescript -import { IoTDataPlaneClient, PublishCommand } from '@aws-sdk/client-iot-data-plane'; +import { + ApiGatewayManagementApiClient, + PostToConnectionCommand, +} from '@aws-sdk/client-apigatewaymanagementapi'; export class Realtime { - private client?: IoTDataPlaneClient; - private endpoint?: string; - - constructor(public name: string, public options: any) {} - - private getClient() { - if (!this.client) { - this.endpoint = process.env[`BLOCKS_${this.name}_ENDPOINT`]; - this.client = new IoTDataPlaneClient({ endpoint: this.endpoint }); - } - return this.client; - } - - async publish(channel: string, message: any) { - const client = this.getClient(); - await client.send(new PublishCommand({ - topic: channel, - payload: Buffer.from(JSON.stringify(message)) - })); + constructor( + private connections: { + forChannel(channel: string): Promise; + }, + ) {} + + async publish(channel: string, message: unknown) { + const client = new ApiGatewayManagementApiClient({ + endpoint: process.env.BLOCKS_RT_CALLBACK_URL, + }); + const connectionIds = await this.connections.forChannel(channel); + + await Promise.all(connectionIds.map((connectionId) => + client.send(new PostToConnectionCommand({ + ConnectionId: connectionId, + Data: JSON.stringify(message), + })), + )); } } ``` @@ -347,7 +349,7 @@ export class Realtime { ```typescript import { Construct } from 'constructs'; import { CfnOutput } from 'aws-cdk-lib'; -// AppSync or IoT Core setup... +// API Gateway WebSocket API, route integrations, and a DynamoDB connection registry. export function materialize(scope: Construct, name: string, options: any) { // Create WebSocket API infrastructure diff --git a/docs/reference/building-block-structure.md b/docs/reference/building-block-structure.md index d6164fd90..2f5b0b2e3 100644 --- a/docs/reference/building-block-structure.md +++ b/docs/reference/building-block-structure.md @@ -299,28 +299,30 @@ Building Blocks with custom client protocols (WebSocket, etc.): **index.ts:** ```typescript -import { IoTDataPlaneClient, PublishCommand } from '@aws-sdk/client-iot-data-plane'; +import { + ApiGatewayManagementApiClient, + PostToConnectionCommand, +} from '@aws-sdk/client-apigatewaymanagementapi'; export class Realtime { - private client?: IoTDataPlaneClient; - private endpoint?: string; - - constructor(public name: string, public options: any) {} - - private getClient() { - if (!this.client) { - this.endpoint = process.env[`BLOCKS_${this.name}_ENDPOINT`]; - this.client = new IoTDataPlaneClient({ endpoint: this.endpoint }); - } - return this.client; - } - - async publish(channel: string, message: any) { - const client = this.getClient(); - await client.send(new PublishCommand({ - topic: channel, - payload: Buffer.from(JSON.stringify(message)) - })); + constructor( + private connections: { + forChannel(channel: string): Promise; + }, + ) {} + + async publish(channel: string, message: unknown) { + const client = new ApiGatewayManagementApiClient({ + endpoint: process.env.BLOCKS_RT_CALLBACK_URL, + }); + const connectionIds = await this.connections.forChannel(channel); + + await Promise.all(connectionIds.map((connectionId) => + client.send(new PostToConnectionCommand({ + ConnectionId: connectionId, + Data: JSON.stringify(message), + })), + )); } } ``` @@ -329,7 +331,7 @@ export class Realtime { ```typescript import { Construct } from 'constructs'; import { CfnOutput } from 'aws-cdk-lib'; -// AppSync or IoT Core setup... +// API Gateway WebSocket API, route integrations, and a DynamoDB connection registry. export function materialize(scope: Construct, name: string, options: any) { // Create WebSocket API infrastructure diff --git a/packages/bb-agent/src/agent.ts b/packages/bb-agent/src/agent.ts index e151a6821..8f76afc1b 100644 --- a/packages/bb-agent/src/agent.ts +++ b/packages/bb-agent/src/agent.ts @@ -196,7 +196,7 @@ export class AgentBase extends Scope { /** * @param scope - Blocks scope parent (determines resource naming and CDK discovery) - * @param id - unique agent ID (used in resource names, keep short for AppSync namespace limits) + * @param id - unique agent ID (used in resource names; keep it short for AWS resource naming limits) * @param config - developer-facing agent configuration * @param modelConfig - which model to use, picked by subclass (model.local or model.deployed) * @param createSnapshotStorage - factory that receives the internal FileBucket and returns the appropriate SnapshotStorage diff --git a/packages/bb-realtime/src/index.ts b/packages/bb-realtime/src/index.ts index f13679548..90f9bb986 100644 --- a/packages/bb-realtime/src/index.ts +++ b/packages/bb-realtime/src/index.ts @@ -51,7 +51,7 @@ globalEmitter.setMaxListeners(1000); // ── Realtime ──────────────────────────────────────────────────────────────── /** - * Real-time pub/sub messaging backed by AWS AppSync Events. + * Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB. * * **When to use:** You need to push data from the server to connected clients * in real time — chat messages, live notifications, dashboard updates, @@ -67,9 +67,9 @@ globalEmitter.setMaxListeners(1000); * - Keep message payloads small — large payloads increase latency and cost * - Return channel handles from API methods for seamless client hydration * - * **Scaling:** WebSocket connections managed by AppSync Events. Automatic scaling. - * $1.00 per million operations. 200 subscriptions per connection (adjustable). - * Message fan-out cost is proportional to subscriber count. + * **Scaling:** API Gateway manages WebSocket connections. The Blocks handler + * fans each publish out to subscribed connections, so latency and cost scale + * with subscriber count. See the package README for limits and cost details. * * @example * ```typescript diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts index 06a6d1374..0ef08ae70 100644 --- a/packages/blocks/src/index.ts +++ b/packages/blocks/src/index.ts @@ -159,12 +159,12 @@ export { DistributedTable, DistributedTableErrors } from '@aws-blocks/bb-distrib export type { DistributedTableOptions, ReadValidationMode, TableKeyConfig, TableKey, PutOptions as DTPutOptions, DeleteOptions as DTDeleteOptions, QueryOptions as DTQueryOptions, ScanOptions as DTScanOptions } from '@aws-blocks/bb-distributed-table'; /** - * **Real-time pub/sub messaging backed by AppSync Events.** + * **Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB.** * * Use for pushing data to connected browser clients: chat, notifications, * live dashboards, collaborative editing. Typed namespaces with schema - * validation on publish. Local dev uses WebSocket bridge; production uses - * AppSync Events API. + * validation on publish. Local dev uses a WebSocket bridge; production uses + * API Gateway WebSocket with DynamoDB-backed connection tracking. * * Package: `@aws-blocks/bb-realtime` * Full docs: `README.md` in the package directory above. diff --git a/test-apps/comprehensive/aws-blocks/knowledge/faq/general.md b/test-apps/comprehensive/aws-blocks/knowledge/faq/general.md index 38f8f6b31..6c90bdfa4 100644 --- a/test-apps/comprehensive/aws-blocks/knowledge/faq/general.md +++ b/test-apps/comprehensive/aws-blocks/knowledge/faq/general.md @@ -18,4 +18,4 @@ Blocks provides AuthBasic for username and password authentication with JWT sess ## Can I use Blocks for real-time features? -Yes, the Realtime Building Block provides typed pub/sub messaging backed by AppSync Events. It supports chat, notifications, live dashboards, and collaborative editing with typed namespaces and schema validation. +Yes, the Realtime Building Block provides typed pub/sub messaging backed by API Gateway WebSocket and DynamoDB. It supports chat, notifications, live dashboards, and collaborative editing with typed namespaces and schema validation.