@absolutejs/sync 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -361,18 +361,75 @@ This is what `@absolutejs/voice` uses to keep its per-audio-frame session state
361
361
  memory while the Drizzle/Postgres store stays the durable source of truth — without
362
362
  it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real time.
363
363
 
364
+ ## Connection broker — one upstream pool, many tenants
365
+
366
+ BYO-Postgres multi-tenancy has a sharp edge: one shard hosting 50 customers, each
367
+ spawning its own PG pool, instantly exceeds a managed provider's connection limit
368
+ (Supabase, Neon, RDS all cap you long before 50 × pool-size). `createConnectionBroker`
369
+ multiplexes ONE upstream connection budget across every tenant on the shard — a
370
+ global in-use cap, optional per-tenant budgets, FIFO queueing with timeouts when at
371
+ cap, and idle harvesting so a burst doesn't pin connections forever. No pgbouncer
372
+ sidecar to run.
373
+
374
+ The broker never imports a DB driver — `create`/`destroy` are yours, so it wraps
375
+ postgres-js, `Bun.sql`, or anything that opens a connection:
376
+
377
+ ```ts
378
+ import postgres from 'postgres';
379
+ import { createConnectionBroker } from '@absolutejs/sync';
380
+
381
+ const broker = createConnectionBroker({
382
+ create: () => postgres(process.env.DATABASE_URL!, { max: 1 }),
383
+ destroy: (sql) => sql.end(),
384
+ maxTotal: 20, // the whole shard shares 20 upstream connections
385
+ maxPerTenant: 3, // one noisy tenant queues against its own budget
386
+ idleReleaseMs: 30_000, // harvest idle connections after a burst
387
+ acquireTimeoutMs: 5_000, // reject (LeaseTimeoutError) instead of hanging
388
+ validate: (sql) => sql`select 1`.then(() => true).catch(() => false)
389
+ });
390
+
391
+ // Per-tenant work leases from the shared budget:
392
+ const rows = await broker.withLease(
393
+ tenantId,
394
+ (sql) => sql`select * from orders where tenant = ${tenantId}`
395
+ );
396
+
397
+ // Or hold a lease across several statements:
398
+ const { conn, release } = await broker.lease(tenantId);
399
+ try {
400
+ /* ... */
401
+ } finally {
402
+ release(); // idempotent; hands the connection to the next queued waiter
403
+ }
404
+
405
+ // Shutdown: stop new leases, wait for outstanding work, close idle connections.
406
+ await broker.drain();
407
+ await broker.dispose();
408
+ ```
409
+
410
+ Released connections return to a LIFO idle pool (the warmest connection is reused
411
+ first) and are handed to queued waiters immediately. `broker.metrics()` returns an
412
+ operator-shaped snapshot (`inUse`, `idle`, `queued`, `byTenant`, cumulative
413
+ lease/timeout/create/destroy counters), and a `tracerProvider` traces every lease as
414
+ a `sync.broker_lease` span carrying `abs.tenant` and the queue wait in milliseconds.
415
+
416
+ This pairs with the tenant-per-engine hosting pattern: each tenant's writers and
417
+ readers call `broker.withLease(tenant, ...)` instead of owning a pool, so adding a
418
+ tenant to a shard adds zero standing connections.
419
+
364
420
  ## API
365
421
 
366
422
  ### `@absolutejs/sync`
367
423
 
368
- | Export | What it is |
369
- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
370
- | `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
371
- | `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
372
- | `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
373
- | `scheduled({ engine, prefix?, onError? })` _(`/scheduled` subpath)_ | Elysia plugin: fires the engine's registered schedules on their cron patterns (via `@elysiajs/cron`). Kept off the main entry so `syncSocket` needs no cron dep. |
374
- | `syncDevtools({ engine, path?, snapshotMs? })` | Elysia plugin: a live devtools dashboard (collections, subscription counts, mutations, schedules, change feed) over SSE. Backed by `engine.inspect()` + `engine.onActivity()`. **1.23** also exposes a Point-in-time replay panel (datetime picker + tables filter) and a `GET <path>/replay?at=<ms>&tables=<csv>` JSON endpoint wrapping `engine.replayTo`. |
375
- | `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
424
+ | Export | What it is |
425
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
426
+ | `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
427
+ | `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
428
+ | `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
429
+ | `scheduled({ engine, prefix?, onError? })` _(`/scheduled` subpath)_ | Elysia plugin: fires the engine's registered schedules on their cron patterns (via `@elysiajs/cron`). Kept off the main entry so `syncSocket` needs no cron dep. |
430
+ | `syncDevtools({ engine, path?, snapshotMs? })` | Elysia plugin: a live devtools dashboard (collections, subscription counts, mutations, schedules, change feed) over SSE. Backed by `engine.inspect()` + `engine.onActivity()`. **1.23** also exposes a Point-in-time replay panel (datetime picker + tables filter) and a `GET <path>/replay?at=<ms>&tables=<csv>` JSON endpoint wrapping `engine.replayTo`. |
431
+ | `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
432
+ | `createConnectionBroker({ create, maxTotal, maxPerTenant?, idleReleaseMs?, acquireTimeoutMs?, validate?, destroy?, onError?, now?, tracerProvider? })` | Multiplex one upstream connection budget across many tenants: `lease`/`withLease`/`metrics`/`drain`/`dispose`. Throws `LeaseTimeoutError` / `ConnectionBrokerDrainedError` (both exported). |
376
433
 
