@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/LICENSE +201 -0
- package/dist/capability.d.ts +21 -0
- package/dist/change.d.ts +129 -0
- package/dist/chunk-YUKHBJYL.mjs +1044 -0
- package/dist/client.d.ts +37 -0
- package/dist/comment.d.ts +43 -0
- package/dist/contact.d.ts +42 -0
- package/dist/errors.d.ts +11 -0
- package/dist/hm-types.d.ts +43109 -0
- package/dist/hm-types.mjs +272 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +891 -0
- package/dist/ref.d.ts +65 -0
- package/dist/signing.d.ts +16 -0
- package/package.json +43 -0
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
|
+
};
|