@absolutejs/sync 2.2.2 → 2.4.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 +115 -32
- package/dist/adapters/drizzle/index.js +2 -2
- package/dist/adapters/drizzle/index.js.map +3 -3
- package/dist/connectionBroker.d.ts +147 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +322 -55
- package/dist/index.js.map +5 -4
- package/dist/manifest.js +3 -2555
- package/dist/manifest.js.map +4 -116
- package/dist/platform.d.ts +139 -0
- package/dist/platform.js +3162 -0
- package/dist/platform.js.map +24 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -4,6 +4,32 @@ Reactive data primitives for [Elysia](https://elysiajs.com) and the AbsoluteJS
|
|
|
4
4
|
ecosystem — kill polling and keep a remote store off your hot path, **on your own
|
|
5
5
|
database and ORM** (Drizzle _or_ Prisma, any DB they support).
|
|
6
6
|
|
|
7
|
+
## Platform-managed runtime
|
|
8
|
+
|
|
9
|
+
Platforms can supply bounded Sync lifecycle settings without inventing their
|
|
10
|
+
own environment parser or plugin assembly. Declare one
|
|
11
|
+
`ABSOLUTE_SYNC_RUNTIME` JSON value, then let the application register its own
|
|
12
|
+
collections, permissions, writers, and authentication on the returned runtime:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Elysia } from 'elysia';
|
|
16
|
+
import { createPlatformSyncRuntime } from '@absolutejs/sync/platform';
|
|
17
|
+
|
|
18
|
+
const managedSync = createPlatformSyncRuntime({
|
|
19
|
+
resolveContext: (request) => authenticate(request)
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
managedSync.engine?.register(ordersCollection);
|
|
23
|
+
new Elysia().use(managedSync.app).listen(3000);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The platform contract controls paths, reconnect history, mutation pressure,
|
|
27
|
+
slow-client behavior, and stable instance identity. A fixed
|
|
28
|
+
`/.well-known/absolute/sync` endpoint lets the runtime host fail readiness when
|
|
29
|
+
an opted-in release forgot to mount the returned app. The platform never owns
|
|
30
|
+
application schemas or authorization rules, so the project remains portable
|
|
31
|
+
and can construct `@absolutejs/sync` explicitly outside managed hosting.
|
|
32
|
+
|
|
7
33
|
- **`createReactiveHub` + `sync` plugin** — push-on-change over SSE. A view
|
|
8
34
|
subscribes to the topics its data depends on; a mutation publishes those topics;
|
|
9
35
|
subscribers refetch (or read the pushed payload) the instant data changes.
|
|
@@ -361,18 +387,75 @@ This is what `@absolutejs/voice` uses to keep its per-audio-frame session state
|
|
|
361
387
|
memory while the Drizzle/Postgres store stays the durable source of truth — without
|
|
362
388
|
it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real time.
|
|
363
389
|
|
|
390
|
+
## Connection broker — one upstream pool, many tenants
|
|
391
|
+
|
|
392
|
+
BYO-Postgres multi-tenancy has a sharp edge: one shard hosting 50 customers, each
|
|
393
|
+
spawning its own PG pool, instantly exceeds a managed provider's connection limit
|
|
394
|
+
(Supabase, Neon, RDS all cap you long before 50 × pool-size). `createConnectionBroker`
|
|
395
|
+
multiplexes ONE upstream connection budget across every tenant on the shard — a
|
|
396
|
+
global in-use cap, optional per-tenant budgets, FIFO queueing with timeouts when at
|
|
397
|
+
cap, and idle harvesting so a burst doesn't pin connections forever. No pgbouncer
|
|
398
|
+
sidecar to run.
|
|
399
|
+
|
|
400
|
+
The broker never imports a DB driver — `create`/`destroy` are yours, so it wraps
|
|
401
|
+
postgres-js, `Bun.sql`, or anything that opens a connection:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
import postgres from 'postgres';
|
|
405
|
+
import { createConnectionBroker } from '@absolutejs/sync';
|
|
406
|
+
|
|
407
|
+
const broker = createConnectionBroker({
|
|
408
|
+
create: () => postgres(process.env.DATABASE_URL!, { max: 1 }),
|
|
409
|
+
destroy: (sql) => sql.end(),
|
|
410
|
+
maxTotal: 20, // the whole shard shares 20 upstream connections
|
|
411
|
+
maxPerTenant: 3, // one noisy tenant queues against its own budget
|
|
412
|
+
idleReleaseMs: 30_000, // harvest idle connections after a burst
|
|
413
|
+
acquireTimeoutMs: 5_000, // reject (LeaseTimeoutError) instead of hanging
|
|
414
|
+
validate: (sql) => sql`select 1`.then(() => true).catch(() => false)
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Per-tenant work leases from the shared budget:
|
|
418
|
+
const rows = await broker.withLease(
|
|
419
|
+
tenantId,
|
|
420
|
+
(sql) => sql`select * from orders where tenant = ${tenantId}`
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Or hold a lease across several statements:
|
|
424
|
+
const { conn, release } = await broker.lease(tenantId);
|
|
425
|
+
try {
|
|
426
|
+
/* ... */
|
|
427
|
+
} finally {
|
|
428
|
+
release(); // idempotent; hands the connection to the next queued waiter
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Shutdown: stop new leases, wait for outstanding work, close idle connections.
|
|
432
|
+
await broker.drain();
|
|
433
|
+
await broker.dispose();
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Released connections return to a LIFO idle pool (the warmest connection is reused
|
|
437
|
+
first) and are handed to queued waiters immediately. `broker.metrics()` returns an
|
|
438
|
+
operator-shaped snapshot (`inUse`, `idle`, `queued`, `byTenant`, cumulative
|
|
439
|
+
lease/timeout/create/destroy counters), and a `tracerProvider` traces every lease as
|
|
440
|
+
a `sync.broker_lease` span carrying `abs.tenant` and the queue wait in milliseconds.
|
|
441
|
+
|
|
442
|
+
This pairs with the tenant-per-engine hosting pattern: each tenant's writers and
|
|
443
|
+
readers call `broker.withLease(tenant, ...)` instead of owning a pool, so adding a
|
|
444
|
+
tenant to a shard adds zero standing connections.
|
|
445
|
+
|
|
364
446
|
## API
|
|
365
447
|
|
|
366
448
|
### `@absolutejs/sync`
|
|
367
449
|
|
|
368
|
-
| Export
|
|
369
|
-
|
|
|
370
|
-
| `createReactiveHub()`
|
|
371
|
-
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })`
|
|
372
|
-
| `syncSocket({ engine, path?, resolveContext? })`
|
|
373
|
-
| `scheduled({ engine, prefix?, onError? })` _(`/scheduled` subpath)_
|
|
374
|
-
| `syncDevtools({ engine, path?, snapshotMs? })`
|
|
375
|
-
| `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })`
|
|
450
|
+
| Export | What it is |
|
|
451
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
452
|
+
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
|
|
453
|
+
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
|
|
454
|
+
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
|
|
455
|
+
| `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. |
|
|
456
|
+
| `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`. |
|
|
457
|
+
| `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
|
|
458
|
+
| `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
459
|
|
|
377
460
|
### `@absolutejs/sync/client`
|
|
378
461
|
|
|
@@ -423,31 +506,31 @@ mutate({
|
|
|
423
506
|
|
|
424
507
|
### `@absolutejs/sync/engine`
|
|
425
508
|
|
|
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).
|
|
509
|
+
| Export | What it is |
|
|
510
|
+
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
511
|
+
| `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
|
|
512
|
+
| `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
|
|
513
|
+
| `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. |
|
|
514
|
+
| `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. |
|
|
515
|
+
| `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
|
|
516
|
+
| `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
|
|
517
|
+
| `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
|
|
518
|
+
| `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
|
|
519
|
+
| `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
|
|
520
|
+
| `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
|
|
521
|
+
| `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
|
|
522
|
+
| `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. |
|
|
523
|
+
| `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`. |
|
|
524
|
+
| `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). |
|
|
525
|
+
| `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. |
|
|
526
|
+
| `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`. |
|
|
527
|
+
| `createTextIndex({ key, fields, tokenize?, stopwords?, k1?, b? })` | Incremental BM25 full-text index (keyword search). Implements `SearchIndex`; usable standalone or inside a search collection. |
|
|
528
|
+
| `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. |
|
|
529
|
+
| `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
530
|
| `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.
|
|
531
|
+
| `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. |
|
|
532
|
+
| `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. |
|
|
533
|
+
| `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
534
|
|
|
452
535
|
### `@absolutejs/sync/crdt`
|
|
453
536
|
|
|
@@ -152,7 +152,7 @@ var classify = (chunks) => {
|
|
|
152
152
|
arrays.push(chunk.map((element) => is2(element, Param2) ? element.value : element));
|
|
153
153
|
} else {
|
|
154
154
|
const raw = chunk.value;
|
|
155
|
-
const text = (Array.isArray(raw) ? raw.join("") : String(raw ?? "")).trim();
|
|
155
|
+
const text = (Array.isArray(raw) ? raw.join("") : String(raw ?? "")).trim().replace(/^[()]+\s*/, "").replace(/\s*[()]+$/, "").trim();
|
|
156
156
|
if (text !== "" && text !== "(" && text !== ")") {
|
|
157
157
|
ops.push(text);
|
|
158
158
|
}
|
|
@@ -278,5 +278,5 @@ export {
|
|
|
278
278
|
UnsupportedDrizzleFilterError
|
|
279
279
|
};
|
|
280
280
|
|
|
281
|
-
//# debugId=
|
|
281
|
+
//# debugId=FE1181A239FBE38C64756E2164756E21
|
|
282
282
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import {\n\tColumn,\n\tgetColumnTable,\n\tgetTableColumns,\n\tgetTableName,\n\tis,\n\tParam,\n\tSQL\n} from 'drizzle-orm';\nimport type { Table } from 'drizzle-orm';\n\n/**\n * Shared topic vocabulary + key resolution for the Drizzle adapter. Both the\n * read side (derive the topics a query depends on) and the write side (publish\n * the topics a mutation invalidates) build on these so the two always agree.\n */\n\n/** The coarse topic every read/write of `table` touches, e.g. `users`. */\nexport const tableTopic = (table: Table): string => getTableName(table);\n\n/** The row-level topic for one key of `table`, e.g. `users:5`. */\nexport const keyTopic = (table: Table, key: string | number): string =>\n\t`${getTableName(table)}:${key}`;\n\ntype ResolvedKey = {\n\t/** JS property name of the key column on the table / result rows. */\n\tproperty: string;\n\t/** Underlying DB column name, as it appears in a SQL expression. */\n\tcolumn: string;\n};\n\n/**\n * Resolve the column to use as the row key: an explicitly requested column (by\n * JS property name), otherwise the table's sole primary key. Returns `undefined`\n * when no single key column applies (composite or missing primary key).\n */\nexport const resolveKeyColumn = (\n\ttable: Table,\n\tkeyColumn?: string\n): ResolvedKey | undefined => {\n\tconst columns = getTableColumns(table);\n\tif (keyColumn !== undefined) {\n\t\tconst column = columns[keyColumn];\n\t\treturn column === undefined\n\t\t\t? undefined\n\t\t\t: { property: keyColumn, column: column.name };\n\t}\n\tconst primaries = Object.entries(columns).filter(\n\t\t([, column]) => column.primary\n\t);\n\tconst primary = primaries.length === 1 ? primaries[0] : undefined;\n\treturn primary === undefined\n\t\t? undefined\n\t\t: { property: primary[0], column: primary[1].name };\n};\n\n/**\n * Best-effort: pull a single key-column equality value out of a Drizzle `where`\n * expression. Recognises only the simple `eq(keyColumn, scalar)` shape — any\n * nesting (`and`/`or`), extra columns/params, a non-`=` operator, or a\n * non-key/cross-table column yields `undefined`.\n *\n * Reads Drizzle's internal `queryChunks`, which is not a stable public API;\n * every branch degrades to `undefined` (coarser topic) rather than throwing, so\n * a Drizzle version bump can only cost precision, never correctness.\n */\nexport const extractKeyFromWhere = (\n\ttable: Table,\n\twhere: SQL,\n\tkeyColumn?: string\n): string | number | undefined => {\n\tconst resolved = resolveKeyColumn(table, keyColumn);\n\tif (resolved === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst chunks: unknown = (where as { queryChunks?: unknown }).queryChunks;\n\tif (!Array.isArray(chunks)) {\n\t\treturn undefined;\n\t}\n\n\tlet column: Column | undefined;\n\tlet param: Param | undefined;\n\tlet tooComplex = false;\n\tlet operator = '';\n\n\tfor (const chunk of chunks) {\n\t\tif (is(chunk, SQL)) {\n\t\t\t// Nested expression (e.g. and/or) — not a simple equality.\n\t\t\treturn undefined;\n\t\t}\n\t\tif (is(chunk, Column)) {\n\t\t\tif (column !== undefined) {\n\t\t\t\ttooComplex = true;\n\t\t\t}\n\t\t\tcolumn = chunk;\n\t\t} else if (is(chunk, Param)) {\n\t\t\tif (param !== undefined) {\n\t\t\t\ttooComplex = true;\n\t\t\t}\n\t\t\tparam = chunk;\n\t\t} else {\n\t\t\tconst value: unknown = (chunk as { value?: unknown }).value;\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\toperator += value.join('');\n\t\t\t}\n\t\t}\n\t}\n\n\tif (tooComplex || column === undefined || param === undefined) {\n\t\treturn undefined;\n\t}\n\tif (operator.trim() !== '=') {\n\t\treturn undefined;\n\t}\n\tif (column.name !== resolved.column) {\n\t\treturn undefined;\n\t}\n\tif (getTableName(getColumnTable(column)) !== getTableName(table)) {\n\t\treturn undefined;\n\t}\n\n\tconst value: unknown = param.value;\n\treturn typeof value === 'string' || typeof value === 'number'\n\t\t? value\n\t\t: undefined;\n};\n\n/**\n * Read the key value from each row (e.g. the output of a mutation's\n * `.returning()`), using the table's primary-key column or an explicit\n * `keyColumn`. Rows without a string/number key are skipped.\n */\nexport const extractRowKeys = (\n\ttable: Table,\n\trows: ReadonlyArray<Record<string, unknown>>,\n\tkeyColumn?: string\n): (string | number)[] => {\n\tconst resolved = resolveKeyColumn(table, keyColumn);\n\tif (resolved === undefined) {\n\t\treturn [];\n\t}\n\tconst keys: (string | number)[] = [];\n\tfor (const row of rows) {\n\t\tconst value = row[resolved.property];\n\t\tif (typeof value === 'string' || typeof value === 'number') {\n\t\t\tkeys.push(value);\n\t\t}\n\t}\n\treturn keys;\n};\n",
|
|
6
6
|
"import { getTableName } from 'drizzle-orm';\nimport type { SQL, Table } from 'drizzle-orm';\nimport type {\n\tCollectionContext,\n\tCollectionDefinition\n} from '../../engine/collection';\nimport type { RowKey } from '../../engine/types';\nimport { matchesDrizzleWhere } from './predicate';\nimport { resolveKeyColumn } from './topics';\n\nexport type DrizzleCollectionOptions<T, P = void, Ctx = CollectionContext> = {\n\t/** Collection name clients subscribe to. */\n\tname: string;\n\t/** The Drizzle table this collection reads (drives change routing + key). */\n\ttable: Table;\n\t/** The query filter, written once — powers both hydrate and the matcher. */\n\twhere: (params: P, ctx: Ctx) => SQL;\n\t/** Run the read for `where` (your `db.select()...`), returning the rows. */\n\tfind: (\n\t\twhere: SQL,\n\t\tparams: P,\n\t\tctx: Ctx\n\t) => Promise<Iterable<T>> | Iterable<T>;\n\t/** Row identity. Defaults to the table's single primary key (else `row.id`). */\n\tkey?: (row: T) => RowKey;\n\t/** Key column JS property, if not the table's primary key. */\n\tkeyColumn?: string;\n\t/** Access control; return false (or throw) to deny the subscription. */\n\tauthorize?: (params: P, ctx: Ctx) => boolean | Promise<boolean>;\n};\n\n/**\n * A sync-engine collection from one Drizzle query — the Drizzle counterpart to\n * `prismaCollection`. You write the `where` once: it drives the DB read\n * (`hydrate`) AND the incremental `match` (via {@link matchesDrizzleWhere}), so\n * the two can't drift and you never hand-maintain a separate predicate. A filter\n * the matcher can't evaluate falls back to a refetch, never a wrong result.\n */\nexport const drizzleCollection = <T, P = void, Ctx = CollectionContext>(\n\toptions: DrizzleCollectionOptions<T, P, Ctx>\n): CollectionDefinition<T, P, Ctx> => {\n\tconst keyProp = resolveKeyColumn(\n\t\toptions.table,\n\t\toptions.keyColumn\n\t)?.property;\n\tconst key =\n\t\toptions.key ??\n\t\t((row: T) =>\n\t\t\tkeyProp !== undefined\n\t\t\t\t? (row as Record<string, RowKey>)[keyProp]!\n\t\t\t\t: (row as { id: RowKey }).id);\n\n\treturn {\n\t\tname: options.name,\n\t\ttables: [getTableName(options.table)],\n\t\thydrate: (params, ctx) =>\n\t\t\toptions.find(options.where(params, ctx), params, ctx),\n\t\tmatch: (row, params, ctx) =>\n\t\t\tmatchesDrizzleWhere(\n\t\t\t\toptions.table,\n\t\t\t\toptions.where(params, ctx),\n\t\t\t\trow as Record<string, unknown>\n\t\t\t),\n\t\tkey,\n\t\tauthorize: options.authorize\n\t};\n};\n",
|
|
7
|
-
"import { Column, getTableColumns, is, Param, SQL } from 'drizzle-orm';\nimport type { Table } from 'drizzle-orm';\n\n/**\n * Thrown when a Drizzle `where` uses something the incremental matcher can't\n * evaluate in JS (an unsupported operator, a function, a cross-table column…).\n * The sync engine catches it and degrades that subscription to a refetch, so the\n * result is never wrong — only less efficient. Mirrors the Prisma adapter.\n */\nexport class UnsupportedDrizzleFilterError extends Error {\n\tconstructor(detail: string) {\n\t\tsuper(`Cannot evaluate Drizzle filter \"${detail}\" incrementally`);\n\t\tthis.name = 'UnsupportedDrizzleFilterError';\n\t}\n}\n\nconst isDate = (value: unknown): value is Date => value instanceof Date;\n\nconst equals = (value: unknown, operand: unknown): boolean => {\n\tif (operand === null) {\n\t\treturn value === null || value === undefined;\n\t}\n\tif (isDate(value) && isDate(operand)) {\n\t\treturn value.getTime() === operand.getTime();\n\t}\n\treturn value === operand;\n};\n\nconst order = (value: unknown): number | string =>\n\tisDate(value) ? value.getTime() : (value as number | string);\n\nconst compare = (value: unknown, operand: unknown): number => {\n\tconst a = order(value);\n\tconst b = order(operand);\n\tif (a < b) {\n\t\treturn -1;\n\t}\n\tif (a > b) {\n\t\treturn 1;\n\t}\n\treturn 0;\n};\n\nconst comparable = (value: unknown): boolean =>\n\tvalue !== null && value !== undefined;\n\ntype Classified = {\n\tcols: Column[];\n\tparams: unknown[];\n\tarrays: unknown[][];\n\tsqls: SQL[];\n\tops: string[];\n};\n\n/**\n * Split a condition's `queryChunks` into its parts. Drizzle interleaves columns,\n * bound `Param`s, value arrays (for `in`), nested `SQL` (connectives), and string\n * chunks that carry the operators/parens. Reading `queryChunks` is Drizzle's\n * internal shape, not a stable API — but every unrecognized form throws below\n * (→ refetch), so a version bump can only cost efficiency, never correctness.\n */\nconst classify = (chunks: unknown[]): Classified => {\n\tconst cols: Column[] = [];\n\tconst params: unknown[] = [];\n\tconst arrays: unknown[][] = [];\n\tconst sqls: SQL[] = [];\n\tconst ops: string[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (is(chunk, SQL)) {\n\t\t\tsqls.push(chunk);\n\t\t} else if (is(chunk, Column)) {\n\t\t\tcols.push(chunk);\n\t\t} else if (is(chunk, Param)) {\n\t\t\tparams.push(chunk.value);\n\t\t} else if (Array.isArray(chunk)) {\n\t\t\tarrays.push(\n\t\t\t\tchunk.map((element) =>\n\t\t\t\t\tis(element, Param) ? element.value : element\n\t\t\t\t)\n\t\t\t);\n\t\t} else {\n\t\t\tconst raw = (chunk as { value?: unknown }).value;\n\t\t\tconst text = (
|
|
7
|
+
"import { Column, getTableColumns, is, Param, SQL } from 'drizzle-orm';\nimport type { Table } from 'drizzle-orm';\n\n/**\n * Thrown when a Drizzle `where` uses something the incremental matcher can't\n * evaluate in JS (an unsupported operator, a function, a cross-table column…).\n * The sync engine catches it and degrades that subscription to a refetch, so the\n * result is never wrong — only less efficient. Mirrors the Prisma adapter.\n */\nexport class UnsupportedDrizzleFilterError extends Error {\n\tconstructor(detail: string) {\n\t\tsuper(`Cannot evaluate Drizzle filter \"${detail}\" incrementally`);\n\t\tthis.name = 'UnsupportedDrizzleFilterError';\n\t}\n}\n\nconst isDate = (value: unknown): value is Date => value instanceof Date;\n\nconst equals = (value: unknown, operand: unknown): boolean => {\n\tif (operand === null) {\n\t\treturn value === null || value === undefined;\n\t}\n\tif (isDate(value) && isDate(operand)) {\n\t\treturn value.getTime() === operand.getTime();\n\t}\n\treturn value === operand;\n};\n\nconst order = (value: unknown): number | string =>\n\tisDate(value) ? value.getTime() : (value as number | string);\n\nconst compare = (value: unknown, operand: unknown): number => {\n\tconst a = order(value);\n\tconst b = order(operand);\n\tif (a < b) {\n\t\treturn -1;\n\t}\n\tif (a > b) {\n\t\treturn 1;\n\t}\n\treturn 0;\n};\n\nconst comparable = (value: unknown): boolean =>\n\tvalue !== null && value !== undefined;\n\ntype Classified = {\n\tcols: Column[];\n\tparams: unknown[];\n\tarrays: unknown[][];\n\tsqls: SQL[];\n\tops: string[];\n};\n\n/**\n * Split a condition's `queryChunks` into its parts. Drizzle interleaves columns,\n * bound `Param`s, value arrays (for `in`), nested `SQL` (connectives), and string\n * chunks that carry the operators/parens. Reading `queryChunks` is Drizzle's\n * internal shape, not a stable API — but every unrecognized form throws below\n * (→ refetch), so a version bump can only cost efficiency, never correctness.\n */\nconst classify = (chunks: unknown[]): Classified => {\n\tconst cols: Column[] = [];\n\tconst params: unknown[] = [];\n\tconst arrays: unknown[][] = [];\n\tconst sqls: SQL[] = [];\n\tconst ops: string[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (is(chunk, SQL)) {\n\t\t\tsqls.push(chunk);\n\t\t} else if (is(chunk, Column)) {\n\t\t\tcols.push(chunk);\n\t\t} else if (is(chunk, Param)) {\n\t\t\tparams.push(chunk.value);\n\t\t} else if (Array.isArray(chunk)) {\n\t\t\tarrays.push(\n\t\t\t\tchunk.map((element) =>\n\t\t\t\t\tis(element, Param) ? element.value : element\n\t\t\t\t)\n\t\t\t);\n\t\t} else {\n\t\t\tconst raw = (chunk as { value?: unknown }).value;\n\t\t\tconst text = (Array.isArray(raw) ? raw.join('') : String(raw ?? ''))\n\t\t\t\t.trim()\n\t\t\t\t.replace(/^[()]+\\s*/, '')\n\t\t\t\t.replace(/\\s*[()]+$/, '')\n\t\t\t\t.trim();\n\t\t\tif (text !== '' && text !== '(' && text !== ')') {\n\t\t\t\tops.push(text);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { arrays, cols, ops, params, sqls };\n};\n\nconst evaluateLeaf = (\n\tcolumn: Column,\n\top: string,\n\tparams: unknown[],\n\tarrays: unknown[][],\n\trow: Record<string, unknown>,\n\tpropFor: (column: Column) => string | undefined\n): boolean => {\n\tconst prop = propFor(column);\n\tif (prop === undefined) {\n\t\tthrow new UnsupportedDrizzleFilterError(`column ${column.name}`);\n\t}\n\tconst value = row[prop];\n\tconst operand = params[0];\n\tswitch (op) {\n\t\tcase '=':\n\t\t\treturn equals(value, operand);\n\t\tcase '<>':\n\t\t\treturn !equals(value, operand);\n\t\tcase '>':\n\t\t\treturn comparable(value) && compare(value, operand) > 0;\n\t\tcase '>=':\n\t\t\treturn comparable(value) && compare(value, operand) >= 0;\n\t\tcase '<':\n\t\t\treturn comparable(value) && compare(value, operand) < 0;\n\t\tcase '<=':\n\t\t\treturn comparable(value) && compare(value, operand) <= 0;\n\t\tcase 'in':\n\t\t\treturn (arrays[0] ?? []).some((item) => equals(value, item));\n\t\tcase 'not in':\n\t\t\treturn !(arrays[0] ?? []).some((item) => equals(value, item));\n\t\tcase 'is null':\n\t\t\treturn value === null || value === undefined;\n\t\tcase 'is not null':\n\t\t\treturn value !== null && value !== undefined;\n\t\tdefault:\n\t\t\tthrow new UnsupportedDrizzleFilterError(op);\n\t}\n};\n\nconst evaluateCondition = (\n\tnode: SQL,\n\trow: Record<string, unknown>,\n\tpropFor: (column: Column) => string | undefined\n): boolean => {\n\tconst { cols, params, arrays, sqls, ops } = classify(\n\t\t(node as unknown as { queryChunks: unknown[] }).queryChunks\n\t);\n\n\t// not (cond)\n\tif (\n\t\tops.length === 1 &&\n\t\tops[0] === 'not' &&\n\t\tsqls.length === 1 &&\n\t\tcols.length === 0\n\t) {\n\t\treturn !evaluateCondition(sqls[0]!, row, propFor);\n\t}\n\t// A lone nested condition wrapped in parens — unwrap and recurse.\n\tif (cols.length === 0 && sqls.length === 1 && ops.length === 0) {\n\t\treturn evaluateCondition(sqls[0]!, row, propFor);\n\t}\n\t// and / or over sub-conditions.\n\tif (cols.length === 0 && sqls.length >= 2 && ops.length > 0) {\n\t\tconst connective = ops[0];\n\t\tif (\n\t\t\t(connective === 'and' || connective === 'or') &&\n\t\t\tops.every((op) => op === connective)\n\t\t) {\n\t\t\tconst results = sqls.map((sql) =>\n\t\t\t\tevaluateCondition(sql, row, propFor)\n\t\t\t);\n\t\t\treturn connective === 'and'\n\t\t\t\t? results.every(Boolean)\n\t\t\t\t: results.some(Boolean);\n\t\t}\n\t\tthrow new UnsupportedDrizzleFilterError(ops.join(' '));\n\t}\n\t// Leaf comparison: one column, one operator.\n\tif (cols.length === 1 && sqls.length === 0 && ops.length === 1) {\n\t\treturn evaluateLeaf(cols[0]!, ops[0]!, params, arrays, row, propFor);\n\t}\n\tthrow new UnsupportedDrizzleFilterError(\n\t\tops.join(' ') || 'unrecognized condition'\n\t);\n};\n\n/**\n * Evaluate a Drizzle `where` condition against a plain row in JS — the\n * incremental matcher for {@link drizzleCollection}. Supports\n * `eq`/`ne`/`gt`/`gte`/`lt`/`lte`, `isNull`/`isNotNull`,\n * `inArray`/`notInArray`, and nested `and`/`or`/`not`; anything else throws\n * {@link UnsupportedDrizzleFilterError} (the engine then refetches). Rows are\n * read by JS property name, as Drizzle returns them.\n */\nexport const matchesDrizzleWhere = (\n\ttable: Table,\n\twhere: SQL,\n\trow: Record<string, unknown>\n): boolean => {\n\tconst nameToProp = new Map<string, string>();\n\tfor (const [prop, column] of Object.entries(getTableColumns(table))) {\n\t\tnameToProp.set(column.name, prop);\n\t}\n\n\treturn evaluateCondition(where, row, (column) =>\n\t\tnameToProp.get(column.name)\n\t);\n};\n",
|
|
8
8
|
"import type { SQL, Table } from 'drizzle-orm';\nimport { extractKeyFromWhere, keyTopic, tableTopic } from './topics';\n\nexport type DeriveReadTopicsOptions = {\n\t/**\n\t * Column (its JS property name on the table) to treat as the row key when\n\t * narrowing to a `table:key` topic. Defaults to the table's single\n\t * primary-key column; composite or absent primary keys disable row-level\n\t * narrowing.\n\t */\n\tkeyColumn?: string;\n};\n\nexport type DerivedReadTopics = {\n\t/** Topics this read depends on — subscribe to all of them. */\n\ttopics: string[];\n\t/**\n\t * `true` when derivation narrowed to a specific row (`table:key`); `false`\n\t * when it fell back to the whole-table topic.\n\t */\n\trowLevel: boolean;\n};\n\n/**\n * Derive the reactive topics a read of `table` (optionally filtered by `where`)\n * depends on. A recognised primary-key equality narrows to a single `table:key`\n * topic; everything else subscribes to the whole-table topic, over-invalidating\n * a little rather than missing an update.\n *\n * @example\n * deriveReadTopics(users); // { topics: ['users'], rowLevel: false }\n * deriveReadTopics(users, eq(users.id, 5)); // { topics: ['users:5'], rowLevel: true }\n * deriveReadTopics(users, gt(users.id, 5)); // { topics: ['users'], rowLevel: false }\n */\nexport const deriveReadTopics = (\n\ttable: Table,\n\twhere?: SQL,\n\toptions: DeriveReadTopicsOptions = {}\n): DerivedReadTopics => {\n\tconst key =\n\t\twhere === undefined\n\t\t\t? undefined\n\t\t\t: extractKeyFromWhere(table, where, options.keyColumn);\n\n\tif (key === undefined) {\n\t\treturn { topics: [tableTopic(table)], rowLevel: false };\n\t}\n\treturn { topics: [keyTopic(table, key)], rowLevel: true };\n};\n",
|
|
9
9
|
"import type { SQL, Table } from 'drizzle-orm';\nimport type { ReactiveHub } from '../../reactiveHub';\nimport {\n\textractKeyFromWhere,\n\textractRowKeys,\n\tkeyTopic,\n\ttableTopic\n} from './topics';\n\n/**\n * Drizzle write-side topic publishing (Tier 2).\n *\n * The mirror of {@link deriveReadTopics}: after a mutation commits, publish the\n * topics it invalidates so subscribed reads refetch. Every change publishes the\n * **table** topic (so list/table queries refresh) plus a **row** topic per\n * affected key (so row-level queries refresh) — the exact topics the read side\n * subscribes to.\n *\n * These are the \"route mutations through us\" change source from the roadmap:\n * call them right after your durable write. They work on any DB Drizzle supports\n * and never touch DB-specific machinery; out-of-band writes (caught later by CDC\n * adapters) are the only thing they miss.\n */\n\n/** The kind of mutation, forwarded in the change-event payload. */\nexport type ChangeOp = 'insert' | 'update' | 'delete';\n\n/** Payload carried by every change event the write side publishes. */\nexport type ChangePayload = {\n\t/** Name of the table that changed. */\n\ttable: string;\n\t/** Mutation kind, when the caller provided it. */\n\top?: ChangeOp;\n\t/** Affected row keys (empty for a table-wide change). */\n\tkeys: (string | number)[];\n};\n\nexport type PublishChangeOptions = {\n\t/** Row keys that changed; each emits a `table:key` topic. */\n\tkeys?: ReadonlyArray<string | number>;\n\top?: ChangeOp;\n};\n\n/**\n * Publish the reactive topics a change to `table` invalidates: the whole-table\n * topic (always) plus a `table:key` topic per affected row. Call after the\n * durable write commits. Returns the (de-duplicated) topics published.\n */\nexport const publishChange = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\toptions: PublishChangeOptions = {}\n): string[] => {\n\tconst name = tableTopic(table);\n\tconst keys = options.keys === undefined ? [] : [...new Set(options.keys)];\n\tconst payload: ChangePayload = { table: name, op: options.op, keys };\n\tconst topics = [\n\t\t...new Set([name, ...keys.map((key) => keyTopic(table, key))])\n\t];\n\tfor (const topic of topics) {\n\t\thub.publish(topic, payload);\n\t}\n\treturn topics;\n};\n\nexport type PublishRowsOptions = {\n\t/** Key column (JS property name); defaults to the table's primary key. */\n\tkeyColumn?: string;\n\top?: ChangeOp;\n};\n\n/**\n * Publish change topics for a set of rows — typically the output of a mutation's\n * `.returning()`, which yields real keys including auto-generated ones. Reads\n * each row's primary-key column (or `keyColumn`) to emit `table:key` topics.\n *\n * @example\n * const rows = await db.insert(users).values(input).returning();\n * publishRows(hub, users, rows, { op: 'insert' });\n */\nexport const publishRows = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\trows: ReadonlyArray<Record<string, unknown>>,\n\toptions: PublishRowsOptions = {}\n): string[] =>\n\tpublishChange(hub, table, {\n\t\tkeys: extractRowKeys(table, rows, options.keyColumn),\n\t\top: options.op\n\t});\n\nexport type PublishWhereOptions = {\n\t/** Key column (JS property name); defaults to the table's primary key. */\n\tkeyColumn?: string;\n\top?: ChangeOp;\n};\n\n/**\n * Publish change topics for an `update`/`delete` identified by a `where` filter.\n * A simple primary-key equality narrows to that row's topic; any other filter\n * publishes just the table topic, so every affected subscriber refetches and\n * re-evaluates.\n *\n * @example\n * await db.update(users).set(patch).where(eq(users.id, id));\n * publishWhere(hub, users, eq(users.id, id), { op: 'update' });\n */\nexport const publishWhere = (\n\thub: Pick<ReactiveHub, 'publish'>,\n\ttable: Table,\n\twhere: SQL,\n\toptions: PublishWhereOptions = {}\n): string[] => {\n\tconst key = extractKeyFromWhere(table, where, options.keyColumn);\n\treturn publishChange(hub, table, {\n\t\tkeys: key === undefined ? [] : [key],\n\t\top: options.op\n\t});\n};\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": "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
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "FE1181A239FBE38C64756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
|
@@ -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';
|