@absolutejs/sync 0.7.0 → 0.8.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 +12 -10
- package/dist/devtools.d.ts +69 -0
- package/dist/engine/devtools.d.ts +55 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +90 -7
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +117 -1
- package/dist/index.js.map +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,10 @@ 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, live full-text + vector search, scheduled functions,
|
|
37
|
-
> Postgres/MySQL/SQLite, incremental aggregations +
|
|
38
|
-
> operator graph) are in place. Everything ships as
|
|
36
|
+
> permissions, live full-text + vector search, scheduled functions, a live
|
|
37
|
+
> devtools dashboard, CDC for Postgres/MySQL/SQLite, incremental aggregations +
|
|
38
|
+
> joins, and a declarative operator graph) are in place. Everything ships as
|
|
39
|
+
> subpaths of this one package.
|
|
39
40
|
|
|
40
41
|
## Install
|
|
41
42
|
|
|
@@ -332,13 +333,14 @@ it, ~3 store round-trips every 20ms ran the voice pipeline far slower than real
|
|
|
332
333
|
|
|
333
334
|
### `@absolutejs/sync`
|
|
334
335
|
|
|
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? })` _(`/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.
|
|
341
|
-
| `
|
|
336
|
+
| Export | What it is |
|
|
337
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
338
|
+
| `createReactiveHub()` | In-memory topic pub/sub (`publish`, `subscribe`, `subscriberCount`). |
|
|
339
|
+
| `sync({ hub, path?, resolveTopics?, heartbeatMs? })` | Elysia plugin: SSE stream of hub events. |
|
|
340
|
+
| `syncSocket({ engine, path?, resolveContext? })` | Elysia WebSocket plugin for the sync engine. |
|
|
341
|
+
| `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. |
|
|
342
|
+
| `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()`. |
|
|
343
|
+
| `createWriteBehindCache({ load, persist, remove?, debounceMs?, evict?, onPersistError? })` | In-memory cache + write-behind persistence. |
|
|
342
344
|
|
|
343
345
|
### `@absolutejs/sync/client`
|
|
344
346
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { SyncEngine } from './engine/syncEngine';
|
|
3
|
+
export type SyncDevtoolsOptions = {
|
|
4
|
+
/** The engine to inspect. */
|
|
5
|
+
engine: SyncEngine;
|
|
6
|
+
/** Route the dashboard is served from (its SSE feed is `<path>/stream`). Default `/sync/devtools`. */
|
|
7
|
+
path?: string;
|
|
8
|
+
/** Snapshot refresh interval (ms) — keeps subscription counts/version current. Default 2000. */
|
|
9
|
+
snapshotMs?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Elysia plugin: a live devtools dashboard for a {@link SyncEngine}. Mount it and
|
|
13
|
+
* open `path` in a browser to watch registered collections (kind, source tables,
|
|
14
|
+
* live subscription counts), mutations, schedules, readers/writers, the
|
|
15
|
+
* change-feed version, and a streaming log of changes + mutation outcomes — over
|
|
16
|
+
* Server-Sent Events. Read-only; safe to leave mounted in dev.
|
|
17
|
+
*/
|
|
18
|
+
export declare const syncDevtools: ({ engine, path, snapshotMs }: SyncDevtoolsOptions) => Elysia<"", {
|
|
19
|
+
decorator: {};
|
|
20
|
+
store: {};
|
|
21
|
+
derive: {};
|
|
22
|
+
resolve: {};
|
|
23
|
+
}, {
|
|
24
|
+
typebox: {};
|
|
25
|
+
error: {};
|
|
26
|
+
}, {
|
|
27
|
+
schema: {};
|
|
28
|
+
standaloneSchema: {};
|
|
29
|
+
macro: {};
|
|
30
|
+
macroFn: {};
|
|
31
|
+
parser: {};
|
|
32
|
+
response: {};
|
|
33
|
+
}, {
|
|
34
|
+
[x: string]: {
|
|
35
|
+
get: {
|
|
36
|
+
body: unknown;
|
|
37
|
+
params: {};
|
|
38
|
+
query: unknown;
|
|
39
|
+
headers: unknown;
|
|
40
|
+
response: {
|
|
41
|
+
200: Response;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
} & {
|
|
46
|
+
[x: string]: {
|
|
47
|
+
get: {
|
|
48
|
+
body: unknown;
|
|
49
|
+
params: {};
|
|
50
|
+
query: unknown;
|
|
51
|
+
headers: unknown;
|
|
52
|
+
response: {
|
|
53
|
+
200: Response;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}, {
|
|
58
|
+
derive: {};
|
|
59
|
+
resolve: {};
|
|
60
|
+
schema: {};
|
|
61
|
+
standaloneSchema: {};
|
|
62
|
+
response: {};
|
|
63
|
+
}, {
|
|
64
|
+
derive: {};
|
|
65
|
+
resolve: {};
|
|
66
|
+
schema: {};
|
|
67
|
+
standaloneSchema: {};
|
|
68
|
+
response: {};
|
|
69
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RowOp } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Devtools introspection — a live window into a running {@link SyncEngine}: what
|
|
4
|
+
* collections are registered, how many clients subscribe to each, which
|
|
5
|
+
* mutations/schedules/readers/writers exist, the change-feed version, and a tail
|
|
6
|
+
* of recent changes. Paired with the activity stream ({@link EngineActivity}) it
|
|
7
|
+
* powers the `syncDevtools` dashboard. Read-only — purely observational.
|
|
8
|
+
*/
|
|
9
|
+
export type CollectionKind = 'view' | 'join' | 'graph' | 'reactive' | 'search';
|
|
10
|
+
/** One registered collection's current state. */
|
|
11
|
+
export type CollectionInspection = {
|
|
12
|
+
name: string;
|
|
13
|
+
kind: CollectionKind;
|
|
14
|
+
/** Source tables it reads (empty for a reactive query — its deps are dynamic). */
|
|
15
|
+
tables: string[];
|
|
16
|
+
/** Active client subscriptions to it right now. */
|
|
17
|
+
subscriptions: number;
|
|
18
|
+
};
|
|
19
|
+
/** A point-in-time snapshot of the engine (see {@link SyncEngine.inspect}). */
|
|
20
|
+
export type EngineInspection = {
|
|
21
|
+
/** Current change-feed version (monotonic). */
|
|
22
|
+
version: number;
|
|
23
|
+
collections: CollectionInspection[];
|
|
24
|
+
mutations: string[];
|
|
25
|
+
schedules: {
|
|
26
|
+
name: string;
|
|
27
|
+
pattern: string;
|
|
28
|
+
}[];
|
|
29
|
+
/** Tables with a registered reader / writer. */
|
|
30
|
+
readers: string[];
|
|
31
|
+
writers: string[];
|
|
32
|
+
/** Most recent changes from the change log (oldest first). */
|
|
33
|
+
recentChanges: {
|
|
34
|
+
version: number;
|
|
35
|
+
table: string;
|
|
36
|
+
op: RowOp;
|
|
37
|
+
}[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A live engine event (see {@link SyncEngine.onActivity}): a committed change or
|
|
41
|
+
* a mutation outcome. `at` is `Date.now()`. (Live subscription counts come from
|
|
42
|
+
* the {@link EngineInspection} snapshot.)
|
|
43
|
+
*/
|
|
44
|
+
export type EngineActivity = {
|
|
45
|
+
type: 'change';
|
|
46
|
+
at: number;
|
|
47
|
+
table: string;
|
|
48
|
+
op: RowOp;
|
|
49
|
+
version: number;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'mutation';
|
|
52
|
+
at: number;
|
|
53
|
+
name: string;
|
|
54
|
+
status: 'ok' | 'error';
|
|
55
|
+
};
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export { defineMutation } from './mutation';
|
|
|
44
44
|
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
45
45
|
export { createSyncEngine, UnauthorizedError } from './syncEngine';
|
|
46
46
|
export type { SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
47
|
+
export type { CollectionInspection, CollectionKind, EngineActivity, EngineInspection } from './devtools';
|
|
47
48
|
export { hydrateRoute, mutateRoute } from './routes';
|
|
48
49
|
export type { SyncRouteContext } from './routes';
|
|
49
50
|
export { createSyncConnection } from './connection';
|
package/dist/engine/index.js
CHANGED
|
@@ -1090,6 +1090,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
1090
1090
|
const changeLogSize = options.changeLogSize ?? 1024;
|
|
1091
1091
|
const changeLog = [];
|
|
1092
1092
|
let version = 0;
|
|
1093
|
+
const activityListeners = new Set;
|
|
1094
|
+
const emitActivity = (event) => {
|
|
1095
|
+
for (const listener of activityListeners) {
|
|
1096
|
+
listener(event);
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1093
1099
|
const runInTransaction = options.transaction;
|
|
1094
1100
|
const instanceId = globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
|
|
1095
1101
|
let clusterBus;
|
|
@@ -1412,6 +1418,13 @@ var createSyncEngine = (options = {}) => {
|
|
|
1412
1418
|
version += 1;
|
|
1413
1419
|
const changeVersion = version;
|
|
1414
1420
|
logChange(changeVersion, { version: changeVersion, table, change });
|
|
1421
|
+
emitActivity({
|
|
1422
|
+
type: "change",
|
|
1423
|
+
at: Date.now(),
|
|
1424
|
+
table,
|
|
1425
|
+
op: change.op,
|
|
1426
|
+
version: changeVersion
|
|
1427
|
+
});
|
|
1415
1428
|
const emissions = [];
|
|
1416
1429
|
for (const subscription of subscriptionsForTable(table)) {
|
|
1417
1430
|
const diff = await subscriptionDiff(subscription, table, change);
|
|
@@ -1440,6 +1453,13 @@ var createSyncEngine = (options = {}) => {
|
|
|
1440
1453
|
const reactiveChanges = [];
|
|
1441
1454
|
for (const { table, change } of changes) {
|
|
1442
1455
|
logChange(batchVersion, { version: batchVersion, table, change });
|
|
1456
|
+
emitActivity({
|
|
1457
|
+
type: "change",
|
|
1458
|
+
at: Date.now(),
|
|
1459
|
+
table,
|
|
1460
|
+
op: change.op,
|
|
1461
|
+
version: batchVersion
|
|
1462
|
+
});
|
|
1443
1463
|
reactiveChanges.push({
|
|
1444
1464
|
table,
|
|
1445
1465
|
key: changedKeyFor(table, change),
|
|
@@ -1816,13 +1836,29 @@ var createSyncEngine = (options = {}) => {
|
|
|
1816
1836
|
}
|
|
1817
1837
|
}
|
|
1818
1838
|
const runHandler = async (tx) => {
|
|
1819
|
-
const { actions, buffered
|
|
1820
|
-
const
|
|
1821
|
-
return { buffered
|
|
1839
|
+
const { actions, buffered } = makeActions(tx, ctx, true);
|
|
1840
|
+
const result = await mutation.handler(args, ctx, actions);
|
|
1841
|
+
return { buffered, result };
|
|
1822
1842
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1843
|
+
try {
|
|
1844
|
+
const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
1845
|
+
await applyChangeBatch(buffered);
|
|
1846
|
+
emitActivity({
|
|
1847
|
+
type: "mutation",
|
|
1848
|
+
at: Date.now(),
|
|
1849
|
+
name,
|
|
1850
|
+
status: "ok"
|
|
1851
|
+
});
|
|
1852
|
+
return result;
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
emitActivity({
|
|
1855
|
+
type: "mutation",
|
|
1856
|
+
at: Date.now(),
|
|
1857
|
+
name,
|
|
1858
|
+
status: "error"
|
|
1859
|
+
});
|
|
1860
|
+
throw error;
|
|
1861
|
+
}
|
|
1826
1862
|
},
|
|
1827
1863
|
registerSchedule: (schedule) => {
|
|
1828
1864
|
schedules.set(schedule.name, schedule);
|
|
@@ -1841,6 +1877,53 @@ var createSyncEngine = (options = {}) => {
|
|
|
1841
1877
|
};
|
|
1842
1878
|
const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
1843
1879
|
await applyChangeBatch(buffered);
|
|
1880
|
+
},
|
|
1881
|
+
inspect: () => {
|
|
1882
|
+
const collections = [...registry.entries()].map(([name, def]) => {
|
|
1883
|
+
const kind = def.kind ?? "view";
|
|
1884
|
+
let tables = [];
|
|
1885
|
+
if (kind === "join") {
|
|
1886
|
+
const join = def;
|
|
1887
|
+
tables = [join.left.table, join.right.table];
|
|
1888
|
+
} else if (kind === "graph") {
|
|
1889
|
+
tables = def.query.tables();
|
|
1890
|
+
} else if (kind === "search") {
|
|
1891
|
+
tables = [
|
|
1892
|
+
def.table
|
|
1893
|
+
];
|
|
1894
|
+
} else if (kind === "view") {
|
|
1895
|
+
tables = def.tables ?? [name];
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
name,
|
|
1899
|
+
kind,
|
|
1900
|
+
tables,
|
|
1901
|
+
subscriptions: active.get(name)?.size ?? 0
|
|
1902
|
+
};
|
|
1903
|
+
});
|
|
1904
|
+
const DEVTOOLS_RECENT = 50;
|
|
1905
|
+
return {
|
|
1906
|
+
version,
|
|
1907
|
+
collections,
|
|
1908
|
+
mutations: [...mutations.keys()],
|
|
1909
|
+
schedules: [...schedules.values()].map((schedule) => ({
|
|
1910
|
+
name: schedule.name,
|
|
1911
|
+
pattern: schedule.pattern
|
|
1912
|
+
})),
|
|
1913
|
+
readers: [...readers.keys()],
|
|
1914
|
+
writers: [...writers.keys()],
|
|
1915
|
+
recentChanges: changeLog.slice(-DEVTOOLS_RECENT).map((entry) => ({
|
|
1916
|
+
version: entry.version,
|
|
1917
|
+
table: entry.table,
|
|
1918
|
+
op: entry.change.op
|
|
1919
|
+
}))
|
|
1920
|
+
};
|
|
1921
|
+
},
|
|
1922
|
+
onActivity: (listener) => {
|
|
1923
|
+
activityListeners.add(listener);
|
|
1924
|
+
return () => {
|
|
1925
|
+
activityListeners.delete(listener);
|
|
1926
|
+
};
|
|
1844
1927
|
}
|
|
1845
1928
|
};
|
|
1846
1929
|
};
|
|
@@ -2115,5 +2198,5 @@ export {
|
|
|
2115
2198
|
SEARCH_SCORE_FIELD
|
|
2116
2199
|
};
|
|
2117
2200
|
|
|
2118
|
-
//# debugId=
|
|
2201
|
+
//# debugId=B0B398375CBCC30B64756E2164756E21
|
|
2119
2202
|
//# sourceMappingURL=index.js.map
|