Skip to content

Architecture ​

How config becomes a running API ​

Monorise uses a small build step to turn entity configs into runnable handlers.

monorise.config.ts + entity configs  →  monorise dev/build (CLI)  →  .monorise/config.ts + handle.ts  →  SST stack
  • monorise.config.ts points to your entity config directory and optional custom routes (Hono). When analytics is enabled, the CLI also generates its analytics schema manifest from entity and named mutual configs.
  • The CLI writes .monorise/handle.ts which exports Lambda handlers used by SST (API + processors + replication).

SST infrastructure ​

The MonoriseCore SST construct provisions the full runtime infrastructure on AWS:

Monorise SST Architecture

The architecture consists of:

  • API Gateway — routes HTTP requests to the Hono Lambda handler
  • DynamoDB single table — stores all entities, mutuals, tags, and unique fields
  • EventBridge bus — publishes entity lifecycle events (created, updated, mutual processed)
  • Processors — SQS-backed Lambda functions that react to events and maintain denormalized data
  • DynamoDB Stream — triggers the replication processor to keep denormalized copies in sync
  • Analytics delivery (optional) — a second DynamoDB Stream subscriber normalizes canonical entity and mutual metadata changes, sends buffered history through Firehose to S3, and materializes typed Athena current-state and history tables daily

Analytics path ​

When MonoriseCore.analytics is enabled, DynamoDB Streams with NEW_AND_OLD_IMAGES are the canonical analytics source. The analytics subscriber records canonical INSERT, MODIFY, and REMOVE changes with available before and after state, excluding derived list, tag, unique, lock, and replication rows. Firehose stores the event history in S3 under daily event_date partitions, with an optional hourly partition for individual datasets. A daily job compacts history to Parquet and merges the latest records into Iceberg current-state tables.

EventBridge is not used for canonical analytics capture because lifecycle events do not provide reliable before-images. It remains available for consumer-defined business-event analytics.

QFunction (processor pattern) ​

Each event-driven processor (mutual, tag, tree) uses the QFunction pattern — an SQS queue paired with a Lambda function, a Dead Letter Queue for failed messages, and a CloudWatch alarm that publishes to an SNS topic when messages land in the DLQ:

Monorise QFunction

The flow:

  1. Events arrive in the SQS queue from EventBridge
  2. Lambda processes the message (e.g., syncs mutual records, recalculates tags)
  3. If processing fails, the message moves to the DLQ after retry exhaustion
  4. A CloudWatch Alarm fires when the DLQ depth exceeds 0
  5. The alarm publishes to the configured SNS topic

Failed messages can be redriven from the DLQ once the issue is resolved.

Key behaviors ​

  • Mutual processor: creates/updates/removes relationship items in both directions with conditional checks and locking.
  • Tag processor: calculates tag diffs and syncs tag items.
  • Tree processor: walks configured relationship paths and publishes derived mutual updates.
  • Replication processor: keeps denormalized copies aligned via stream updates (uses replication indexes).
  • Analytics delivery: captures canonical stream changes into durable S3 history and refreshes Athena current-state tables daily when enabled.

API reference ​

The default Hono API exposes the following routes under /core. All entity routes are validated by entityTypeCheck middleware, and mutual routes by mutualTypeCheck middleware.

Entity endpoints ​

MethodRouteDescription
GET/entity/:entityTypeList entities (paginated, ?limit=20&query=...)
POST/entity/:entityTypeCreate entity
GET/entity/:entityType/unique/:field/:valueGet entity by unique field
GET/entity/:entityType/:entityIdGet entity by ID
PUT/entity/:entityType/:entityIdUpsert entity (full replacement)
PATCH/entity/:entityType/:entityIdUpdate entity (partial); supports optional named $condition
DELETE/entity/:entityType/:entityIdDelete entity
POST/entity/:entityType/:entityId/adjustAtomic numeric adjustment (body: { field: delta })

Mutual endpoints ​

MethodRouteDescription
GET/mutual/:byEntityType/:byEntityId/:entityTypeList mutuals (entities related to a given entity)
POST/mutual/:byEntityType/:byEntityId/:entityType/:entityIdCreate mutual relationship
GET/mutual/:byEntityType/:byEntityId/:entityType/:entityIdGet specific mutual
PATCH/mutual/:byEntityType/:byEntityId/:entityType/:entityIdUpdate mutual data
DELETE/mutual/:byEntityType/:byEntityId/:entityType/:entityIdDelete mutual relationship

Tag endpoints ​

MethodRouteDescription
GET/tag/:entityType/:tagNameQuery tagged entities (?group=...&query=...&start=...&end=...&limit=...&lastKey=...)

Transaction endpoint ​

MethodRouteDescription
POST/transactionExecute entity operations atomically

Custom routes can be mounted under /core/app/* via customRoutes in your monorise config.

Conditional updates ($condition) ​

Define named updateConditions in the entity config, then send the permitted condition name with the update. The server resolves the name to a DynamoDB ConditionExpression; raw operators never need to be client-facing.

json
{
  "status": "confirmed",
  "confirmedAt": "2026-04-13T00:00:00.000Z",
  "$condition": "confirm"
}

$condition is optional for entity updates, but required for adjustments when the entity defines adjustmentConditions. A failed condition returns 409 CONFLICT. See Entities: Conditional writes for configuration examples.

Response behavior for PATCH:

  • 200 OK: update applied
  • 409 CONFLICT: condition failed (CONDITIONAL_CHECK_FAILED); a static condition on a missing entity also lands here
  • 404 NOT_FOUND: entity missing without a condition or while resolving a function condition
  • 400 BAD_REQUEST: validation errors, unique-value conflicts, or an unknown/undefined condition name

Legacy $where

Raw $where conditions are deprecated and disabled by default. Enable allowLegacyWhere only for trusted compatibility callers; new clients must use named conditions. A compatibility request places the raw clauses under $where, for example { "status": "confirmed", "$where": { "status": { "$eq": "pending" } } }.

Data layout cheat sheet ​

Access patternKey structure
Entity metadataPK = <entityType>#<entityId>, SK = #METADATA#
Entity listPK = LIST#<entityType>, SK = <entityType>#<entityId>
Mutual recordsMUTUAL#<id> item + two directional lookups
Tag recordsPK = TAG#<entityType>#<tagName>[#group], SK = <sortValue?>#<entityType>#<entityId>
Unique fieldsPK = UNIQUE#<field>#<value>, SK = <entityType>

The replication indexes (R1PK/R2PK) support fast updates of denormalized items when entity data changes.

Released under the MIT License.