@interncom/diplomatic 0.5.0 → 0.6.1

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.
@@ -39,11 +39,32 @@ export type IOp = IDeleteOp | IMutateOp;
39
39
  export declare function isDeleteOp(op: IOp): op is IDeleteOp;
40
40
  export declare function isMutateOp(op: IOp): op is IMutateOp;
41
41
  export interface IInsertParams<T> extends IMsgEntBody<T> {
42
+ /** Optional 8-byte id material for the new eid; else random. */
42
43
  id?: Uint8Array;
43
44
  }
44
- export interface IUpsertParams<T> extends IMsgEntBody<T> {
45
- eid?: EntityID;
46
- }
45
+ /**
46
+ * Revision of an ent used as the base for update/delete.
47
+ * Apps almost always already hold this (the row being edited).
48
+ */
49
+ export interface IEntRev {
50
+ eid: EntityID;
51
+ ctr: number;
52
+ /** Last-write time of this rev (eid.ts + off of the latest msg for this eid). */
53
+ updatedAt: Date;
54
+ }
55
+ export interface IUpdateParams<T> extends IMsgEntBody<T> {
56
+ prior: IEntRev;
57
+ /** Clock-skew recovery; default client-wide (usually true). */
58
+ force?: boolean;
59
+ }
60
+ /** Prefer `{ prior }`; `{ eid }` loads prior from the message archive. */
61
+ export type IDeleteParams = {
62
+ prior: IEntRev;
63
+ force?: boolean;
64
+ } | {
65
+ eid: EntityID;
66
+ force?: boolean;
67
+ };
47
68
  export interface IStorage {
48
69
  addUser: (pubKey: PublicKey) => Promise<ValStat<void>>;
49
70
  hasUser: (pubKey: PublicKey) => Promise<ValStat<boolean>>;
@@ -172,6 +193,12 @@ export interface IStateManager {
172
193
  * updated shared application state in IndexedDB).
173
194
  */
174
195
  notify: (types: Iterable<string>) => void;
196
+ /**
197
+ * Peer wrote durable application state for these eids (e.g. sync worker).
198
+ * With CachedEntDB: pull those eids from durable; type-level UI notifies
199
+ * fire only if mem changed.
200
+ */
201
+ refresh: (eids: Iterable<EntityID>) => Promise<void>;
175
202
  on: (type: string, listener: () => void) => void;
176
203
  off: (type: string, listener: () => void) => void;
177
204
  }
@@ -1,23 +1,42 @@
1
1
  import { Status } from "./shared/consts";
2
- import { IMessage, IMsgEntBody, IOp, IStateManager } from "./shared/types";
2
+ import { EntityID, IMessage, IMsgEntBody, IOp, IStateManager } from "./shared/types";
3
3
  import { ValStat } from "./shared/valstat";
4
4
  import type { Applier } from "./types";
5
5
  export declare function isMsgEntBody(bodDec: unknown): bodDec is IMsgEntBody;
6
6
  export declare function msgToOp(msg: IMessage): ValStat<IOp>;
7
+ type PeerIngestFn = (eids: Iterable<EntityID>) => Promise<void>;
7
8
  export declare class StateManager implements IStateManager {
8
9
  applier: Applier;
9
10
  private emitter;
10
11
  private clearer;
11
- private onTypes;
12
+ /** Worker / peer: eids successfully applied to durable EntDB. */
13
+ private onDirtyEids;
14
+ /**
15
+ * When set (CachedEntDB), type events are driven by the cache's subscribe
16
+ * path (immediate + durable ingest). apply() does not re-emit.
17
+ */
18
+ private cacheDriven;
19
+ private peerIngest;
12
20
  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);
21
+ /**
22
+ * Called with eids that successfully applied (e.g. worker posts dirty).
23
+ */
24
+ onDirtyEids?: (eids: EntityID[]) => void, opts?: {
25
+ cacheDriven?: boolean;
26
+ peerIngest?: PeerIngestFn;
27
+ });
15
28
  apply: (msgs: IMessage[]) => Promise<Status[]>;
16
29
  /** Clear application state and notify all type subscribers. */
17
30
  clear: () => Promise<Status>;
18
- /** Notify type subscribers without applying msgs (shared-IDB peer updates). */
31
+ /** Notify type subscribers without applying msgs. */
19
32
  notify: (types: Iterable<string>) => void;
33
+ /**
34
+ * Peer (e.g. sync worker) updated durable EntDB for these eids.
35
+ * Cache pulls those rows; apps still hear type-level events via subscribe.
36
+ */
37
+ refresh: (eids: Iterable<EntityID>) => Promise<void>;
20
38
  on: (opType: string, listener: () => void) => void;
