Skip to content

Installation

bash
npm install @statesync/core

What's inside

The core package provides everything needed to wire up the invalidation-pull protocol:

  • EnginecreateRevisionSync() creates the sync handle
  • RevisionisCanonicalRevision, compareRevisions, ZERO_REVISION
  • RetrywithRetry, withRetryReporting wrap a provider with exponential backoff
  • ThrottlecreateThrottledHandler controls refresh rate on rapid invalidations
  • LoggercreateConsoleLogger, tagLogger, noopLogger
  • TypesTopic, Revision, InvalidationEvent, SnapshotEnvelope, InvalidationSubscriber, SnapshotProvider, SnapshotApplier, Logger, SyncPhase, SyncErrorContext

createRevisionSync

ts
import { createRevisionSync } from '@statesync/core';

const handle = createRevisionSync({
  topic: 'app-config',
  subscriber: mySubscriber,
  provider: myProvider,
  applier: myApplier,
});

await handle.start();

Options

OptionTypeRequiredDescription
topicstringYesNon-empty topic identifier
subscriberInvalidationSubscriberYesSubscribes to invalidation events
providerSnapshotProvider<T>YesFetches snapshots
applierSnapshotApplier<T>YesApplies snapshots to local state
shouldRefresh(event: InvalidationEvent) => booleanNoFilter invalidation events before refresh
loggerLoggerNoLogger instance for debug/warn/error
onError(ctx: SyncErrorContext) => voidNoError callback with phase context
throttlingInvalidationThrottlingOptionsNoControl refresh rate on rapid events

Handle methods

MethodReturnsDescription
start()Promise<void>Subscribe + initial refresh. Throws if subscribe or first refresh fails.
stop()voidUnsubscribe + stop all pending refreshes. Idempotent.
refresh()Promise<void>Manually trigger a snapshot pull. Coalesces if already in-flight.
getLocalRevision()RevisionReturns the last applied revision (starts at "0").

Throttling

Control how fast invalidation events trigger refreshes:

ts
const handle = createRevisionSync({
  topic: 'app-config',
  subscriber,
  provider,
  applier,
  throttling: {
    debounceMs: 100,   // Wait 100ms of silence
    throttleMs: 500,   // Max 1 refresh per 500ms
    leading: true,     // Fire on first event (default: true)
    trailing: true,    // Fire after quiet period (default: true)
  },
});
OptionTypeDefaultDescription
debounceMsnumberWait for N ms of silence before refresh
throttleMsnumberAt most 1 refresh per N ms
leadingbooleantrueFire immediately on first event
trailingbooleantrueFire after the quiet period ends

Retry

Wrap a SnapshotProvider with exponential backoff:

ts
import { withRetry, withRetryReporting } from '@statesync/core';

// Simple retry
const retryProvider = withRetry(myProvider, {
  maxAttempts: 3,
  initialDelayMs: 500,
  backoffMultiplier: 2,
  maxDelayMs: 10_000,
});

// Retry with logging/reporting
const retryProvider = withRetryReporting(myProvider, {
  topic: 'app-config',
  policy: { maxAttempts: 5 },
  logger,
  onError,
});

RetryPolicy

OptionTypeDefaultDescription
maxAttemptsnumber3Max attempts (including first try)
initialDelayMsnumber500Initial delay before first retry
backoffMultipliernumber2Exponential backoff factor
maxDelayMsnumber10000Maximum delay cap

Logging

ts
import { createConsoleLogger, tagLogger, noopLogger } from '@statesync/core';

// Console logger with debug output
const base = createConsoleLogger({ prefix: '[my-app]', debug: true });

// Add static tags to all log calls
const logger = tagLogger(base, { windowId: 'main', userId: '123' });

// Disable all logging
const silent = noopLogger;

createConsoleLogger options

OptionTypeDefaultDescription
prefixstring"[state-sync]"Prefix for log lines
debugbooleanfalseEnable debug-level output

Protocol types

ts
import type {
  Topic,
  Revision,
  InvalidationEvent,
  SnapshotEnvelope,
  InvalidationSubscriber,
  SnapshotProvider,
  SnapshotApplier,
  Logger,
  SyncPhase,
  SyncErrorContext,
} from '@statesync/core';

Revision is a branded string (string & { __brand: 'Revision' }) — canonical decimal u64 format (e.g. "0", "42", "18446744073709551615").

SyncPhase covers: 'start', 'subscribe', 'invalidation', 'refresh', 'getSnapshot', 'apply', 'protocol', 'throttle'.

Quick wiring

ts
import { createConsoleLogger, createRevisionSync, tagLogger } from '@statesync/core';

const base = createConsoleLogger({ debug: true });
const logger = tagLogger(base, { windowId: 'main' });

const handle = createRevisionSync({
  topic: 'app-config',
  subscriber: mySubscriber,
  provider: myProvider,
  applier: myApplier,
  logger,
  onError(ctx) {
    console.error(`[${ctx.phase}]`, ctx.error);
  },
  throttling: { debounceMs: 100 },
});

await handle.start();

// Later
handle.stop();

See also

Released under the MIT License.