@seed-hypermedia/client 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/dist/index.mjs ADDED
@@ -0,0 +1,891 @@
1
+ import {
2
+ HMActionSchema,
3
+ HMRequestSchema,
4
+ HYPERMEDIA_SCHEME,
5
+ entityQueryPathToHmIdPath,
6
+ getHMQueryString,
7
+ hmIdPathToEntityQueryPath,
8
+ packBaseId,
9
+ packHmId,
10
+ serializeBlockRange
11
+ } from "./chunk-YUKHBJYL.mjs";
12
+
13
+ // src/change.ts
14
+ import { decode as cborDecode, encode as cborEncode3 } from "@ipld/dag-cbor";
15
+ import { CID as CID2 } from "multiformats";
16
+ import * as Block from "multiformats/block";
17
+ import { sha256 } from "multiformats/hashes/sha2";
18
+
19
+ // src/ref.ts
20
+ import { encode as cborEncode2 } from "@ipld/dag-cbor";
21
+ import { CID } from "multiformats";
22
+ import { base58btc } from "multiformats/bases/base58";
23
+
24
+ // src/signing.ts
25
+ import { encode as cborEncode } from "@ipld/dag-cbor";
26
+ var cborCodec = {
27
+ code: 113,
28
+ encode: (input) => cborEncode(input),
29
+ name: "DAG-CBOR"
30
+ };
31
+ function normalizeBytes(data) {
32
+ const normalized = new Uint8Array(data.byteLength);
33
+ normalized.set(data);
34
+ return normalized;
35
+ }
36
+ async function signObject(signer, data) {
37
+ return await signer.sign(cborEncode(data));
38
+ }
39
+ function toPublishInput(blobData, extraBlobs) {
40
+ return {
41
+ blobs: [
42
+ { data: normalizeBytes(blobData) },
43
+ ...(extraBlobs || []).map((b) => ({
44
+ ...b.cid ? { cid: b.cid } : {},
45
+ data: normalizeBytes(b.data)
46
+ }))
47
+ ]
48
+ };
49
+ }
50
+
51
+ // src/ref.ts
52
+ function bytesEqual(a, b) {
53
+ if (a.length !== b.length) return false;
54
+ for (let i = 0; i < a.length; i++) {
55
+ if (a[i] !== b[i]) return false;
56
+ }
57
+ return true;
58
+ }
59
+ function buildUnsignedRef({
60
+ signerKey,
61
+ space,
62
+ path,
63
+ genesis,
64
+ generation,
65
+ capability
66
+ }) {
67
+ const signerBytes = new Uint8Array(signerKey);
68
+ const spaceBytes = new Uint8Array(base58btc.decode(space));
69
+ const unsigned = {
70
+ type: "Ref",
71
+ signer: signerBytes,
72
+ ts: BigInt(Date.now()),
73
+ sig: new Uint8Array(64),
74
+ genesisBlob: CID.parse(genesis),
75
+ generation
76
+ };
77
+ if (!bytesEqual(spaceBytes, signerBytes)) {
78
+ unsigned.space = spaceBytes;
79
+ }
80
+ if (path) {
81
+ unsigned.path = path;
82
+ }
83
+ if (capability) {
84
+ unsigned.capability = CID.parse(capability);
85
+ }
86
+ return unsigned;
87
+ }
88
+ async function createVersionRef(input, signer) {
89
+ const signerKey = await signer.getPublicKey();
90
+ const unsigned = buildUnsignedRef({
91
+ signerKey,
92
+ space: input.space,
93
+ path: input.path,
94
+ genesis: input.genesis,
95
+ generation: input.generation,
96
+ capability: input.capability
97
+ });
98
+ unsigned.heads = input.version.split(".").map((v) => CID.parse(v));
99
+ unsigned.sig = await signObject(signer, unsigned);
100
+ return toPublishInput(cborEncode2(unsigned));
101
+ }
102
+ async function createTombstoneRef(input, signer) {
103
+ const signerKey = await signer.getPublicKey();
104
+ const unsigned = buildUnsignedRef({
105
+ signerKey,
106
+ space: input.space,
107
+ path: input.path,
108
+ genesis: input.genesis,
109
+ generation: input.generation,
110
+ capability: input.capability
111
+ });
112
+ unsigned.heads = [];
113
+ unsigned.sig = await signObject(signer, unsigned);
114
+ return toPublishInput(cborEncode2(unsigned));
115
+ }
116
+ async function createRedirectRef(input, signer) {
117
+ const signerKey = await signer.getPublicKey();
118
+ const unsigned = buildUnsignedRef({
119
+ signerKey,
120
+ space: input.space,
121
+ path: input.path,
122
+ genesis: input.genesis,
123
+ generation: input.generation,
124
+ capability: input.capability
125
+ });
126
+ unsigned.heads = [];
127
+ const redirect = {
128
+ path: input.targetPath
129
+ };
130
+ if (input.targetSpace && input.targetSpace !== input.space) {
131
+ redirect.space = new Uint8Array(base58btc.decode(input.targetSpace));
132
+ }
133
+ if (input.republish) {
134
+ redirect.republish = true;
135
+ }
136
+ unsigned.redirect = redirect;
137
+ unsigned.sig = await signObject(signer, unsigned);
138
+ return toPublishInput(cborEncode2(unsigned));
139
+ }
140
+
141
+ // src/change.ts
142
+ function createChangeOps(input) {
143
+ const ts = input.ts ?? BigInt(Date.now());
144
+ const unsigned = {
145
+ type: "Change",
146
+ body: {
147
+ ops: input.ops,
148
+ opCount: input.ops.length
149
+ },
150
+ signer: null,
151
+ ts,
152
+ sig: null,
153
+ genesis: input.genesisCid,
154
+ deps: input.deps,
155
+ depth: input.depth
156
+ };
157
+ return { unsignedBytes: cborEncode3(unsigned), ts };
158
+ }
159
+ async function createChange(unsignedBytes, signer) {
160
+ const change = cborDecode(unsignedBytes);
161
+ const genesis = change.genesis instanceof CID2 ? change.genesis : null;
162
+ change.signer = new Uint8Array(await signer.getPublicKey());
163
+ change.sig = new Uint8Array(64);
164
+ change.sig = await signer.sign(cborEncode3(change));
165
+ const block = await Block.encode({ value: change, codec: cborCodec, hasher: sha256 });
166
+ return { bytes: block.bytes, cid: block.cid, genesis };
167
+ }
168
+ var signPreparedChange = async (unsignedBytes, signer) => {
169
+ const result = await createChange(unsignedBytes, signer);
170
+ return { signedBytes: result.bytes, cid: result.cid, genesis: result.genesis };
171
+ };
172
+ async function createGenesisChange(signer) {
173
+ const pubKey = await signer.getPublicKey();
174
+ const unsigned = {
175
+ type: "Change",
176
+ signer: new Uint8Array(pubKey),
177
+ sig: new Uint8Array(64),
178
+ ts: BigInt(0)
179
+ };
180
+ unsigned.sig = await signer.sign(cborEncode3(unsigned));
181
+ const block = await Block.encode({ value: unsigned, codec: cborCodec, hasher: sha256 });
182
+ return { bytes: block.bytes, cid: block.cid };
183
+ }
184
+ async function createDocumentChangeFromOps(input, signer) {
185
+ const { unsignedBytes, ts } = createChangeOps(input);
186
+ const { bytes, cid } = await createChange(unsignedBytes, signer);
187
+ return { bytes, cid, ts };
188
+ }
189
+ async function createDocumentChange(input, signer) {
190
+ const ops = protoChangesToOps(input.changes);
191
+ const { unsignedBytes } = createChangeOps({ ops, genesisCid: input.genesisCid, deps: input.deps, depth: input.depth });
192
+ const { bytes, cid } = await createChange(unsignedBytes, signer);
193
+ return { bytes, cid };
194
+ }
195
+ function protoChangesToOps(changes) {
196
+ return changes.map((change) => {
197
+ const op = change.op;
198
+ if (!op || !op.case) throw new Error("Invalid change: missing op");
199
+ switch (op.case) {
200
+ case "setMetadata":
201
+ return {
202
+ type: "SetAttributes",
203
+ attrs: [{ key: [op.value.key], value: op.value.value }]
204
+ };
205
+ default:
206
+ throw new Error(
207
+ `Op "${op.case}" is not supported for client-side change creation. Use PrepareDocumentChange + createChange instead.`
208
+ );
209
+ }
210
+ });
211
+ }
212
+ async function signDocumentChange(input, signer) {
213
+ const { bytes: signedBytes, cid: changeCid, genesis: changeGenesis } = await createChange(input.unsignedChange, signer);
214
+ const effectiveGenesis = input.genesis || (changeGenesis ? changeGenesis.toString() : changeCid.toString());
215
+ const effectiveGeneration = input.generation != null ? Number(input.generation) : Date.now();
216
+ const refBlobs = await createVersionRef(
217
+ {
218
+ space: input.account,
219
+ path: input.path ?? "",
220
+ genesis: effectiveGenesis,
221
+ version: changeCid.toString(),
222
+ generation: effectiveGeneration,
223
+ capability: input.capability
224
+ },
225
+ signer
226
+ );
227
+ return {
228
+ changeCid,
229
+ publishInput: {
230
+ blobs: [{ data: normalizeBytes(signedBytes), cid: changeCid.toString() }, ...refBlobs.blobs]
231
+ }
232
+ };
233
+ }
234
+
235
+ // src/client.ts
236
+ import { encode as cborEncode4 } from "@ipld/dag-cbor";
237
+ import { deserialize } from "superjson";
238
+
239
+ // src/errors.ts
240
+ var SeedClientError = class extends Error {
241
+ status;
242
+ body;
243
+ constructor(message, status, body) {
244
+ super(message);
245
+ this.name = "SeedClientError";
246
+ this.status = status;
247
+ this.body = body;
248
+ }
249
+ };
250
+ var SeedNetworkError = class extends Error {
251
+ constructor(message, options) {
252
+ super(message, options);
253
+ this.name = "SeedNetworkError";
254
+ }
255
+ };
256
+ var SeedValidationError = class extends Error {
257
+ constructor(message) {
258
+ super(message);
259
+ this.name = "SeedValidationError";
260
+ }
261
+ };
262
+
263
+ // src/client.ts
264
+ var PRIMITIVE_VALUE_KEY = "__value";
265
+ function serializeQueryString(data, _schema) {
266
+ const params = new URLSearchParams();
267
+ if (typeof data !== "object" || data === null) {
268
+ if (data !== void 0 && data !== null) {
269
+ params.append(PRIMITIVE_VALUE_KEY, String(data));
270
+ }
271
+ const queryString2 = params.toString();
272
+ return queryString2 ? `?${queryString2}` : "";
273
+ }
274
+ for (const [key, value] of Object.entries(data)) {
275
+ if (value === void 0 || value === null) {
276
+ continue;
277
+ }
278
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
279
+ params.append(key, String(value));
280
+ } else {
281
+ params.append(key, JSON.stringify(value));
282
+ }
283
+ }
284
+ const queryString = params.toString();
285
+ return queryString ? `?${queryString}` : "";
286
+ }
287
+ var QUERY_PARAM_SERIALIZERS = {
288
+ Account: (input) => ({
289
+ id: input
290
+ }),
291
+ Resource: (input) => ({
292
+ id: packHmId(input)
293
+ }),
294
+ ResourceMetadata: (input) => ({
295
+ id: packHmId(input)
296
+ }),
297
+ ListCitations: (input) => ({
298
+ targetId: packHmId(input.targetId)
299
+ }),
300
+ ListChanges: (input) => ({
301
+ targetId: packHmId(input.targetId)
302
+ }),
303
+ ListCapabilities: (input) => ({
304
+ targetId: packHmId(input.targetId)
305
+ })
306
+ };
307
+ function createSeedClient(baseUrl, options) {
308
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
309
+ const fetchFn = (options == null ? void 0 : options.fetch) ?? globalThis.fetch;
310
+ const defaultHeaders = (options == null ? void 0 : options.headers) ?? {};
311
+ async function request(key, input) {
312
+ const requestSchema = HMRequestSchema.options.find((schema) => schema.shape.key.value === key);
313
+ if (!requestSchema) {
314
+ throw new SeedValidationError(`Unknown request key: ${key}`);
315
+ }
316
+ let validatedInput;
317
+ try {
318
+ validatedInput = requestSchema.shape.input.parse(input);
319
+ } catch (err) {
320
+ throw new SeedValidationError(`Invalid input for ${key}: ${err instanceof Error ? err.message : String(err)}`);
321
+ }
322
+ let response;
323
+ const isAction = HMActionSchema.options.some((s) => s.shape.key.value === key);
324
+ if (isAction) {
325
+ const url = `${normalizedBaseUrl}/api/${key}`;
326
+ try {
327
+ response = await fetchFn(url, {
328
+ method: "POST",
329
+ headers: {
330
+ Accept: "application/json",
331
+ "Content-Type": "application/cbor",
332
+ ...defaultHeaders
333
+ },
334
+ body: new Uint8Array(cborEncode4(stripUndefined(validatedInput)))
335
+ });
336
+ } catch (err) {
337
+ throw new SeedNetworkError(
338
+ `Network error fetching ${key}: ${err instanceof Error ? err.message : String(err)}`,
339
+ { cause: err }
340
+ );
341
+ }
342
+ } else {
343
+ const inputToParams = QUERY_PARAM_SERIALIZERS[key];
344
+ let queryString;
345
+ if (inputToParams) {
346
+ const params = inputToParams(validatedInput);
347
+ const searchParams = new URLSearchParams(params);
348
+ queryString = searchParams.toString() ? `?${searchParams.toString()}` : "";
349
+ } else if (!validatedInput) {
350
+ queryString = "";
351
+ } else {
352
+ queryString = serializeQueryString(validatedInput, requestSchema.shape.input);
353
+ }
354
+ const url = `${normalizedBaseUrl}/api/${key}${queryString}`;
355
+ try {
356
+ response = await fetchFn(url, {
357
+ method: "GET",
358
+ headers: {
359
+ Accept: "application/json",
360
+ ...defaultHeaders
361
+ }
362
+ });
363
+ } catch (err) {
364
+ throw new SeedNetworkError(
365
+ `Network error fetching ${key}: ${err instanceof Error ? err.message : String(err)}`,
366
+ { cause: err }
367
+ );
368
+ }
369
+ }
370
+ if (!response.ok) {
371
+ let errorBody;
372
+ try {
373
+ errorBody = await response.text();
374
+ } catch {
375
+ }
376
+ throw new SeedClientError(
377
+ `HTTP ${response.status} from ${key}: ${response.statusText}`,
378
+ response.status,
379
+ errorBody
380
+ );
381
+ }
382
+ const rawJson = await response.json();
383
+ const deserialized = deserialize(rawJson);
384
+ return requestSchema.shape.output.parse(deserialized);
385
+ }
386
+ function publish(input) {
387
+ return request("PublishBlobs", input);
388
+ }
389
+ async function publishDocument(input, signer) {
390
+ if (!input.genesis && !input.baseVersion) {
391
+ const genesisChange = await createGenesisChange(signer);
392
+ const contentChange = await createDocumentChange(
393
+ {
394
+ changes: input.changes,
395
+ genesisCid: genesisChange.cid,
396
+ deps: [genesisChange.cid],
397
+ depth: 1
398
+ },
399
+ signer
400
+ );
401
+ const ref = await createVersionRef(
402
+ {
403
+ space: input.account,
404
+ path: input.path ?? "",
405
+ genesis: genesisChange.cid.toString(),
406
+ version: contentChange.cid.toString(),
407
+ generation: input.generation != null ? Number(input.generation) : 1,
408
+ capability: input.capability
409
+ },
410
+ signer
411
+ );
412
+ await publish({
413
+ blobs: [
414
+ { data: genesisChange.bytes, cid: genesisChange.cid.toString() },
415
+ { data: contentChange.bytes, cid: contentChange.cid.toString() },
416
+ ...ref.blobs
417
+ ]
418
+ });
419
+ return;
420
+ }
421
+ const { unsignedChange } = await request("PrepareDocumentChange", {
422
+ account: input.account,
423
+ path: input.path,
424
+ baseVersion: input.baseVersion,
425
+ changes: input.changes,
426
+ capability: input.capability,
427
+ visibility: input.visibility
428
+ });
429
+ const { publishInput } = await signDocumentChange(
430
+ {
431
+ account: input.account,
432
+ path: input.path,
433
+ unsignedChange,
434
+ genesis: input.genesis,
435
+ generation: input.generation,
436
+ capability: input.capability
437
+ },
438
+ signer
439
+ );
440
+ await publish(publishInput);
441
+ }
442
+ return {
443
+ baseUrl: normalizedBaseUrl,
444
+ request,
445
+ publish,
446
+ publishBlobs: publish,
447
+ publishDocument
448
+ };
449
+ }
450
+ function stripUndefined(obj) {
451
+ if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
452
+ if (ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer) return obj;
453
+ if (Array.isArray(obj)) return obj.map(stripUndefined);
454
+ const result = {};
455
+ for (const [k, v] of Object.entries(obj)) {
456
+ if (v !== void 0) result[k] = stripUndefined(v);
457
+ }
458
+ return result;
459
+ }
460
+
461
+ // src/comment.ts
462
+ import { encode as cborEncode5 } from "@ipld/dag-cbor";
463
+ import { CID as CID3 } from "multiformats";
464
+ import { base58btc as base58btc2 } from "multiformats/bases/base58";
465
+ function trimTrailingEmptyBlocks(blocks) {
466
+ let end = blocks.length;
467
+ while (end > 0) {
468
+ const node = blocks[end - 1];
469
+ if (!isEmptyBlockNode(node)) break;
470
+ end--;
471
+ }
472
+ return blocks.slice(0, end);
473
+ }
474
+ function isEmptyBlockNode(node) {
475
+ const { block, children } = node;
476
+ if (children && children.length > 0) return false;
477
+ if (block.type !== "Paragraph" && block.type !== "Heading") return false;
478
+ return !block.text || block.text.trim() === "";
479
+ }
480
+ function annotationsToPublishable(annotations) {
481
+ return annotations.map((annotation) => {
482
+ const { type, starts, ends } = annotation;
483
+ if (type === "Bold") return { type: "Bold", starts, ends };
484
+ if (type === "Italic") return { type: "Italic", starts, ends };
485
+ if (type === "Underline") return { type: "Underline", starts, ends };
486
+ if (type === "Strike") return { type: "Strike", starts, ends };
487
+ if (type === "Code") return { type: "Code", starts, ends };
488
+ if (type === "Link") return { type: "Link", starts, ends, link: annotation.link || "" };
489
+ if (type === "Embed") return { type: "Embed", starts, ends, link: annotation.link || "" };
490
+ throw new Error(`Unsupported annotation type: ${type}`);
491
+ });
492
+ }
493
+ function blockToPublishable(blockNode) {
494
+ const block = blockNode.block;
495
+ if (block.type === "Paragraph") {
496
+ if (block.text === "" || block.text === void 0) return null;
497
+ return {
498
+ id: block.id,
499
+ type: "Paragraph",
500
+ text: block.text,
501
+ annotations: annotationsToPublishable(block.annotations || []),
502
+ ...block.attributes,
503
+ children: blocksToPublishable(blockNode.children || [])
504
+ };
505
+ }
506
+ if (block.type === "Heading") {
507
+ return {
508
+ id: block.id,
509
+ type: "Heading",
510
+ text: block.text,
511
+ annotations: annotationsToPublishable(block.annotations || []),
512
+ ...block.attributes,
513
+ children: blocksToPublishable(blockNode.children || [])
514
+ };
515
+ }
516
+ if (block.type === "Math") {
517
+ return {
518
+ id: block.id,
519
+ type: "Math",
520
+ text: block.text,
521
+ annotations: annotationsToPublishable(block.annotations || []),
522
+ ...block.attributes,
523
+ children: blocksToPublishable(blockNode.children || [])
524
+ };
525
+ }
526
+ if (block.type === "Code") {
527
+ return {
528
+ id: block.id,
529
+ type: "Code",
530
+ text: block.text,
531
+ annotations: annotationsToPublishable(block.annotations || []),
532
+ ...block.attributes,
533
+ children: blocksToPublishable(blockNode.children || [])
534
+ };
535
+ }
536
+ if (block.type === "Image") {
537
+ return {
538
+ id: block.id,
539
+ type: "Image",
540
+ text: block.text,
541
+ link: block.link,
542
+ annotations: annotationsToPublishable(block.annotations || []),
543
+ ...block.attributes,
544
+ children: blocksToPublishable(blockNode.children || [])
545
+ };
546
+ }
547
+ if (block.type === "Video") {
548
+ return {
549
+ id: block.id,
550
+ type: "Video",
551
+ text: "",
552
+ link: block.link,
553
+ ...block.attributes,
554
+ children: blocksToPublishable(blockNode.children || [])
555
+ };
556
+ }
557
+ if (block.type === "File") {
558
+ return {
559
+ id: block.id,
560
+ type: "File",
561
+ link: block.link,
562
+ ...block.attributes,
563
+ children: blocksToPublishable(blockNode.children || [])
564
+ };
565
+ }
566
+ if (block.type === "Button") {
567
+ return {
568
+ id: block.id,
569
+ type: "Button",
570
+ text: block.text,
571
+ link: block.link,
572
+ ...block.attributes,
573
+ children: blocksToPublishable(blockNode.children || [])
574
+ };
575
+ }
576
+ if (block.type === "Embed") {
577
+ return {
578
+ id: block.id,
579
+ type: "Embed",
580
+ link: block.link,
581
+ ...block.attributes,
582
+ children: blocksToPublishable(blockNode.children || [])
583
+ };
584
+ }
585
+ if (block.type === "WebEmbed") {
586
+ return {
587
+ id: block.id,
588
+ type: "WebEmbed",
589
+ link: block.link,
590
+ ...block.attributes,
591
+ children: blocksToPublishable(blockNode.children || [])
592
+ };
593
+ }
594
+ throw new Error(`Unsupported block type: ${block.type}`);
595
+ }
596
+ function blocksToPublishable(blockNodes) {
597
+ return blockNodes.map((blockNode) => {
598
+ const block = blockToPublishable(blockNode);
599
+ if (!block) return null;
600
+ return block;
601
+ }).filter((blockNode) => blockNode !== null);
602
+ }
603
+ function cleanContentOfUndefined(content) {
604
+ content.forEach((blockNode) => {
605
+ const { block, children } = blockNode;
606
+ if (typeof block.text === "undefined") block.text = "";
607
+ if (children) cleanContentOfUndefined(children);
608
+ });
609
+ }
610
+ function createUnsignedComment({
611
+ content,
612
+ docId,
613
+ docVersion,
614
+ signerKey,
615
+ replyCommentVersion,
616
+ rootReplyCommentVersion,
617
+ visibility
618
+ }) {
619
+ const unsignedComment = {
620
+ type: "Comment",
621
+ body: blocksToPublishable(content),
622
+ space: new Uint8Array(base58btc2.decode(docId.uid)),
623
+ version: docVersion,
624
+ signer: new Uint8Array(signerKey),
625
+ ts: BigInt(Date.now()),
626
+ sig: new Uint8Array(64),
627
+ path: hmIdPathToEntityQueryPath(docId.path),
628
+ replyParent: replyCommentVersion || void 0,
629
+ threadRoot: rootReplyCommentVersion || void 0
630
+ };
631
+ if (!unsignedComment.replyParent) delete unsignedComment.replyParent;
632
+ if (!unsignedComment.threadRoot) delete unsignedComment.threadRoot;
633
+ if (visibility) unsignedComment.visibility = visibility;
634
+ return unsignedComment;
635
+ }
636
+ async function createSignedComment(comment, signer) {
637
+ const commentForSigning = {
638
+ ...comment,
639
+ version: comment.version.split(".").map((v) => CID3.parse(v))
640
+ };
641
+ if (comment.threadRoot) commentForSigning.threadRoot = CID3.parse(comment.threadRoot);
642
+ if (comment.replyParent) commentForSigning.replyParent = CID3.parse(comment.replyParent);
643
+ commentForSigning.sig = await signObject(signer, commentForSigning);
644
+ return commentForSigning;
645
+ }
646
+ async function createCommentBlob({
647
+ content,
648
+ docId,
649
+ docVersion,
650
+ signer,
651
+ replyCommentVersion,
652
+ rootReplyCommentVersion,
653
+ visibility
654
+ }) {
655
+ const signerKey = await signer.getPublicKey();
656
+ cleanContentOfUndefined(content);
657
+ const unsignedComment = createUnsignedComment({
658
+ content,
659
+ docId,
660
+ docVersion,
661
+ signerKey,
662
+ replyCommentVersion,
663
+ rootReplyCommentVersion,
664
+ visibility
665
+ });
666
+ const signedComment = await createSignedComment(unsignedComment, signer);
667
+ return cborEncode5(signedComment);
668
+ }
669
+ function generateBlockId(length = 8) {
670
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
671
+ let result = "";
672
+ for (let i = 0; i < length; i++) {
673
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
674
+ }
675
+ return result;
676
+ }
677
+ function wrapQuotedContent(content, input) {
678
+ if (!input.quotingBlockId) return content;
679
+ return [
680
+ {
681
+ block: {
682
+ id: generateBlockId(8),
683
+ type: "Embed",
684
+ text: "",
685
+ attributes: {
686
+ childrenType: "Group",
687
+ view: "Content"
688
+ },
689
+ annotations: [],
690
+ link: packHmId({
691
+ ...input.docId,
692
+ blockRef: input.quotingBlockId,
693
+ version: input.docVersion
694
+ })
695
+ },
696
+ children: content
697
+ }
698
+ ];
699
+ }
700
+ async function resolveCommentContentAndBlobs(input) {
701
+ const defaultPrepareAttachments = async (_binaries) => ({
702
+ blobs: [],
703
+ resultCIDs: []
704
+ });
705
+ if ("getContent" in input) {
706
+ const { blockNodes: rawBlockNodes, blobs } = await input.getContent(
707
+ input.prepareAttachments || defaultPrepareAttachments
708
+ );
709
+ return {
710
+ content: wrapQuotedContent(trimTrailingEmptyBlocks(rawBlockNodes), input),
711
+ blobs
712
+ };
713
+ }
714
+ return {
715
+ content: wrapQuotedContent(trimTrailingEmptyBlocks(input.content), input),
716
+ blobs: input.blobs || []
717
+ };
718
+ }
719
+ async function createComment(input, signer) {
720
+ const { content, blobs } = await resolveCommentContentAndBlobs(input);
721
+ const comment = await createCommentBlob({
722
+ content,
723
+ docId: input.docId,
724
+ docVersion: input.docVersion,
725
+ signer,
726
+ replyCommentVersion: input.replyCommentVersion,
727
+ rootReplyCommentVersion: input.rootReplyCommentVersion,
728
+ visibility: input.visibility
729
+ });
730
+ return toPublishInput(comment, blobs);
731
+ }
732
+ async function deleteComment(input, signer) {
733
+ const parts = input.commentId.split("/");
734
+ const tsid = parts[1];
735
+ if (!tsid) {
736
+ throw new Error(`Invalid comment ID format: ${input.commentId}`);
737
+ }
738
+ const signerKey = await signer.getPublicKey();
739
+ const tombstone = {
740
+ type: "Comment",
741
+ id: tsid,
742
+ body: [],
743
+ space: new Uint8Array(base58btc2.decode(input.targetAccount)),
744
+ path: input.targetPath,
745
+ version: input.targetVersion.split(".").map((v) => CID3.parse(v)),
746
+ signer: new Uint8Array(signerKey),
747
+ ts: BigInt(Date.now()),
748
+ sig: new Uint8Array(64)
749
+ };
750
+ if (input.visibility) tombstone.visibility = input.visibility;
751
+ tombstone.sig = await signObject(signer, tombstone);
752
+ const encoded = cborEncode5(tombstone);
753
+ return toPublishInput(encoded, []);
754
+ }
755
+
756
+ // src/contact.ts
757
+ import { encode as cborEncode6, decode as cborDecode2 } from "@ipld/dag-cbor";
758
+ import { base58btc as base58btc3 } from "multiformats/bases/base58";
759
+ function extractTsid(contactId) {
760
+ const parts = contactId.split("/");
761
+ const tsid = parts[1];
762
+ if (!tsid) {
763
+ throw new Error(`Invalid contact ID format: ${contactId}. Expected "authority/tsid".`);
764
+ }
765
+ return tsid;
766
+ }
767
+ async function computeTSID(timestampMs, blobData) {
768
+ const buf = new ArrayBuffer(8);
769
+ new DataView(buf).setBigUint64(0, timestampMs, false);
770
+ const tsBytes = new Uint8Array(buf, 2, 6);
771
+ const hashBuffer = await crypto.subtle.digest("SHA-256", blobData);
772
+ const hashBytes = new Uint8Array(hashBuffer, 0, 4);
773
+ const tsidBytes = new Uint8Array(10);
774
+ tsidBytes.set(tsBytes, 0);
775
+ tsidBytes.set(hashBytes, 6);
776
+ return base58btc3.encode(tsidBytes);
777
+ }
778
+ async function createContact(input, signer) {
779
+ const signerKey = await signer.getPublicKey();
780
+ const ts = BigInt(Date.now());
781
+ const unsigned = {
782
+ type: "Contact",
783
+ signer: new Uint8Array(signerKey),
784
+ sig: new Uint8Array(64),
785
+ ts,
786
+ subject: new Uint8Array(base58btc3.decode(input.subjectUid)),
787
+ name: input.name
788
+ };
789
+ unsigned.sig = await signObject(signer, unsigned);
790
+ const encoded = cborEncode6(unsigned);
791
+ const authority = base58btc3.encode(new Uint8Array(signerKey));
792
+ const tsid = await computeTSID(ts, encoded);
793
+ return {
794
+ ...toPublishInput(encoded),
795
+ recordId: `${authority}/${tsid}`
796
+ };
797
+ }
798
+ async function updateContact(input, signer) {
799
+ const signerKey = await signer.getPublicKey();
800
+ const tsid = extractTsid(input.contactId);
801
+ const unsigned = {
802
+ type: "Contact",
803
+ signer: new Uint8Array(signerKey),
804
+ sig: new Uint8Array(64),
805
+ ts: BigInt(Date.now()),
806
+ id: tsid,
807
+ subject: new Uint8Array(base58btc3.decode(input.subjectUid)),
808
+ name: input.name
809
+ };
810
+ unsigned.sig = await signObject(signer, unsigned);
811
+ return toPublishInput(cborEncode6(unsigned));
812
+ }
813
+ async function deleteContact(input, signer) {
814
+ const signerKey = await signer.getPublicKey();
815
+ const tsid = extractTsid(input.contactId);
816
+ const unsigned = {
817
+ type: "Contact",
818
+ signer: new Uint8Array(signerKey),
819
+ sig: new Uint8Array(64),
820
+ ts: BigInt(Date.now()),
821
+ id: tsid
822
+ };
823
+ unsigned.sig = await signObject(signer, unsigned);
824
+ return toPublishInput(cborEncode6(unsigned));
825
+ }
826
+ async function contactRecordIdFromBlob(blobData) {
827
+ const decoded = cborDecode2(blobData);
828
+ if (decoded.type !== "Contact") {
829
+ throw new Error(`Expected Contact blob, got "${decoded.type}"`);
830
+ }
831
+ const signerBytes = decoded.signer;
832
+ const ts = BigInt(decoded.ts);
833
+ const authority = base58btc3.encode(new Uint8Array(signerBytes));
834
+ const tsid = await computeTSID(ts, blobData);
835
+ return `${authority}/${tsid}`;
836
+ }
837
+
838
+ // src/capability.ts
839
+ import { encode as cborEncode7 } from "@ipld/dag-cbor";
840
+ import { base58btc as base58btc4 } from "multiformats/bases/base58";
841
+ async function createCapability(input, signer) {
842
+ const signerKey = await signer.getPublicKey();
843
+ const ts = BigInt(Date.now());
844
+ const unsigned = {
845
+ type: "Capability",
846
+ signer: new Uint8Array(signerKey),
847
+ sig: new Uint8Array(64),
848
+ ts,
849
+ delegate: new Uint8Array(base58btc4.decode(input.delegateUid)),
850
+ role: input.role
851
+ };
852
+ if (input.path) {
853
+ unsigned.path = input.path;
854
+ }
855
+ if (input.label) {
856
+ unsigned.label = input.label;
857
+ }
858
+ unsigned.sig = await signObject(signer, unsigned);
859
+ return toPublishInput(cborEncode7(unsigned));
860
+ }
861
+ export {
862
+ HYPERMEDIA_SCHEME,
863
+ SeedClientError,
864
+ SeedNetworkError,
865
+ SeedValidationError,
866
+ contactRecordIdFromBlob,
867
+ createCapability,
868
+ createChange,
869
+ createChangeOps,
870
+ createComment,
871
+ createContact,
872
+ createDocumentChange,
873
+ createDocumentChangeFromOps,
874
+ createGenesisChange,
875
+ createRedirectRef,
876
+ createSeedClient,
877
+ createTombstoneRef,
878
+ createVersionRef,
879
+ deleteComment,
880
+ deleteContact,
881
+ entityQueryPathToHmIdPath,
882
+ getHMQueryString,
883
+ hmIdPathToEntityQueryPath,
884
+ packBaseId,
885
+ packHmId,
886
+ serializeBlockRange,
887
+ signDocumentChange,
888
+ signPreparedChange,
889
+ trimTrailingEmptyBlocks,
890
+ updateContact
891
+ };