Skip to content

Conversation

@takaokouji
Copy link
Contributor

Summary

Server-side implementation of polling-based event notification fallback for Mesh v2. Adds recordEventsByNode mutation and getEventsSince query to support environments where WebSocket connections are blocked.

Implementation Details

GraphQL Schema Changes

New Types and Fields

  • RecordEventsPayload: Response type for recordEventsByNode mutation
    • nextSince: Cursor for next getEventsSince call
  • Event.cursor: Sort Key (SK) for cursor-based pagination
  • Group.useWebSocket: Protocol selection flag
  • Group.pollingIntervalSeconds: Polling interval (when useWebSocket=false)
  • Node.useWebSocket: Inherited from group
  • Node.pollingIntervalSeconds: Inherited from group

New Mutations

  • createGroup: Added useWebSocket parameter
  • recordEventsByNode: Records events to DynamoDB (polling only)

New Queries

  • getEventsSince: Retrieves events since last fetch (cursor-based)

Server-side Timestamp Management

IMPORTANT: recordEventsByNode uses server-side timestamps, not client-side event.firedAt.

Reason: 1+ second delay exists from device event firing to recordEventsByNode call.

Implementation:

const serverTimestamp = util.time.nowISO8601();
const items = events.map(event => ({
  pk: `GROUP#${groupId}@${domain}`,
  sk: `EVENT#${serverTimestamp}#${util.autoId()}`,
  timestamp: serverTimestamp,  // Server-side timestamp
  ttl: util.time.nowEpochSeconds() + parseInt(ctx.env.MESH_EVENT_TTL_SECONDS)
}));

Benefits:

  • Time consistency (no client clock skew)
  • Order guarantee (request reception order)
  • Consistency with since parameter in getEventsSince

Cursor-based Pagination

getEventsSince uses Sort Key (SK) as cursor:

// Request
const sk = since.startsWith('EVENT#') ? since : `EVENT#${since}`;
return {
  operation: 'Query',
  query: {
    expression: 'pk = :pk AND sk > :sk',
    expressionValues: {
      ':pk': `GROUP#${groupId}@${domain}`,
      ':sk': sk
    }
  },
  limit: 100
};

// Response
return ctx.result.items.map(item => ({
  // ...
  cursor: item.sk  // Return SK as cursor
}));

Environment Variables

  • MESH_EVENT_TTL_SECONDS: 10 (events auto-delete after 10 seconds)
  • MESH_POLLING_INTERVAL_SECONDS: 2 (poll every 2 seconds)

CDK Stack Changes

  • Added environment variables to AppSync resolvers
  • Added recordEventsByNode and getEventsSince resolvers
  • Updated Lambda function to handle useWebSocket flag

Testing

Unit Tests

  • recordEventsByNode saves events with server-side timestamps
  • getEventsSince retrieves events with cursor pagination
  • createGroup accepts useWebSocket parameter

Integration Tests

  • Event recording and retrieval flow
  • Cursor-based pagination
  • TTL auto-deletion (10 seconds)

Cost Optimization

  • WebSocket Priority: Uses WebSocket when available (no DynamoDB writes)
  • Short TTL: Events auto-delete after 10 seconds to minimize storage cost
  • Efficient Query: Cursor-based pagination with 100-item limit

Related

🤖 Generated with Claude Code

- Updated GraphQL schema with recordEventsByNode and getEventsSince
- Implemented JS resolvers for polling support
- Updated createGroup and joinGroup to handle useWebSocket flag
- Added environment variables for polling interval and event TTL
- Updated documentation and tests

Co-Authored-By: Gemini <[email protected]>
… to Lambda

- Moved recordEventsByNode from JS pipeline to Ruby Lambda (due to JS limitation)
- Implemented RecordEventsUseCase and record_events method in DynamoDBRepository
- Renamed Lambda resources to MeshV2LambdaFunction/DataSource for clarity
- Added MESH_EVENT_TTL_SECONDS and MESH_POLLING_INTERVAL_SECONDS to Lambda env
- Updated request specs to include required useWebSocket argument
- Removed unused JS resolver file
…e type

- Added createdAt to Node type in schema
- Added createdAt to HeartbeatPayload and MemberHeartbeatPayload
- Return createdAt in joinGroup, renewHeartbeat, and updateNodeTTL functions
- Added createdAt and expiresAt to CreateGroupUseCase in Lambda
- Return nextSince correctly in RecordEventsUseCase
- Reverted schema changes for createdAt
- Reverted Lambda and JS function changes for createdAt
- Retained record_events batch processing improvements
- Fixed missing attributeValues in joinGroupFunction.js
- Fixed formatting and indentation in updateNodeTTL.js
- Fixed incorrect function name in mesh-v2-stack.ts
@takaokouji takaokouji merged commit 7270050 into main Jan 11, 2026
3 checks passed
@takaokouji takaokouji deleted the feature/polling-fallback branch January 11, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants