@ossy/fold
v3.0.5
Published
Pure resource event fold algebra
Readme
@ossy/fold
Pure resource event fold algebra for the Ossy platform. Isomorphic (Node + browser), no I/O — the same functions run on the server when rebuilding snapshots and on the client for optimistic UI.
Named after the FP / event-sourcing fold: reducing a stream of events into resource state.
Core concepts
A resource is a versioned platform instance with a standard envelope (resourceId, type, name, location, belongsTo, access, audit fields) plus schema-defined content.
Events in eventstore use this shape:
{
id: 'V1StGXR8_Z', // unique event id
type: '@ossy/booking/schema/booking', // schema id
resourceId: 'x7kP2mN9qR', // resource instance
event: 'Patched', // lifecycle name
version: 7,
payload: { … },
created: 1718640000000,
createdBy: 'user_…',
}Standard lifecycle events:
| event | Semantics | payload |
|---------|-----------|-----------|
| Created | Birth | Full envelope + full content |
| Patched | Partial merge | Partial envelope and/or partial content; undefined keys omitted; deep merge |
| Updated | Hard replace | Full content required |
| Deleted | Tombstone | Folded state gets status: ['removed'] |
Installation
npm install @ossy/foldResource folding
import { fold, createDefaultReducer, applyPatch, applyUpdate } from '@ossy/fold'
// Fold a single event
const state = fold({}, {
id: 'evt1',
type: '@ossy/booking/schema/booking',
resourceId: 'res1',
event: 'Created',
version: 1,
created: Date.now(),
createdBy: 'user1',
payload: {
name: 'Booking',
location: '/b/',
belongsTo: 'ws1',
access: 'workspace',
content: { clientEmail: '[email protected]' },
},
})
// Fold an entire stream
const reduce = createDefaultReducer()
const snapshot = reduce(events, initialState)
// Custom handlers for non-lifecycle events
const reduceWithHooks = createDefaultReducer(schema, {
Signed: (state, event) => ({ ...state, signedAt: event.created }),
})API reference
| Export | Signature | Description |
|---|---|---|
| fold | (state, event, handlers?) => state | Fold one event into resource state. Custom handlers[event.event] override defaults. |
| createDefaultReducer | (schema?, handlers?) => (events, state?) => state | Reduce an event array left-to-right. |
| applyPatch | (base, partial) => state | Deep merge; omit undefined keys (used by Patched). |
| applyUpdate | (state, content) => state | Replace content (used by Updated). |
Projection helpers
Projection aggregates (kind: 'projection') fold foreign resource events into scope-keyed read models.
import { matchesSource, applyProjection, resolveProjectionUpdates } from '@ossy/fold'
// Match event against a projection source descriptor
matchesSource(
{ type: '@ossy/booking/schema/booking', event: 'Created' },
event,
)
// Incremental projection step
const next = applyProjection(event, currentState, ProjectionClass)
// Resolve all projection updates for one event
const updates = resolveProjectionUpdates(event, registeredProjections)
// => [{ projection, scopeId, state }, …]Match helpers
| Export | Description |
|---|---|
| getByPath(obj, path) | Read a dotted path (payload.content.status). |
| matchesGlob(pattern, value) | Glob match for trigger match values (ws_*). |
| matchesSource(source, evt) | Match type, event, and optional match glob fields on payload. |
| applyProjection(event, state, projection) | Run projection.Apply when a source matches; returns null when no match. |
| resolveProjectionUpdates(event, projections) | Collect scope-keyed updates for all matching projections. |
Where it is used
| Consumer | Role |
|---|---|
| @ossy/resources | createDefaultReducer when saving resource snapshots |
| @ossy/event-store | createDefaultReducer in aggregate rebuild; matchesSource in projection dispatch and push invalidation |
| @ossy/sdk-react | fold for optimistic read-cache updates |
Design constraints
- Strictly pure — no Mongo, no network, no side effects. Server and client must stay in sync.
- Envelope is uniform —
*.schema.jsdeclaresfields[]forcontentonly; lifecycle reducers handle the platform envelope. - Custom events — pass a
handlersmap tofold/createDefaultReducerfor schema-specific event names beyond the four lifecycle events.
