@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 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, CDC for Postgres/MySQL/SQLite, incremental aggregations + joins,
37
- > and a declarative operator graph) are in place.
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
- | `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
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 | What it is |
332
- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
333
- | `createSyncEngine()` | Registry + view syncer: `register`, `subscribe`, `applyChange`, `connectSource`, `registerMutation`, `registerWriter`, `runMutation`. |
334
- | `defineCollection({ name, hydrate, key?, match?, authorize?, tables? })` | Define a syncable collection. |
335
- | `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. |
336
- | `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. |
337
- | `createAggregate({ key, groupBy?, value? })` | Incremental count/sum/avg/min/max by group. |
338
- | `createMaterializedView({ key, match, equals? })` | The predicate-matching IVM primitive (`apply`/`reset` → diffs). |
339
- | `createPollingChangeSource({ poll, intervalMs?, startSeq?, onProcessed? })` | DB-agnostic CDC `ChangeSource` that tails a changelog (outbox) table. |
340
- | `engine.connectCluster(bus)` + `createInMemoryClusterBus()` | Horizontal scale: fan changes across server instances over a `ClusterBus` (BYO Redis/Postgres; in-memory bus for dev). |
341
- | `createPresenceHub()` + `syncSocket({ engine, presence })` | Ephemeral room-scoped presence (online / typing / cursors) over the same socket — not persisted, auto-cleaned on disconnect. |
342
- | `query(source).filter().map().join().leftJoin().groupBy().orderBy()` | Declarative incremental query builder (the operator graph). |
343
- | `defineGraphCollection({ name, query, key, authorize? })` | Run a `query` as a live collection. |
344
- | `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. |
345
- | `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`. |
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
 
@@ -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';