@metalabel/dfos-web-relay 0.11.0 → 0.13.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/README.md CHANGED
@@ -38,18 +38,18 @@ serve({ port: 4444 });
38
38
 
39
39
  ## Routes
40
40
 
41
- | Method | Path | Description |
42
- | ------ | ---------------------------------------- | ----------------------------------------------------------- |
43
- | `GET` | `/.well-known/dfos-relay` | Relay metadata (DID, protocol version) |
44
- | `POST` | `/operations` | Submit signed operations (identity, content, countersig) |
45
- | `GET` | `/identities/:did` | Get identity chain state and operation log |
46
- | `GET` | `/content/:contentId` | Get content chain state and operation log |
47
- | `GET` | `/operations/:cid` | Get a single operation by CID |
48
- | `GET` | `/countersignatures/:cid` | Get countersignatures for an operation |
49
- | `GET` | `/operations/:cid/countersignatures` | Same as above (alias) |
50
- | `PUT` | `/content/:contentId/blob/:operationCID` | Upload blob (auth required) |
51
- | `GET` | `/content/:contentId/blob` | Download blob at head (standing auth, or auth + credential) |
52
- | `GET` | `/content/:contentId/blob/:ref` | Download blob at specific operation ref |
41
+ | Method | Path | Description |
42
+ | ------ | --------------------------------------------- | ----------------------------------------------------------- |
43
+ | `GET` | `/.well-known/dfos-relay` | Relay metadata (DID, protocol version) |
44
+ | `POST` | `/proof/v1/operations` | Submit signed operations (identity, content, countersig) |
45
+ | `GET` | `/proof/v1/identities/:did` | Get identity chain state and operation log |
46
+ | `GET` | `/proof/v1/content/:contentId` | Get content chain state and operation log |
47
+ | `GET` | `/proof/v1/operations/:cid` | Get a single operation by CID |
48
+ | `GET` | `/proof/v1/countersignatures/:cid` | Get countersignatures for an operation |
49
+ | `GET` | `/proof/v1/operations/:cid/countersignatures` | Same as above (alias) |
50
+ | `PUT` | `/content/:contentId/blob/:operationCID` | Upload blob (auth required) |
51
+ | `GET` | `/content/:contentId/blob` | Download blob at head (standing auth, or auth + credential) |
52
+ | `GET` | `/content/:contentId/blob/:ref` | Download blob at specific operation ref |
53
53
 
54
54
  ## Blob Authorization
55
55
 
