@absolutejs/sync 0.1.0 → 0.2.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 +51 -34
- package/dist/adapters/drizzle/collection.d.ts +27 -0
- package/dist/adapters/drizzle/index.d.ts +3 -0
- package/dist/adapters/drizzle/index.js +139 -2
- package/dist/adapters/drizzle/index.js.map +5 -3
- package/dist/adapters/drizzle/predicate.d.ts +20 -0
- package/dist/angular/index.js +2 -2
- package/dist/angular/index.js.map +3 -3
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +357 -2
- package/dist/client/index.js.map +6 -4
- package/dist/client/presence.d.ts +37 -0
- package/dist/client/syncClient.d.ts +53 -0
- package/dist/engine/cluster.d.ts +41 -0
- package/dist/engine/connection.d.ts +33 -1
- package/dist/engine/index.d.ts +8 -2
- package/dist/engine/index.js +542 -37
- package/dist/engine/index.js.map +9 -6
- package/dist/engine/mutation.d.ts +39 -3
- package/dist/engine/presence.d.ts +46 -0
- package/dist/engine/reactive.d.ts +67 -0
- package/dist/engine/socket.d.ts +4 -1
- package/dist/engine/syncEngine.d.ts +33 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +175 -9
- package/dist/index.js.map +6 -5
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +3 -3
- package/dist/svelte/index.js +2 -2
- package/dist/svelte/index.js.map +3 -3
- package/dist/vue/index.js +2 -2
- package/dist/vue/index.js.map +3 -3
- package/package.json +3 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PresenceMember } from '../engine/presence';
|
|
2
|
+
export type { PresenceMember } from '../engine/presence';
|
|
3
|
+
export type PresenceClientOptions<S> = {
|
|
4
|
+
/** WebSocket URL of the {@link syncSocket} endpoint (e.g. `ws://host/sync/ws`). */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Presence room to join (e.g. a document id or channel). */
|
|
7
|
+
room: string;
|
|
8
|
+
/** This member's initial state (e.g. `{ name, typing: false }`). */
|
|
9
|
+
state: S;
|
|
10
|
+
/** Stable id for this member; defaults to a random one per client. */
|
|
11
|
+
memberId?: string;
|
|
12
|
+
/** WebSocket implementation; defaults to the global one. */
|
|
13
|
+
webSocketImpl?: typeof WebSocket;
|
|
14
|
+
/** Initial reconnect backoff (ms); doubles per attempt. Defaults to 500. */
|
|
15
|
+
reconnectMs?: number;
|
|
16
|
+
/** Max reconnect backoff (ms). Defaults to 10000. */
|
|
17
|
+
maxReconnectMs?: number;
|
|
18
|
+
};
|
|
19
|
+
export type PresenceClient<S> = {
|
|
20
|
+
/** This member's id. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** Current members in the room (including this one). */
|
|
23
|
+
get: () => PresenceMember<S>[];
|
|
24
|
+
/** Subscribe to member changes; returns an unsubscribe. */
|
|
25
|
+
subscribe: (listener: (members: PresenceMember<S>[]) => void) => () => void;
|
|
26
|
+
/** Update this member's own state (e.g. set `typing: true`). */
|
|
27
|
+
set: (state: S) => void;
|
|
28
|
+
/** Leave the room and close the socket. */
|
|
29
|
+
close: () => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Browser client for {@link createPresenceHub} presence: join a room, see who's
|
|
33
|
+
* present (and their state — typing, cursor…), and publish your own. Opens its
|
|
34
|
+
* own small socket to the sync endpoint and re-joins on reconnect.
|
|
35
|
+
* Framework-agnostic (`get` + `subscribe`, ready for `useSyncExternalStore`).
|
|
36
|
+
*/
|
|
37
|
+
export declare const createPresence: <S>(options: PresenceClientOptions<S>) => PresenceClient<S>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RowKey } from '../engine/types';
|
|
2
|
+
import type { MutateOptions, SyncCollectionState, SyncCollectionStatus } from './syncCollection';
|
|
3
|
+
export type SyncClientOptions = {
|
|
4
|
+
/** WebSocket URL of the {@link syncSocket} endpoint (e.g. `ws://host/sync/ws`). */
|
|
5
|
+
url: string;
|
|
6
|
+
/** WebSocket implementation; defaults to the global one (pass for tests/SSR). */
|
|
7
|
+
webSocketImpl?: typeof WebSocket;
|
|
8
|
+
/** Initial reconnect backoff (ms); doubles per attempt. Defaults to 500. */
|
|
9
|
+
reconnectMs?: number;
|
|
10
|
+
/** Max reconnect backoff (ms). Defaults to 10000. */
|
|
11
|
+
maxReconnectMs?: number;
|
|
12
|
+
/** Called with the message of any server `error` frame. */
|
|
13
|
+
onError?: (message: unknown) => void;
|
|
14
|
+
};
|
|
15
|
+
export type SyncCollectionHandleOptions<T> = {
|
|
16
|
+
/** Registered collection name to subscribe to. */
|
|
17
|
+
collection: string;
|
|
18
|
+
/** Query params forwarded to the server collection. */
|
|
19
|
+
params?: unknown;
|
|
20
|
+
/** Row identity. Defaults to `row.id`. */
|
|
21
|
+
key?: (row: T) => RowKey;
|
|
22
|
+
};
|
|
23
|
+
export type SyncCollectionHandle<T> = {
|
|
24
|
+
/** Current state snapshot (stable until the next change). */
|
|
25
|
+
get: () => SyncCollectionState<T>;
|
|
26
|
+
/** Subscribe to state changes; returns an unsubscribe. */
|
|
27
|
+
subscribe: (listener: (state: SyncCollectionState<T>) => void) => () => void;
|
|
28
|
+
/** Run a server mutation, optionally applying it optimistically. */
|
|
29
|
+
mutate: <R = unknown>(options: MutateOptions<T>) => Promise<R>;
|
|
30
|
+
/** Unsubscribe this collection (the socket stays open for others). */
|
|
31
|
+
close: () => void;
|
|
32
|
+
};
|
|
33
|
+
export type SyncClient = {
|
|
34
|
+
/** Subscribe to a collection over the shared socket. */
|
|
35
|
+
collection: <T>(options: SyncCollectionHandleOptions<T>) => SyncCollectionHandle<T>;
|
|
36
|
+
/** Close the socket and every handle. */
|
|
37
|
+
close: () => void;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A multiplexed sync client: one WebSocket serving many live collections. Its
|
|
41
|
+
* reason to exist over per-collection {@link createSyncCollection} is the
|
|
42
|
+
* **consistent frame** — when one atomic mutation touches several collections,
|
|
43
|
+
* the server bundles the diffs into a single `frame` and this client applies
|
|
44
|
+
* them all (to every collection's confirmed state) before notifying any
|
|
45
|
+
* listener, so a view reading multiple collections never paints a torn
|
|
46
|
+
* intermediate where one moved and the other hasn't.
|
|
47
|
+
*
|
|
48
|
+
* Reads: subscribe, apply snapshot then diffs/frames, resume on reconnect.
|
|
49
|
+
* Writes: per-collection optimistic overlay, reconciled on ack/reject and
|
|
50
|
+
* replayed on reconnect (make server mutations idempotent).
|
|
51
|
+
*/
|
|
52
|
+
export declare const createSyncClient: (options: SyncClientOptions) => SyncClient;
|
|
53
|
+
export type { SyncCollectionState, SyncCollectionStatus };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RowChange } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Horizontal scale — the seam for running the engine across many server
|
|
4
|
+
* instances. The in-process hub only reaches subscribers on the same instance;
|
|
5
|
+
* a {@link ClusterBus} fans every instance's committed changes out to the
|
|
6
|
+
* others, so a mutation on instance A reaches subscribers on instance B.
|
|
7
|
+
*
|
|
8
|
+
* Client-agnostic like the {@link ChangeSource} seam: you supply `publish` +
|
|
9
|
+
* `subscribe` over your bus of choice (Redis stream, Postgres `LISTEN/NOTIFY`,
|
|
10
|
+
* NATS…), and the engine handles fan-out and loop prevention (each message is
|
|
11
|
+
* tagged with the originating instance, and an instance ignores its own).
|
|
12
|
+
*
|
|
13
|
+
* Note: version cursors are per-instance, so a client that reconnects to a
|
|
14
|
+
* *different* instance falls back to a fresh snapshot (correct, just not a
|
|
15
|
+
* catch-up diff) — use sticky sessions if you want cross-instance resume.
|
|
16
|
+
*/
|
|
17
|
+
/** A committed change as it travels over the bus. */
|
|
18
|
+
export type ClusterChange = {
|
|
19
|
+
table: string;
|
|
20
|
+
change: RowChange<unknown>;
|
|
21
|
+
};
|
|
22
|
+
export type ClusterMessage = {
|
|
23
|
+
/** The instance that produced these changes (so peers ignore their own). */
|
|
24
|
+
origin: string;
|
|
25
|
+
changes: ClusterChange[];
|
|
26
|
+
};
|
|
27
|
+
export type ClusterBus = {
|
|
28
|
+
/** Broadcast this instance's committed changes to the others. */
|
|
29
|
+
publish: (message: ClusterMessage) => void | Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Receive other instances' changes; return a function that stops listening.
|
|
32
|
+
* The engine filters out messages from its own `origin`.
|
|
33
|
+
*/
|
|
34
|
+
subscribe: (onMessage: (message: ClusterMessage) => void) => (() => void | Promise<void>) | Promise<() => void | Promise<void>>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* An in-process {@link ClusterBus} — for tests, local dev, or several engines in
|
|
38
|
+
* one process. **Not** cross-process: real horizontal scale needs a bus that
|
|
39
|
+
* spans machines (Redis stream, Postgres `LISTEN/NOTIFY`).
|
|
40
|
+
*/
|
|
41
|
+
export declare const createInMemoryClusterBus: () => ClusterBus;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PresenceHub, PresenceMember } from './presence';
|
|
1
2
|
import type { SyncEngine } from './syncEngine';
|
|
2
3
|
/**
|
|
3
4
|
* Wire protocol for the sync-engine WebSocket. One connection multiplexes many
|
|
@@ -19,6 +20,25 @@ export type ClientFrame = {
|
|
|
19
20
|
mutationId: number;
|
|
20
21
|
name: string;
|
|
21
22
|
args?: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'presence-join';
|
|
25
|
+
room: string;
|
|
26
|
+
memberId: string;
|
|
27
|
+
state: unknown;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'presence-set';
|
|
30
|
+
room: string;
|
|
31
|
+
state: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'presence-leave';
|
|
34
|
+
room: string;
|
|
35
|
+
};
|
|
36
|
+
/** One subscription's delta within a {@link ServerFrame} `frame`. */
|
|
37
|
+
export type FrameDiff<T = unknown> = {
|
|
38
|
+
id: string;
|
|
39
|
+
added: T[];
|
|
40
|
+
removed: T[];
|
|
41
|
+
changed: T[];
|
|
22
42
|
};
|
|
23
43
|
/** Server → client. `version` is the change-feed watermark this frame brings. */
|
|
24
44
|
export type ServerFrame<T = unknown> = {
|
|
@@ -33,6 +53,16 @@ export type ServerFrame<T = unknown> = {
|
|
|
33
53
|
removed: T[];
|
|
34
54
|
changed: T[];
|
|
35
55
|
version?: number;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'frame';
|
|
58
|
+
version?: number;
|
|
59
|
+
diffs: FrameDiff<T>[];
|
|
60
|
+
} | {
|
|
61
|
+
type: 'presence';
|
|
62
|
+
room: string;
|
|
63
|
+
joined: PresenceMember<T>[];
|
|
64
|
+
updated: PresenceMember<T>[];
|
|
65
|
+
left: string[];
|
|
36
66
|
} | {
|
|
37
67
|
type: 'error';
|
|
38
68
|
id?: string;
|
|
@@ -52,6 +82,8 @@ export type SyncConnectionOptions = {
|
|
|
52
82
|
ctx: unknown;
|
|
53
83
|
/** Send a frame to the client (the transport serializes it). */
|
|
54
84
|
send: (frame: ServerFrame) => void;
|
|
85
|
+
/** Optional presence hub; enables the `presence-*` frames (see createPresenceHub). */
|
|
86
|
+
presence?: PresenceHub;
|
|
55
87
|
};
|
|
56
88
|
export type SyncConnection = {
|
|
57
89
|
/** Handle one client frame (a parsed object or a raw JSON string). */
|
|
@@ -68,4 +100,4 @@ export type SyncConnection = {
|
|
|
68
100
|
* Pure (no WebSocket import) so it can be unit-tested with a fake `send`; the
|
|
69
101
|
* Elysia `syncSocket` plugin is the thin adapter that feeds it socket events.
|
|
70
102
|
*/
|
|
71
|
-
export declare const createSyncConnection: ({ engine, ctx, send }: SyncConnectionOptions) => SyncConnection;
|
|
103
|
+
export declare const createSyncConnection: ({ engine, ctx, send, presence }: SyncConnectionOptions) => SyncConnection;
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -20,15 +20,21 @@ export type { AggregateOpOptions, Change, JoinNode, JoinNodeOptions, Materialize
|
|
|
20
20
|
export type { ChangeSource, EmitChange, ParsedChange, RowChange, RowKey, RowOp, ViewDiff } from './types';
|
|
21
21
|
export { createPollingChangeSource, parseOutboxRow } from './pollingSource';
|
|
22
22
|
export type { OutboxRow, PollingChangeSourceOptions } from './pollingSource';
|
|
23
|
+
export { createInMemoryClusterBus } from './cluster';
|
|
24
|
+
export type { ClusterBus, ClusterChange, ClusterMessage } from './cluster';
|
|
25
|
+
export { createPresenceHub } from './presence';
|
|
26
|
+
export type { PresenceDiff, PresenceHandle, PresenceHub, PresenceMember } from './presence';
|
|
23
27
|
export { defineCollection, defineJoinCollection } from './collection';
|
|
24
28
|
export type { CollectionContext, CollectionDefinition, JoinCollectionDefinition, JoinSide } from './collection';
|
|
29
|
+
export { defineReactiveQuery } from './reactive';
|
|
30
|
+
export type { ReactiveQueryContext, ReactiveQueryDefinition, ReadHandle, TableReader } from './reactive';
|
|
25
31
|
export { defineGraphCollection, query } from './graph';
|
|
26
32
|
export type { GraphCollectionDefinition, GraphInstance, GraphSource, GroupByOptions, JoinOptions, OrderByQueryOptions, Query } from './graph';
|
|
27
33
|
export { defineMutation } from './mutation';
|
|
28
|
-
export type { MutationActions, MutationDefinition, MutationHandler } from './mutation';
|
|
34
|
+
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
29
35
|
export { createSyncEngine, UnauthorizedError } from './syncEngine';
|
|
30
36
|
export type { SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
31
37
|
export { hydrateRoute, mutateRoute } from './routes';
|
|
32
38
|
export type { SyncRouteContext } from './routes';
|
|
33
39
|
export { createSyncConnection } from './connection';
|
|
34
|
-
export type { ClientFrame, ServerFrame, SyncConnection, SyncConnectionOptions } from './connection';
|
|
40
|
+
export type { ClientFrame, FrameDiff, ServerFrame, SyncConnection, SyncConnectionOptions } from './connection';
|