@absolutejs/sync 0.4.0 → 0.5.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 +52 -18
- package/dist/engine/index.d.ts +6 -0
- package/dist/engine/index.js +266 -4
- package/dist/engine/index.js.map +7 -4
- package/dist/engine/search.d.ts +61 -0
- package/dist/engine/syncEngine.d.ts +7 -0
- package/dist/engine/textIndex.d.ts +33 -0
- package/dist/engine/vectorIndex.d.ts +27 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { CollectionContext } from './collection';
|
|
2
|
+
import type { RowKey } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A scored search result: the matched row and its relevance score (higher is
|
|
5
|
+
* more relevant). A search collection sorts hits descending and tags each
|
|
6
|
+
* emitted row with its score (see {@link SEARCH_SCORE_FIELD}).
|
|
7
|
+
*/
|
|
8
|
+
export type SearchHit<T> = {
|
|
9
|
+
row: T;
|
|
10
|
+
score: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* An incremental search index over a row set, queried by `Q` (a string for
|
|
14
|
+
* full-text, a vector for similarity). Maintained as rows are added/removed, so
|
|
15
|
+
* the collection that owns it stays live as the corpus changes.
|
|
16
|
+
* {@link createTextIndex} and {@link createVectorIndex} implement it.
|
|
17
|
+
*/
|
|
18
|
+
export type SearchIndex<T, Q> = {
|
|
19
|
+
/** Add or replace a row (upsert by key). */
|
|
20
|
+
add: (row: T) => void;
|
|
21
|
+
/** Remove a row by key. */
|
|
22
|
+
remove: (key: RowKey) => void;
|
|
23
|
+
/** Top-`limit` hits for `query`, sorted by descending score. */
|
|
24
|
+
search: (query: Q, limit: number) => SearchHit<T>[];
|
|
25
|
+
/** Number of indexed rows. */
|
|
26
|
+
size: () => number;
|
|
27
|
+
/** Drop every indexed row. */
|
|
28
|
+
clear: () => void;
|
|
29
|
+
};
|
|
30
|
+
/** The field a search collection adds to each emitted row carrying its score. */
|
|
31
|
+
export declare const SEARCH_SCORE_FIELD = "_score";
|
|
32
|
+
export type SearchCollectionDefinition<T, Query = string, Ctx = CollectionContext> = {
|
|
33
|
+
/** Collection name — its identity for subscribe. */
|
|
34
|
+
name: string;
|
|
35
|
+
kind: 'search';
|
|
36
|
+
/** Source table whose committed changes keep the index live. */
|
|
37
|
+
table: string;
|
|
38
|
+
/** Build the (empty) index — e.g. `() => createTextIndex({ ... })`. */
|
|
39
|
+
index: () => SearchIndex<T, Query>;
|
|
40
|
+
/** The full corpus to index on first subscribe (e.g. a DB read). */
|
|
41
|
+
source: () => Promise<Iterable<T>> | Iterable<T>;
|
|
42
|
+
/** Row identity. */
|
|
43
|
+
key: (row: T) => RowKey;
|
|
44
|
+
/** Max results returned. Defaults to 20. */
|
|
45
|
+
limit?: number;
|
|
46
|
+
/** Access control: return `false` (or throw) to deny the subscription. */
|
|
47
|
+
authorize?: (query: Query, ctx: Ctx) => boolean | Promise<boolean>;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Define a live search collection: an index (full-text via {@link createTextIndex}
|
|
51
|
+
* or vector via {@link createVectorIndex}) maintained from a source table's
|
|
52
|
+
* change feed. The subscription's `params` *are* the query — a string for
|
|
53
|
+
* full-text, a vector for similarity. Register it with
|
|
54
|
+
* {@link SyncEngine.registerSearch}; the client receives the ranked top-K as a
|
|
55
|
+
* normal collection, re-ranked live as rows change. Each emitted row carries its
|
|
56
|
+
* relevance under {@link SEARCH_SCORE_FIELD}, so the client can sort by it.
|
|
57
|
+
*
|
|
58
|
+
* The corpus is the whole table; a row-level read permission on the table (see
|
|
59
|
+
* {@link definePermissions}) still filters a caller's hits.
|
|
60
|
+
*/
|
|
61
|
+
export declare const defineSearchCollection: <T, Query = string, Ctx = CollectionContext>(definition: Omit<SearchCollectionDefinition<T, Query, Ctx>, "kind">) => SearchCollectionDefinition<T, Query, Ctx>;
|
|
@@ -3,6 +3,7 @@ import type { GraphCollectionDefinition } from './graph';
|
|
|
3
3
|
import type { MutationDefinition, TableWriter, TransactionRunner } from './mutation';
|
|
4
4
|
import type { ReactiveQueryDefinition, TableReader } from './reactive';
|
|
5
5
|
import type { PermissionsDefinition, TablePermissions } from './permissions';
|
|
6
|
+
import type { SearchCollectionDefinition } from './search';
|
|
6
7
|
import type { ClusterBus } from './cluster';
|
|
7
8
|
import type { ChangeSource, RowChange, ViewDiff } from './types';
|
|
8
9
|
/**
|
|
@@ -46,6 +47,12 @@ export type SyncEngine = {
|
|
|
46
47
|
registerJoin: <L, R, Out, P = void, Ctx = CollectionContext>(collection: JoinCollectionDefinition<L, R, Out, P, Ctx>) => void;
|
|
47
48
|
/** Register an operator-graph collection (see {@link defineGraphCollection}). */
|
|
48
49
|
registerGraph: <Out, P = void, Ctx = CollectionContext>(collection: GraphCollectionDefinition<Out, P, Ctx>) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Register a live search collection (see {@link defineSearchCollection}): a
|
|
52
|
+
* full-text or vector index maintained from a source table's change feed and
|
|
53
|
+
* queried by the subscription's params, returning the ranked top-K live.
|
|
54
|
+
*/
|
|
55
|
+
registerSearch: <T, Query = string, Ctx = CollectionContext>(collection: SearchCollectionDefinition<T, Query, Ctx>) => void;
|
|
49
56
|
/**
|
|
50
57
|
* Open a live subscription: authorize, hydrate the initial set, and stream
|
|
51
58
|
* diffs as changes arrive. Rejects with {@link UnauthorizedError} on deny.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RowKey } from './types';
|
|
2
|
+
import type { SearchIndex } from './search';
|
|
3
|
+
/**
|
|
4
|
+
* An incremental full-text index with BM25 ranking — the keyword-search half of
|
|
5
|
+
* the search surface (see {@link createVectorIndex} for semantic search). Pure
|
|
6
|
+
* and dependency-free: an in-memory inverted index maintained as rows are
|
|
7
|
+
* added/removed, so a {@link defineSearchCollection} stays live as the corpus
|
|
8
|
+
* changes. For a large corpus back it with your DB's FTS instead; this is the
|
|
9
|
+
* BYO, no-extension default.
|
|
10
|
+
*/
|
|
11
|
+
export type TextIndexOptions<T> = {
|
|
12
|
+
/** Row identity. */
|
|
13
|
+
key: (row: T) => RowKey;
|
|
14
|
+
/** Fields whose text is indexed. Their values are stringified and joined. */
|
|
15
|
+
fields: (keyof T)[];
|
|
16
|
+
/**
|
|
17
|
+
* Split text into terms. Defaults to lowercase alphanumeric runs. Provide your
|
|
18
|
+
* own for stemming, n-grams, or a different alphabet — used for both indexing
|
|
19
|
+
* and querying, so the two always agree.
|
|
20
|
+
*/
|
|
21
|
+
tokenize?: (text: string) => string[];
|
|
22
|
+
/** Terms to drop (e.g. `the`, `a`). Applied after `tokenize`. */
|
|
23
|
+
stopwords?: Iterable<string>;
|
|
24
|
+
/** BM25 term-frequency saturation. Defaults to 1.5. */
|
|
25
|
+
k1?: number;
|
|
26
|
+
/** BM25 length normalization (0–1). Defaults to 0.75. */
|
|
27
|
+
b?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Build an incremental BM25 full-text index over rows of `T`. Implements the
|
|
31
|
+
* {@link SearchIndex} interface, so it plugs straight into a search collection.
|
|
32
|
+
*/
|
|
33
|
+
export declare const createTextIndex: <T>(options: TextIndexOptions<T>) => SearchIndex<T, string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RowKey } from './types';
|
|
2
|
+
import type { SearchIndex } from './search';
|
|
3
|
+
/**
|
|
4
|
+
* An incremental vector index for semantic / similarity search — the embeddings
|
|
5
|
+
* half of the search surface (see {@link createTextIndex} for keyword search).
|
|
6
|
+
* Pure and dependency-free: an exact (brute-force) k-NN over in-memory vectors,
|
|
7
|
+
* maintained as rows are added/removed, so a {@link defineSearchCollection}
|
|
8
|
+
* stays live. Pairs naturally with `@absolutejs/ai` / `@absolutejs/rag` for RAG
|
|
9
|
+
* retrieval on your own data. Exact search is O(n·d) per query — fine for tens
|
|
10
|
+
* of thousands of vectors; for more, back it with pgvector and a real ANN index.
|
|
11
|
+
*/
|
|
12
|
+
/** Similarity metric. `cosine`/`dot` rank higher = closer; `euclidean` too (negated distance). */
|
|
13
|
+
export type VectorMetric = 'cosine' | 'dot' | 'euclidean';
|
|
14
|
+
export type VectorIndexOptions<T> = {
|
|
15
|
+
/** Row identity. */
|
|
16
|
+
key: (row: T) => RowKey;
|
|
17
|
+
/** Extract a row's embedding vector. */
|
|
18
|
+
embedding: (row: T) => number[];
|
|
19
|
+
/** Similarity metric. Defaults to `cosine`. */
|
|
20
|
+
metric?: VectorMetric;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Build an incremental vector index over rows of `T`. Implements the
|
|
24
|
+
* {@link SearchIndex} interface (queried by a query vector), so it plugs
|
|
25
|
+
* straight into a search collection.
|
|
26
|
+
*/
|
|
27
|
+
export declare const createVectorIndex: <T>(options: VectorIndexOptions<T>) => SearchIndex<T, number[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Lightweight reactive-push and write-behind-cache primitives for Elysia and the AbsoluteJS ecosystem — kill polling and keep a remote store off your hot path, without adopting a whole sync-engine backend.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|