21
39
  off: (opType: string, listener: () => void) => void;
22
40
  }
23
41
  export declare const nullStateManager: IStateManager;
42
+ export {};
@@ -2,7 +2,7 @@ import type { SyncProgressEvent } from "./progress";
2
2
  import { Status } from "./shared/consts";
3
3
  import type { Enclave } from "./shared/enclave";
4
4
  import type { EncodedMessage } from "./shared/message";
5
- import type { EntityID, Hash, HostHandle, IHostConnectionInfo, IHostMetadata, IInsertParams, IMessageHead, IOp, IUpsertParams, MasterSeed, SerializedContent } from "./shared/types";
5
+ import type { EntityID, Hash, HostHandle, IDeleteParams, IEntRev, IHostConnectionInfo, IHostMetadata, IInsertParams, IMessageHead, IOp, IUpdateParams, MasterSeed, SerializedContent } from "./shared/types";
6
6
  import { ValStat } from "./shared/valstat";
7
7
  import { ICrypto } from "./shared/types";
8
8
  export interface IMsgParts {
@@ -27,6 +27,7 @@ export interface IDiplomaticClientXferState {
27
27
  export type Applier = (ops: IOp[]) => Promise<{
28
28
  stats: Status[];
29
29
  types: Set<string>;
30
+ eids: EntityID[];
30
31
  }>;
31
32
  export interface ISeedStore {
32
33
  save: (seed: MasterSeed) => Promise<Enclave>;
@@ -191,10 +192,13 @@ export interface IClient<Handle extends HostHandle> {
191
192
  connect(): Promise<void>;
192
193
  disconnect(): Promise<void>;
193
194
  insertRaw(content: SerializedContent): Promise<ValStat<IMessageHead>>;
194
- upsertRaw(eid: EntityID, content: SerializedContent, force?: boolean): Promise<ValStat<IMessageHead>>;
195
+ updateRaw(prior: IEntRev, content: SerializedContent | undefined, force?: boolean): Promise<ValStat<IMessageHead>>;
195
196
  insert<T = unknown>(op: IInsertParams<T>): Promise<ValStat<IMessageHead>>;
196
- upsert<T = unknown>(op: IUpsertParams<T>, force?: boolean): Promise<ValStat<IMessageHead>>;
197
- delete(eid: EntityID): Promise<ValStat<IMessageHead>>;
197
+ update<T = unknown>(op: IUpdateParams<T>): Promise<ValStat<IMessageHead>>;
198
+ /**
199
+ * Delete by `{ prior }` (preferred) or `{ eid }` (archive lookup).
200
+ */
201
+ delete(op: IDeleteParams): Promise<ValStat<IMessageHead>>;
198
202
  /** Allocate an entity id (optional 8-byte id material; else random). */
199
203
  genEID(id?: Uint8Array): Promise<ValStat<EntityID>>;
200
204
  sync(): Promise<Status>;
@@ -1,6 +1,6 @@
1
1
  import { IClock } from "../shared/clock";
2
2
  import { Status } from "../shared/consts";
3
- import type { EntityID, IHostConnectionInfo, IInsertParams, IMessageHead, IStateManager, IUpsertParams, MasterSeed, SerializedContent } from "../shared/types";
3
+ import type { EntityID, IDeleteParams, IEntRev, IHostConnectionInfo, IInsertParams, IMessageHead, IStateManager, IUpdateParams, MasterSeed, SerializedContent } from "../shared/types";
4
4
  import type { ValStat } from "../shared/valstat";
5
5
  import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IHostRow, IStateEmitter, IStore } from "../types";
6
6
  /**
@@ -13,6 +13,11 @@ import type { IClient, IDiplomaticClientState, IDiplomaticClientXferState, IHost
13
13
  * connect also probes with `ping`. Early construction (module scope) is fine even
14
14
  * if `ready` fired before `onmessage` was set — the ping still succeeds once the
15
15
  * worker has finished init (cmds are held until then on the worker side).
16
+ *
17
+ * After the ready barrier, connect hydrates `clientState` / `xferState` from the
18
+ * shared main-thread store (and recovers via getClientState/getXferState if the
19
+ * unsolicited events were dropped). Without that, the façade defaults to
20
+ * `hasSeed: false` and apps flash the unauthenticated UI until a later event.
16
21
  */
17
22
  export type WorkerClientOptions = {
18
23
  /** Already-constructed module Worker running `@interncom/diplomatic/worker`. */
@@ -60,8 +65,23 @@ export declare class WorkerClient implements IClient<URL> {
60
65
  * Handshake: wait for unsolicited `ready` **or** a successful probe `ping`.
61
66
  * The probe covers the common case where the app started the Worker early and
62
67
  * `ready` was dropped before this thread set `onmessage`.
68
+ *
69
+ * Then hydrate client/xfer state from the shared store (and RPC) so
70
+ * `clientState.get()` is correct before connect returns — unsolicited
71
+ * `clientState` events are often lost when the Worker starts before
72
+ * `onmessage` is attached.
63
73
  */
64
74
  static connect(state: IStateManager, store: IStore<URL>, opts: WorkerClientOptions): Promise<WorkerClient>;
75
+ /**
76
+ * Snapshot seed/host/queues from the shared main-thread store.
77
+ * `connected` stays false until the worker reports otherwise.
78
+ */
79
+ private hydrateStateFromStore;
80
+ /**
81
+ * Request current client/xfer state from the worker. Recovers when unsolicited
82
+ * `clientState` / `xferState` events fired before `onmessage` was set.
83
+ */
84
+ private pullRemoteState;
65
85
  /** Resolve the ready barrier (idempotent). */
66
86
  private markReady;
67
87
  /**
@@ -85,12 +105,12 @@ export declare class WorkerClient implements IClient<URL> {
85
105
  hosts(): Promise<IHostRow<URL>[]>;
86
106
  connect(listen?: boolean, sync?: boolean): Promise<void>;
87
107
  disconnect(): Promise<void>;
88
- /** Local UI write: archive + apply on main (fast UI); upload/sync via worker. */
108
+ /** Local UI write: archive + apply on main (cache notifies UI); sync via worker. */
89
109
  insertRaw(content: SerializedContent): Promise<ValStat<IMessageHead>>;
90
- upsertRaw(eid: EntityID, content: SerializedContent | undefined, force?: boolean): Promise<ValStat<IMessageHead>>;
110
+ updateRaw(prior: IEntRev, content: SerializedContent | undefined, force?: boolean): Promise<ValStat<IMessageHead>>;
91
111
  insert<T = unknown>(op: IInsertParams<T>): Promise<ValStat<IMessageHead>>;
92
- upsert<T = unknown>(op: IUpsertParams<T>, force?: boolean): Promise<ValStat<IMessageHead>>;
93
- delete(eid: EntityID): Promise<ValStat<IMessageHead>>;
112
+ update<T = unknown>(op: IUpdateParams<T>): Promise<ValStat<IMessageHead>>;
113
+ delete(op: IDeleteParams): Promise<ValStat<IMessageHead>>;
94
114
  genEID(id?: Uint8Array): Promise<ValStat<EntityID>>;
95
115
  sync(): Promise<Status>;
96
116
  wipe(): Promise<void>;
@@ -1,5 +1,4 @@
1
1
  import type { Status } from "../shared/consts";
2
- import type { IMessageHead } from "../shared/types";
3
2
  import type { SyncProgressEvent } from "../progress";
4
3
  import type { IDiplomaticClientState, IDiplomaticClientXferState } from "../types";
5
4
  /** Host identity as plain data (URL → string for structured clone). */
@@ -38,40 +37,6 @@ export type WorkerCmd = {
38
37
  } | {
39
38
  id: number;
40
39
  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
40
  } | {
76
41
  id: number;
77
42
  op: "import";
@@ -108,17 +73,19 @@ export type WorkerEvent = {
108
73
  kind: "xferState";
109
74
  state: IDiplomaticClientXferState;
110
75
  }
111
- /** Application state (e.g. EntDB) changed for these op types; re-read IDB. */
76
+ /**
77
+ * Application state (e.g. EntDB) changed for these eids.
78
+ * Main cache pulls only those rows from shared IDB, then notifies by type.
79
+ */
112
80
  | {
113
81
  kind: "dirty";
114
- types: string[];
82
+ eids: Uint8Array[];
115
83
  } | {
116
84
  kind: "wiped";
117
85
  } | WorkerReply;
118
86
  export declare function isWorkerEvent(data: unknown): data is WorkerEvent;
119
87
  export declare function isWorkerCmd(data: unknown): data is WorkerCmd;
120
88
  /** Narrow helpers for typed replies (avoid casts at call sites). */
121
- export declare function headFromUnknown(v: unknown): IMessageHead | undefined;
122
89
  export declare function statusFromUnknown(v: unknown): Status | undefined;
123
90
  export declare function clientStateFromUnknown(v: unknown): IDiplomaticClientState | undefined;
124
91
  export declare function progressFromUnknown(v: unknown): SyncProgressEvent | undefined;