@metalabel/dfos-protocol 0.0.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.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @metalabel/dfos-protocol
2
+
3
+ Ed25519 signed chain primitives, verifiable content, and registry for the DFOS Protocol.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @metalabel/dfos-protocol
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ // Chain verification
15
+ import { verifyContentChain, verifyIdentityChain } from '@metalabel/dfos-protocol/chain';
16
+ // Crypto primitives
17
+ import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-protocol/crypto';
18
+ // Reference registry server
19
+ import { createRegistryServer } from '@metalabel/dfos-protocol/registry';
20
+ ```
21
+
22
+ ## Subpath Exports
23
+
24
+ | Export | Description |
25
+ | ----------------------------------- | ------------------------------------------------------------------- |
26
+ | `@metalabel/dfos-protocol/chain` | Identity and content chain signing, verification, multikey encoding |
27
+ | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
28
+ | `@metalabel/dfos-protocol/registry` | Hono-based registry server, in-memory store, Zod schemas |
29
+
30
+ ## Protocol Specification
31
+
32
+ See [PROTOCOL.md](./PROTOCOL.md) for the complete protocol specification with worked examples and test vectors.
33
+
34
+ ## Examples
35
+
36
+ The `examples/` directory contains deterministic reference chain fixtures that can be independently verified by any Ed25519 + dag-cbor implementation:
37
+
38
+ - `identity-genesis.json` — single create operation
39
+ - `identity-rotation.json` — genesis + key rotation
40
+ - `identity-delete.json` — genesis + delete (terminal)
41
+ - `content-lifecycle.json` — create + update (with both documents)
42
+ - `content-delete.json` — create + delete
43
+
44
+ ## API Documentation
45
+
46
+ See [openapi.yaml](./openapi.yaml) for the registry API specification.
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,196 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Function that signs a byte array and returns a signature */
4
+ type Signer = (message: Uint8Array) => Promise<Uint8Array>;
5
+ declare const MultikeyPublicKey: z.ZodObject<{
6
+ id: z.ZodString;
7
+ type: z.ZodLiteral<"Multikey">;
8
+ publicKeyMultibase: z.ZodString;
9
+ }, z.core.$strict>;
10
+ type MultikeyPublicKey = z.infer<typeof MultikeyPublicKey>;
11
+ declare const IdentityOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
+ version: z.ZodLiteral<1>;
13
+ type: z.ZodLiteral<"create">;
14
+ authKeys: z.ZodArray<z.ZodObject<{
15
+ id: z.ZodString;
16
+ type: z.ZodLiteral<"Multikey">;
17
+ publicKeyMultibase: z.ZodString;
18
+ }, z.core.$strict>>;
19
+ assertKeys: z.ZodArray<z.ZodObject<{
20
+ id: z.ZodString;
21
+ type: z.ZodLiteral<"Multikey">;
22
+ publicKeyMultibase: z.ZodString;
23
+ }, z.core.$strict>>;
24
+ controllerKeys: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ type: z.ZodLiteral<"Multikey">;
27
+ publicKeyMultibase: z.ZodString;
28
+ }, z.core.$strict>>;
29
+ createdAt: z.ZodISODateTime;
30
+ }, z.core.$strict>, z.ZodObject<{
31
+ version: z.ZodLiteral<1>;
32
+ type: z.ZodLiteral<"update">;
33
+ previousOperationCID: z.ZodString;
34
+ authKeys: z.ZodArray<z.ZodObject<{
35
+ id: z.ZodString;
36
+ type: z.ZodLiteral<"Multikey">;
37
+ publicKeyMultibase: z.ZodString;
38
+ }, z.core.$strict>>;
39
+ assertKeys: z.ZodArray<z.ZodObject<{
40
+ id: z.ZodString;
41
+ type: z.ZodLiteral<"Multikey">;
42
+ publicKeyMultibase: z.ZodString;
43
+ }, z.core.$strict>>;
44
+ controllerKeys: z.ZodArray<z.ZodObject<{
45
+ id: z.ZodString;
46
+ type: z.ZodLiteral<"Multikey">;
47
+ publicKeyMultibase: z.ZodString;
48
+ }, z.core.$strict>>;
49
+ createdAt: z.ZodISODateTime;
50
+ }, z.core.$strict>, z.ZodObject<{
51
+ version: z.ZodLiteral<1>;
52
+ type: z.ZodLiteral<"delete">;
53
+ previousOperationCID: z.ZodString;
54
+ createdAt: z.ZodISODateTime;
55
+ }, z.core.$strict>], "type">;
56
+ type IdentityOperation = z.infer<typeof IdentityOperation>;
57
+ declare const VerifiedIdentity: z.ZodObject<{
58
+ did: z.ZodString;
59
+ isDeleted: z.ZodBoolean;
60
+ authKeys: z.ZodArray<z.ZodObject<{
61
+ id: z.ZodString;
62
+ type: z.ZodLiteral<"Multikey">;
63
+ publicKeyMultibase: z.ZodString;
64
+ }, z.core.$strict>>;
65
+ assertKeys: z.ZodArray<z.ZodObject<{
66
+ id: z.ZodString;
67
+ type: z.ZodLiteral<"Multikey">;
68
+ publicKeyMultibase: z.ZodString;
69
+ }, z.core.$strict>>;
70
+ controllerKeys: z.ZodArray<z.ZodObject<{
71
+ id: z.ZodString;
72
+ type: z.ZodLiteral<"Multikey">;
73
+ publicKeyMultibase: z.ZodString;
74
+ }, z.core.$strict>>;
75
+ }, z.core.$strict>;
76
+ type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
77
+ declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
78
+ version: z.ZodLiteral<1>;
79
+ type: z.ZodLiteral<"create">;
80
+ documentCID: z.ZodString;
81
+ createdAt: z.ZodISODateTime;
82
+ note: z.ZodNullable<z.ZodString>;
83
+ }, z.core.$strict>, z.ZodObject<{
84
+ version: z.ZodLiteral<1>;
85
+ type: z.ZodLiteral<"update">;
86
+ previousOperationCID: z.ZodString;
87
+ documentCID: z.ZodNullable<z.ZodString>;
88
+ createdAt: z.ZodISODateTime;
89
+ note: z.ZodNullable<z.ZodString>;
90
+ }, z.core.$strict>, z.ZodObject<{
91
+ version: z.ZodLiteral<1>;
92
+ type: z.ZodLiteral<"delete">;
93
+ previousOperationCID: z.ZodString;
94
+ createdAt: z.ZodISODateTime;
95
+ note: z.ZodNullable<z.ZodString>;
96
+ }, z.core.$strict>], "type">;
97
+ type ContentOperation = z.infer<typeof ContentOperation>;
98
+
99
+ /** Ed25519 public key multicodec value */
100
+ declare const ED25519_PUB_MULTICODEC = 237;
101
+ /** Ed25519 private key multicodec value */
102
+ declare const ED25519_PRIV_MULTICODEC = 4864;
103
+ /**
104
+ * Encode an Ed25519 public key as a W3C Multikey multibase string
105
+ *
106
+ * Format: 'z' + base58btc([0xed, 0x01] + publicKeyBytes)
107
+ * Result starts with "z6Mk..."
108
+ */
109
+ declare const encodeEd25519Multikey: (publicKeyBytes: Uint8Array) => string;
110
+ /**
111
+ * Decode a Multikey multibase string to raw key bytes and codec
112
+ *
113
+ * Supports Ed25519 public keys (z6Mk... prefix)
114
+ */
115
+ declare const decodeMultikey: (multibase: string) => {
116
+ keyBytes: Uint8Array;
117
+ codec: number;
118
+ };
119
+
120
+ /**
121
+ * Derive a prefixed chain identifier from CID bytes
122
+ *
123
+ * Used for identity DIDs: deriveChainIdentifier(cidBytes, 'did:dfos') → 'did:dfos:xxxx'
124
+ */
125
+ declare const deriveChainIdentifier: (cidBytes: Uint8Array, prefix: string) => string;
126
+ /**
127
+ * Derive a bare entity identifier from CID bytes
128
+ *
129
+ * Returns the raw 22-char hash with no prefix. Applications may add
130
+ * their own prefix for routing (e.g., post_xxxx) — that's semantic sugar.
131
+ */
132
+ declare const deriveEntityId: (cidBytes: Uint8Array) => string;
133
+
134
+ /**
135
+ * Sign an identity operation as a JWS and derive the operation CID
136
+ */
137
+ declare const signIdentityOperation: (input: {
138
+ operation: IdentityOperation;
139
+ signer: Signer;
140
+ keyId: string;
141
+ /** DID of the identity — omit for genesis (bare kid) */
142
+ identityDID?: string;
143
+ }) => Promise<{
144
+ jwsToken: string;
145
+ operationCID: string;
146
+ }>;
147
+ /**
148
+ * Verify a log of JWS identity operations and derive the identity
149
+ *
150
+ * Walks the chain from genesis, verifying signatures and chain integrity.
151
+ * Returns the final verified identity state.
152
+ */
153
+ declare const verifyIdentityChain: (input: {
154
+ didPrefix: string;
155
+ log: string[];
156
+ }) => Promise<VerifiedIdentity>;
157
+
158
+ interface VerifiedContentChain {
159
+ /** Entity identifier — bare 22-char hash derived from genesis CID */
160
+ entityId: string;
161
+ /** CID of the genesis operation */
162
+ genesisCID: string;
163
+ /** CID of the most recent operation */
164
+ headCID: string;
165
+ /** Whether the chain has been terminated by a delete */
166
+ isDeleted: boolean;
167
+ /** The current documentCID (null if cleared or deleted) */
168
+ currentDocumentCID: string | null;
169
+ /** Number of operations in the chain */
170
+ length: number;
171
+ }
172
+ /**
173
+ * Sign a content chain operation as a JWS and derive the operation CID
174
+ */
175
+ declare const signContentOperation: (input: {
176
+ operation: ContentOperation;
177
+ signer: Signer;
178
+ /** kid for the JWS header — should be a DID URL: "did:dfos:xxx#key_yyy" */
179
+ kid: string;
180
+ }) => Promise<{
181
+ jwsToken: string;
182
+ operationCID: string;
183
+ }>;
184
+ /**
185
+ * Verify a content chain's structural integrity and signatures
186
+ *
187
+ * The caller provides a key resolver to look up public keys from kid values.
188
+ * This keeps the content chain protocol independent of identity resolution.
189
+ */
190
+ declare const verifyContentChain: (input: {
191
+ log: string[];
192
+ /** Resolve a kid (DID URL) to the raw Ed25519 public key bytes */
193
+ resolveKey: (kid: string) => Promise<Uint8Array>;
194
+ }) => Promise<VerifiedContentChain>;
195
+
196
+ export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveEntityId, encodeEd25519Multikey, signContentOperation, signIdentityOperation, verifyContentChain, verifyIdentityChain };
@@ -0,0 +1,33 @@
1
+ import {
2
+ ContentOperation,
3
+ ED25519_PRIV_MULTICODEC,
4
+ ED25519_PUB_MULTICODEC,
5
+ IdentityOperation,
6
+ MultikeyPublicKey,
7
+ VerifiedIdentity,
8
+ decodeMultikey,
9
+ deriveChainIdentifier,
10
+ deriveEntityId,
11
+ encodeEd25519Multikey,
12
+ signContentOperation,
13
+ signIdentityOperation,
14
+ verifyContentChain,
15
+ verifyIdentityChain
16
+ } from "../chunk-LWC4PWGZ.js";
17
+ import "../chunk-ZXXP5W5N.js";
18
+ export {
19
+ ContentOperation,
20
+ ED25519_PRIV_MULTICODEC,
21
+ ED25519_PUB_MULTICODEC,
22
+ IdentityOperation,
23
+ MultikeyPublicKey,
24
+ VerifiedIdentity,
25
+ decodeMultikey,
26
+ deriveChainIdentifier,
27
+ deriveEntityId,
28
+ encodeEd25519Multikey,
29
+ signContentOperation,
30
+ signIdentityOperation,
31
+ verifyContentChain,
32
+ verifyIdentityChain
33
+ };
@@ -0,0 +1,330 @@
1
+ import {
2
+ MultikeyPublicKey,
3
+ decodeMultikey,
4
+ verifyContentChain,
5
+ verifyIdentityChain
6
+ } from "./chunk-LWC4PWGZ.js";
7
+ import {
8
+ dagCborCanonicalEncode,
9
+ decodeJwsUnsafe
10
+ } from "./chunk-ZXXP5W5N.js";
11
+
12
+ // src/registry/schemas.ts
13
+ import { z } from "zod";
14
+ var OperationEntry = z.strictObject({
15
+ cid: z.string(),
16
+ jwsToken: z.string(),
17
+ createdAt: z.string()
18
+ });
19
+ var PaginatedOperations = z.strictObject({
20
+ operations: z.array(OperationEntry),
21
+ nextCursor: z.string().nullable()
22
+ });
23
+ var PaginationParams = z.object({
24
+ cursor: z.string().optional(),
25
+ limit: z.coerce.number().int().min(1).max(100).default(25)
26
+ });
27
+ var SubmitIdentityChainRequest = z.strictObject({
28
+ chain: z.array(z.string()).min(1)
29
+ });
30
+ var SubmitIdentityChainResponse = z.strictObject({
31
+ did: z.string(),
32
+ isDeleted: z.boolean(),
33
+ authKeys: z.array(MultikeyPublicKey),
34
+ assertKeys: z.array(MultikeyPublicKey),
35
+ controllerKeys: z.array(MultikeyPublicKey)
36
+ });
37
+ var ResolveIdentityResponse = SubmitIdentityChainResponse;
38
+ var IdentityOperationsParams = PaginationParams;
39
+ var IdentityOperationsResponse = PaginatedOperations;
40
+ var SubmitContentChainRequest = z.strictObject({
41
+ chain: z.array(z.string()).min(1)
42
+ });
43
+ var SubmitContentChainResponse = z.strictObject({
44
+ entityId: z.string(),
45
+ isDeleted: z.boolean(),
46
+ currentDocumentCID: z.string().nullable(),
47
+ genesisCID: z.string(),
48
+ headCID: z.string()
49
+ });
50
+ var ResolveEntityResponse = SubmitContentChainResponse;
51
+ var EntityOperationsParams = PaginationParams;
52
+ var EntityOperationsResponse = PaginatedOperations;
53
+ var ResolveOperationResponse = z.strictObject({
54
+ cid: z.string(),
55
+ jwsToken: z.string()
56
+ });
57
+ var ResolveDocumentResponse = z.strictObject({
58
+ cid: z.string(),
59
+ content: z.unknown()
60
+ });
61
+ var RegistryError = z.strictObject({
62
+ error: z.string(),
63
+ message: z.string()
64
+ });
65
+
66
+ // src/registry/server.ts
67
+ import { Hono } from "hono";
68
+
69
+ // src/registry/store.ts
70
+ var ChainStore = class {
71
+ identities = /* @__PURE__ */ new Map();
72
+ entities = /* @__PURE__ */ new Map();
73
+ documents = /* @__PURE__ */ new Map();
74
+ // --- identities ---
75
+ getIdentityChain(did) {
76
+ return this.identities.get(did);
77
+ }
78
+ /**
79
+ * Submit an identity chain. Returns 'accepted' | 'noop' | 'conflict'.
80
+ * - accepted: chain was stored or extended
81
+ * - noop: submitted chain is same as or prefix of stored chain
82
+ * - conflict: submitted chain diverges from stored chain (fork)
83
+ */
84
+ submitIdentityChain(did, operations) {
85
+ return this.submitChain(this.identities, did, operations);
86
+ }
87
+ // --- entities ---
88
+ getEntityChain(entityId) {
89
+ return this.entities.get(entityId);
90
+ }
91
+ submitEntityChain(entityId, operations) {
92
+ return this.submitChain(this.entities, entityId, operations);
93
+ }
94
+ // --- documents ---
95
+ getDocument(cid) {
96
+ return this.documents.get(cid);
97
+ }
98
+ setDocument(cid, content) {
99
+ this.documents.set(cid, content);
100
+ }
101
+ // --- operations (lookup across all chains) ---
102
+ getOperation(cid) {
103
+ for (const chain of this.identities.values()) {
104
+ const op = chain.operations.find((o) => o.cid === cid);
105
+ if (op) return op;
106
+ }
107
+ for (const chain of this.entities.values()) {
108
+ const op = chain.operations.find((o) => o.cid === cid);
109
+ if (op) return op;
110
+ }
111
+ return void 0;
112
+ }
113
+ // --- shared chain submission logic ---
114
+ submitChain(store, id, operations) {
115
+ const existing = store.get(id);
116
+ if (!existing) {
117
+ store.set(id, { operations });
118
+ return "accepted";
119
+ }
120
+ if (operations.length <= existing.operations.length) {
121
+ for (let i = 0; i < operations.length; i++) {
122
+ if (operations[i].cid !== existing.operations[i].cid) {
123
+ return "conflict";
124
+ }
125
+ }
126
+ return "noop";
127
+ }
128
+ for (let i = 0; i < existing.operations.length; i++) {
129
+ if (operations[i].cid !== existing.operations[i].cid) {
130
+ return "conflict";
131
+ }
132
+ }
133
+ store.set(id, { operations });
134
+ return "accepted";
135
+ }
136
+ };
137
+
138
+ // src/registry/server.ts
139
+ var err = (code, message) => ({
140
+ error: code,
141
+ message
142
+ });
143
+ var extractOperationEntries = async (chain) => {
144
+ const entries = [];
145
+ for (const jwsToken of chain) {
146
+ const decoded = decodeJwsUnsafe(jwsToken);
147
+ if (!decoded) throw new Error("invalid JWS token");
148
+ const payload = decoded.payload;
149
+ const encoded = await dagCborCanonicalEncode(payload);
150
+ entries.push({
151
+ cid: encoded.cid.toString(),
152
+ jwsToken,
153
+ createdAt: payload.createdAt ?? ""
154
+ });
155
+ }
156
+ return entries;
157
+ };
158
+ var paginate = (operations, cursor, limit) => {
159
+ const reversed = [...operations].reverse();
160
+ let startIdx = 0;
161
+ if (cursor) {
162
+ const cursorIdx = reversed.findIndex((op) => op.cid === cursor);
163
+ if (cursorIdx >= 0) startIdx = cursorIdx + 1;
164
+ }
165
+ const page = reversed.slice(startIdx, startIdx + limit);
166
+ const hasMore = startIdx + limit < reversed.length;
167
+ return {
168
+ operations: page,
169
+ nextCursor: hasMore ? page[page.length - 1].cid : null
170
+ };
171
+ };
172
+ var paginationParams = (c) => ({
173
+ cursor: c.query("cursor"),
174
+ limit: Math.min(Math.max(parseInt(c.query("limit") ?? "25"), 1), 100)
175
+ });
176
+ var createKeyResolver = (store) => async (kid) => {
177
+ const hashIdx = kid.indexOf("#");
178
+ if (hashIdx < 0) throw new Error(`invalid kid format: ${kid}`);
179
+ const did = kid.substring(0, hashIdx);
180
+ const keyId = kid.substring(hashIdx + 1);
181
+ const identityChain = store.getIdentityChain(did);
182
+ if (!identityChain) throw new Error(`identity not found: ${did}`);
183
+ const identity = await verifyIdentityChain({
184
+ didPrefix: "did:dfos",
185
+ log: identityChain.operations.map((o) => o.jwsToken)
186
+ });
187
+ const allKeys = [...identity.authKeys, ...identity.assertKeys, ...identity.controllerKeys];
188
+ const key = allKeys.find((k) => k.id === keyId);
189
+ if (!key) throw new Error(`key not found: ${keyId}`);
190
+ return decodeMultikey(key.publicKeyMultibase).keyBytes;
191
+ };
192
+ var createRegistryServer = (store = new ChainStore()) => {
193
+ const app = new Hono();
194
+ const resolveKey = createKeyResolver(store);
195
+ app.post("/identities", async (c) => {
196
+ const body = await c.req.json();
197
+ if (!body.chain || !Array.isArray(body.chain) || body.chain.length === 0) {
198
+ return c.json(err("BAD_REQUEST", "chain must be a non-empty array of JWS tokens"), 400);
199
+ }
200
+ let verified;
201
+ try {
202
+ verified = await verifyIdentityChain({
203
+ didPrefix: "did:dfos",
204
+ log: body.chain
205
+ });
206
+ } catch (e) {
207
+ return c.json(err("BAD_REQUEST", `chain verification failed: ${e.message}`), 400);
208
+ }
209
+ const operations = await extractOperationEntries(body.chain);
210
+ const result = store.submitIdentityChain(verified.did, operations);
211
+ if (result === "conflict") {
212
+ return c.json(err("CONFLICT", "submitted chain conflicts with stored chain"), 409);
213
+ }
214
+ return c.json(
215
+ {
216
+ did: verified.did,
217
+ isDeleted: verified.isDeleted,
218
+ authKeys: verified.authKeys,
219
+ assertKeys: verified.assertKeys,
220
+ controllerKeys: verified.controllerKeys
221
+ },
222
+ result === "accepted" ? 201 : 200
223
+ );
224
+ });
225
+ app.get("/identities/:did", async (c) => {
226
+ const did = c.req.param("did");
227
+ const chain = store.getIdentityChain(did);
228
+ if (!chain) return c.json(err("NOT_FOUND", "identity not found"), 404);
229
+ const verified = await verifyIdentityChain({
230
+ didPrefix: "did:dfos",
231
+ log: chain.operations.map((o) => o.jwsToken)
232
+ });
233
+ return c.json({
234
+ did: verified.did,
235
+ isDeleted: verified.isDeleted,
236
+ authKeys: verified.authKeys,
237
+ assertKeys: verified.assertKeys,
238
+ controllerKeys: verified.controllerKeys
239
+ });
240
+ });
241
+ app.get("/identities/:did/operations", (c) => {
242
+ const did = c.req.param("did");
243
+ const chain = store.getIdentityChain(did);
244
+ if (!chain) return c.json(err("NOT_FOUND", "identity not found"), 404);
245
+ const { cursor, limit } = paginationParams(c.req);
246
+ return c.json(paginate(chain.operations, cursor, limit));
247
+ });
248
+ app.post("/entities", async (c) => {
249
+ const body = await c.req.json();
250
+ if (!body.chain || !Array.isArray(body.chain) || body.chain.length === 0) {
251
+ return c.json(err("BAD_REQUEST", "chain must be a non-empty array of JWS tokens"), 400);
252
+ }
253
+ const operations = await extractOperationEntries(body.chain);
254
+ let verified;
255
+ try {
256
+ verified = await verifyContentChain({ log: body.chain, resolveKey });
257
+ } catch (e) {
258
+ return c.json(err("BAD_REQUEST", `chain verification failed: ${e.message}`), 400);
259
+ }
260
+ const result = store.submitEntityChain(verified.entityId, operations);
261
+ if (result === "conflict") {
262
+ return c.json(err("CONFLICT", "submitted chain conflicts with stored chain"), 409);
263
+ }
264
+ return c.json(
265
+ {
266
+ entityId: verified.entityId,
267
+ isDeleted: verified.isDeleted,
268
+ currentDocumentCID: verified.currentDocumentCID,
269
+ genesisCID: verified.genesisCID,
270
+ headCID: verified.headCID
271
+ },
272
+ result === "accepted" ? 201 : 200
273
+ );
274
+ });
275
+ app.get("/entities/:entityId", (c) => {
276
+ const entityId = c.req.param("entityId");
277
+ const chain = store.getEntityChain(entityId);
278
+ if (!chain) return c.json(err("NOT_FOUND", "entity not found"), 404);
279
+ const genesis = chain.operations[0];
280
+ const head = chain.operations[chain.operations.length - 1];
281
+ const headDecoded = decodeJwsUnsafe(head.jwsToken);
282
+ const headPayload = headDecoded?.payload;
283
+ const headType = headPayload?.type;
284
+ return c.json({
285
+ entityId,
286
+ isDeleted: headType === "delete",
287
+ currentDocumentCID: headType === "delete" ? null : headPayload?.documentCID ?? null,
288
+ genesisCID: genesis.cid,
289
+ headCID: head.cid
290
+ });
291
+ });
292
+ app.get("/entities/:entityId/operations", (c) => {
293
+ const entityId = c.req.param("entityId");
294
+ const chain = store.getEntityChain(entityId);
295
+ if (!chain) return c.json(err("NOT_FOUND", "entity not found"), 404);
296
+ const { cursor, limit } = paginationParams(c.req);
297
+ return c.json(paginate(chain.operations, cursor, limit));
298
+ });
299
+ app.get("/operations/:cid", (c) => {
300
+ const cid = c.req.param("cid");
301
+ const op = store.getOperation(cid);
302
+ if (!op) return c.json(err("NOT_FOUND", "operation not found"), 404);
303
+ return c.json({ cid: op.cid, jwsToken: op.jwsToken });
304
+ });
305
+ app.get("/documents/:cid", (c) => {
306
+ const cid = c.req.param("cid");
307
+ const content = store.getDocument(cid);
308
+ if (content === void 0) return c.json(err("NOT_FOUND", "document not found"), 404);
309
+ return c.json({ cid, content });
310
+ });
311
+ return { app, store };
312
+ };
313
+
314
+ export {
315
+ SubmitIdentityChainRequest,
316
+ SubmitIdentityChainResponse,
317
+ ResolveIdentityResponse,
318
+ IdentityOperationsParams,
319
+ IdentityOperationsResponse,
320
+ SubmitContentChainRequest,
321
+ SubmitContentChainResponse,
322
+ ResolveEntityResponse,
323
+ EntityOperationsParams,
324
+ EntityOperationsResponse,
325
+ ResolveOperationResponse,
326
+ ResolveDocumentResponse,
327
+ RegistryError,
328
+ ChainStore,
329
+ createRegistryServer
330
+ };