Files
aris/packages/aris-core
kenneth 181160b018 feat(core): add FeedEngine for FeedSource orchestration
Introduces FeedEngine that consumes FeedSource instances and manages
the dependency graph for context flow and item collection.

- Validates dependency graph (missing deps, circular references)
- Topologically sorts sources for execution order
- Runs fetchContext() in dependency order, accumulating context
- Runs fetchItems() on all sources with final context
- Supports reactive updates via onContextUpdate/onItemsUpdate
- Graceful error handling (continues after source failures)

Marks DataSource, ContextProvider, ContextBridge, Reconciler, and
FeedController as deprecated in favor of FeedSource + FeedEngine.

Co-authored-by: Ona <no-reply@ona.com>
2026-01-24 22:42:00 +00:00
..
2026-01-18 23:32:47 +00:00

@aris/core

Core orchestration layer for ARIS feed reconciliation.

Overview

flowchart TB
    subgraph Sources["Feed Sources (Graph)"]
        LS[Location Source]
        WS[Weather Source]
        TS[TFL Source]
        CS[Calendar Source]
    end

    LS --> WS
    LS --> TS

    subgraph Controller["FeedController"]
        direction TB
        C1[Holds context]
        C2[Manages source graph]
        C3[Reconciles on update]
        C4[Notifies subscribers]
    end

    Sources --> Controller
    Controller --> Sub[Subscribers]

Concepts

FeedSource

A unified interface for sources that provide context and/or feed items. Sources form a dependency graph.

interface FeedSource<TItem extends FeedItem = FeedItem> {
	readonly id: string
	readonly dependencies?: readonly string[]

	// Context production (optional)
	onContextUpdate?(
		callback: (update: Partial<Context>) => void,
		getContext: () => Context,
	): () => void
	fetchContext?(context: Context): Promise<Partial<Context>>

	// Feed item production (optional)
	onItemsUpdate?(callback: (items: TItem[]) => void, getContext: () => Context): () => void
	fetchItems?(context: Context): Promise<TItem[]>
}

A source may:

  • Provide context for other sources (implement fetchContext/onContextUpdate)
  • Produce feed items (implement fetchItems/onItemsUpdate)
  • Both

Context Keys

Each package exports typed context keys for type-safe access:

import { contextKey, type ContextKey } from "@aris/core"

interface Location {
	lat: number
	lng: number
}

export const LocationKey: ContextKey<Location> = contextKey("location")

Usage

Define a Context-Only Source

import type { FeedSource } from "@aris/core"

const locationSource: FeedSource = {
	id: "location",

	onContextUpdate(callback, _getContext) {
		const watchId = navigator.geolocation.watchPosition((pos) => {
			callback({
				[LocationKey]: { lat: pos.coords.latitude, lng: pos.coords.longitude },
			})
		})
		return () => navigator.geolocation.clearWatch(watchId)
	},

	async fetchContext() {
		const pos = await getCurrentPosition()
		return {
			[LocationKey]: { lat: pos.coords.latitude, lng: pos.coords.longitude },
		}
	},
}

Define a Source with Dependencies

import type { FeedSource, FeedItem } from "@aris/core"
import { contextValue } from "@aris/core"

type WeatherItem = FeedItem<"weather", { temp: number; condition: string }>

const weatherSource: FeedSource<WeatherItem> = {
	id: "weather",
	dependencies: ["location"],

	async fetchContext(context) {
		const location = contextValue(context, LocationKey)
		if (!location) return {}

		const weather = await fetchWeatherApi(location)
		return { [WeatherKey]: weather }
	},

	async fetchItems(context) {
		const weather = contextValue(context, WeatherKey)
		if (!weather) return []

		return [
			{
				id: `weather-${Date.now()}`,
				type: "weather",
				priority: 0.5,
				timestamp: new Date(),
				data: { temp: weather.temp, condition: weather.condition },
			},
		]
	},
}

Graph Behavior

The source graph:

  1. Validates all dependencies exist
  2. Detects circular dependencies
  3. Topologically sorts sources

On refresh:

  1. fetchContext runs in dependency order
  2. fetchItems runs on all sources
  3. Combined items returned to subscribers

On reactive update:

  1. Source pushes context update via onContextUpdate callback
  2. Dependent sources re-run fetchContext
  3. Affected sources re-run fetchItems
  4. Subscribers notified

API

Context

Export Description
ContextKey<T> Branded type for type-safe context keys
contextKey<T>(key) Creates a typed context key
contextValue(context, key) Type-safe context value accessor
Context Time + arbitrary key-value bag

Feed

Export Description
FeedSource<TItem> Unified source interface
FeedItem<TType, TData> Single item in the feed

Legacy (deprecated)

Export Description
DataSource<TItem, TConfig> Use FeedSource instead
ContextProvider<T> Use FeedSource instead
ContextBridge Use source graph instead