@absolutejs/sync 0.4.0 → 0.6.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 +86 -24
- package/dist/engine/index.d.ts +8 -0
- package/dist/engine/index.js +361 -66
- package/dist/engine/index.js.map +8 -4
- package/dist/engine/schedule.d.ts +39 -0
- package/dist/engine/search.d.ts +61 -0
- package/dist/engine/syncEngine.d.ts +22 -0
- package/dist/engine/textIndex.d.ts +33 -0
- package/dist/engine/vectorIndex.d.ts +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +4 -3
- package/dist/scheduled.d.ts +47 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -33,9 +33,9 @@ top-N ordering are maintained incrementally through a composable operator graph
|
|
|
33
33
|
> write-behind cache), Tier 2 (Drizzle + Prisma topic adapters, `createLiveQuery`),
|
|
34
34
|
> and Tier 3 (sync engine: collections, WebSocket diff transport, optimistic
|
|
35
35
|
> mutations + offline queue, a local-first client cache, declarative row-level
|
|
36
|
-
> permissions,
|
|
37
|
-
>
|
|
38
|
-
> Everything ships as subpaths of this one package.
|
|
36
|
+
> permissions, live full-text + vector search, scheduled functions, CDC for
|
|
37
|
+
> Postgres/MySQL/SQLite, incremental aggregations + joins, and a declarative
|
|
38
|
+
> operator graph) are in place. Everything ships as subpaths of this one package.
|
|
39
39
|
|
|
40
40
|
## Install
|
|
41
41
|
|
|
@@ -249,6 +249,63 @@ await orders.mutate({
|
|
|
249
249
|
});
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
- **Live search.** A `defineSearchCollection` is a full-text or vector index kept
|
|
253
|
+
live from a table's change feed. The subscription's `params` are the query (a
|
|
254
|
+
string for keyword search, an embedding for similarity); the ranked top-K stream
|
|
255
|
+
back as an ordinary collection and re-rank as rows change. Read permissions on
|
|
256
|
+
the source table still scope a caller's hits. Standalone, `createTextIndex` and
|
|
257
|
+
`createVectorIndex` are reusable (e.g. RAG retrieval with `@absolutejs/rag`).
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// server
|
|
261
|
+
engine.registerSearch(
|
|
262
|
+
defineSearchCollection<Doc>({
|
|
263
|
+
name: 'docSearch',
|
|
264
|
+
table: 'docs',
|
|
265
|
+
index: () =>
|
|
266
|
+
createTextIndex({
|
|
267
|
+
key: (d) => d.id,
|
|
268
|
+
fields: ['title', 'body']
|
|
269
|
+
}),
|
|
270
|
+
source: () => db.select().from(docs), // the corpus to index
|
|
271
|
+
key: (d) => d.id
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// client — params are the query; each result row carries `_score`
|
|
276
|
+
const results = createSyncCollection<Doc>({
|
|
277
|
+
url,
|
|
278
|
+
collection: 'docSearch',
|
|
279
|
+
params: 'quick brown fox' // a vector for createVectorIndex
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- **Scheduled functions.** Register server-side work that runs on a cron pattern;
|
|
284
|
+
whatever it writes via `ctx.actions` goes live through the change feed (and it can
|
|
285
|
+
read current state via `ctx.db`). Cron decides _when_ (via `@elysiajs/cron`, an
|
|
286
|
+
optional peer); the engine makes the effect _live_. It doesn't reinvent jobs —
|
|
287
|
+
for durable, retryable work a schedule can `enqueue` into
|
|
288
|
+
[`@absolutejs/queue`](https://github.com/absolutejs/queue).
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { scheduled } from '@absolutejs/sync';
|
|
292
|
+
|
|
293
|
+
engine.registerSchedule({
|
|
294
|
+
name: 'digest',
|
|
295
|
+
pattern: '0 8 * * 1', // Mondays 08:00 (6-field for seconds: '*/5 * * * * *')
|
|
296
|
+
run: async ({ db, actions }) => {
|
|
297
|
+
const stale = await db.all('reports');
|
|
298
|
+
await actions.insert('digests', {
|
|
299
|
+
id: crypto.randomUUID(),
|
|
300
|
+
at: Date.now()
|
|
301
|
+
});
|
|
302
|
+
// or: queue.enqueue('email.send', { … }) for durable delivery
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
new Elysia().use(syncSocket({ engine })).use(scheduled({ engine })); // wires cron
|
|
307
|
+
```
|
|
308
|
+
|
|
252
309
|
## Write-behind cache — keep a remote store off your hot path
|
|
253
310
|
|
|
254
311
|
```ts
|
|
@@ -275,12 +332,13 @@ it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real
|
|
|
275
332
|
|
|
276
333
|
### `@absolutejs/sync`
|
|
277
334
|
|
|
278
|
-
| Export | What it is
|
|
279
|
-
| ------------------------------------------------------------------------------------------ |
|
|
280
|
-
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`).
|
|
281
|
-
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events.
|
|
282
|
-
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine.
|
|
283
|
-
| `
|
|
335
|
+
| Export | What it is |
|
|
336
|
+
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
337
|
+
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
|
|
338
|
+
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
|
|
339
|
+
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
|
|
340
|
+
| `scheduled({ engine, prefix?, onError? })` | Elysia plugin: fires the engine's registered schedules on their cron patterns (via `@elysiajs/cron`). |
|
|
341
|
+
| `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
|
|
284
342
|
|
|
285
343
|
### `@absolutejs/sync/client`
|
|
286
344
|
|
|
@@ -328,21 +386,25 @@ mutate({
|
|
|
328
386
|
|
|
329
387
|
### `@absolutejs/sync/engine`
|
|
330
388
|
|
|
331
|
-
| Export
|
|
332
|
-
|
|
|
333
|
-
| `createSyncEngine()`
|
|
334
|
-
| `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })`
|
|
335
|
-
| `defineMutation({ name, handler, authorize? })`
|
|
336
|
-
| `registerWriter(table, { insert, update, delete })`
|
|
337
|
-
| `createAggregate({ key, groupBy?, value? })`
|
|
338
|
-
| `createMaterializedView({ key, match, equals? })`
|
|
339
|
-
| `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })`
|
|
340
|
-
| `engine.connectCluster(bus)` + `createInMemoryClusterBus()`
|
|
341
|
-
| `createPresenceHub()` + `syncSocket({ engine, presence })`
|
|
342
|
-
| `query(source).filter().map().join().leftJoin().groupBy().orderBy()`
|
|
343
|
-
| `defineGraphCollection({ name, query, key, authorize? })`
|
|
344
|
-
| `defineReactiveQuery({ name, run, key })` + `registerReactive` / `registerReader`
|
|
345
|
-
| `definePermissions({ [table]: { read?, insert?, update?, delete?, write? } })`
|
|
389
|
+
| Export | What it is |
|
|
390
|
+
| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
391
|
+
| `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
|
|
392
|
+
| `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
|
|
393
|
+
| `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. |
|
|
394
|
+
| `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. |
|
|
395
|
+
| `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
|
|
396
|
+
| `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
|
|
397
|
+
| `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
|
|
398
|
+
| `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
|
|
399
|
+
| `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
|
|
400
|
+
| `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
|
|
401
|
+
| `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
|
|
402
|
+
| `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. |
|
|
403
|
+
| `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`. |
|
|
404
|
+
| `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`. |
|
|
405
|
+
| `createTextIndex({ key, fields, tokenize?, stopwords?, k1?, b? })` | Incremental BM25 full-text index (keyword search). Implements `SearchIndex`; usable standalone or inside a search collection. |
|
|
406
|
+
| `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. |
|
|
407
|
+
| `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). |
|
|
346
408
|
|
|
347
409
|
### `@absolutejs/sync/postgres`
|
|
348
410
|
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -32,6 +32,14 @@ export { defineGraphCollection, query } from './graph';
|
|
|
32
32
|
export type { GraphCollectionDefinition, GraphInstance, GraphSource, GroupByOptions, JoinOptions, OrderByQueryOptions, Query } from './graph';
|
|
33
33
|
export { definePermissions } from './permissions';
|
|
34
34
|
export type { PermissionsDefinition, ReadRule, TablePermissions, WriteRule } from './permissions';
|
|
35
|
+
export { defineSearchCollection, SEARCH_SCORE_FIELD } from './search';
|
|
36
|
+
export type { SearchCollectionDefinition, SearchHit, SearchIndex } from './search';
|
|
37
|
+
export { createTextIndex } from './textIndex';
|
|
38
|
+
export type { TextIndexOptions } from './textIndex';
|
|
39
|
+
export { createVectorIndex } from './vectorIndex';
|
|
40
|
+
export type { VectorIndexOptions, VectorMetric } from './vectorIndex';
|
|
41
|
+
export { defineSchedule } from './schedule';
|
|
42
|
+
export type { ScheduleContext, ScheduleDefinition } from './schedule';
|
|
35
43
|
export { defineMutation } from './mutation';
|
|
36
44
|
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
37
45
|
export { createSyncEngine, UnauthorizedError } from './syncEngine';
|