@presage-kit/core
The core package provides the adaptive client, rules engine, tracker, persistence drivers, and all shared types.
pnpm add @presage-kit/corecreateAdaptiveClient(config)
Section intitulée « createAdaptiveClient(config) »Creates an AdaptiveClient instance — the central hub for rules, tracking, and context management.
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({ rules: [/* ... */], tracker: { adapters: [/* ... */], builtIn: { maxEvents: 500 }, }, persistence: { driver: createLocalStorageDriver('my-app'), }, maturity: { newMaxSessions: 5, dormantDaysInactive: 21, },})AdaptiveClientConfig
Section intitulée « AdaptiveClientConfig »interface AdaptiveClientConfig { rules?: AdaptationRule[] tracker?: { adapters?: TrackerAdapter[] builtIn?: Partial<BuiltInTrackerConfig> } persistence?: { driver: StorageDriver } maturity?: Partial<MaturityConfig>}| Field | Type | Default | Description |
|---|---|---|---|
rules | AdaptationRule[] | [] | Initial set of adaptation rules |
tracker.adapters | TrackerAdapter[] | [] | External analytics adapters |
tracker.builtIn.maxEvents | number | 1000 | Maximum events in memory |
tracker.builtIn.maxAgeMs | number | 2592000000 (30 days) | Max event age before pruning |
persistence.driver | StorageDriver | null | Storage driver for traits and signals |
maturity | Partial<MaturityConfig> | See defaults | Maturity threshold overrides |
AdaptiveClient
Section intitulée « AdaptiveClient »interface AdaptiveClient { state: ReadableAtom<AdaptiveStoreState> identify(userId: string, traits?: UserTraits): void updateTraits(traits: Partial<UserTraits>): void track(event: string, properties?: Record<string, unknown>): void resolve(point: AdaptationPoint): ResolvedAdaptation evaluateAction(adaptationId: string): AdaptationAction | null getContext(): UserContext destroy(): void}A reactive atom containing the current store state. Subscribe to it for change notifications.
const unsubscribe = client.state.subscribe((state) => { console.log('Context changed:', state.context)})identify(userId, traits?)
Section intitulée « identify(userId, traits?) »Associates the current session with a user. Merges provided traits with existing traits.
client.identify('user-123', { role: 'admin', plan: 'enterprise', signupDate: '2025-01-15', company: 'Acme Corp', companySize: 150,})Also forwards the identify call to all registered tracker adapters.
updateTraits(traits)
Section intitulée « updateTraits(traits) »Merges new traits into the existing traits without requiring a full identify() call.
client.updateTraits({ plan: 'enterprise', locale: 'fr' })track(event, properties?)
Section intitulée « track(event, properties?) »Records an event. Triggers debounced signal recomputation (100ms).
client.track('feature_used', { featureId: 'export' })client.track('click', { elementId: 'sidebar-settings' })client.track('custom_signal', { signalId: 'engagement', value: 3 })client.track('page_view', { path: '/dashboard' })resolve(point)
Section intitulée « resolve(point) »Resolves an adaptation point by evaluating rules and returning the selected variant.
const result = client.resolve({ id: 'onboarding', variants: ['guided-tour', 'standard', 'minimal'], defaultVariant: 'standard', strategy: { type: 'rules' },})
console.log(result.selectedVariant) // 'guided-tour'console.log(result.reason) // 'Rule "new-user-tour" matched'evaluateAction(adaptationId)
Section intitulée « evaluateAction(adaptationId) »Evaluates rules for an adaptation point and returns the raw action (or null if no rule matches). Useful for imperative control flow.
const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'reorder') { // Reorder navigation items}getContext()
Section intitulée « getContext() »Returns the current UserContext snapshot.
const ctx = client.getContext()console.log(ctx.traits.role) // 'admin'console.log(ctx.signals.sessionCount) // 12console.log(ctx.maturity) // 'active'destroy()
Section intitulée « destroy() »Tears down the client. Cancels pending timers and unsubscribes from state changes.
client.destroy()AdaptiveStoreState
Section intitulée « AdaptiveStoreState »interface AdaptiveStoreState { context: UserContext isReady: boolean}UserContext
Section intitulée « UserContext »interface UserContext { traits: UserTraits signals: BehavioralSignals maturity: Maturity}UserTraits
Section intitulée « UserTraits »interface UserTraits { userId?: string role?: string plan?: string signupDate?: string company?: string companySize?: number locale?: string [key: string]: unknown}Extensible with any custom key-value pair.
BehavioralSignals
Section intitulée « BehavioralSignals »interface BehavioralSignals { sessionCount: number totalEvents: number featureUsage: Record<string, number> lastSeenAt: string // ISO 8601 timestamp firstSeenAt: string // ISO 8601 timestamp currentSessionDuration: number daysSinceSignup: number clickMap: Record<string, number> customSignals: Record<string, number>}Maturity
Section intitulée « Maturity »type Maturity = 'new' | 'onboarding' | 'active' | 'power' | 'dormant'MaturityConfig
Section intitulée « MaturityConfig »interface MaturityConfig { newMaxSessions: number // Default: 3 onboardingMaxSessions: number // Default: 10 powerMinFeatures: number // Default: 5 dormantDaysInactive: number // Default: 14}AdaptationRule
Section intitulée « AdaptationRule »interface AdaptationRule { id: string adaptationId: string priority: number conditions: ConditionGroup action: AdaptationAction}Condition
Section intitulée « Condition »interface Condition { field: string operator: ConditionOperator value: unknown}ConditionOperator
Section intitulée « ConditionOperator »type ConditionOperator = | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'notContains' | 'exists' | 'notExists' | 'between' | 'matches'ConditionGroup
Section intitulée « ConditionGroup »interface ConditionGroup { all?: (Condition | ConditionGroup)[] any?: (Condition | ConditionGroup)[] not?: Condition | ConditionGroup}AdaptationAction
Section intitulée « AdaptationAction »type AdaptationAction = | { type: 'show'; variantId: string } | { type: 'hide' } | { type: 'reorder'; order: string[] } | { type: 'modify'; props: Record<string, unknown> }AdaptationPoint
Section intitulée « AdaptationPoint »interface AdaptationPoint { id: string variants: readonly string[] defaultVariant: string strategy: Strategy}ResolvedAdaptation
Section intitulée « ResolvedAdaptation »interface ResolvedAdaptation { adaptationId: string selectedVariant: string strategy: Strategy['type'] // 'rules' reason: string // e.g. 'Rule "my-rule" matched' or 'default' resolvedAt: number // timestamp}Strategy
Section intitulée « Strategy »type Strategy = { type: 'rules' }TrackerAdapter
Section intitulée « TrackerAdapter »Interface for plugging external analytics services into the tracker.
interface TrackerAdapter { name: string track(event: TrackerEvent): void | Promise<void> identify(userId: string, traits: UserTraits): void | Promise<void> flush?(): Promise<void>}TrackerEvent
Section intitulée « TrackerEvent »interface TrackerEvent { name: string properties?: Record<string, unknown> timestamp: number userId?: string sessionId: string}BuiltInTrackerConfig
Section intitulée « BuiltInTrackerConfig »interface BuiltInTrackerConfig { maxEvents: number // Default: 1000 maxAgeMs: number // Default: 2592000000 (30 days)}StorageDriver
Section intitulée « StorageDriver »Interface for custom persistence drivers.
interface StorageDriver { get<T>(key: string): T | null set<T>(key: string, value: T): void remove(key: string): void clear(): void}createLocalStorageDriver(prefix)
Section intitulée « createLocalStorageDriver(prefix) »Creates a StorageDriver backed by localStorage. Keys are stored as prefix:key.
import { createLocalStorageDriver } from '@presage-kit/core'
const driver = createLocalStorageDriver('my-app')// Stores as 'my-app:traits', 'my-app:signals'Gracefully returns null on get() when localStorage is unavailable (e.g., SSR).
createMemoryDriver()
Section intitulée « createMemoryDriver() »Creates an in-memory StorageDriver. Data is lost when the process exits.
import { createMemoryDriver } from '@presage-kit/core'
const driver = createMemoryDriver()Orderable
Section intitulée « Orderable »Base interface for items that can be reordered by reorderItems() and the AdaptiveOrder/useAdaptiveOrder APIs. Every item must have a unique id.
interface Orderable { id: string [key: string]: unknown}The NavItem interface used by AdaptiveNav/useAdaptiveNav extends Orderable.
reorderItems(items, order)
Section intitulée « reorderItems(items, order) »A pure utility function that reorders items according to an order array. Items listed in order appear first (in that order), followed by any remaining items not mentioned in order, preserving their original relative order.
import { reorderItems } from '@presage-kit/core'
const items = [ { id: 'a', label: 'Alpha' }, { id: 'b', label: 'Beta' }, { id: 'c', label: 'Gamma' },]
const reordered = reorderItems(items, ['c', 'a'])// [{ id: 'c', label: 'Gamma' }, { id: 'a', label: 'Alpha' }, { id: 'b', label: 'Beta' }]Parameters:
| Param | Type | Description |
|---|---|---|
items | T[] (extends Orderable) | Array of items to reorder |
order | string[] | Array of id values specifying the desired order |
Return type: T[] — A new array with items reordered. The original array is not mutated.
This is the same reorder logic used internally by AdaptiveOrder, AdaptiveNav, and useAdaptiveOrder.
RulesEngine
Section intitulée « RulesEngine »The rules engine can be used directly for testing or advanced use cases.
import { RulesEngine } from '@presage-kit/core'
const engine = new RulesEngine()addRule(rule)
Section intitulée « addRule(rule) »Adds a single rule. Re-sorts rules by priority.
engine.addRule({ id: 'my-rule', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }] }, action: { type: 'show', variantId: 'guided-tour' },})addRules(rules)
Section intitulée « addRules(rules) »Adds multiple rules at once. Re-sorts once.
engine.addRules([rule1, rule2, rule3])removeRule(ruleId)
Section intitulée « removeRule(ruleId) »Removes a rule by its ID.
engine.removeRule('my-rule')evaluate(adaptationId, context)
Section intitulée « evaluate(adaptationId, context) »Evaluates all rules for the given adaptation ID against a UserContext. Returns the first matching action, or null.
const action = engine.evaluate('onboarding', { traits: { role: 'admin' }, signals: { sessionCount: 2, totalEvents: 10, /* ... */ }, maturity: 'new',})getRules()
Section intitulée « getRules() »Returns all registered rules as a readonly array.
const rules = engine.getRules() // readonly AdaptationRule[]atom(initialValue)
Section intitulée « atom(initialValue) »A minimal reactive primitive for advanced use cases. Follows the Svelte store contract.
import { atom } from '@presage-kit/core'
const count = atom(0)
// Readconsole.log(count.get()) // 0
// Writecount.set(1)
// Subscribe (called immediately with current value)const unsub = count.subscribe((value, oldValue) => { console.log(`Changed from ${oldValue} to ${value}`)})
// Listen (NOT called immediately)const unlisten = count.listen((value, oldValue) => { console.log(`Changed from ${oldValue} to ${value}`)})
// Cleanupunsub()unlisten()ReadableAtom<T>
Section intitulée « ReadableAtom<T> »interface ReadableAtom<T> { get(): T subscribe(listener: Listener<T>): Unsubscribe listen(listener: Listener<T>): Unsubscribe}WritableAtom<T>
Section intitulée « WritableAtom<T> »Extends ReadableAtom with a set() method.
interface WritableAtom<T> extends ReadableAtom<T> { set(value: T): void}Listener<T>
Section intitulée « Listener<T> »type Listener<T> = (value: T, oldValue: T) => voidUnsubscribe
Section intitulée « Unsubscribe »type Unsubscribe = () => void