@arcote.tech/arc 0.7.15 → 0.7.17
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/dist/adapters/event-publisher.d.ts +1 -8
- package/dist/adapters/event-wire.d.ts +28 -22
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/module-sync-coordinator.d.ts +40 -0
- package/dist/adapters/module-sync-coordinator.test.d.ts +2 -0
- package/dist/adapters/query-wire.d.ts +2 -2
- package/dist/context-element/aggregate/aggregate-element.d.ts +0 -10
- package/dist/context-element/view/view.d.ts +0 -10
- package/dist/data-storage/data-storage-master.d.ts +2 -4
- package/dist/data-storage/data-storage-observable.d.ts +2 -4
- package/dist/data-storage/data-storage-observable.test.d.ts +2 -0
- package/dist/data-storage/data-storage.abstract.d.ts +1 -14
- package/dist/data-storage/store-state-master.d.ts +1 -11
- package/dist/index.js +330 -312
- package/dist/model/live-query/diff.d.ts +42 -0
- package/dist/model/live-query/diff.test.d.ts +2 -0
- package/dist/model/live-query/index.d.ts +2 -0
- package/dist/model/live-query/live-query-subscription.d.ts +66 -0
- package/dist/model/scoped-model.d.ts +9 -1
- package/dist/streaming/index.d.ts +1 -1
- package/dist/streaming/streaming-event-publisher.d.ts +8 -10
- package/dist/streaming/streaming-query-cache.d.ts +35 -96
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff between two query results → positional deltas for the wire.
|
|
3
|
+
*
|
|
4
|
+
* The server re-executes a subscribed query and diffs the previous result
|
|
5
|
+
* against the new one. For lists of items with `_id` it produces minimal
|
|
6
|
+
* `set`/`delete` changes WITH target positions — the client applies them
|
|
7
|
+
* blindly (remove-by-id + splice at index), so result order is always the
|
|
8
|
+
* server's order (only the query handler knows its orderBy).
|
|
9
|
+
*
|
|
10
|
+
* Correctness over cleverness: the deltas are verified by simulating their
|
|
11
|
+
* application; any mismatch falls back to a full snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export type QueryResultChange = {
|
|
14
|
+
type: "set";
|
|
15
|
+
id: string;
|
|
16
|
+
item: any;
|
|
17
|
+
index: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: "delete";
|
|
20
|
+
id: string;
|
|
21
|
+
};
|
|
22
|
+
export type QueryDiff = {
|
|
23
|
+
kind: "none";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "changes";
|
|
26
|
+
changes: QueryResultChange[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: "snapshot";
|
|
29
|
+
result: any;
|
|
30
|
+
};
|
|
31
|
+
export declare function diffResults(prev: any, next: any): QueryDiff;
|
|
32
|
+
/**
|
|
33
|
+
* Apply positional deltas to a result list — the exact client-side
|
|
34
|
+
* algorithm (exported so client cache and tests share one implementation):
|
|
35
|
+
* all deletes first, then sets in ascending index order.
|
|
36
|
+
*/
|
|
37
|
+
export declare function applyQueryChanges(result: Array<{
|
|
38
|
+
_id: string;
|
|
39
|
+
}>, changes: QueryResultChange[]): Array<{
|
|
40
|
+
_id: string;
|
|
41
|
+
}>;
|
|
42
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveQuery — a server-side query subscription that owns its deltas.
|
|
3
|
+
*
|
|
4
|
+
* The SAME query (any descriptor: bare view find or custom clientQuery
|
|
5
|
+
* handler) is executed with an ObservableDataStorage wrapped around the
|
|
6
|
+
* real storage. Every `find` the handler makes — already merged with token
|
|
7
|
+
* restrictions by ScopedStore — gets tracked. After each store commit,
|
|
8
|
+
* `resolveQueryChange` updates the tracked results in memory; when any of
|
|
9
|
+
* them changed, the descriptor is re-executed AGAINST THE CACHE (0 SQL)
|
|
10
|
+
* and the new result is diffed against the previous one.
|
|
11
|
+
*
|
|
12
|
+
* The transport layer (WS) only forwards `onUpdate` payloads — all query
|
|
13
|
+
* logic (filtering, scoping, ordering) lives here, in the query layer.
|
|
14
|
+
*/
|
|
15
|
+
import { type ContextDescriptor } from "../context-accessor";
|
|
16
|
+
import type { ModelLike } from "../model-like";
|
|
17
|
+
import { type QueryResultChange } from "./diff";
|
|
18
|
+
export type LiveQueryUpdate = {
|
|
19
|
+
type: "changes";
|
|
20
|
+
changes: QueryResultChange[];
|
|
21
|
+
} | {
|
|
22
|
+
type: "snapshot";
|
|
23
|
+
result: any;
|
|
24
|
+
};
|
|
25
|
+
export declare class LiveQuery {
|
|
26
|
+
private readonly model;
|
|
27
|
+
private readonly descriptor;
|
|
28
|
+
private readonly scope;
|
|
29
|
+
private readonly rawToken;
|
|
30
|
+
private readonly onUpdate;
|
|
31
|
+
private observable;
|
|
32
|
+
private adapters;
|
|
33
|
+
private lastResult;
|
|
34
|
+
private scheduled;
|
|
35
|
+
private running;
|
|
36
|
+
private rerunRequested;
|
|
37
|
+
private stopped;
|
|
38
|
+
constructor(model: ModelLike<any>, descriptor: ContextDescriptor, scope: string, rawToken: string | null, onUpdate: (update: LiveQueryUpdate) => void);
|
|
39
|
+
/**
|
|
40
|
+
* Execute the descriptor with tracking and return the initial result.
|
|
41
|
+
*/
|
|
42
|
+
start(): Promise<any>;
|
|
43
|
+
/**
|
|
44
|
+
* Close the initial-execute window: the store listener is registered
|
|
45
|
+
* before the read transaction, but trackQuery happens after the await —
|
|
46
|
+
* a commit in between could slip past the tracked entry. One forced
|
|
47
|
+
* re-execute (cache-backed, no SQL when nothing changed) diffs out any
|
|
48
|
+
* missed delta.
|
|
49
|
+
*
|
|
50
|
+
* Call AFTER the initial result has been delivered (e.g. the snapshot
|
|
51
|
+
* message was sent) — otherwise the catch-up delta could overtake it.
|
|
52
|
+
*/
|
|
53
|
+
flush(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Stop tracking — unsubscribes all store listeners.
|
|
56
|
+
*/
|
|
57
|
+
stop(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Coalesce re-executes: a single commit can touch multiple stores and
|
|
60
|
+
* fire onChange several times — one microtask handles them all. A commit
|
|
61
|
+
* landing DURING a re-execute requests another pass afterwards.
|
|
62
|
+
*/
|
|
63
|
+
private schedule;
|
|
64
|
+
private run;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=live-query-subscription.d.ts.map
|
|
@@ -21,7 +21,15 @@ export declare class ScopedModel<Context extends ArcContextAny> implements Model
|
|
|
21
21
|
private readonly scopedAdapters;
|
|
22
22
|
private tokenListeners;
|
|
23
23
|
constructor(parent: ModelLike<Context>, scopeName: string);
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Set this scope's token. Returns a promise that resolves when the platform
|
|
26
|
+
* module loader has synced the chunks for the new token state (so routes in
|
|
27
|
+
* newly-gated modules exist before the caller navigates). With no module-sync
|
|
28
|
+
* provider registered (server, SSR, tests, app-without-platform) it resolves
|
|
29
|
+
* immediately — preserving the historical synchronous semantics for the many
|
|
30
|
+
* server-side `setToken(rawToken)` callers that ignore the return value.
|
|
31
|
+
*/
|
|
32
|
+
setToken(token: string | null): Promise<void>;
|
|
25
33
|
getToken(): string | null;
|
|
26
34
|
getDecoded(): DecodedToken | null;
|
|
27
35
|
getParams(): Record<string, any> | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { StreamingQueryCache } from "./streaming-query-cache";
|
|
2
|
-
export type {
|
|
2
|
+
export type { QuerySubscriptionHandle } from "./streaming-query-cache";
|
|
3
3
|
export { StreamingEventPublisher } from "./streaming-event-publisher";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* StreamingEventPublisher - Event publisher for streaming mode (no local database)
|
|
3
3
|
*
|
|
4
|
-
* When events are emitted
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* When events are emitted locally (client-side mutate handlers), they are
|
|
5
|
+
* sent to the server via EventWire; the server commits them and pushes the
|
|
6
|
+
* resulting query deltas back through live query subscriptions. No local
|
|
7
|
+
* optimistic apply — the client holds query RESULTS, not view data, so it
|
|
8
|
+
* cannot compute how an event affects a custom query handler's output.
|
|
9
9
|
*/
|
|
10
10
|
import type { EventPublisher, EventWithSyncStatus } from "../adapters/event-publisher";
|
|
11
11
|
import type { EventWire } from "../adapters/event-wire";
|
|
12
12
|
import type { ArcEventAny } from "../context-element/event/event";
|
|
13
13
|
import type { ArcEventInstance } from "../context-element/event/instance";
|
|
14
14
|
import type { ArcViewAny } from "../context-element/view/view";
|
|
15
|
-
import type { StreamingQueryCache } from "./streaming-query-cache";
|
|
16
15
|
/**
|
|
17
16
|
* StreamingEventPublisher
|
|
18
17
|
*/
|
|
19
18
|
export declare class StreamingEventPublisher implements EventPublisher {
|
|
20
|
-
private readonly cache;
|
|
21
19
|
private readonly eventWire;
|
|
22
20
|
private views;
|
|
23
21
|
private subscribers;
|
|
24
|
-
constructor(
|
|
22
|
+
constructor(eventWire: EventWire);
|
|
25
23
|
/**
|
|
26
24
|
* Register views for event handling
|
|
27
25
|
*/
|
|
28
26
|
registerViews(views: ArcViewAny[]): void;
|
|
29
27
|
/**
|
|
30
28
|
* Publish an event
|
|
31
|
-
* 1.
|
|
32
|
-
* 2. Send to server
|
|
29
|
+
* 1. Notify local subscribers
|
|
30
|
+
* 2. Send to server (server commits → live query deltas come back)
|
|
33
31
|
*/
|
|
34
32
|
publish(event: ArcEventInstance<ArcEventAny>): Promise<void>;
|
|
35
33
|
/**
|
|
@@ -1,113 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StreamingQueryCache -
|
|
2
|
+
* StreamingQueryCache - per-query result cache for streaming mode
|
|
3
3
|
*
|
|
4
|
-
* Used when client connects without local database
|
|
5
|
-
*
|
|
6
|
-
* the server
|
|
7
|
-
* `
|
|
4
|
+
* Used when the client connects without a local database. Each unique
|
|
5
|
+
* (scope, descriptor) gets ONE live subscription over the EventWire:
|
|
6
|
+
* the server executes the query with tracking (LiveQuery), sends a full
|
|
7
|
+
* `query-snapshot`, then positional `query-changes` deltas. The client
|
|
8
|
+
* applies deltas blindly — ALL query logic (filtering, scoping, ordering)
|
|
9
|
+
* lives on the server, in the query layer.
|
|
8
10
|
*
|
|
9
11
|
* Features:
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
12
|
+
* - Dedup: many components with the same query share one subscription
|
|
13
|
+
* (refCount + UNSUBSCRIBE_DELAY grace window for quick remounts)
|
|
14
|
+
* - Snapshot/delta application with listener notifications
|
|
15
|
+
* - Scope invalidation on token change (workspace switch / re-auth)
|
|
14
16
|
*/
|
|
15
17
|
import type { EventWire } from "../adapters/event-wire";
|
|
16
|
-
import type {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
export type CacheChangeListener = (events: ListenerEvent<any>[] | null) => void;
|
|
26
|
-
export interface StreamingQueryCacheStore<Item extends {
|
|
27
|
-
_id: string;
|
|
28
|
-
}> {
|
|
29
|
-
find(options?: FindOptions<Item>): Item[];
|
|
30
|
-
findOne(where?: Record<string, any>): Item | undefined;
|
|
31
|
-
subscribe(listener: CacheChangeListener): () => void;
|
|
32
|
-
hasData(): boolean;
|
|
18
|
+
import type { ContextDescriptor } from "../model/context-accessor";
|
|
19
|
+
export interface QuerySubscriptionHandle {
|
|
20
|
+
/** Current state — stable shape for React reads. */
|
|
21
|
+
read(): {
|
|
22
|
+
result: any;
|
|
23
|
+
loading: boolean;
|
|
24
|
+
};
|
|
25
|
+
unsubscribe(): void;
|
|
33
26
|
}
|
|
34
|
-
/**
|
|
35
|
-
* StreamingQueryCache - Main cache class
|
|
36
|
-
*/
|
|
37
27
|
export declare class StreamingQueryCache {
|
|
38
|
-
private
|
|
39
|
-
private views;
|
|
40
|
-
private activeStreams;
|
|
41
|
-
private pendingUnsubscribes;
|
|
28
|
+
private entries;
|
|
42
29
|
private static UNSUBSCRIBE_DELAY_MS;
|
|
43
|
-
|
|
44
|
-
* subscribing the same view must hold separate replicas. */
|
|
45
|
-
private storeKey;
|
|
46
|
-
/**
|
|
47
|
-
* Register views that this cache will handle
|
|
48
|
-
*/
|
|
49
|
-
registerViews(views: ArcViewAny[]): void;
|
|
50
|
-
/**
|
|
51
|
-
* Get the replica store for a view in a given scope
|
|
52
|
-
*/
|
|
53
|
-
getStore<Item extends {
|
|
54
|
-
_id: string;
|
|
55
|
-
}>(viewName: string, scope?: string): StreamingQueryCacheStore<Item>;
|
|
56
|
-
/**
|
|
57
|
-
* Check if a replica has received its snapshot
|
|
58
|
-
*/
|
|
59
|
-
hasData(viewName: string, scope?: string): boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Register an active stream for a key (increment ref count if exists)
|
|
62
|
-
* Returns object with unsubscribe function and whether stream was reused
|
|
63
|
-
*/
|
|
64
|
-
registerStream(key: string, createStream: () => {
|
|
65
|
-
unsubscribe: () => void;
|
|
66
|
-
}): {
|
|
67
|
-
unsubscribe: () => void;
|
|
68
|
-
wasReused: boolean;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Unregister from a stream. When refCount hits 0, delays actual WS
|
|
72
|
-
* unsubscribe by UNSUBSCRIBE_DELAY_MS. If re-registered within the
|
|
73
|
-
* window, the existing subscription is reused (cache serves immediately).
|
|
74
|
-
*/
|
|
75
|
-
private unregisterStream;
|
|
76
|
-
/**
|
|
77
|
-
* Subscribe to a view replica via WebSocket with deduplication.
|
|
78
|
-
* Multiple callers share a single WS subscription per (scope, view).
|
|
79
|
-
* Snapshot → setAll (listeners get null → full re-query);
|
|
80
|
-
* deltas → applyChanges (listeners get ListenerEvent[] → local resolve).
|
|
81
|
-
* Returns unsubscribe function that decrements refcount.
|
|
82
|
-
*/
|
|
83
|
-
subscribeView(viewName: string, eventWire: EventWire, scope?: string): () => void;
|
|
30
|
+
private entryKey;
|
|
84
31
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* token until the page reload).
|
|
90
|
-
*
|
|
91
|
-
* Bypasses both `refCount` (other subscribers still mounted) and the
|
|
92
|
-
* UNSUBSCRIBE_DELAY_MS grace window — both became invalid the moment the
|
|
93
|
-
* token changed. React's `useQuery` re-subscribes immediately afterwards
|
|
94
|
-
* via the `subKey` change (token is in the key), getting a fresh stream.
|
|
95
|
-
*
|
|
96
|
-
* Bonus: each affected store is also cleared so any in-progress render
|
|
97
|
-
* that reads `store.find()` between `setToken` and the new WS snapshot
|
|
98
|
-
* arriving gets `[]` rather than stale rows from the previous workspace.
|
|
32
|
+
* Subscribe to a live query. Identical (scope, descriptor) pairs share
|
|
33
|
+
* one WS subscription; the last unsubscriber tears it down after a grace
|
|
34
|
+
* window (instant remounts reuse the cached result without a snapshot
|
|
35
|
+
* round-trip).
|
|
99
36
|
*/
|
|
100
|
-
|
|
37
|
+
subscribe(descriptor: ContextDescriptor, scope: string, eventWire: EventWire, onChange: () => void): QuerySubscriptionHandle;
|
|
101
38
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
39
|
+
* Force-drop every cached query of `scope`. Called when the scope's token
|
|
40
|
+
* changes (workspace switch / re-auth) — cached results and the WS
|
|
41
|
+
* subscriptions behind them were computed with the previous token.
|
|
42
|
+
* React's `useQuery` re-subscribes immediately afterwards (token is part
|
|
43
|
+
* of its subscription key), getting fresh snapshots.
|
|
106
44
|
*/
|
|
107
|
-
|
|
45
|
+
invalidateScope(scope: string, eventWire?: EventWire): void;
|
|
108
46
|
/**
|
|
109
|
-
* Clear all cached
|
|
47
|
+
* Clear all cached queries and tear down their subscriptions.
|
|
110
48
|
*/
|
|
111
|
-
clear(): void;
|
|
49
|
+
clear(eventWire?: EventWire): void;
|
|
50
|
+
private notify;
|
|
112
51
|
}
|
|
113
52
|
//# sourceMappingURL=streaming-query-cache.d.ts.map
|
package/package.json
CHANGED