377
434
  ### `@absolutejs/sync/client`
378
435
 
@@ -423,31 +480,31 @@ mutate({
423
480
 
424
481
  ### `@absolutejs/sync/engine`
425
482
 
426
- | Export | What it is |
427
- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
428
- | `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
429
- | `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
430
- | `defineMutation({ name, handler, authorize? })` | Define a server mutation. Its `handler` gets `actions.insert/update/delete` (write through a registered `TableWriter` → persists + emits in one step) plus `actions.change` (escape hatch). Changes commit atomically. |
431
- | `registerWriter(table, { insert, update, delete })` | Teach the engine how to persist a table (any ORM), so writes auto-emit — you can't write without going live. |
432
- | `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
433
- | `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
434
- | `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
435
- | `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
436
- | `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
437
- | `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
438
- | `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
439
- | `defineReactiveQuery({ name, run, key })` + `registerReactive` / `registerReader` | Read-set-tracked query: `run(ctx)` reads via `ctx.db` (`all`/`get`/`where`) and re-runs only when the rows/ranges it read change — no `match`, no manual emit. |
440
- | `definePermissions({ [table]: { read?, insert?, update?, delete?, write? } })` | Declarative row-level access control. Pass as `createSyncEngine({ permissions })` or `registerPermissions(table, rules)`. Read rules filter every row emitted; write rules gate `actions.insert/update/delete`. |
441
- | `defineSchema({ [table]: { fields, version?, migrate? } })` + `field` kit | Declarative row schema. Pass as `createSyncEngine({ schemas })` or `registerSchema(table, schema)`. Writes are validated (bad write → `SchemaError`); `migrate` lazily upcasts rows on read (no DB migration needed). |
442
- | `registerCrdt(table, { [field]: mergeable })` | Declare CRDT fields (a `CrdtMergeable` like `rgaText`, or `yjsText` from `@absolutejs/sync-yjs`). The engine **merges** those fields on `actions.insert/update` instead of overwriting — conflict-free collaborative editing with no merge code — and auto-registers a `"<table>:merge"` mutation the `useCollaborativeText` hooks call. |
443
- | `defineSearchCollection({ name, table, index, source, key, limit? })` + `registerSearch` | Live search collection: the subscription's `params` are the query (string/vector), the ranked top-K stream back as a normal collection, re-ranked as rows change. Each row carries its score under `_score`. |
444
- | `createTextIndex({ key, fields, tokenize?, stopwords?, k1?, b? })` | Incremental BM25 full-text index (keyword search). Implements `SearchIndex`; usable standalone or inside a search collection. |
445
- | `createVectorIndex({ key, embedding, metric? })` | Incremental vector index (cosine/dot/euclidean exact k-NN) for semantic search — pairs with `@absolutejs/ai` / `@absolutejs/rag` for RAG retrieval on your own data. |
446
- | `defineSchedule({ name, pattern, run })` + `registerSchedule` / `runSchedule` | Scheduled function: `run({ db, actions })` fires on a cron `pattern`; its writes go live through the change feed. Wire triggers with the `scheduled` plugin (or call `runSchedule(name)` on demand). |
483
+ | Export | What it is |
484
+ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
485
+ | `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
486
+ | `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
487
+ | `defineMutation({ name, handler, authorize? })` | Define a server mutation. Its `handler` gets `actions.insert/update/delete` (write through a registered `TableWriter` → persists + emits in one step) plus `actions.change` (escape hatch). Changes commit atomically. |
488
+ | `registerWriter(table, { insert, update, delete })` | Teach the engine how to persist a table (any ORM), so writes auto-emit — you can't write without going live. |
489
+ | `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
490
+ | `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
491
+ | `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
492
+ | `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
493
+ | `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
494
+ | `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
495
+ | `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
496
+ | `defineReactiveQuery({ name, run, key })` + `registerReactive` / `registerReader` | Read-set-tracked query: `run(ctx)` reads via `ctx.db` (`all`/`get`/`where`) and re-runs only when the rows/ranges it read change — no `match`, no manual emit. |
497
+ | `definePermissions({ [table]: { read?, insert?, update?, delete?, write? } })` | Declarative row-level access control. Pass as `createSyncEngine({ permissions })` or `registerPermissions(table, rules)`. Read rules filter every row emitted; write rules gate `actions.insert/update/delete`. |
498
+ | `defineSchema({ [table]: { fields, version?, migrate? } })` + `field` kit | Declarative row schema. Pass as `createSyncEngine({ schemas })` or `registerSchema(table, schema)`. Writes are validated (bad write → `SchemaError`); `migrate` lazily upcasts rows on read (no DB migration needed). |
499
+ | `registerCrdt(table, { [field]: mergeable })` | Declare CRDT fields (a `CrdtMergeable` like `rgaText`, or `yjsText` from `@absolutejs/sync-yjs`). The engine **merges** those fields on `actions.insert/update` instead of overwriting — conflict-free collaborative editing with no merge code — and auto-registers a `"<table>:merge"` mutation the `useCollaborativeText` hooks call. |
500
+ | `defineSearchCollection({ name, table, index, source, key, limit? })` + `registerSearch` | Live search collection: the subscription's `params` are the query (string/vector), the ranked top-K stream back as a normal collection, re-ranked as rows change. Each row carries its score under `_score`. |
501
+ | `createTextIndex({ key, fields, tokenize?, stopwords?, k1?, b? })` | Incremental BM25 full-text index (keyword search). Implements `SearchIndex`; usable standalone or inside a search collection. |
502
+ | `createVectorIndex({ key, embedding, metric? })` | Incremental vector index (cosine/dot/euclidean exact k-NN) for semantic search — pairs with `@absolutejs/ai` / `@absolutejs/rag` for RAG retrieval on your own data. |
503
+ | `defineSchedule({ name, pattern, run })` + `registerSchedule` / `runSchedule` | Scheduled function: `run({ db, actions })` fires on a cron `pattern`; its writes go live through the change feed. Wire triggers with the `scheduled` plugin (or call `runSchedule(name)` on demand). |
447
504
  | `engine.replayTo({ at, tables? })` _(1.22)_ | Walk the bounded change log forward to a target timestamp and return `{ asOfVersion, asOfAt, rows, truncated }`. Forensic incident response ("what did the tenant see at 14:32?") + restore-from-time ("revert to 2 hours ago"). `truncated: true` when the log doesn't extend back to `at` — widen `changeLogRetainMs` for forensic use cases. |
448
- | `engine.fence({ reason })` _(1.24)_ | Pause new mutations on the engine — the source half of tenant migration. `runMutation` rejects with `EngineFencedError`; subscribe / hydrate / streamChanges stay open. Multiple fences compose; every handle must `lift()` before the engine unfences. `lift()` is idempotent. |
449
- | `engine.exportSnapshot({ tables?, ctx? })` _(1.24)_ | Walk every registered reader's `all(ctx)` and return a portable `EngineSnapshot { sourceInstanceId, version, exportedAt, tables }`. Detached from `ChangeLogSnapshot` — snapshots carry live state, not history. |
450
- | `engine.importSnapshot(snapshot, { tables?, onProgress?, ctx? })` _(1.24)_ | Bulk-load an `EngineSnapshot` on the target via each table's registered writer. Returns `{ tablesImported, rowsImported, perTable, skipped }`. Tables in the snapshot without a writer on the target surface in `skipped` so misconfigured targets don't silently drop rows. |
505
+ | `engine.fence({ reason })` _(1.24)_ | Pause new mutations on the engine — the source half of tenant migration. `runMutation` rejects with `EngineFencedError`; subscribe / hydrate / streamChanges stay open. Multiple fences compose; every handle must `lift()` before the engine unfences. `lift()` is idempotent. |
506
+ | `engine.exportSnapshot({ tables?, ctx? })` _(1.24)_ | Walk every registered reader's `all(ctx)` and return a portable `EngineSnapshot { sourceInstanceId, version, exportedAt, tables }`. Detached from `ChangeLogSnapshot` — snapshots carry live state, not history. |
507
+ | `engine.importSnapshot(snapshot, { tables?, onProgress?, ctx? })` _(1.24)_ | Bulk-load an `EngineSnapshot` on the target via each table's registered writer. Returns `{ tablesImported, rowsImported, perTable, skipped }`. Tables in the snapshot without a writer on the target surface in `skipped` so misconfigured targets don't silently drop rows. |
451
508
 
452
509
  ### `@absolutejs/sync/crdt`
453
510
 
@@ -0,0 +1,147 @@
1
+ import { type TracerProvider as TelemetryTracerProvider } from '@absolutejs/telemetry';
2
+ /**
3
+ * Thrown by `broker.lease` when `acquireTimeoutMs` elapses before a
4
+ * connection frees up. The tenant and timeout carry through so operators
5
+ * can correlate timeouts to the tenant that starved.
6
+ */
7
+ export declare class LeaseTimeoutError extends Error {
8
+ readonly tenant: string;
9
+ readonly timeoutMs: number;
10
+ constructor(tenant: string, timeoutMs: number);
11
+ }
12
+ /**
13
+ * Thrown by `broker.lease` after `drain()`/`dispose()` — the broker is
14
+ * winding down and no new leases are issued. Queued waiters that were
15
+ * still pending at `dispose()` are rejected with this too.
16
+ */
17
+ export declare class ConnectionBrokerDrainedError extends Error {
18
+ constructor();
19
+ }
20
+ export type ConnectionBrokerOptions<Conn> = {
21
+ /**
22
+ * Open one upstream connection. Called lazily — only when a lease needs
23
+ * a connection and the idle pool is empty. The broker never imports a
24
+ * DB driver; bring postgres-js, `Bun.sql`, or anything else.
25
+ */
26
+ create: () => Promise<Conn> | Conn;
27
+ /**
28
+ * Global in-use cap — the whole point of the broker. One shard hosting
29
+ * many tenants shares this many upstream connections, total, no matter
30
+ * how many tenants lease concurrently. Required, must be > 0.
31
+ */
32
+ maxTotal: number;
33
+ /**
34
+ * Give up on a queued lease after this many milliseconds and reject
35
+ * with {@link LeaseTimeoutError}. Defaults to waiting forever.
36
+ */
37
+ acquireTimeoutMs?: number;
38
+ /**
39
+ * Close one upstream connection. Called for validation failures, idle
40
+ * releases, and `dispose()`. Defaults to dropping the reference.
41
+ */
42
+ destroy?: (conn: Conn) => Promise<void> | void;
43
+ /**
44
+ * Destroy pooled-idle connections that have sat unused for this many
45
+ * milliseconds, so a burst doesn't pin connections against the managed
46
+ * provider's limit forever. Swept on an unref'd timer and lazily on
47
+ * every `lease`. Defaults to keeping idle connections indefinitely.
48
+ */
49
+ idleReleaseMs?: number;
50
+ /**
51
+ * Per-tenant in-use cap, independent of `maxTotal` — one noisy tenant
52
+ * queues against its own budget instead of starving the shard.
53
+ * Defaults to no per-tenant cap.
54
+ */
55
+ maxPerTenant?: number;
56
+ /**
57
+ * Clock used for idle-age accounting. Injectable so idle-release tests
58
+ * run without real timers. Defaults to `Date.now`.
59
+ */
60
+ now?: () => number;
61
+ /**
62
+ * Called when `create`, `destroy`, or `validate` throws. The broker
63
+ * stays consistent either way (a failed create rejects that lease and
64
+ * frees the slot; a failed destroy drops the connection reference).
65
+ * Defaults to a no-op.
66
+ */
67
+ onError?: (error: unknown, phase: 'create' | 'destroy' | 'validate') => void;
68
+ /**
69
+ * Optional OpenTelemetry tracer provider. When set, every lease is
70
+ * traced as a `sync.broker_lease` span carrying `abs.tenant` and the
71
+ * queue wait in milliseconds. Noop when unset.
72
+ */
73
+ tracerProvider?: TelemetryTracerProvider;
74
+ /**
75
+ * Health-check a pooled connection before reuse. Return false (or
76
+ * throw) and the broker destroys it and tries the next idle connection
77
+ * — or creates a fresh one — so a server-side idle disconnect never
78
+ * reaches a caller. Defaults to trusting pooled connections.
79
+ */
80
+ validate?: (conn: Conn) => Promise<boolean> | boolean;
81
+ };
82
+ export type ConnectionLease<Conn> = {
83
+ conn: Conn;
84
+ /** Return the connection to the pool. Idempotent. */
85
+ release: () => void;
86
+ };
87
+ export type ConnectionBrokerMetrics = {
88
+ /** Connections currently leased out (or reserved for an in-flight create). */
89
+ inUse: number;
90
+ /** Connections sitting in the idle pool. */
91
+ idle: number;
92
+ /** Lease calls waiting for a connection to free up. */
93
+ queued: number;
94
+ /** In-use count per tenant — only tenants with at least one lease appear. */
95
+ byTenant: Record<string, number>;
96
+ cumulative: {
97
+ leases: number;
98
+ releases: number;
99
+ timeouts: number;
100
+ created: number;
101
+ destroyed: number;
102
+ validationFailures: number;
103
+ };
104
+ };
105
+ export type ConnectionBroker<Conn> = {
106
+ /**
107
+ * Lease a connection for a tenant. Resolves immediately when under
108
+ * both caps (reusing the most-recently-released idle connection first,
109
+ * for cache warmth); otherwise queues FIFO until a release frees the
110
+ * cap that blocked it.
111
+ */
112
+ lease: (tenant: string) => Promise<ConnectionLease<Conn>>;
113
+ /** Lease, run `fn`, release in `finally` — even when `fn` throws. */
114
+ withLease: <Result>(tenant: string, fn: (conn: Conn) => Promise<Result> | Result) => Promise<Result>;
115
+ /** Operator-shaped snapshot, same spirit as `engine.metrics()`. */
116
+ metrics: () => ConnectionBrokerMetrics;
117
+ /**
118
+ * Stop issuing new leases ({@link ConnectionBrokerDrainedError}) and
119
+ * resolve once every outstanding lease has been released and the queue
120
+ * has drained through. Follow with `dispose()` to close idle
121
+ * connections.
122
+ */
123
+ drain: () => Promise<void>;
124
+ /**
125
+ * Drain-if-needed, reject queued waiters, destroy every idle
126
+ * connection, and stop the sweep timer. Connections still leased out
127
+ * are destroyed on their release instead of returning to the pool.
128
+ */
129
+ dispose: () => Promise<void>;
130
+ };
131
+ /**
132
+ * A generic connection lease broker: multiplex ONE upstream connection
133
+ * budget across many tenants.
134
+ *
135
+ * The BYO-Postgres problem this solves: one shard hosting 50 customers,
136
+ * each spawning its own PG pool, instantly exceeds a managed provider's
137
+ * connection limit. Instead, give every tenant `broker.lease(tenant)`
138
+ * over a single caller-supplied `create`/`destroy` pair — the broker
139
+ * enforces a global in-use cap (`maxTotal`), optional per-tenant budgets
140
+ * (`maxPerTenant`), FIFO queueing with timeouts when at cap, LIFO idle
141
+ * reuse for cache warmth, and idle harvesting (`idleReleaseMs`) so a
142
+ * burst doesn't pin connections forever.
143
+ *
144
+ * The broker never imports a DB driver — `Conn` is whatever `create`
145
+ * returns (a postgres-js `sql`, a `Bun.sql`, an HTTP client, …).
146
+ */
147
+ export declare const createConnectionBroker: <Conn>(options: ConnectionBrokerOptions<Conn>) => ConnectionBroker<Conn>;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { createWriteBehindCache } from './writeBehindCache';
2
2
  export type { WriteBehindCache, WriteBehindCacheOptions } from './writeBehindCache';
3
+ export { ConnectionBrokerDrainedError, createConnectionBroker, LeaseTimeoutError } from './connectionBroker';
4
+ export type { ConnectionBroker, ConnectionBrokerMetrics, ConnectionBrokerOptions, ConnectionLease } from './connectionBroker';
3
5
  export { createReactiveHub } from './reactiveHub';
4
6
  export type { ReactiveEvent, ReactiveHub, ReactiveListener } from './reactiveHub';
5
7
  export { sync } from './plugin';