@metalabel/dfos-client 0.29.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/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/chunk-63XCFYOE.js +16 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +766 -0
- package/dist/memory-CL1DM6Ud.d.ts +5 -0
- package/dist/siwd.d.ts +130 -0
- package/dist/siwd.js +260 -0
- package/dist/store/index.d.ts +14 -0
- package/dist/store/index.js +49 -0
- package/dist/types-ByxTj1u-.d.ts +324 -0
- package/package.json +70 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { VerifiedIdentity, VerifiedContentChain } from '@metalabel/dfos-protocol/chain';
|
|
2
|
+
import { VerifiedDFOSCredential, Attenuation } from '@metalabel/dfos-protocol/credentials';
|
|
3
|
+
import { PeerClient } from '@metalabel/dfos-web-relay/peer-client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The two axes v1 genuinely cannot check:
|
|
7
|
+
* - `revocation` — non-revocation is never provable (a relay can only attest to
|
|
8
|
+
* what it has seen, and can withhold), so a credential's unrevoked status is
|
|
9
|
+
* honest absence-of-evidence, not proof.
|
|
10
|
+
* - `tip` — tip freshness is never PROVEN in v1 (head proofs are v2 /
|
|
11
|
+
* `tipProven`). The axis is carried whenever the answer's freshness rests on
|
|
12
|
+
* a cached head: either the cache alone (all relays unreachable) or relays'
|
|
13
|
+
* empty-delta claim against it (a relay that never saw the cached head reports
|
|
14
|
+
* the same empty page as one that is genuinely caught up).
|
|
15
|
+
*/
|
|
16
|
+
type UnverifiableAxis = 'revocation' | 'tip';
|
|
17
|
+
/** Trust is DATA. `ok` = the value verified; `unverifiable` lists honest gaps. */
|
|
18
|
+
interface Trust {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
unverifiable?: UnverifiableAxis[];
|
|
21
|
+
}
|
|
22
|
+
/** One relay's answer to a fan-out. `digest` is a content digest for quorum. */
|
|
23
|
+
interface RelayResponse {
|
|
24
|
+
url: string;
|
|
25
|
+
ok: boolean;
|
|
26
|
+
digest: string;
|
|
27
|
+
}
|
|
28
|
+
/** Where an answer came from and whether relays agreed. Stays thin. */
|
|
29
|
+
interface Provenance {
|
|
30
|
+
answeredBy: string;
|
|
31
|
+
responses: RelayResponse[];
|
|
32
|
+
agreed: boolean;
|
|
33
|
+
fromCache: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** A proven value wrapped in trust + provenance. */
|
|
36
|
+
interface Resolved<T> {
|
|
37
|
+
value: T;
|
|
38
|
+
trust: Trust;
|
|
39
|
+
provenance: Provenance;
|
|
40
|
+
}
|
|
41
|
+
/** No-throw verification outcome. `ok` routes the decision; `error` explains a no. */
|
|
42
|
+
interface VerifyResult<T> {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
value?: T;
|
|
45
|
+
error?: string;
|
|
46
|
+
unverifiable?: UnverifiableAxis[];
|
|
47
|
+
}
|
|
48
|
+
interface ResolvedContent {
|
|
49
|
+
chain: VerifiedContentChain;
|
|
50
|
+
/** The creator identity — resolved as a side effect of key resolution. */
|
|
51
|
+
creator: VerifiedIdentity;
|
|
52
|
+
/** The current document blob, when fetched. */
|
|
53
|
+
document?: DocumentBlob;
|
|
54
|
+
}
|
|
55
|
+
interface ResolvedCredential {
|
|
56
|
+
credential: VerifiedDFOSCredential;
|
|
57
|
+
/** The issuer identity, verified. */
|
|
58
|
+
issuer: VerifiedIdentity;
|
|
59
|
+
/** Revocation status per the effective revocation checker (see Trust.unverifiable). */
|
|
60
|
+
revoked: boolean;
|
|
61
|
+
}
|
|
62
|
+
interface DocumentBlob {
|
|
63
|
+
bytes: Uint8Array;
|
|
64
|
+
documentCID: string;
|
|
65
|
+
mediaType?: string;
|
|
66
|
+
/** The parsed document if it was JSON — the on-wire form relays store. */
|
|
67
|
+
decoded?: unknown;
|
|
68
|
+
/** Whether the fetched bytes re-derive to `documentCID` (content-address check). */
|
|
69
|
+
integrity: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Discriminated result of the paste-a-string dispatcher. */
|
|
72
|
+
type Resolution = ({
|
|
73
|
+
kind: 'identity';
|
|
74
|
+
} & Resolved<VerifiedIdentity>) | ({
|
|
75
|
+
kind: 'content';
|
|
76
|
+
} & Resolved<ResolvedContent>) | ({
|
|
77
|
+
kind: 'credential';
|
|
78
|
+
} & Resolved<ResolvedCredential>);
|
|
79
|
+
/**
|
|
80
|
+
* Check whether a credential has been revoked. Default: `() => false` (honest).
|
|
81
|
+
*
|
|
82
|
+
* `asOfUnix` is the protocol's revocation as-of basis (see the protocol's
|
|
83
|
+
* `RevocationChecker`): supplied when folding committed history, so a credential
|
|
84
|
+
* revoked AFTER an operation was signed does not invalidate it; omitted for live
|
|
85
|
+
* "is it revoked right now" checks. Omitted **or `<= 0`** means timeless — `0` is
|
|
86
|
+
* the Go twin's in-band sentinel, so "as of epoch 0" is not expressible there and
|
|
87
|
+
* must not be expressible here either.
|
|
88
|
+
*/
|
|
89
|
+
type RevChecker = (issuerDID: string, credentialCID: string, asOfUnix?: number) => Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* The bound protocol-lib callbacks — spread straight into `verifyContentChain`,
|
|
92
|
+
* `verifyDFOSCredential`, or any DFOS verifier. This is the trunk product.
|
|
93
|
+
*/
|
|
94
|
+
interface Callbacks {
|
|
95
|
+
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
96
|
+
resolveIdentity: (did: string) => Promise<VerifiedIdentity | undefined>;
|
|
97
|
+
isRevoked: RevChecker;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A minimal async key/value cache. Values are JSON-serializable records the
|
|
101
|
+
* client owns; consumers never construct them. `memoryStore()` is the default;
|
|
102
|
+
* `indexedDbStore()` (behind `./store`) is the only heavy, browser-only adapter.
|
|
103
|
+
*/
|
|
104
|
+
interface Store {
|
|
105
|
+
get(key: string): Promise<unknown | undefined>;
|
|
106
|
+
set(key: string, value: unknown): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/** A raw JWS operation from a relay log — cid + token, unverified until folded. */
|
|
109
|
+
interface LogOp {
|
|
110
|
+
cid: string;
|
|
111
|
+
jwsToken: string;
|
|
112
|
+
/**
|
|
113
|
+
* Relay-asserted operation kind — present on global-log entries, absent on
|
|
114
|
+
* chain logs. A ROUTING HINT for indexers/browsers, never a verification
|
|
115
|
+
* input: folds re-derive everything from the JWS itself.
|
|
116
|
+
*/
|
|
117
|
+
kind?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Relay-asserted chain identifier (DID / contentId / targetCID by kind).
|
|
120
|
+
* Same hint-only status as `kind`.
|
|
121
|
+
*/
|
|
122
|
+
chainId?: string;
|
|
123
|
+
}
|
|
124
|
+
/** Options for a global-log page read. */
|
|
125
|
+
interface GlobalLogOptions extends CallOptions {
|
|
126
|
+
/** Page size, 1–1000 (relay-enforced cap). Default 100. */
|
|
127
|
+
limit?: number;
|
|
128
|
+
}
|
|
129
|
+
/** A page of the global operation log — a seam, not a sync engine (v1). */
|
|
130
|
+
interface GlobalLogPage {
|
|
131
|
+
entries: LogOp[];
|
|
132
|
+
next: string | null;
|
|
133
|
+
provenance: Provenance;
|
|
134
|
+
}
|
|
135
|
+
/** A global-log page, or a relay-local resume position rejected with HTTP 400. */
|
|
136
|
+
type GlobalLogResult = GlobalLogPage | 'invalid-cursor';
|
|
137
|
+
/** Parsed `/.well-known/dfos-relay` body, passed through untouched. */
|
|
138
|
+
interface RelayHealth {
|
|
139
|
+
url: string;
|
|
140
|
+
ok: boolean;
|
|
141
|
+
did?: string;
|
|
142
|
+
capabilities?: Record<string, unknown>;
|
|
143
|
+
[key: string]: unknown;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The relay's capability flags this client cares about, MERGED across the relay
|
|
147
|
+
* set (true when any relay advertises it). `index` gates whether a browser can
|
|
148
|
+
* populate from `/index/v0` instead of replaying the full log.
|
|
149
|
+
*/
|
|
150
|
+
interface IndexCapabilities {
|
|
151
|
+
index: boolean;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Time-ordered enumeration selector for the `/identities` and `/content` index
|
|
155
|
+
* routes: newest chains first (`genesisAt.desc`) or most recently active first
|
|
156
|
+
* (`headAt.desc`). Absent this param the route enumerates in its lexical default.
|
|
157
|
+
* In ordered mode `after`/`next` are OPAQUE cursor tokens — pass `next` back
|
|
158
|
+
* verbatim, never parse or construct one.
|
|
159
|
+
*/
|
|
160
|
+
type IndexOrder = 'genesisAt.desc' | 'headAt.desc';
|
|
161
|
+
/** Recency ordering for operation-derived index rows. */
|
|
162
|
+
type IndexRecencyOrder = 'createdAt.desc' | 'ingestedAt.desc';
|
|
163
|
+
/**
|
|
164
|
+
* The `profile/v1 → name` well-known projection on an identity row. ATTRIBUTION
|
|
165
|
+
* TIER by construction: the `anchor` is controller-signed (strong), but `name`
|
|
166
|
+
* is whatever the anchored document says — verify by fetching + re-hashing the
|
|
167
|
+
* bytes to the committed documentCID. Fields are null on the relay's circuit
|
|
168
|
+
* breakers (unheld bytes, wrong/missing schema, non-string name).
|
|
169
|
+
*/
|
|
170
|
+
interface IndexIdentityProfile {
|
|
171
|
+
anchor: string;
|
|
172
|
+
publicRead: boolean;
|
|
173
|
+
docSchema: string | null;
|
|
174
|
+
name: string | null;
|
|
175
|
+
}
|
|
176
|
+
/** One row of the identity index. Mirrors GET /index/v0/identities, nullability included. */
|
|
177
|
+
interface IndexIdentityRow {
|
|
178
|
+
did: string;
|
|
179
|
+
headCID: string;
|
|
180
|
+
opCount: number;
|
|
181
|
+
genesisAt: string;
|
|
182
|
+
headAt: string;
|
|
183
|
+
isDeleted: boolean;
|
|
184
|
+
profile: IndexIdentityProfile | null;
|
|
185
|
+
}
|
|
186
|
+
/** A page of the identity index. `next` is a `did` cursor (null on the last page). */
|
|
187
|
+
interface IndexIdentitiesPage {
|
|
188
|
+
identities: IndexIdentityRow[];
|
|
189
|
+
next: string | null;
|
|
190
|
+
}
|
|
191
|
+
/** One row of the content index. Mirrors GET /index/v0/content, nullability included. */
|
|
192
|
+
interface IndexContentRow {
|
|
193
|
+
contentId: string;
|
|
194
|
+
genesisCID: string;
|
|
195
|
+
headCID: string;
|
|
196
|
+
creatorDID: string;
|
|
197
|
+
isDeleted: boolean;
|
|
198
|
+
opCount: number;
|
|
199
|
+
genesisAt: string;
|
|
200
|
+
headAt: string;
|
|
201
|
+
currentDocumentCID: string | null;
|
|
202
|
+
publicRead: boolean;
|
|
203
|
+
docSchema: string | null;
|
|
204
|
+
title: string | null;
|
|
205
|
+
}
|
|
206
|
+
/** A page of the content index. `next` is a `contentId` cursor (null on the last page). */
|
|
207
|
+
interface IndexContentPage {
|
|
208
|
+
content: IndexContentRow[];
|
|
209
|
+
next: string | null;
|
|
210
|
+
}
|
|
211
|
+
/** One row of the countersignatures-by-witness index. Carries the full self-proving JWS. */
|
|
212
|
+
interface IndexCountersignatureRow {
|
|
213
|
+
cid: string;
|
|
214
|
+
targetCID: string;
|
|
215
|
+
relation: string | null;
|
|
216
|
+
jwsToken: string;
|
|
217
|
+
}
|
|
218
|
+
/** A page of the countersignatures-by-witness index. `next` is a `cid` cursor. */
|
|
219
|
+
interface IndexCountersignaturesPage {
|
|
220
|
+
witness: string;
|
|
221
|
+
countersignatures: IndexCountersignatureRow[];
|
|
222
|
+
next: string | null;
|
|
223
|
+
}
|
|
224
|
+
/** One row of the public-credentials index. Carries the full self-proving JWS. */
|
|
225
|
+
interface IndexCredentialRow {
|
|
226
|
+
cid: string;
|
|
227
|
+
issuerDID: string;
|
|
228
|
+
aud: '*';
|
|
229
|
+
att: Attenuation[];
|
|
230
|
+
exp: number;
|
|
231
|
+
jwsToken: string;
|
|
232
|
+
}
|
|
233
|
+
/** A page of the public-credentials index. `next` is a `cid` cursor. */
|
|
234
|
+
interface IndexCredentialsPage {
|
|
235
|
+
credentials: IndexCredentialRow[];
|
|
236
|
+
next: string | null;
|
|
237
|
+
}
|
|
238
|
+
/** Per-call overrides. */
|
|
239
|
+
interface CallOptions {
|
|
240
|
+
/** Override the client's relay set for this call. */
|
|
241
|
+
relays?: string[];
|
|
242
|
+
/** Bypass the cache and re-fetch from genesis. */
|
|
243
|
+
fresh?: boolean;
|
|
244
|
+
}
|
|
245
|
+
interface ClientConfig {
|
|
246
|
+
/** Ordered, untrusted relay URLs. IMMUTABLE — a relay switch is a new client. */
|
|
247
|
+
relays: string[];
|
|
248
|
+
/** Cache backend. Default `memoryStore()`. */
|
|
249
|
+
store?: Store;
|
|
250
|
+
/** Distinct-digest agreement threshold. Default 1 (first-wins). */
|
|
251
|
+
quorum?: number;
|
|
252
|
+
/** Revocation checker. Default `() => false` (honest — status is unverifiable). */
|
|
253
|
+
isRevoked?: RevChecker;
|
|
254
|
+
/** Injected fetch for blob/health/revocation calls. Default `globalThis.fetch`. */
|
|
255
|
+
fetch?: typeof fetch;
|
|
256
|
+
/**
|
|
257
|
+
* Per-request timeout in ms (default 10000). A hung relay must fail so
|
|
258
|
+
* failover can move on — applied to every HTTP request the client makes,
|
|
259
|
+
* including the default peer-client transport.
|
|
260
|
+
*/
|
|
261
|
+
timeoutMs?: number;
|
|
262
|
+
/** Clock injection (unix ms). Default `Date.now`. */
|
|
263
|
+
now?: () => number;
|
|
264
|
+
/** Injected log transport. Default `createHttpPeerClient({ fetch })`. */
|
|
265
|
+
peerClient?: PeerClient;
|
|
266
|
+
}
|
|
267
|
+
interface Client {
|
|
268
|
+
/** The bound protocol-lib callbacks — spread into any verifier. */
|
|
269
|
+
callbacks(options?: CallOptions): Callbacks;
|
|
270
|
+
/** Paste-a-string dispatcher → a typed, trust-wrapped resolution. */
|
|
271
|
+
resolve(ref: string, options?: CallOptions): Promise<Resolution>;
|
|
272
|
+
identity(did: string, options?: CallOptions): Promise<Resolved<VerifiedIdentity>>;
|
|
273
|
+
content(contentId: string, options?: CallOptions): Promise<Resolved<ResolvedContent>>;
|
|
274
|
+
credential(jws: string, options?: CallOptions): Promise<Resolved<ResolvedCredential>>;
|
|
275
|
+
document(contentId: string, options?: CallOptions): Promise<Resolved<DocumentBlob>>;
|
|
276
|
+
/** No-throw, self-routing "is this legit". */
|
|
277
|
+
verify(jws: string, options?: CallOptions): Promise<VerifyResult<unknown>>;
|
|
278
|
+
/** Raw floor. */
|
|
279
|
+
log(kind: 'identity' | 'content', id: string, options?: CallOptions): Promise<Resolved<LogOp[]>>;
|
|
280
|
+
globalLog(after?: string, options?: GlobalLogOptions): Promise<GlobalLogResult>;
|
|
281
|
+
health(options?: CallOptions): Promise<RelayHealth[]>;
|
|
282
|
+
/**
|
|
283
|
+
* Index (v0) — non-authoritative discovery hints. Rows are CLAIMS, not proofs:
|
|
284
|
+
* verify one by fetching its chain (`identity`/`content`/`log`) and folding.
|
|
285
|
+
* Gate on `capabilities().index` before preferring these over full-log sync.
|
|
286
|
+
*/
|
|
287
|
+
capabilities(options?: CallOptions): Promise<IndexCapabilities>;
|
|
288
|
+
indexIdentities(params?: {
|
|
289
|
+
did?: string;
|
|
290
|
+
hasPublicProfile?: boolean;
|
|
291
|
+
nameContains?: string;
|
|
292
|
+
order?: IndexOrder;
|
|
293
|
+
after?: string;
|
|
294
|
+
limit?: number;
|
|
295
|
+
}, options?: CallOptions): Promise<IndexIdentitiesPage>;
|
|
296
|
+
indexContent(params?: {
|
|
297
|
+
contentId?: string;
|
|
298
|
+
creator?: string;
|
|
299
|
+
signer?: string;
|
|
300
|
+
docSchema?: string;
|
|
301
|
+
documentCID?: string;
|
|
302
|
+
publicRead?: boolean;
|
|
303
|
+
isDeleted?: boolean;
|
|
304
|
+
titleContains?: string;
|
|
305
|
+
order?: IndexOrder;
|
|
306
|
+
after?: string;
|
|
307
|
+
limit?: number;
|
|
308
|
+
}, options?: CallOptions): Promise<IndexContentPage>;
|
|
309
|
+
indexCountersignatures(witness: string, params?: {
|
|
310
|
+
relation?: string;
|
|
311
|
+
order?: IndexRecencyOrder;
|
|
312
|
+
after?: string;
|
|
313
|
+
limit?: number;
|
|
314
|
+
}, options?: CallOptions): Promise<IndexCountersignaturesPage>;
|
|
315
|
+
indexCredentials(params?: {
|
|
316
|
+
issuer?: string;
|
|
317
|
+
resource?: string;
|
|
318
|
+
action?: string;
|
|
319
|
+
after?: string;
|
|
320
|
+
limit?: number;
|
|
321
|
+
}, options?: CallOptions): Promise<IndexCredentialsPage>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type { ClientConfig as C, DocumentBlob as D, GlobalLogOptions as G, IndexCapabilities as I, LogOp as L, Provenance as P, RevChecker as R, Store as S, Trust as T, UnverifiableAxis as U, VerifyResult as V, Client as a, Callbacks as b, CallOptions as c, GlobalLogPage as d, GlobalLogResult as e, IndexContentPage as f, IndexContentRow as g, IndexCountersignatureRow as h, IndexCountersignaturesPage as i, IndexCredentialRow as j, IndexCredentialsPage as k, IndexIdentitiesPage as l, IndexIdentityProfile as m, IndexIdentityRow as n, IndexOrder as o, IndexRecencyOrder as p, RelayHealth as q, RelayResponse as r, Resolution as s, Resolved as t, ResolvedContent as u, ResolvedCredential as v };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metalabel/dfos-client",
|
|
3
|
+
"version": "0.29.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "DFOS Client — read-only resolve + verify orchestration over untrusted relays. Fetch, resolve, verify-orchestration, cache; all crypto truth comes from @metalabel/dfos-protocol",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Metalabel <hello@metalabel.com> (https://metalabel.com)",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/metalabel/dfos.git",
|
|
11
|
+
"directory": "packages/dfos-client"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://protocol.dfos.com",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dfos",
|
|
16
|
+
"client",
|
|
17
|
+
"did",
|
|
18
|
+
"verifiable",
|
|
19
|
+
"resolve",
|
|
20
|
+
"siwd",
|
|
21
|
+
"ed25519",
|
|
22
|
+
"jws",
|
|
23
|
+
"metalabel"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./src/index.ts",
|
|
27
|
+
"./siwd": "./src/siwd.ts",
|
|
28
|
+
"./store": "./src/store/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./siwd": {
|
|
37
|
+
"import": "./dist/siwd.js",
|
|
38
|
+
"types": "./dist/siwd.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./store": {
|
|
41
|
+
"import": "./dist/store/index.js",
|
|
42
|
+
"types": "./dist/store/index.d.ts"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"README.md"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"prepublishOnly": "pnpm build"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@metalabel/dfos-protocol": "^0.29.0",
|
|
61
|
+
"@metalabel/dfos-web-relay": "^0.29.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@metalabel/dfos-protocol": "workspace:*",
|
|
65
|
+
"@metalabel/dfos-web-relay": "workspace:*",
|
|
66
|
+
"@types/node": "^24.10.4",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"vitest": "^4.1.8"
|
|
69
|
+
}
|
|
70
|
+
}
|