Skip to content

Fix projection and reducer registration with Chronicle server#15

Merged
einari merged 9 commits into
mainfrom
fix/chronicle-typescript-client-projections-reducers
May 11, 2026
Merged

Fix projection and reducer registration with Chronicle server#15
einari merged 9 commits into
mainfrom
fix/chronicle-typescript-client-projections-reducers

Conversation

@einari
Copy link
Copy Markdown
Collaborator

@einari einari commented May 11, 2026

Fixed

  • Projections no longer trigger unnecessary server-side replays on every client restart; a deterministic djb2 hash over the sorted definition JSON replaces new Date() for the LastUpdated field.
  • Projection registration now retries up to 5 times with exponential back-off when Chronicle returns a transient UNKNOWN/UNAVAILABLE gRPC error on clean databases (Orleans grain contention race).
  • Reducers now register their read models with the correct MongoDB sink before starting observations, so reducer state is persisted to MongoDB instead of being silently discarded.
  • JsonSchemaGenerator no longer emits empty property schemas when tsx/esbuild strips design:type metadata; properties with unresolvable types are omitted and the server's fallback path is used instead.
  • @modelBound decorator removed; model-bound projections are now discovered automatically from classes decorated with @fromEvent and @readModel, eliminating a redundant annotation.
  • toContractsGuid moved to connection/Guid.ts so it is shared across EventSequences, Projections, and Reducers without duplication.
  • @projection() decorator accepts an optional readModelType constructor parameter for cases where generic inference is insufficient.
  • setFromContext() accepts a PropertyAccessor<EventContext> in addition to a plain string for type-safe context property selection.

einari added 9 commits May 11, 2026 10:26
The function was previously inlined in EventSequence.ts. Moving it to
the connection module lets Reducers and Projections reuse it without
duplicating the Guid-to-protobuf conversion logic.
Provides named Guid constants for the well-known Chronicle sink types
so Projections and Reducers can reference them by name rather than
embedding raw UUID strings.
When tsx/esbuild strips design:type metadata, mapRuntimeTypeToSchema
returns an empty object {}. Previously these empty schemas were included,
producing a required list that caused server-side Guid.Parse failures on
non-Guid event source IDs. Now any property with no resolved type is
omitted from both properties and required, and a model with zero resolved
properties emits an empty schema so the server falls back to accepting all
event content as-is.
…omEvent

The separate @ModelBound decorator and ModelBoundProjection DecoratorType
were redundant — any class decorated with @fromEvent and @readModel is
by definition a model-bound projection. The discovery path now uses those
existing decorators, removing the need for an extra annotation and the
ModelBoundProjection entry from the type registry.
…tUpdated

Three related fixes:
- Wrap each projection registration in registerWithRetry() with up to 5
  attempts and exponential back-off (2 s → 15 s cap). Addresses a race
  where Chronicle's Orleans grain returns UNKNOWN on a clean database when
  two projections register simultaneously.
- Replace new Date().toISOString() with a deterministic djb2 hash over
  the sorted definition JSON. Previously every client restart produced a
  new timestamp, causing the server to treat every registration as a
  definition change and trigger a full replay.
- Add optional readModelType parameter to @Projection() so declarative
  projections can declare their read model constructor explicitly when
  TypeScript generic inference is insufficient.
- Add PropertyAccessor overload to setFromContext() for type-safe
  context property selection.
…ations

Reducers were registering observations without first registering their
read models with the kernel, causing the server to use a null/default sink
instead of MongoDB. Added registerReadModels() called before
startObservation() to declare each reducer's read model schema, sink type,
and observer binding so Chronicle persists reducer state to MongoDB.
…ADME

- Export WellKnownSinks from the package root index so consumers can
  reference sink identifiers without importing from internal paths.
- Add debug-level log of raw event content in Reactors to aid diagnosing
  event dispatch issues.
- Remove @modelBoundProjection from the decorator list in README; it has
  been replaced by @fromEvent.
…ixed events

- Assign predefined Guid strings as event source IDs for seeded employees
  so projections using id: Guid on the read model parse correctly.
- Add address, city, zipCode, country fields to all four event types and
  the EmployeeState read model.
- Expand EmployeeStateReducer from interface to @readModel class with full
  address fields and an employeeAddressSet handler.
- Add employeeAddressSet handler to HrNotificationReactor.
- Fix employeeMoved to use event.city / event.country rather than the
  stale event.newCity field.
- Extend projections (declarative and model-bound) to project address
  fields from EmployeeAddressSet and EmployeeMoved events.
@einari einari merged commit 5702abc into main May 11, 2026
1 check passed
@einari einari deleted the fix/chronicle-typescript-client-projections-reducers branch May 11, 2026 18:01
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.

1 participant