Aller au contenu

@presage-kit/core

Le package core fournit le client adaptatif, le moteur de règles, le tracker, les drivers de persistance et tous les types partagés.

Fenêtre de terminal
pnpm add @presage-kit/core

Crée une instance AdaptiveClient — le hub central pour les règles, le suivi et la gestion du contexte.

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,
},
})
interface AdaptiveClientConfig {
rules?: AdaptationRule[]
tracker?: {
adapters?: TrackerAdapter[]
builtIn?: Partial<BuiltInTrackerConfig>
}
persistence?: {
driver: StorageDriver
}
maturity?: Partial<MaturityConfig>
}
ChampTypeDéfautDescription
rulesAdaptationRule[][]Ensemble initial de règles d’adaptation
tracker.adaptersTrackerAdapter[][]Adaptateurs d’analytique externes
tracker.builtIn.maxEventsnumber1000Nombre maximum d’événements en mémoire
tracker.builtIn.maxAgeMsnumber2592000000 (30 jours)Age maximum des événements avant suppression
persistence.driverStorageDrivernullDriver de stockage pour les traits et signaux
maturityPartial<MaturityConfig>Voir les défautsSurcharges des seuils de maturité
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
}

Un atome réactif contenant l’état courant du store. Abonnez-vous pour recevoir les notifications de changement.

const unsubscribe = client.state.subscribe((state) => {
console.log('Context changed:', state.context)
})

Associe la session courante à un utilisateur. Fusionne les traits fournis avec les traits existants.

client.identify('user-123', {
role: 'admin',
plan: 'enterprise',
signupDate: '2025-01-15',
company: 'Acme Corp',
companySize: 150,
})

Transmet également l’appel identify à tous les adaptateurs de tracker enregistrés.

Fusionne de nouveaux traits dans les traits existants sans nécessiter un appel complet à identify().

client.updateTraits({ plan: 'enterprise', locale: 'fr' })

Enregistre un événement. Déclenche un recalcul différé des signaux (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' })

Résout un point d’adaptation en évaluant les règles et en retournant la variante sélectionnée.

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'

Evalue les règles pour un point d’adaptation et retourne l’action brute (ou null si aucune règle ne correspond). Utile pour le contrôle de flux impératif.

const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'reorder') {
// Reorder navigation items
}

Retourne un instantané du UserContext courant.

const ctx = client.getContext()
console.log(ctx.traits.role) // 'admin'
console.log(ctx.signals.sessionCount) // 12
console.log(ctx.maturity) // 'active'

Détruit le client. Annule les timers en attente et se désabonne des changements d’état.

client.destroy()
interface AdaptiveStoreState {
context: UserContext
isReady: boolean
}
interface UserContext {
traits: UserTraits
signals: BehavioralSignals
maturity: Maturity
}
interface UserTraits {
userId?: string
role?: string
plan?: string
signupDate?: string
company?: string
companySize?: number
locale?: string
[key: string]: unknown
}

Extensible avec n’importe quelle paire clé-valeur personnalisée.

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>
}
type Maturity = 'new' | 'onboarding' | 'active' | 'power' | 'dormant'
interface MaturityConfig {
newMaxSessions: number // Default: 3
onboardingMaxSessions: number // Default: 10
powerMinFeatures: number // Default: 5
dormantDaysInactive: number // Default: 14
}
interface AdaptationRule {
id: string
adaptationId: string
priority: number
conditions: ConditionGroup
action: AdaptationAction
}
interface Condition {
field: string
operator: ConditionOperator
value: unknown
}
type ConditionOperator =
| 'eq' | 'neq'
| 'gt' | 'gte' | 'lt' | 'lte'
| 'in' | 'notIn'
| 'contains' | 'notContains'
| 'exists' | 'notExists'
| 'between'
| 'matches'
interface ConditionGroup {
all?: (Condition | ConditionGroup)[]
any?: (Condition | ConditionGroup)[]
not?: Condition | ConditionGroup
}
type AdaptationAction =
| { type: 'show'; variantId: string }
| { type: 'hide' }
| { type: 'reorder'; order: string[] }
| { type: 'modify'; props: Record<string, unknown> }
interface AdaptationPoint {
id: string
variants: readonly string[]
defaultVariant: string
strategy: Strategy
}
interface ResolvedAdaptation {
adaptationId: string
selectedVariant: string
strategy: Strategy['type'] // 'rules'
reason: string // e.g. 'Rule "my-rule" matched' or 'default'
resolvedAt: number // timestamp
}
type Strategy = { type: 'rules' }

Interface pour brancher des services d’analytique externes sur le tracker.

interface TrackerAdapter {
name: string
track(event: TrackerEvent): void | Promise<void>
identify(userId: string, traits: UserTraits): void | Promise<void>
flush?(): Promise<void>
}
interface TrackerEvent {
name: string
properties?: Record<string, unknown>
timestamp: number
userId?: string
sessionId: string
}
interface BuiltInTrackerConfig {
maxEvents: number // Default: 1000
maxAgeMs: number // Default: 2592000000 (30 days)
}

Interface pour les drivers de persistance personnalisés.

interface StorageDriver {
get<T>(key: string): T | null
set<T>(key: string, value: T): void
remove(key: string): void
clear(): void
}

Crée un StorageDriver basé sur localStorage. Les clés sont stockées sous la forme prefix:key.

import { createLocalStorageDriver } from '@presage-kit/core'
const driver = createLocalStorageDriver('my-app')
// Stores as 'my-app:traits', 'my-app:signals'

Retourne gracieusement null sur get() quand localStorage n’est pas disponible (ex. SSR).

Crée un StorageDriver en mémoire. Les données sont perdues quand le processus se termine.

import { createMemoryDriver } from '@presage-kit/core'
const driver = createMemoryDriver()

Le moteur de règles peut être utilisé directement pour les tests ou les cas d’utilisation avancés.

import { RulesEngine } from '@presage-kit/core'
const engine = new RulesEngine()

Ajoute une seule règle. Re-trie les règles par priorité.

engine.addRule({
id: 'my-rule',
adaptationId: 'onboarding',
priority: 10,
conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }] },
action: { type: 'show', variantId: 'guided-tour' },
})

Ajoute plusieurs règles en une fois. Re-trie une seule fois.

engine.addRules([rule1, rule2, rule3])

Supprime une règle par son ID.

engine.removeRule('my-rule')

Evalue toutes les règles pour l’ID d’adaptation donné par rapport à un UserContext. Retourne la première action correspondante, ou null.

const action = engine.evaluate('onboarding', {
traits: { role: 'admin' },
signals: { sessionCount: 2, totalEvents: 10, /* ... */ },
maturity: 'new',
})

Retourne toutes les règles enregistrées sous forme de tableau en lecture seule.

const rules = engine.getRules() // readonly AdaptationRule[]

Une primitive réactive minimaliste pour les cas d’utilisation avancés. Suit le contrat du store Svelte.

import { atom } from '@presage-kit/core'
const count = atom(0)
// Read
console.log(count.get()) // 0
// Write
count.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}`)
})
// Cleanup
unsub()
unlisten()
interface ReadableAtom<T> {
get(): T
subscribe(listener: Listener<T>): Unsubscribe
listen(listener: Listener<T>): Unsubscribe
}

Etend ReadableAtom avec une méthode set().

interface WritableAtom<T> extends ReadableAtom<T> {
set(value: T): void
}
type Listener<T> = (value: T, oldValue: T) => void
type Unsubscribe = () => void