package/dist/index.d.ts CHANGED
@@ -17,6 +17,13 @@ interface RelayOptions {
17
17
  content?: boolean;
18
18
  /** Whether the global operation log is enabled (default: true) */
19
19
  log?: boolean;
20
+ /**
21
+ * Whether this relay accepts writes (default: true). When false, it is a LITE
22
+ * pull-only proof node: POST /proof/v1/operations is rejected (501), so neither
23
+ * client writes nor peer gossip-in are accepted. The node still ingests by
24
+ * PULLING from peers (syncFromPeers polls their /log).
25
+ */
26
+ write?: boolean;
20
27
  /** Peer relay configurations */
21
28
  peers?: PeerConfig[];
22
29
  /** Injected peer client — if omitted, a default HTTP implementation is used */
@@ -405,16 +412,19 @@ declare const createKeyResolver: (store: RelayStore) => (kid: string) => Promise
405
412
  */
406
413
  declare const createHistoricalIdentityResolver: (store: RelayStore) => (did: string) => Promise<{
407
414
  authKeys: {
415
+ [x: string]: unknown;
408
416
  id: string;
409
417
  type: "Multikey";
410
418
  publicKeyMultibase: string;
411
419
  }[];
412
420
  assertKeys: {
421
+ [x: string]: unknown;
413
422
  id: string;
414
423
  type: "Multikey";
415
424
  publicKeyMultibase: string;
416
425
  }[];
417
426
  controllerKeys: {
427
+ [x: string]: unknown;
418
428
  id: string;
419
429
  type: "Multikey";
420
430
  publicKeyMultibase: string;
package/dist/index.js CHANGED
@@ -934,6 +934,9 @@ var bootstrapWithKeyMaterial = async (store, params) => {
934
934
  return { did, profileArtifactJws };
935
935
  };
936
936
 
937
+ // src/types.ts
938
+ var PROOF_BASE_PATH = "/proof/v1";
939
+
937
940
  // src/peer-client.ts
938
941
  var createHttpPeerClient = () => {
939
942
  const fetchJSON = async (url) => {
@@ -947,7 +950,7 @@ var createHttpPeerClient = () => {
947
950
  };
948
951
  return {
949
952
  async getIdentityLog(peerUrl, did, params) {
950
- const url = new URL(`/identities/${encodeURIComponent(did)}/log`, peerUrl);
953
+ const url = new URL(`${PROOF_BASE_PATH}/identities/${encodeURIComponent(did)}/log`, peerUrl);
951
954
  if (params?.after) url.searchParams.set("after", params.after);
952
955
  if (params?.limit) url.searchParams.set("limit", String(params.limit));
953
956
  const data = await fetchJSON(url.toString());
@@ -955,7 +958,10 @@ var createHttpPeerClient = () => {
955
958
  return { entries: data.entries, cursor: data.cursor ?? null };
956
959
  },
957
960
  async getContentLog(peerUrl, contentId, params) {
958
- const url = new URL(`/content/${encodeURIComponent(contentId)}/log`, peerUrl);
961
+ const url = new URL(
962
+ `${PROOF_BASE_PATH}/content/${encodeURIComponent(contentId)}/log`,
963
+ peerUrl
964
+ );
959
965
  if (params?.after) url.searchParams.set("after", params.after);
960
966
  if (params?.limit) url.searchParams.set("limit", String(params.limit));
961
967
  const data = await fetchJSON(url.toString());
@@ -963,7 +969,7 @@ var createHttpPeerClient = () => {
963
969
  return { entries: data.entries, cursor: data.cursor ?? null };
964
970
  },
965
971
  async getOperationLog(peerUrl, params) {
966
- const url = new URL("/log", peerUrl);
972
+ const url = new URL(`${PROOF_BASE_PATH}/log`, peerUrl);
967
973
  if (params?.after) url.searchParams.set("after", params.after);
968
974
  if (params?.limit) url.searchParams.set("limit", String(params.limit));
969
975
  const data = await fetchJSON(url.toString());
@@ -972,7 +978,7 @@ var createHttpPeerClient = () => {
972
978
  },
973
979
  async submitOperations(peerUrl, operations) {
974
980
  try {
975
- const res = await fetch(new URL("/operations", peerUrl).toString(), {
981
+ const res = await fetch(new URL(`${PROOF_BASE_PATH}/operations`, peerUrl).toString(), {
976
982
  method: "POST",
977
983
  headers: { "Content-Type": "application/json" },
978
984
  body: JSON.stringify({ operations })
@@ -1173,6 +1179,7 @@ var createRelay = async (options) => {
1173
1179
  const { store } = options;
1174
1180
  const contentEnabled = options.content !== false;
1175
1181
  const logEnabled = options.log !== false;
1182
+ const writeEnabled = options.write !== false;
1176
1183
  const maxAuthTokenTTLSeconds = options.maxAuthTokenTTLSeconds ?? DEFAULT_MAX_AUTH_TOKEN_TTL_SECONDS;
1177
1184
  const peers = options.peers ?? [];
1178
1185
  const peerClient = options.peerClient;
@@ -1235,14 +1242,17 @@ var createRelay = async (options) => {
1235
1242
  version: RELAY_VERSION,
1236
1243
  capabilities: {
1237
1244
  proof: true,
1245
+ write: writeEnabled,
1238
1246
  content: contentEnabled,
1239
- documents: contentEnabled,
1240
1247
  log: logEnabled
1241
1248
  },
1242
1249
  profile: profileArtifactJws
1243
1250
  });
1244
1251
  });
1245
- app.post("/operations", async (c) => {
1252
+ app.post(`${PROOF_BASE_PATH}/operations`, async (c) => {
1253
+ if (!writeEnabled) {
1254
+ return c.json({ error: "this relay is pull-only; writes are disabled" }, 501);
1255
+ }
1246
1256
  if (exceedsBodyCap(c.req.header("content-length"))) {
1247
1257
  return c.json({ error: "request body too large" }, 413);
1248
1258
  }
@@ -1259,7 +1269,7 @@ var createRelay = async (options) => {
1259
1269
  const results = await ingestWithGossip(parsed.data.operations);
1260
1270
  return c.json({ results });
1261
1271
  });
1262
- app.get("/operations/:cid", async (c) => {
1272
+ app.get(`${PROOF_BASE_PATH}/operations/:cid`, async (c) => {
1263
1273
  const cid = c.req.param("cid");
1264
1274
  const op = await store.getOperation(cid);
1265
1275
  if (!op) return c.json({ error: "not found" }, 404);
@@ -1270,7 +1280,7 @@ var createRelay = async (options) => {
1270
1280
  chainId: op.chainId
1271
1281
  });
1272
1282
  });
1273
- app.get("/identities/:did/log", async (c) => {
1283
+ app.get(`${PROOF_BASE_PATH}/identities/:did/log`, async (c) => {
1274
1284
  const did = c.req.param("did");
1275
1285
  const chain = await store.getIdentityChain(did);
1276
1286
  if (!chain) return c.json({ error: "not found" }, 404);
@@ -1289,7 +1299,7 @@ var createRelay = async (options) => {
1289
1299
  const cursor = page.length === limit ? page[page.length - 1].cid : null;
1290
1300
  return c.json({ entries: page, cursor });
1291
1301
  });
1292
- app.get("/identities/:did{.+}", async (c) => {
1302
+ app.get(`${PROOF_BASE_PATH}/identities/:did{.+}`, async (c) => {
1293
1303
  const did = c.req.param("did");
1294
1304
  let chain = await store.getIdentityChain(did);
1295
1305
  if (!chain && readThroughPeers.length > 0 && peerClient) {
@@ -1316,7 +1326,7 @@ var createRelay = async (options) => {
1316
1326
  state: chain.state
1317
1327
  });
1318
1328
  });
1319
- app.get("/content/:contentId/log", async (c) => {
1329
+ app.get(`${PROOF_BASE_PATH}/content/:contentId/log`, async (c) => {
1320
1330
  const contentId = c.req.param("contentId");
1321
1331
  const chain = await store.getContentChain(contentId);
1322
1332
  if (!chain) return c.json({ error: "not found" }, 404);
@@ -1370,7 +1380,7 @@ var createRelay = async (options) => {
1370
1380
  nextCursor: result.cursor
1371
1381
  });
1372
1382
  });
1373
- app.get("/content/:contentId", async (c) => {
1383
+ app.get(`${PROOF_BASE_PATH}/content/:contentId`, async (c) => {
1374
1384
  const contentId = c.req.param("contentId");
1375
1385
  let chain = await store.getContentChain(contentId);
1376
1386
  if (!chain && readThroughPeers.length > 0 && peerClient) {
@@ -1398,7 +1408,7 @@ var createRelay = async (options) => {
1398
1408
  state: chain.state
1399
1409
  });
1400
1410
  });
1401
- app.get("/countersignatures/:cid", async (c) => {
1411
+ app.get(`${PROOF_BASE_PATH}/countersignatures/:cid`, async (c) => {
1402
1412
  const cid = c.req.param("cid");
1403
1413
  const op = await store.getOperation(cid);
1404
1414
  if (!op) {
@@ -1409,14 +1419,14 @@ var createRelay = async (options) => {
1409
1419
  const countersigs = await store.getCountersignatures(cid);
1410
1420
  return c.json({ operationCID: cid, countersignatures: countersigs });
1411
1421
  });
1412
- app.get("/operations/:cid/countersignatures", async (c) => {
1422
+ app.get(`${PROOF_BASE_PATH}/operations/:cid/countersignatures`, async (c) => {
1413
1423
  const cid = c.req.param("cid");
1414
1424
  const op = await store.getOperation(cid);
1415
1425
  if (!op) return c.json({ error: "not found" }, 404);
1416
1426
  const countersigs = await store.getCountersignatures(cid);
1417
1427
  return c.json({ operationCID: cid, countersignatures: countersigs });
1418
1428
  });
1419
- app.get("/log", async (c) => {
1429
+ app.get(`${PROOF_BASE_PATH}/log`, async (c) => {
1420
1430
  if (!logEnabled) return c.json({ error: "global log not available" }, 501);
1421
1431
  const afterParam = c.req.query("after");
1422
1432
  const limit = parseLimit(c.req.query("limit"), 100, 1e3);
package/openapi.yaml CHANGED
@@ -33,7 +33,7 @@ paths:
33
33
  did:
34
34
  type: string
35
35
  description: The relay's DID
36
- example: 'did:dfos:e3vvtck42d4eacdnzvtrn6'
36
+ example: 'did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr'
37
37
  protocol:
38
38
  type: string
39
39
  enum: [dfos-web-relay]
@@ -42,11 +42,14 @@ paths:
42
42
  example: '0.8.0'
43
43
  capabilities:
44
44
  type: object
45
- required: [proof, content, documents, log]
45
+ required: [proof, write, content, documents, log]
46
46
  properties:
47
47
  proof:
48
48
  type: boolean
49
49
  description: Always true — a relay without proof plane is not a relay
50
+ write:
51
+ type: boolean
52
+ description: Whether the relay accepts writes via POST /proof/v1/operations (false = lite pull-only node)
50
53
  content:
51
54
  type: boolean
52
55
  description: Whether the relay supports content plane (blob upload/download)
@@ -55,12 +58,12 @@ paths:
55
58
  description: Whether the relay serves the documents endpoint
56
59
  log:
57
60
  type: boolean
58
- description: Whether the global operation log is available (GET /log)
61
+ description: Whether the global operation log is available (GET /proof/v1/log)
59
62
  profile:
60
63
  type: string
61
64
  description: The relay's profile artifact as a compact JWS token
62
65
 
63
- /operations:
66
+ /proof/v1/operations:
64
67
  post:
65
68
  operationId: ingestOperations
66
69
  summary: Submit operations for ingestion
@@ -99,8 +102,10 @@ paths:
99
102
  $ref: '#/components/schemas/IngestionResult'
100
103
  '400':
101
104
  $ref: '#/components/responses/BadRequest'
105
+ '501':
106
+ description: Writes disabled — relay is a lite pull-only node (capabilities.write is false)
102
107
 
103
- /operations/{cid}:
108
+ /proof/v1/operations/{cid}:
104
109
  get:
105
110
  operationId: getOperation
106
111
  summary: Get an operation by CID
@@ -122,7 +127,7 @@ paths:
122
127
  '404':
123
128
  $ref: '#/components/responses/NotFound'
124
129
 
125
- /operations/{cid}/countersignatures:
130
+ /proof/v1/operations/{cid}/countersignatures:
126
131
  get:
127
132
  operationId: getCountersignatures
128
133
  summary: Get countersignatures for an operation
@@ -152,7 +157,7 @@ paths:
152
157
  '404':
153
158
  $ref: '#/components/responses/NotFound'
154
159
 
155
- /identities/{did}:
160
+ /proof/v1/identities/{did}:
156
161
  get:
157
162
  operationId: getIdentityChain
158
163
  summary: Get an identity chain by DID
@@ -163,7 +168,7 @@ paths:
163
168
  required: true
164
169
  schema:
165
170
  type: string
166
- description: 'DID of the identity (e.g., did:dfos:e3vvtck42d4eacdnzvtrn6)'
171
+ description: 'DID of the identity (e.g., did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr)'
167
172
  responses:
168
173
  '200':
169
174
  description: Identity chain state and log
@@ -174,7 +179,7 @@ paths:
174
179
  '404':
175
180
  $ref: '#/components/responses/NotFound'
176
181
 
177
- /content/{contentId}:
182
+ /proof/v1/content/{contentId}:
178
183
  get:
179
184
  operationId: getContentChain
180
185
  summary: Get a content chain by content ID
@@ -414,9 +419,9 @@ paths:
414
419
  '404':
415
420
  $ref: '#/components/responses/NotFound'
416
421
  '501':
417
- description: Documents capability not enabled
422
+ description: Content plane not enabled (the documents endpoint is part of the content plane)
418
423
 
419
- /log:
424
+ /proof/v1/log:
420
425
  get:
421
426
  operationId: getLog
422
427
  summary: Paginated global log of all accepted operations
@@ -472,7 +477,7 @@ paths:
472
477
  '501':
473
478
  description: Global log capability not enabled
474
479
 
475
- /identities/{did}/log:
480
+ /proof/v1/identities/{did}/log:
476
481
  get:
477
482
  operationId: getIdentityLog
478
483
  summary: Paginated log of identity chain operations
@@ -525,7 +530,7 @@ paths:
525
530
  '404':
526
531
  $ref: '#/components/responses/NotFound'
527
532
 
528
- /content/{contentId}/log:
533
+ /proof/v1/content/{contentId}/log:
529
534
  get:
530
535
  operationId: getContentLog
531
536
  summary: Paginated log of content chain operations
@@ -578,7 +583,7 @@ paths:
578
583
  '404':
579
584
  $ref: '#/components/responses/NotFound'
580
585
 
581
- /countersignatures/{cid}:
586
+ /proof/v1/countersignatures/{cid}:
582
587
  get:
583
588
  operationId: getCountersignaturesByCID
584
589
  summary: Get countersignatures for an operation CID
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-web-relay",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Web Relay — verifying HTTP relay for identity chains, content chains, and content blobs",
6
6
  "license": "MIT",
@@ -45,14 +45,14 @@
45
45
  "zod": "^4.4.3"
46
46
  },
47
47
  "peerDependencies": {
48
- "@metalabel/dfos-protocol": "^0.11.0"
48
+ "@metalabel/dfos-protocol": "^0.13.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^24.10.4",
52
52
  "tsup": "^8.5.1",
53
53
  "tsx": "^4.22.4",
54
54
  "vitest": "^4.1.8",
55
- "@metalabel/dfos-protocol": "0.11.0"
55
+ "@metalabel/dfos-protocol": "0.13.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",