@githolon/client 0.1.2 → 0.1.6
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/LICENSE.md +9 -0
- package/package.json +6 -2
- package/src/index.d.ts +255 -0
package/LICENSE.md
CHANGED
|
@@ -24,6 +24,15 @@ with it; we keep the rest for now.
|
|
|
24
24
|
- offer the Nomos runtime, or anything materially similar, as a hosted service;
|
|
25
25
|
- reverse-engineer the holon wasm runtime.
|
|
26
26
|
|
|
27
|
+
## Data retention
|
|
28
|
+
|
|
29
|
+
This is a pre-release we are actively evaluating. Workspaces you retire stop
|
|
30
|
+
counting toward your quota but are NOT deleted: **we retain all workspace data
|
|
31
|
+
(ledgers, law, intents) and may examine it to evaluate how the product is
|
|
32
|
+
used.** Don't put anything in a pre-release workspace you wouldn't want the
|
|
33
|
+
builders to read. If you need something truly expunged, ask:
|
|
34
|
+
jack@captainapp.co.uk.
|
|
35
|
+
|
|
27
36
|
## The rest
|
|
28
37
|
|
|
29
38
|
Provided **as is**, with no warranty of any kind; to the maximum extent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nomos Cloud web client — a LOCAL holon for offline-first apps: pulls the workspace ledger (git) from Nomos Cloud, replays it into the in-memory SQLite projection inside the wasm32-wasip1 GitHolon, and exposes dispatch / watch / sync. The browser runs the SAME byte-identical holon artifact the edge runs.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
"url": "git+https://github.com/Captain-App/nomos2.git",
|
|
10
10
|
"directory": "cloud/web-client"
|
|
11
11
|
},
|
|
12
|
+
"types": "./src/index.d.ts",
|
|
12
13
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./src/index.d.ts",
|
|
16
|
+
"default": "./src/index.mjs"
|
|
17
|
+
}
|
|
14
18
|
},
|
|
15
19
|
"files": [
|
|
16
20
|
"src"
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// ─── @githolon/client — hand-authored declarations for src/index.mjs ───────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Keep this file honest: every member below mirrors what `connect()` in index.mjs actually
|
|
4
|
+
// builds and returns. The runtime is plain JS; this is its reference manual — hover any
|
|
5
|
+
// member for the semantics (offline-first authoring, edge admission, dead letters).
|
|
6
|
+
|
|
7
|
+
/** Options for {@link connect}. */
|
|
8
|
+
export interface ConnectOptions {
|
|
9
|
+
/** Nomos Cloud base URL (e.g. `https://nomos.captainapp.co.uk`). */
|
|
10
|
+
cloud: string;
|
|
11
|
+
/** Workspace name — must already exist (`POST /v1/workspaces/:ws`). */
|
|
12
|
+
workspace: string;
|
|
13
|
+
/**
|
|
14
|
+
* Stable client identity: drives the untrusted session branch (`session/<clientId>`)
|
|
15
|
+
* and the HLC replica id. Defaults to a random id per connect — pass your own so the
|
|
16
|
+
* same app instance keeps the same offer lane across reconnects.
|
|
17
|
+
*/
|
|
18
|
+
clientId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Bearer token for keyed clouds — needed for `sync()`'s push when the cloud sets
|
|
21
|
+
* `NOMOS_CLOUD_KEY`. Reads and admission stay open without it.
|
|
22
|
+
*/
|
|
23
|
+
authToken?: string;
|
|
24
|
+
/**
|
|
25
|
+
* A {@link Holon.export} snapshot: hydrate the workspace directory from these bytes
|
|
26
|
+
* INSTEAD of cloning, then catch up via an automatic pull. Pending un-synced local
|
|
27
|
+
* commits survive the restore and remain syncable.
|
|
28
|
+
*/
|
|
29
|
+
restoreFrom?: Uint8Array;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One projection row: the aggregate's wire type, its id, and the folded fields.
|
|
34
|
+
* Fields are partial folds — read them as possibly-absent (`unknown` per key).
|
|
35
|
+
*/
|
|
36
|
+
export interface AggregateRow {
|
|
37
|
+
type: string;
|
|
38
|
+
id: string;
|
|
39
|
+
data: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One parked intent in the DEAD-LETTER QUEUE: refused-but-LEGITIMATE work, with full
|
|
44
|
+
* provenance. The intent bytes stay in the queue (and ride `export()`/restore) — work
|
|
45
|
+
* is never lost; the app decides (retry after a law fix, or discard explicitly).
|
|
46
|
+
*/
|
|
47
|
+
export interface DeadLetter {
|
|
48
|
+
/** The content-derived intent id (absent only if the commit carried none). */
|
|
49
|
+
id?: string;
|
|
50
|
+
/** Short (10-hex) commit oid of the refused local commit — also accepted as a retry/discard key. */
|
|
51
|
+
oid: string;
|
|
52
|
+
/** Who refused it: `"edge"` (the admission gate) or `"local"` (a replay under changed local law). */
|
|
53
|
+
source: "edge" | "local";
|
|
54
|
+
/** The refusal verdict, verbatim from the law (truncated to 300 chars). */
|
|
55
|
+
error: string;
|
|
56
|
+
/** The intent's domain, when it could be read from the intent bytes. */
|
|
57
|
+
domain: string | null;
|
|
58
|
+
/** The intent's directive id, when it could be read from the intent bytes. */
|
|
59
|
+
directiveId: string | null;
|
|
60
|
+
/** ISO timestamp of the (latest) refusal. */
|
|
61
|
+
at: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One refusal inside an admission report (`deadLettered` marks a domain rejection vs an attack-lane drop). */
|
|
65
|
+
export interface AdmissionRejection {
|
|
66
|
+
oid?: string;
|
|
67
|
+
error?: string;
|
|
68
|
+
/** True when the edge parked the work in the workspace DLQ (domain rejection) rather than dropping it. */
|
|
69
|
+
deadLettered?: boolean;
|
|
70
|
+
[k: string]: unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One judged session branch inside an admission report. */
|
|
74
|
+
export interface AdmissionSession {
|
|
75
|
+
/** Intents the edge re-admitted and merged to main. */
|
|
76
|
+
admitted?: unknown[];
|
|
77
|
+
/** Intents the edge refused (routed to the DLQ or dropped — see {@link AdmissionRejection.deadLettered}). */
|
|
78
|
+
rejected?: AdmissionRejection[];
|
|
79
|
+
[k: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The edge holon's admission verdict (`POST /v1/workspaces/:ws/admit`): each pending
|
|
84
|
+
* session branch judged — every carried intent re-admitted under the workspace's own
|
|
85
|
+
* law (verification, never trust) and merged to `main`, or refused.
|
|
86
|
+
*/
|
|
87
|
+
export interface AdmissionReport {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
sessions?: AdmissionSession[];
|
|
90
|
+
[k: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Result of {@link Holon.pull} (and the `.converged` leg of {@link Holon.sync}):
|
|
95
|
+
* incremental convergence onto canonical main. When the remote advanced, local main is
|
|
96
|
+
* reset onto it and the local-only intents the canon doesn't already carry (by
|
|
97
|
+
* content-derived id) are rebase-replayed through `apply_intent` — the same
|
|
98
|
+
* re-admission primitive the edge runs.
|
|
99
|
+
*/
|
|
100
|
+
export interface PullResult {
|
|
101
|
+
/** True when remote main already equals the local head — nothing to do. */
|
|
102
|
+
upToDate: boolean;
|
|
103
|
+
/** The canonical remote main sha we now sit on. */
|
|
104
|
+
remoteHead: string;
|
|
105
|
+
/** Local-only intents re-admitted locally on top of the adopted canon. */
|
|
106
|
+
replayed?: { oid: string; id?: string | null; head?: string }[];
|
|
107
|
+
/** Local intents the canon already carried (edge-sealed copies, same intent id) — deduped, never double-folded. */
|
|
108
|
+
skipped?: { oid: string; id?: string | null }[];
|
|
109
|
+
/** Replays the local law refused or that errored (domain refusals also land in the DLQ). */
|
|
110
|
+
rejected?: { oid: string; id?: string | null; error: string }[];
|
|
111
|
+
/** Edge-refused attack-lane intents dropped from the lineage (present only when non-empty). */
|
|
112
|
+
dropped?: { oid: string; id?: string | null; edgeRejected: true }[];
|
|
113
|
+
/** Edge- or locally-refused LEGITIMATE work parked in the dead-letter queue (present only when non-empty). */
|
|
114
|
+
deadLettered?: { oid: string; id?: string | null; error?: string }[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Result of {@link Holon.sync} — the offline-first exchange:
|
|
119
|
+
* up (push local commits to the untrusted session branch), judge (edge admission),
|
|
120
|
+
* down (converge onto canonical main in the same call).
|
|
121
|
+
*/
|
|
122
|
+
export interface SyncResult {
|
|
123
|
+
/** The session branch the local commits were pushed to (`session/<clientId>`), or null when there was nothing local to push. */
|
|
124
|
+
pushed: string | null;
|
|
125
|
+
/**
|
|
126
|
+
* The edge holon's admission verdict for this round. NULL when there was nothing to
|
|
127
|
+
* push (or `admit: false`): a pull-only sync converging in place is normal, not an
|
|
128
|
+
* error — the edge is only asked to judge when we actually offered work.
|
|
129
|
+
*/
|
|
130
|
+
admission: AdmissionReport | null;
|
|
131
|
+
/** The in-call convergence (adopt canonical main + rebase-replay) that follows a successful admission; null otherwise. */
|
|
132
|
+
converged: PullResult | null;
|
|
133
|
+
/** True when remote main advanced and we adopted it (the next query folds the delta). */
|
|
134
|
+
pulled: boolean;
|
|
135
|
+
/** True when remote main advanced while we hold un-merged local work — wait for edge admission instead of force-adopting. */
|
|
136
|
+
diverged: boolean;
|
|
137
|
+
/** Always false — convergence happens in place on this instance; no reconnect is ever required. */
|
|
138
|
+
reconnectRequired: boolean;
|
|
139
|
+
/** The local ledger tip at the start of the sync. */
|
|
140
|
+
localHead: string;
|
|
141
|
+
/** Remote main as of this sync (undefined only if the remote advertised no main). */
|
|
142
|
+
remoteMain: string | undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Outcome of {@link Holon.retryDeadLetter}. */
|
|
146
|
+
export interface RetryDeadLetterResult {
|
|
147
|
+
ok: boolean;
|
|
148
|
+
/** New local head when the retry re-applied cleanly (it rides the next sync). */
|
|
149
|
+
head?: string;
|
|
150
|
+
/** True when the pull found the intent already admitted on canonical main — nothing to re-apply. */
|
|
151
|
+
alreadyOnMain?: boolean;
|
|
152
|
+
/** The (fresh) refusal when the law still says no — the entry stays parked, error updated. */
|
|
153
|
+
error?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A LOCAL holon: the byte-identical wasm32-wasip1 GitHolon the edge runs, hydrated from
|
|
158
|
+
* the workspace's git ledger and folded into an in-memory SQLite projection. All reads
|
|
159
|
+
* and writes are local; `sync()` is the only network exchange. All operations are
|
|
160
|
+
* serialized onto the one wasm instance.
|
|
161
|
+
*/
|
|
162
|
+
export interface Holon {
|
|
163
|
+
/** The stable client identity — names the session branch (`session/<clientId>`) and seeds the HLC replica. */
|
|
164
|
+
clientId: string;
|
|
165
|
+
/** The 63-bit HLC replica id derived from `clientId`, as a decimal string. */
|
|
166
|
+
replica: string;
|
|
167
|
+
|
|
168
|
+
/** Local head — the client's own ledger tip (commit sha of local `main`). */
|
|
169
|
+
head(): Promise<string>;
|
|
170
|
+
|
|
171
|
+
/** Read an aggregate's row(s) from the LOCAL projection by id (folds any un-folded ledger delta first). */
|
|
172
|
+
queryById(aggregateId: string): Promise<AggregateRow[]>;
|
|
173
|
+
|
|
174
|
+
/** Run a declared domain query against the LOCAL projection (routed by the read manifest — works offline). */
|
|
175
|
+
query(queryId: string, params?: Record<string, unknown>): Promise<AggregateRow[]>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* LOCAL-FIRST write: author an intent under the workspace's INSTALLED law (resolved from
|
|
179
|
+
* the pulled ledger — works fully offline) and commit it to the local ledger. Returns the
|
|
180
|
+
* new local head; the write becomes durable on the cloud via `sync()`. A law refusal
|
|
181
|
+
* throws, verbatim — the write never silently disappears. `domainHash` is required: it is
|
|
182
|
+
* the content hash of the installed law you are authoring under (the generated client
|
|
183
|
+
* bakes it in).
|
|
184
|
+
*/
|
|
185
|
+
dispatch(domain: string, directiveId: string, payload: unknown, opts: { domainHash: string }): Promise<string>;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Watch a declared query: polls the local projection and fires `cb(rows)` whenever the
|
|
189
|
+
* result changes (purely local — sync separately). Returns a `stop()` function.
|
|
190
|
+
*/
|
|
191
|
+
watch(queryId: string, params: Record<string, unknown> | undefined, cb: (rows: AggregateRow[]) => void, opts?: { intervalMs?: number }): () => void;
|
|
192
|
+
|
|
193
|
+
/** Watch a single aggregate id (local reactive read, like {@link Holon.watch}). Returns a `stop()` function. */
|
|
194
|
+
watchById(aggregateId: string, cb: (rows: AggregateRow[]) => void, opts?: { intervalMs?: number }): () => void;
|
|
195
|
+
|
|
196
|
+
/** A declared count's maintained O(1) value from the LOCAL projection. */
|
|
197
|
+
count(countId: string, groupKey?: string): Promise<number>;
|
|
198
|
+
|
|
199
|
+
/** A declared sum's maintained O(1) total from the LOCAL projection. */
|
|
200
|
+
sum(sumId: string, groupKey?: string): Promise<number>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* SYNC — the offline-first exchange: push local commits to the untrusted session branch
|
|
204
|
+
* (the edge holon validates and merges to main — admission, not trust), then converge
|
|
205
|
+
* onto canonical main in the same call. With `admit: false` the push lands but the edge
|
|
206
|
+
* is not asked to judge now (pushes also self-admit via a debounced alarm).
|
|
207
|
+
*/
|
|
208
|
+
sync(opts?: { admit?: boolean }): Promise<SyncResult>;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* PULL — incremental convergence onto canonical main on demand: fetch origin main; if
|
|
212
|
+
* unchanged, `{ upToDate: true }`. Otherwise adopt the remote head (the projection
|
|
213
|
+
* re-folds from it at the next query) and rebase-replay the local-only intents the
|
|
214
|
+
* canon doesn't already carry.
|
|
215
|
+
*/
|
|
216
|
+
pull(): Promise<PullResult>;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* EXPORT — serialize the workspace directory (the bare nomos.git ledger + manifests +
|
|
220
|
+
* dead letters) to bytes. Persist them anywhere (IndexedDB, a file); hand them back via
|
|
221
|
+
* `connect({ restoreFrom })` to resume WITHOUT a clone — pending un-synced commits included.
|
|
222
|
+
*/
|
|
223
|
+
export(): Promise<Uint8Array>;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The DEAD-LETTER QUEUE: refused-but-legitimate intents with full provenance. Refused
|
|
227
|
+
* work is NEVER lost — it parks here (durably; rides export/restore) until the app
|
|
228
|
+
* retries it (after a law fix) or explicitly discards it. The raw intent bytes stay in
|
|
229
|
+
* the queue but are not returned here — only the metadata.
|
|
230
|
+
*/
|
|
231
|
+
deadLetters(): Promise<DeadLetter[]>;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Retry one dead-lettered intent by id (or short oid) — the UNJAM after the domain dev
|
|
235
|
+
* ships a law fix: pulls canon first (it may already be admitted), else re-applies
|
|
236
|
+
* locally so it rides the next sync. Resolves (removes) the entry on success; on
|
|
237
|
+
* refusal the entry stays with its error refreshed.
|
|
238
|
+
*/
|
|
239
|
+
retryDeadLetter(id: string): Promise<RetryDeadLetterResult>;
|
|
240
|
+
|
|
241
|
+
/** Discard one dead letter by id (or short oid) — the APP's explicit choice, never the runtime's. */
|
|
242
|
+
discardDeadLetter(id: string): Promise<{ ok: boolean; removed: number }>;
|
|
243
|
+
|
|
244
|
+
/** Runtime vitals: the wasm instance's current linear-memory footprint in MiB. */
|
|
245
|
+
stats(): { wasmLinearMemMB: number };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Open a LOCAL holon for `workspace`, hydrated from Nomos Cloud: fetches the
|
|
250
|
+
* byte-identical holon wasm the edge runs, git-pulls the workspace ledger `main`, and
|
|
251
|
+
* replays it locally — verifying as it goes (content-addressed law; custody is never
|
|
252
|
+
* trusted). The returned {@link Holon} reads and writes entirely locally; `sync()` is
|
|
253
|
+
* the only exchange with the cloud.
|
|
254
|
+
*/
|
|
255
|
+
export function connect(opts: ConnectOptions): Promise<Holon>;
|