@interncom/diplomatic 0.2.7 → 0.3.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/dist/cli/index.mjs +1 -1
- package/dist/cli/shared/singleton.d.ts +1 -0
- package/dist/cli/shared/types.d.ts +6 -1
- package/dist/web/client.d.ts +62 -3
- package/dist/web/coalesce.d.ts +28 -0
- package/dist/web/index.d.ts +9 -3
- package/dist/web/index.mjs +1 -1
- package/dist/web/openClient.d.ts +141 -0
- package/dist/web/progress.d.ts +24 -0
- package/dist/web/react/useClient.d.ts +43 -9
- package/dist/web/saveBlob.d.ts +2 -0
- package/dist/web/shared/singleton.d.ts +1 -0
- package/dist/web/shared/types.d.ts +6 -1
- package/dist/web/state.d.ts +7 -2
- package/dist/web/stores/idb/msgs.d.ts +4 -5
- package/dist/web/stores/idb/store.d.ts +4 -0
- package/dist/web/stores/memory/msgs.d.ts +4 -5
- package/dist/web/sync.d.ts +11 -6
- package/dist/web/types.d.ts +42 -7
- package/dist/web/worker/client.d.ts +77 -0
- package/dist/web/worker/entry.d.ts +1 -0
- package/dist/web/worker/protocol.d.ts +125 -0
- package/dist/web/worker/runtime.d.ts +21 -0
- package/dist/web/worker.mjs +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { IClock } from "./shared/clock";
|
|
2
|
+
import type { IStateManager } from "./shared/types";
|
|
3
|
+
import type { IClient, IStore } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* How to construct the Worker you pass to {@link openDiplomaticClient} /
|
|
6
|
+
* {@link WorkerClient.connect} / `useClient`.
|
|
7
|
+
*
|
|
8
|
+
* The library accepts only a live `Worker` instance. Instantiation is bundler-
|
|
9
|
+
* and hosting-specific — the app owns that step so DIPLOMATIC never guesses a
|
|
10
|
+
* script URL that would 404 under a different tool.
|
|
11
|
+
*
|
|
12
|
+
* The worker entry is the package export `@interncom/diplomatic/worker`
|
|
13
|
+
* (built as `worker.mjs` in the published package). It must run as a
|
|
14
|
+
* **module** worker (`type: "module"`).
|
|
15
|
+
*
|
|
16
|
+
* ## Vite (recommended for SPA templates)
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
|
|
20
|
+
* const worker = new DiplomaticWorker();
|
|
21
|
+
* await openDiplomaticClient({ state, worker });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* `?worker` makes Vite emit a real worker asset and a constructor. Create the
|
|
25
|
+
* instance once (module scope or `useMemo`/`useRef`) — not on every render.
|
|
26
|
+
*
|
|
27
|
+
* ## webpack / Rollup / esbuild / Parcel (`new URL` + import.meta.url)
|
|
28
|
+
*
|
|
29
|
+
* Point at the package worker file so the bundler copies it next to the app:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const worker = new Worker(
|
|
33
|
+
* new URL("@interncom/diplomatic/worker", import.meta.url),
|
|
34
|
+
* { type: "module" },
|
|
35
|
+
* );
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* If the package path does not resolve, import the built file explicitly:
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const worker = new Worker(
|
|
42
|
+
* new URL(
|
|
43
|
+
* "../node_modules/@interncom/diplomatic/dist/worker.mjs",
|
|
44
|
+
* import.meta.url,
|
|
45
|
+
* ),
|
|
46
|
+
* { type: "module" },
|
|
47
|
+
* );
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* (Exact relative path depends on your app layout; prefer the package export
|
|
51
|
+
* when your bundler supports it.)
|
|
52
|
+
*
|
|
53
|
+
* ## Plain HTML / CDN / static hosting (no bundler)
|
|
54
|
+
*
|
|
55
|
+
* Host `worker.mjs` (from the package `dist/`) as a same-origin static asset,
|
|
56
|
+
* then:
|
|
57
|
+
*
|
|
58
|
+
* ```html
|
|
59
|
+
* <script type="module">
|
|
60
|
+
* import { openDiplomaticClient } from "https://cdn.example/diplomatic.js";
|
|
61
|
+
* const worker = new Worker("/path/to/worker.mjs", { type: "module" });
|
|
62
|
+
* const { client } = await openDiplomaticClient({ state, worker });
|
|
63
|
+
* </script>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* The worker script must be **same-origin** (or CORS-enabled for classic
|
|
67
|
+
* workers; module workers generally need same-origin). Do not invent a path
|
|
68
|
+
* into `node_modules` from the browser — copy or serve the built file.
|
|
69
|
+
*
|
|
70
|
+
* ## React (`useClient`)
|
|
71
|
+
*
|
|
72
|
+
* Same rules: build the Worker once, pass the instance:
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
|
|
76
|
+
* const syncWorker = new DiplomaticWorker(); // module scope
|
|
77
|
+
* useClient({ seed, host, worker: syncWorker });
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* ## Lifecycle
|
|
81
|
+
*
|
|
82
|
+
* Workers die with the page (tab close / full navigation). Mid-session death
|
|
83
|
+
* is rare; if you recreate a Worker yourself, open a new client against it.
|
|
84
|
+
* `dispose` / `WorkerClient.terminate` stops the worker DIPLOMATIC is using.
|
|
85
|
+
*/
|
|
86
|
+
type OpenDiplomaticClientBase = {
|
|
87
|
+
state: IStateManager;
|
|
88
|
+
clock?: IClock;
|
|
89
|
+
/** Max wait for worker ready when using a worker (default 15s). */
|
|
90
|
+
readyTimeoutMs?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Debounce local write → worker sync (default `defaultSyncDebounceMs`).
|
|
93
|
+
* Use `0` in tests for an immediate upload-queue handoff.
|
|
94
|
+
*/
|
|
95
|
+
syncDebounceMs?: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Worker path: protocol sync off the main thread. Main and worker both use
|
|
99
|
+
* IndexedDB (shared durable state). A custom `store` is a type error and a
|
|
100
|
+
* runtime error — the worker always opens IDB, so a MemoryStore (etc.) on
|
|
101
|
+
* main would silently diverge.
|
|
102
|
+
*/
|
|
103
|
+
export type OpenDiplomaticClientWorkerOptions = OpenDiplomaticClientBase & {
|
|
104
|
+
/**
|
|
105
|
+
* App-constructed sync Worker (see module docs above for how to create it).
|
|
106
|
+
* Misconfiguration or ready timeout throws — no silent main-thread fallback.
|
|
107
|
+
*/
|
|
108
|
+
worker: Worker;
|
|
109
|
+
store?: never;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Main-thread path: SyncClient on the page. Optional custom store; default is
|
|
113
|
+
* IndexedDB. No automatic memory fallback when IndexedDB is missing.
|
|
114
|
+
*/
|
|
115
|
+
export type OpenDiplomaticClientMainOptions = OpenDiplomaticClientBase & {
|
|
116
|
+
worker?: undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Protocol message store. Default: IndexedDB (`openIDBStore`).
|
|
119
|
+
* Pass explicitly for non-IDB backends (e.g. `new MemoryStore(crypto)`).
|
|
120
|
+
* Incompatible with `worker` (see {@link OpenDiplomaticClientWorkerOptions}).
|
|
121
|
+
*/
|
|
122
|
+
store?: IStore<URL>;
|
|
123
|
+
};
|
|
124
|
+
export type OpenDiplomaticClientOptions = OpenDiplomaticClientWorkerOptions | OpenDiplomaticClientMainOptions;
|
|
125
|
+
export type OpenedDiplomaticClient = {
|
|
126
|
+
client: IClient<URL>;
|
|
127
|
+
/** Where protocol sync is running. */
|
|
128
|
+
mode: "worker" | "main";
|
|
129
|
+
/** Tear down worker or disconnect main client. */
|
|
130
|
+
dispose: () => void;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Open a browser client.
|
|
134
|
+
*
|
|
135
|
+
* - **Worker path:** pass `worker` (already constructed). Always IndexedDB.
|
|
136
|
+
* - **Main path:** omit `worker` → SyncClient on the page; optional `store`.
|
|
137
|
+
*
|
|
138
|
+
* See {@link OpenDiplomaticClientOptions} for Worker instantiation recipes.
|
|
139
|
+
*/
|
|
140
|
+
export declare function openDiplomaticClient(opts: OpenDiplomaticClientOptions): Promise<OpenedDiplomaticClient>;
|
|
141
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Sync / import progress phase ticks.
|
|
2
|
+
*
|
|
3
|
+
* Exposed only via `IDiplomaticClientXferState.progress` on `client.xferState`
|
|
4
|
+
* (get + listen / `useClientXferState`). There is no separate progress channel. */
|
|
5
|
+
export type SyncPhase = "peek" | "push" | "pull" | "apply" | "import" | "idle";
|
|
6
|
+
export interface SyncProgressEvent {
|
|
7
|
+
phase: SyncPhase;
|
|
8
|
+
/** Host label when the work is host-scoped. */
|
|
9
|
+
host?: string;
|
|
10
|
+
/** Items completed in this phase so far (when known). */
|
|
11
|
+
done?: number;
|
|
12
|
+
/** Total items for this phase (when known up front). */
|
|
13
|
+
total?: number;
|
|
14
|
+
/** Bytes completed (optional; push/pull soft budgets). */
|
|
15
|
+
bytesDone?: number;
|
|
16
|
+
/** Bytes total for the phase snapshot (optional). */
|
|
17
|
+
bytesTotal?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Resting progress when no sync/import is in flight. */
|
|
20
|
+
export declare const idleProgress: SyncProgressEvent;
|
|
21
|
+
export type ProgressFn = (ev: SyncProgressEvent) => void;
|
|
22
|
+
export declare const defaultPeekProgressEvery = 50;
|
|
23
|
+
/** Emit when `done` is 1, every `every` heads, and on the final head. */
|
|
24
|
+
export declare function shouldEmitItemProgress(done: number, total: number, every: number): boolean;
|
|
@@ -1,17 +1,51 @@
|
|
|
1
|
-
import { SyncClient } from "../client";
|
|
2
|
-
import { HostHandle, IHostConnectionInfo, IStateManager, MasterSeed } from "../shared/types";
|
|
3
|
-
import type { IDiplomaticClientState, IDiplomaticClientXferState } from "../types";
|
|
4
1
|
import { IEntDB } from "../entdb/entdb";
|
|
5
2
|
import { IClock } from "../shared/clock";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function
|
|
3
|
+
import { IHostConnectionInfo, IStateManager, MasterSeed } from "../shared/types";
|
|
4
|
+
import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IStore } from "../types";
|
|
5
|
+
export declare function useClientState(client: Pick<IClient<URL>, "clientState">): IDiplomaticClientState | undefined;
|
|
6
|
+
export declare function useClientXferState(client: Pick<IClient<URL>, "xferState">): IDiplomaticClientXferState | undefined;
|
|
7
|
+
export declare function useSyncOnResume(client: Pick<IClient<URL>, "connect" | "sync">): void;
|
|
8
|
+
type UseClientBase = {
|
|
10
9
|
clock?: IClock;
|
|
11
10
|
seed?: MasterSeed;
|
|
12
11
|
host?: IHostConnectionInfo<URL>;
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
readyTimeoutMs?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Worker path: always IndexedDB on main + worker. Custom `store` is forbidden
|
|
16
|
+
* (type + runtime) so durable state cannot diverge.
|
|
17
|
+
*
|
|
18
|
+
* Vite:
|
|
19
|
+
* import DiplomaticWorker from "@interncom/diplomatic/worker?worker";
|
|
20
|
+
* const syncWorker = new DiplomaticWorker();
|
|
21
|
+
* useClient({ worker: syncWorker, seed, host });
|
|
22
|
+
*/
|
|
23
|
+
export type UseClientWorkerOptions = UseClientBase & {
|
|
24
|
+
/**
|
|
25
|
+
* App-constructed sync Worker. Create once (module scope or useMemo/useRef),
|
|
26
|
+
* not each render. See `openDiplomaticClient` for instantiation recipes.
|
|
27
|
+
*/
|
|
28
|
+
worker: Worker;
|
|
29
|
+
store?: never;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Main-thread path. Optional custom store; default IndexedDB.
|
|
33
|
+
*/
|
|
34
|
+
export type UseClientMainOptions = UseClientBase & {
|
|
35
|
+
worker?: undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Protocol store override. Default: IndexedDB. Pass explicitly for
|
|
38
|
+
* MemoryStore or other backends. Incompatible with `worker`.
|
|
39
|
+
*/
|
|
40
|
+
store?: IStore<URL>;
|
|
41
|
+
};
|
|
42
|
+
export type UseClientOptions = UseClientWorkerOptions | UseClientMainOptions;
|
|
43
|
+
export declare function useClient(opts?: UseClientOptions): {
|
|
44
|
+
client?: IClient<URL>;
|
|
15
45
|
entDB?: IEntDB;
|
|
16
46
|
stateMgr: IStateManager;
|
|
47
|
+
mode?: "worker" | "main";
|
|
48
|
+
/** Set when open/init fails (e.g. misconfigured worker). */
|
|
49
|
+
error?: Error;
|
|
17
50
|
};
|
|
51
|
+
export {};
|
|
@@ -11,6 +11,7 @@ export declare class SingletonStateManager implements IStateManager {
|
|
|
11
11
|
constructor(singletonType: string);
|
|
12
12
|
apply(messages: IMessage[]): Promise<Status[]>;
|
|
13
13
|
clear: () => Promise<Status>;
|
|
14
|
+
notify: (types: Iterable<string>) => void;
|
|
14
15
|
on: (event: string, listener: () => void) => void;
|
|
15
16
|
off: (event: string, listener: () => void) => void;
|
|
16
17
|
}
|
|
@@ -165,8 +165,13 @@ export interface ITransport {
|
|
|
165
165
|
}
|
|
166
166
|
export interface IStateManager {
|
|
167
167
|
apply: (msgs: IMessage[]) => Promise<Status[]>;
|
|
168
|
-
/** Drop local application state
|
|
168
|
+
/** Drop local application state and notify subscribers. */
|
|
169
169
|
clear: () => Promise<Status>;
|
|
170
|
+
/**
|
|
171
|
+
* Notify type subscribers without applying msgs (e.g. after a peer thread
|
|
172
|
+
* updated shared application state in IndexedDB).
|
|
173
|
+
*/
|
|
174
|
+
notify: (types: Iterable<string>) => void;
|
|
170
175
|
on: (type: string, listener: () => void) => void;
|
|
171
176
|
off: (type: string, listener: () => void) => void;
|
|
172
177
|
}
|
package/dist/web/state.d.ts
CHANGED
|
@@ -8,10 +8,15 @@ export declare class StateManager implements IStateManager {
|
|
|
8
8
|
applier: Applier;
|
|
9
9
|
private emitter;
|
|
10
10
|
private clearer;
|
|
11
|
-
|
|
11
|
+
private onTypes;
|
|
12
|
+
constructor(applier: Applier, clearer: () => Promise<Status>,
|
|
13
|
+
/** Optional hook after a successful apply batch (e.g. worker dirty signal). */
|
|
14
|
+
onTypes?: (types: Set<string>) => void);
|
|
12
15
|
apply: (msgs: IMessage[]) => Promise<Status[]>;
|
|
13
|
-
/** Clear
|
|
16
|
+
/** Clear application state and notify all type subscribers. */
|
|
14
17
|
clear: () => Promise<Status>;
|
|
18
|
+
/** Notify type subscribers without applying msgs (shared-IDB peer updates). */
|
|
19
|
+
notify: (types: Iterable<string>) => void;
|
|
15
20
|
on: (opType: string, listener: () => void) => void;
|
|
16
21
|
off: (opType: string, listener: () => void) => void;
|
|
17
22
|
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { Status } from "../../shared/consts";
|
|
2
2
|
import { ICrypto } from "../../shared/types";
|
|
3
3
|
import { EntityID, Hash } from "../../shared/types";
|
|
4
|
-
import { IMessageStore,
|
|
4
|
+
import { IMessageStore, IStorableMessage, IStoredMessage } from "../../types";
|
|
5
5
|
export declare class IDBMessageStore implements IMessageStore {
|
|
6
6
|
private crypto;
|
|
7
7
|
db: IDBDatabase;
|
|
8
8
|
constructor(db: IDBDatabase, crypto: ICrypto);
|
|
9
|
-
add(messages:
|
|
10
|
-
key: Hash;
|
|
11
|
-
data: IStoredMessageData;
|
|
12
|
-
}[]): Promise<Status[]>;
|
|
9
|
+
add(messages: IStorableMessage[]): Promise<Status[]>;
|
|
13
10
|
del(keys: Iterable<Hash>): Promise<void>;
|
|
14
11
|
get(key: Hash): Promise<IStoredMessage | undefined>;
|
|
15
12
|
has(key: Hash): Promise<boolean>;
|
|
16
13
|
list(): Promise<Iterable<IStoredMessage>>;
|
|
17
14
|
last(eid: EntityID): Promise<IStoredMessage | undefined>;
|
|
15
|
+
listUnapplied(): Promise<IStoredMessage[]>;
|
|
16
|
+
markApplied(keys: Iterable<Hash>): Promise<void>;
|
|
18
17
|
wipe(): Promise<void>;
|
|
19
18
|
}
|
|
@@ -10,6 +10,10 @@ export declare const HOSTS_TABLE = "hosts";
|
|
|
10
10
|
export declare const UPLOAD_QUEUE_TABLE = "uploadQueue";
|
|
11
11
|
export declare const DOWNLOAD_QUEUE_TABLE = "downloadQueue";
|
|
12
12
|
export declare const MESSAGES_TABLE = "messages";
|
|
13
|
+
/** Index on messages.apld — pending apply is apld === false. */
|
|
14
|
+
export declare const MESSAGES_APLD_INDEX = "apld";
|
|
15
|
+
/** Schema version: v3 adds messages.apld index for the apply queue. */
|
|
16
|
+
export declare const DIPLOMATIC_STORE_DB_VERSION = 3;
|
|
13
17
|
export declare class IDBStore implements IStore<URL> {
|
|
14
18
|
seed: IDBSeedStore;
|
|
15
19
|
hosts: IDBHostStore;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { ICrypto } from "../../shared/types";
|
|
2
2
|
import { EntityID, Hash } from "../../shared/types";
|
|
3
|
-
import { IMessageStore, IStoredMessage, IStoredMessageData } from "../../types";
|
|
3
|
+
import { IMessageStore, IStorableMessage, IStoredMessage, IStoredMessageData } from "../../types";
|
|
4
4
|
import { Status } from "../../shared/consts";
|
|
5
5
|
export declare class MemoryMessageStore implements IMessageStore {
|
|
6
6
|
private crypto;
|
|
7
7
|
messages: Map<string, IStoredMessageData>;
|
|
8
8
|
constructor(crypto: ICrypto);
|
|
9
|
-
add(messages:
|
|
10
|
-
key: Hash;
|
|
11
|
-
data: IStoredMessageData;
|
|
12
|
-
}[]): Promise<Status[]>;
|
|
9
|
+
add(messages: IStorableMessage[]): Promise<Status[]>;
|
|
13
10
|
del(keys: Iterable<Hash>): Promise<void>;
|
|
14
11
|
get(key: Hash): Promise<IStoredMessage | undefined>;
|
|
15
12
|
has(key: Hash): Promise<boolean>;
|
|
16
13
|
list(): Promise<Iterable<IStoredMessage>>;
|
|
17
14
|
last(eid: EntityID): Promise<IStoredMessage | undefined>;
|
|
15
|
+
listUnapplied(): Promise<IStoredMessage[]>;
|
|
16
|
+
markApplied(keys: Iterable<Hash>): Promise<void>;
|
|
18
17
|
wipe(): Promise<void>;
|
|
19
18
|
}
|
package/dist/web/sync.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { IClock } from "./shared/clock";
|
|
|
3
3
|
import { Status } from "./shared/consts";
|
|
4
4
|
import { Enclave } from "./shared/enclave";
|
|
5
5
|
import { Hash, HostHandle, IBag, ICrypto } from "./shared/types";
|
|
6
|
-
import {
|
|
6
|
+
import { ProgressFn } from "./progress";
|
|
7
|
+
import { IDownloadMessage, IHostRow, IMsgParts, IStore, type IStoredMessageWrite } from "./types";
|
|
7
8
|
/** Default soft cap for one push/pull request (~1 MiB). Apps with large
|
|
8
9
|
* payloads (e.g. media) should raise maxPushBytes / maxPullBytes. */
|
|
9
10
|
export declare const defaultMaxPushBytes: number;
|
|
@@ -19,6 +20,10 @@ export interface ISyncParams<Handle extends HostHandle> {
|
|
|
19
20
|
maxPushBytes?: number;
|
|
20
21
|
/** Soft max body bytes (head.len) per pull request. Oversized items go alone. */
|
|
21
22
|
maxPullBytes?: number;
|
|
23
|
+
/** Optional progress sink (phases + item/batch ticks). */
|
|
24
|
+
onProgress?: ProgressFn;
|
|
25
|
+
/** Peek progress stride in heads (default `defaultPeekProgressEvery`). */
|
|
26
|
+
peekProgressEvery?: number;
|
|
22
27
|
}
|
|
23
28
|
/** Push one batch of sealed bags; deq successes; advance lastSeq from store. */
|
|
24
29
|
export declare function pushBatch<Handle extends HostHandle>(conn: Pick<DiplomaticClientAPI<Handle>, "push">, store: IStore<Handle>, hostLabel: string, bags: IBag[], hashes: Hash[]): Promise<Status>;
|
|
@@ -27,14 +32,14 @@ export declare function pullBatch<Handle extends HostHandle>(conn: Pick<Diplomat
|
|
|
27
32
|
enqueueUpload: boolean;
|
|
28
33
|
triggerUpload: boolean;
|
|
29
34
|
}) => Promise<Status[]>): Promise<Status>;
|
|
30
|
-
export declare function syncPeek<Handle extends HostHandle>({ conn, store, enclave, host, crypto }: ISyncParams<Handle>): Promise<Status>;
|
|
31
|
-
export declare function syncPush<Handle extends HostHandle>({ conn, store, host, maxPushBytes }: ISyncParams<Handle>): Promise<Status>;
|
|
32
|
-
export declare function syncPull<Handle extends HostHandle>({ conn, store, enclave, host, crypto, maxPullBytes }: ISyncParams<Handle>, apply: (parts: IMsgParts[], options?: {
|
|
35
|
+
export declare function syncPeek<Handle extends HostHandle>({ conn, store, enclave, host, crypto, onProgress, peekProgressEvery, }: ISyncParams<Handle>): Promise<Status>;
|
|
36
|
+
export declare function syncPush<Handle extends HostHandle>({ conn, store, host, maxPushBytes, onProgress }: ISyncParams<Handle>): Promise<Status>;
|
|
37
|
+
export declare function syncPull<Handle extends HostHandle>({ conn, store, enclave, host, crypto, maxPullBytes, onProgress, }: ISyncParams<Handle>, apply: (parts: IMsgParts[], options?: {
|
|
33
38
|
enqueueUpload: boolean;
|
|
34
39
|
triggerUpload: boolean;
|
|
35
40
|
}) => Promise<Status[]>): Promise<Status>;
|
|
36
|
-
export declare function msg2StoredMsgData({ head, body }: IMsgParts):
|
|
37
|
-
export declare function handleNotif<Handle extends HostHandle>(bytes: Uint8Array, { conn, store, enclave, host, crypto, clock, maxPullBytes, maxPushBytes, }: ISyncParams<Handle>, apply: (parts: IMsgParts[], options?: {
|
|
41
|
+
export declare function msg2StoredMsgData({ head, body }: IMsgParts): IStoredMessageWrite;
|
|
42
|
+
export declare function handleNotif<Handle extends HostHandle>(bytes: Uint8Array, { conn, store, enclave, host, crypto, clock, maxPullBytes, maxPushBytes, onProgress, peekProgressEvery, }: ISyncParams<Handle>, apply: (parts: IMsgParts[], options?: {
|
|
38
43
|
enqueueUpload: boolean;
|
|
39
44
|
triggerUpload: boolean;
|
|
40
45
|
}) => Promise<Status[]>, scheduleSync: () => void): Promise<void>;
|
package/dist/web/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SyncProgressEvent } from "./progress";
|
|
1
2
|
import { Status } from "./shared/consts";
|
|
2
3
|
import type { Enclave } from "./shared/enclave";
|
|
3
4
|
import type { EncodedMessage } from "./shared/message";
|
|
@@ -13,9 +14,15 @@ export interface IDiplomaticClientState {
|
|
|
13
14
|
hasHost: boolean;
|
|
14
15
|
connected: boolean;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Transfer / sync activity: queue depths + current phase progress.
|
|
19
|
+
* Subscribe via `xferState.listen` / `useClientXferState`; snapshot with `get()`.
|
|
20
|
+
*/
|
|
16
21
|
export interface IDiplomaticClientXferState {
|
|
17
22
|
numUploads: number;
|
|
18
23
|
numDownloads: number;
|
|
24
|
+
/** Latest sync/import phase tick; `{ phase: "idle" }` when not in flight. */
|
|
25
|
+
progress: SyncProgressEvent;
|
|
19
26
|
}
|
|
20
27
|
export type Applier = (ops: IOp[]) => Promise<{
|
|
21
28
|
stats: Status[];
|
|
@@ -58,21 +65,45 @@ export interface IDownloadQueue {
|
|
|
58
65
|
count: () => Promise<number>;
|
|
59
66
|
wipe(): Promise<void>;
|
|
60
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Archive fields shared by read and write.
|
|
70
|
+
* `apld`: true once the msg has been applied by the application state manager;
|
|
71
|
+
* false while still pending apply.
|
|
72
|
+
*/
|
|
73
|
+
export interface IStoredMessageFields {
|
|
74
|
+
eid: EntityID;
|
|
75
|
+
off?: number;
|
|
76
|
+
ctr?: number;
|
|
77
|
+
body?: EncodedMessage;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What may come back from IDB (pre-apld rows can omit the field).
|
|
81
|
+
* Prefer {@link normalizeStoredMessageData} before use.
|
|
82
|
+
*/
|
|
83
|
+
export interface IStoredMessageData extends IStoredMessageFields {
|
|
84
|
+
apld?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Required shape for every put into the message archive.
|
|
88
|
+
* Callers must set `apld` (false until applied, then true).
|
|
89
|
+
*/
|
|
90
|
+
export type IStoredMessageWrite = IStoredMessageFields & {
|
|
91
|
+
apld: boolean;
|
|
92
|
+
};
|
|
61
93
|
export interface IStorableMessage {
|
|
62
94
|
key: Hash;
|
|
63
|
-
data:
|
|
95
|
+
data: IStoredMessageWrite;
|
|
64
96
|
}
|
|
65
97
|
export interface IStoredMessage {
|
|
66
98
|
hash: Hash;
|
|
67
99
|
head: IMessageHead;
|
|
68
100
|
body?: EncodedMessage;
|
|
101
|
+
applied: boolean;
|
|
69
102
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
body?: EncodedMessage;
|
|
75
|
-
}
|
|
103
|
+
/** Coerce legacy rows missing `apld` to pending (`false`). */
|
|
104
|
+
export declare function normalizeStoredMessageData(data: IStoredMessageData): IStoredMessageWrite;
|
|
105
|
+
/** Pending apply when not yet marked applied. */
|
|
106
|
+
export declare function isPendingApply(data: IStoredMessageData): boolean;
|
|
76
107
|
export declare function toStoredMessage(hash: Hash, data: IStoredMessageData, crypto: ICrypto): Promise<IStoredMessage>;
|
|
77
108
|
export interface IMessageStore {
|
|
78
109
|
add: (messages: IStorableMessage[]) => Promise<Status[]>;
|
|
@@ -81,6 +112,10 @@ export interface IMessageStore {
|
|
|
81
112
|
del: (keys: Iterable<Hash>) => Promise<void>;
|
|
82
113
|
list: () => Promise<Iterable<IStoredMessage>>;
|
|
83
114
|
last: (eid: EntityID) => Promise<IStoredMessage | undefined>;
|
|
115
|
+
/** Messages stored but not yet applied (apld === false). */
|
|
116
|
+
listUnapplied: () => Promise<IStoredMessage[]>;
|
|
117
|
+
/** Mark archive rows as applied (apld = true). */
|
|
118
|
+
markApplied: (keys: Iterable<Hash>) => Promise<void>;
|
|
84
119
|
wipe(): Promise<void>;
|
|
85
120
|
}
|
|
86
121
|
export interface IStore<Handle extends HostHandle> {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { IClock } from "../shared/clock";
|
|
2
|
+
import { Status } from "../shared/consts";
|
|
3
|
+
import type { EntityID, IHostConnectionInfo, IInsertParams, IMessageHead, IStateManager, IUpsertParams, MasterSeed, SerializedContent } from "../shared/types";
|
|
4
|
+
import type { ValStat } from "../shared/valstat";
|
|
5
|
+
import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IStateEmitter, IStore } from "../types";
|
|
6
|
+
/**
|
|
7
|
+
* Options for attaching to an app-owned sync Worker.
|
|
8
|
+
*
|
|
9
|
+
* Provide a live `Worker` — the library never constructs one. See
|
|
10
|
+
* `openDiplomaticClient` for bundler/CDN instantiation recipes.
|
|
11
|
+
*/
|
|
12
|
+
export type WorkerClientOptions = {
|
|
13
|
+
/** Already-constructed module Worker running `@interncom/diplomatic/worker`. */
|
|
14
|
+
worker: Worker;
|
|
15
|
+
/** Max wait for worker `ready` (default 15s). Failures throw; no fallback. */
|
|
16
|
+
readyTimeoutMs?: number;
|
|
17
|
+
clock?: IClock;
|
|
18
|
+
/**
|
|
19
|
+
* Debounce local write → worker `sync` (default {@link defaultSyncDebounceMs}).
|
|
20
|
+
* Use `0` in tests for immediate handoff (no timers).
|
|
21
|
+
*/
|
|
22
|
+
syncDebounceMs?: number;
|
|
23
|
+
};
|
|
24
|
+
export declare class WorkerClient implements IClient<URL> {
|
|
25
|
+
private worker;
|
|
26
|
+
private state;
|
|
27
|
+
/** Main-thread writer: msg archive + local apply for fast UI. */
|
|
28
|
+
private local;
|
|
29
|
+
private nextId;
|
|
30
|
+
private pending;
|
|
31
|
+
private ready;
|
|
32
|
+
private resolveReady;
|
|
33
|
+
private rejectReady;
|
|
34
|
+
/** Debounced local write → worker upload/sync. */
|
|
35
|
+
private scheduledSync;
|
|
36
|
+
private cachedClientState;
|
|
37
|
+
private cachedXferState;
|
|
38
|
+
clientState: IStateEmitter<IDiplomaticClientState>;
|
|
39
|
+
xferState: IStateEmitter<IDiplomaticClientXferState>;
|
|
40
|
+
private constructor();
|
|
41
|
+
private failReady;
|
|
42
|
+
/**
|
|
43
|
+
* Run any pending debounced worker sync now and await it.
|
|
44
|
+
* Useful in tests (with real debounce) or after a burst of local writes.
|
|
45
|
+
*/
|
|
46
|
+
flushScheduledSync(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Attach to an app-provided Worker. `store` is the shared protocol IDB (main
|
|
49
|
+
* connection) used for local msg writes; worker opens its own connection.
|
|
50
|
+
* Throws if the worker never becomes ready — does not fall back to main thread.
|
|
51
|
+
*/
|
|
52
|
+
static connect(state: IStateManager, store: IStore<URL>, opts: WorkerClientOptions): Promise<WorkerClient>;
|
|
53
|
+
/** Lightweight RPC check after connect. */
|
|
54
|
+
ping(): Promise<void>;
|
|
55
|
+
/** Terminate the worker (drops protocol DB connection in that thread). */
|
|
56
|
+
terminate(): void;
|
|
57
|
+
private onMessage;
|
|
58
|
+
private allocId;
|
|
59
|
+
private request;
|
|
60
|
+
private requestStatus;
|
|
61
|
+
private serializeHost;
|
|
62
|
+
setSeed(seed: MasterSeed): Promise<void>;
|
|
63
|
+
link(host: IHostConnectionInfo<URL>, connect?: boolean): Promise<void>;
|
|
64
|
+
unlink(label: string): Promise<void>;
|
|
65
|
+
connect(listen?: boolean, sync?: boolean): Promise<void>;
|
|
66
|
+
disconnect(): Promise<void>;
|
|
67
|
+
/** Local UI write: archive + apply on main (fast UI); upload/sync via worker. */
|
|
68
|
+
insertRaw(content: SerializedContent): Promise<ValStat<IMessageHead>>;
|
|
69
|
+
upsertRaw(eid: EntityID, content: SerializedContent | undefined, force?: boolean): Promise<ValStat<IMessageHead>>;
|
|
70
|
+
insert<T = unknown>(op: IInsertParams<T>): Promise<ValStat<IMessageHead>>;
|
|
71
|
+
upsert<T = unknown>(op: IUpsertParams<T>, force?: boolean): Promise<ValStat<IMessageHead>>;
|
|
72
|
+
delete(eid: EntityID): Promise<ValStat<IMessageHead>>;
|
|
73
|
+
sync(): Promise<Status>;
|
|
74
|
+
wipe(): Promise<void>;
|
|
75
|
+
import(file: File): Promise<Status>;
|
|
76
|
+
export(filename: string, _extension?: string): Promise<Status>;
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Status } from "../shared/consts";
|
|
2
|
+
import type { IMessageHead } from "../shared/types";
|
|
3
|
+
import type { SyncProgressEvent } from "../progress";
|
|
4
|
+
import type { IDiplomaticClientState, IDiplomaticClientXferState } from "../types";
|
|
5
|
+
/** Host identity as plain data (URL → string for structured clone). */
|
|
6
|
+
export interface SerializedHost {
|
|
7
|
+
handle: string;
|
|
8
|
+
label: string;
|
|
9
|
+
idx: number;
|
|
10
|
+
}
|
|
11
|
+
export type WorkerCmd = {
|
|
12
|
+
id: number;
|
|
13
|
+
op: "ping";
|
|
14
|
+
} | {
|
|
15
|
+
id: number;
|
|
16
|
+
op: "setSeed";
|
|
17
|
+
seed: Uint8Array;
|
|
18
|
+
} | {
|
|
19
|
+
id: number;
|
|
20
|
+
op: "link";
|
|
21
|
+
host: SerializedHost;
|
|
22
|
+
connect?: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
id: number;
|
|
25
|
+
op: "unlink";
|
|
26
|
+
label: string;
|
|
27
|
+
} | {
|
|
28
|
+
id: number;
|
|
29
|
+
op: "connect";
|
|
30
|
+
listen?: boolean;
|
|
31
|
+
sync?: boolean;
|
|
32
|
+
} | {
|
|
33
|
+
id: number;
|
|
34
|
+
op: "disconnect";
|
|
35
|
+
} | {
|
|
36
|
+
id: number;
|
|
37
|
+
op: "sync";
|
|
38
|
+
} | {
|
|
39
|
+
id: number;
|
|
40
|
+
op: "wipe";
|
|
41
|
+
} | {
|
|
42
|
+
id: number;
|
|
43
|
+
op: "insertRaw";
|
|
44
|
+
body: Uint8Array;
|
|
45
|
+
} | {
|
|
46
|
+
id: number;
|
|
47
|
+
op: "upsertRaw";
|
|
48
|
+
eid: Uint8Array;
|
|
49
|
+
body?: Uint8Array;
|
|
50
|
+
force?: boolean;
|
|
51
|
+
} | {
|
|
52
|
+
id: number;
|
|
53
|
+
op: "insert";
|
|
54
|
+
params: {
|
|
55
|
+
type: string;
|
|
56
|
+
body: unknown;
|
|
57
|
+
gid?: string;
|
|
58
|
+
pid?: Uint8Array;
|
|
59
|
+
};
|
|
60
|
+
} | {
|
|
61
|
+
id: number;
|
|
62
|
+
op: "upsert";
|
|
63
|
+
params: {
|
|
64
|
+
type: string;
|
|
65
|
+
body: unknown;
|
|
66
|
+
eid?: Uint8Array;
|
|
67
|
+
gid?: string;
|
|
68
|
+
pid?: Uint8Array;
|
|
69
|
+
};
|
|
70
|
+
force?: boolean;
|
|
71
|
+
} | {
|
|
72
|
+
id: number;
|
|
73
|
+
op: "delete";
|
|
74
|
+
eid: Uint8Array;
|
|
75
|
+
} | {
|
|
76
|
+
id: number;
|
|
77
|
+
op: "import";
|
|
78
|
+
bytes: Uint8Array;
|
|
79
|
+
} | {
|
|
80
|
+
id: number;
|
|
81
|
+
op: "export";
|
|
82
|
+
} | {
|
|
83
|
+
id: number;
|
|
84
|
+
op: "getClientState";
|
|
85
|
+
} | {
|
|
86
|
+
id: number;
|
|
87
|
+
op: "getXferState";
|
|
88
|
+
};
|
|
89
|
+
export type WorkerReply = {
|
|
90
|
+
kind: "reply";
|
|
91
|
+
id: number;
|
|
92
|
+
ok: true;
|
|
93
|
+
result?: unknown;
|
|
94
|
+
} | {
|
|
95
|
+
kind: "reply";
|
|
96
|
+
id: number;
|
|
97
|
+
ok: false;
|
|
98
|
+
status: Status;
|
|
99
|
+
};
|
|
100
|
+
export type WorkerEvent = {
|
|
101
|
+
kind: "ready";
|
|
102
|
+
} | {
|
|
103
|
+
kind: "clientState";
|
|
104
|
+
state: IDiplomaticClientState;
|
|
105
|
+
}
|
|
106
|
+
/** Queues + sync phase progress (see IDiplomaticClientXferState.progress). */
|
|
107
|
+
| {
|
|
108
|
+
kind: "xferState";
|
|
109
|
+
state: IDiplomaticClientXferState;
|
|
110
|
+
}
|
|
111
|
+
/** Application state (e.g. EntDB) changed for these op types; re-read IDB. */
|
|
112
|
+
| {
|
|
113
|
+
kind: "dirty";
|
|
114
|
+
types: string[];
|
|
115
|
+
} | {
|
|
116
|
+
kind: "wiped";
|
|
117
|
+
} | WorkerReply;
|
|
118
|
+
export declare function isWorkerEvent(data: unknown): data is WorkerEvent;
|
|
119
|
+
export declare function isWorkerCmd(data: unknown): data is WorkerCmd;
|
|
120
|
+
/** Narrow helpers for typed replies (avoid casts at call sites). */
|
|
121
|
+
export declare function headFromUnknown(v: unknown): IMessageHead | undefined;
|
|
122
|
+
export declare function statusFromUnknown(v: unknown): Status | undefined;
|
|
123
|
+
export declare function clientStateFromUnknown(v: unknown): IDiplomaticClientState | undefined;
|
|
124
|
+
export declare function progressFromUnknown(v: unknown): SyncProgressEvent | undefined;
|
|
125
|
+
export declare function xferStateFromUnknown(v: unknown): IDiplomaticClientXferState | undefined;
|