@bcts/hubert 1.0.0-alpha.17

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.
Files changed (104) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +18 -0
  3. package/dist/arid-derivation-1CJuU-kZ.cjs +150 -0
  4. package/dist/arid-derivation-1CJuU-kZ.cjs.map +1 -0
  5. package/dist/arid-derivation-CbqACjdg.mjs +126 -0
  6. package/dist/arid-derivation-CbqACjdg.mjs.map +1 -0
  7. package/dist/bin/hubert.cjs +384 -0
  8. package/dist/bin/hubert.cjs.map +1 -0
  9. package/dist/bin/hubert.d.cts +1 -0
  10. package/dist/bin/hubert.d.mts +1 -0
  11. package/dist/bin/hubert.mjs +383 -0
  12. package/dist/bin/hubert.mjs.map +1 -0
  13. package/dist/chunk-CbDLau6x.cjs +34 -0
  14. package/dist/hybrid/index.cjs +14 -0
  15. package/dist/hybrid/index.d.cts +3 -0
  16. package/dist/hybrid/index.d.mts +3 -0
  17. package/dist/hybrid/index.mjs +6 -0
  18. package/dist/hybrid-BZhumygj.mjs +356 -0
  19. package/dist/hybrid-BZhumygj.mjs.map +1 -0
  20. package/dist/hybrid-dX5JLumO.cjs +410 -0
  21. package/dist/hybrid-dX5JLumO.cjs.map +1 -0
  22. package/dist/index-BEzpUC7r.d.mts +380 -0
  23. package/dist/index-BEzpUC7r.d.mts.map +1 -0
  24. package/dist/index-C2F6ugLL.d.mts +210 -0
  25. package/dist/index-C2F6ugLL.d.mts.map +1 -0
  26. package/dist/index-CUnDouMb.d.mts +215 -0
  27. package/dist/index-CUnDouMb.d.mts.map +1 -0
  28. package/dist/index-CV6lZJqY.d.cts +380 -0
  29. package/dist/index-CV6lZJqY.d.cts.map +1 -0
  30. package/dist/index-CY3TCzIm.d.cts +217 -0
  31. package/dist/index-CY3TCzIm.d.cts.map +1 -0
  32. package/dist/index-DEr4SR1J.d.cts +215 -0
  33. package/dist/index-DEr4SR1J.d.cts.map +1 -0
  34. package/dist/index-T1LHanIb.d.mts +217 -0
  35. package/dist/index-T1LHanIb.d.mts.map +1 -0
  36. package/dist/index-jyzuOhFB.d.cts +210 -0
  37. package/dist/index-jyzuOhFB.d.cts.map +1 -0
  38. package/dist/index.cjs +60 -0
  39. package/dist/index.d.cts +161 -0
  40. package/dist/index.d.cts.map +1 -0
  41. package/dist/index.d.mts +161 -0
  42. package/dist/index.d.mts.map +1 -0
  43. package/dist/index.mjs +10 -0
  44. package/dist/ipfs/index.cjs +13 -0
  45. package/dist/ipfs/index.d.cts +3 -0
  46. package/dist/ipfs/index.d.mts +3 -0
  47. package/dist/ipfs/index.mjs +5 -0
  48. package/dist/ipfs-BRMMCBjv.mjs +1 -0
  49. package/dist/ipfs-CetOVQcO.cjs +0 -0
  50. package/dist/kv-BAmhmMOo.cjs +425 -0
  51. package/dist/kv-BAmhmMOo.cjs.map +1 -0
  52. package/dist/kv-C-emxv0w.mjs +375 -0
  53. package/dist/kv-C-emxv0w.mjs.map +1 -0
  54. package/dist/kv-DJiKvypY.mjs +403 -0
  55. package/dist/kv-DJiKvypY.mjs.map +1 -0
  56. package/dist/kv-store-DmngWWuw.d.mts +183 -0
  57. package/dist/kv-store-DmngWWuw.d.mts.map +1 -0
  58. package/dist/kv-store-ww-AUyLd.d.cts +183 -0
  59. package/dist/kv-store-ww-AUyLd.d.cts.map +1 -0
  60. package/dist/kv-yjvQa_LH.cjs +457 -0
  61. package/dist/kv-yjvQa_LH.cjs.map +1 -0
  62. package/dist/logging-hmzNzifq.mjs +158 -0
  63. package/dist/logging-hmzNzifq.mjs.map +1 -0
  64. package/dist/logging-qc9uMgil.cjs +212 -0
  65. package/dist/logging-qc9uMgil.cjs.map +1 -0
  66. package/dist/mainline/index.cjs +12 -0
  67. package/dist/mainline/index.d.cts +3 -0
  68. package/dist/mainline/index.d.mts +3 -0
  69. package/dist/mainline/index.mjs +5 -0
  70. package/dist/mainline-D_jfeFMh.cjs +0 -0
  71. package/dist/mainline-cFIuXbo-.mjs +1 -0
  72. package/dist/server/index.cjs +14 -0
  73. package/dist/server/index.d.cts +3 -0
  74. package/dist/server/index.d.mts +3 -0
  75. package/dist/server/index.mjs +3 -0
  76. package/dist/server-BBNRZ30D.cjs +912 -0
  77. package/dist/server-BBNRZ30D.cjs.map +1 -0
  78. package/dist/server-DVyk9gqU.mjs +836 -0
  79. package/dist/server-DVyk9gqU.mjs.map +1 -0
  80. package/package.json +125 -0
  81. package/src/arid-derivation.ts +155 -0
  82. package/src/bin/hubert.ts +667 -0
  83. package/src/error.ts +89 -0
  84. package/src/hybrid/error.ts +77 -0
  85. package/src/hybrid/index.ts +24 -0
  86. package/src/hybrid/kv.ts +236 -0
  87. package/src/hybrid/reference.ts +176 -0
  88. package/src/index.ts +145 -0
  89. package/src/ipfs/error.ts +83 -0
  90. package/src/ipfs/index.ts +24 -0
  91. package/src/ipfs/kv.ts +476 -0
  92. package/src/ipfs/value.ts +85 -0
  93. package/src/kv-store.ts +128 -0
  94. package/src/logging.ts +88 -0
  95. package/src/mainline/error.ts +108 -0
  96. package/src/mainline/index.ts +23 -0
  97. package/src/mainline/kv.ts +411 -0
  98. package/src/server/error.ts +83 -0
  99. package/src/server/index.ts +29 -0
  100. package/src/server/kv.ts +211 -0
  101. package/src/server/memory-kv.ts +191 -0
  102. package/src/server/server-kv.ts +92 -0
  103. package/src/server/server.ts +369 -0
  104. package/src/server/sqlite-kv.ts +295 -0
@@ -0,0 +1,356 @@
1
+ import { i as verbosePrintln, o as HubertError } from "./logging-hmzNzifq.mjs";
2
+ import { t as IpfsKv } from "./kv-DJiKvypY.mjs";
3
+ import { t as MainlineDhtKv } from "./kv-C-emxv0w.mjs";
4
+ import { ARID } from "@bcts/components";
5
+ import { Envelope } from "@bcts/envelope";
6
+ import { KnownValue } from "@bcts/known-values";
7
+
8
+ //#region src/hybrid/error.ts
9
+ /**
10
+ * Hybrid-specific errors.
11
+ *
12
+ * Port of hybrid/error.rs from hubert-rust.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * Base class for Hybrid-specific errors.
18
+ *
19
+ * @category Hybrid
20
+ */
21
+ var HybridError = class extends HubertError {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "HybridError";
25
+ }
26
+ };
27
+ /**
28
+ * Referenced IPFS content not found.
29
+ *
30
+ * Port of `Error::ContentNotFound` from hybrid/error.rs line 4-5.
31
+ *
32
+ * @category Hybrid
33
+ */
34
+ var ContentNotFoundError = class extends HybridError {
35
+ constructor() {
36
+ super("Referenced IPFS content not found");
37
+ this.name = "ContentNotFoundError";
38
+ }
39
+ };
40
+ /**
41
+ * Not a reference envelope.
42
+ *
43
+ * Port of `Error::NotReferenceEnvelope` from hybrid/error.rs line 7-8.
44
+ *
45
+ * @category Hybrid
46
+ */
47
+ var NotReferenceEnvelopeError = class extends HybridError {
48
+ constructor() {
49
+ super("Not a reference envelope");
50
+ this.name = "NotReferenceEnvelopeError";
51
+ }
52
+ };
53
+ /**
54
+ * Invalid ARID in reference envelope.
55
+ *
56
+ * Port of `Error::InvalidReferenceArid` from hybrid/error.rs line 10-11.
57
+ *
58
+ * @category Hybrid
59
+ */
60
+ var InvalidReferenceAridError = class extends HybridError {
61
+ constructor() {
62
+ super("Invalid ARID in reference envelope");
63
+ this.name = "InvalidReferenceAridError";
64
+ }
65
+ };
66
+ /**
67
+ * No id assertion found in reference envelope.
68
+ *
69
+ * Port of `Error::NoIdAssertion` from hybrid/error.rs line 13-14.
70
+ *
71
+ * @category Hybrid
72
+ */
73
+ var NoIdAssertionError = class extends HybridError {
74
+ constructor() {
75
+ super("No id assertion found in reference envelope");
76
+ this.name = "NoIdAssertionError";
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ //#region src/hybrid/reference.ts
82
+ /**
83
+ * Reference envelope utilities for hybrid storage.
84
+ *
85
+ * Port of hybrid/reference.rs from hubert-rust.
86
+ *
87
+ * @module
88
+ */
89
+ /**
90
+ * Known value for dereferenceVia predicate.
91
+ * @internal
92
+ */
93
+ const DEREFERENCE_VIA = new KnownValue(8);
94
+ /**
95
+ * Known value for id predicate.
96
+ * @internal
97
+ */
98
+ const ID = new KnownValue(2);
99
+ /**
100
+ * Creates a reference envelope that points to content stored in IPFS.
101
+ *
102
+ * Reference envelopes are small envelopes stored in the DHT that contain
103
+ * a pointer to the actual envelope stored in IPFS. This allows the hybrid
104
+ * storage layer to transparently handle large envelopes that exceed the
105
+ * DHT size limit.
106
+ *
107
+ * Port of `create_reference_envelope()` from hybrid/reference.rs lines 31-39.
108
+ *
109
+ * # Format
110
+ *
111
+ * ```text
112
+ * '' [
113
+ * 'dereferenceVia': "ipfs",
114
+ * 'id': <ARID>,
115
+ * "size": <number>
116
+ * ]
117
+ * ```
118
+ *
119
+ * @param referenceArid - The ARID used to look up the actual envelope in IPFS
120
+ * @param actualSize - Size of the actual envelope in bytes (for diagnostics)
121
+ * @returns A reference envelope that can be stored in the DHT
122
+ *
123
+ * @category Hybrid
124
+ */
125
+ function createReferenceEnvelope(referenceArid, actualSize) {
126
+ return Envelope.unit().addAssertion(DEREFERENCE_VIA, "ipfs").addAssertion(ID, referenceArid).addAssertion("size", actualSize);
127
+ }
128
+ /**
129
+ * Checks if an envelope is a reference envelope.
130
+ *
131
+ * A reference envelope contains `dereferenceVia: "ipfs"` and an `id` assertion.
132
+ *
133
+ * Port of `is_reference_envelope()` from hybrid/reference.rs lines 53-91.
134
+ *
135
+ * @param envelope - The envelope to check
136
+ * @returns `true` if this is a reference envelope, `false` otherwise
137
+ *
138
+ * @category Hybrid
139
+ */
140
+ function isReferenceEnvelope(envelope) {
141
+ if (!envelope.isSubjectUnit) return false;
142
+ const assertions = envelope.assertions();
143
+ let hasDereferenceVia = false;
144
+ let hasId = false;
145
+ for (const assertion of assertions) try {
146
+ const predicateSubject = assertion.predicate().subject();
147
+ if (predicateSubject instanceof KnownValue) {
148
+ const kv = predicateSubject;
149
+ if (kv.value === DEREFERENCE_VIA.value) {
150
+ const objectSubject = assertion.object().subject();
151
+ if (typeof objectSubject === "string" && objectSubject === "ipfs") hasDereferenceVia = true;
152
+ }
153
+ if (kv.value === ID.value) hasId = true;
154
+ }
155
+ } catch {
156
+ continue;
157
+ }
158
+ return hasDereferenceVia && hasId;
159
+ }
160
+ /**
161
+ * Extracts the reference ARID from a reference envelope.
162
+ *
163
+ * Port of `extract_reference_arid()` from hybrid/reference.rs lines 104-129.
164
+ *
165
+ * @param envelope - The reference envelope
166
+ * @returns The reference ARID
167
+ * @throws {NotReferenceEnvelopeError} If the envelope is not a reference envelope
168
+ * @throws {InvalidReferenceAridError} If the ARID cannot be extracted
169
+ * @throws {NoIdAssertionError} If no id assertion is found
170
+ *
171
+ * @category Hybrid
172
+ */
173
+ function extractReferenceArid(envelope) {
174
+ if (!isReferenceEnvelope(envelope)) throw new NotReferenceEnvelopeError();
175
+ const assertions = envelope.assertions();
176
+ for (const assertion of assertions) try {
177
+ const predicateSubject = assertion.predicate().subject();
178
+ if (predicateSubject instanceof KnownValue) {
179
+ if (predicateSubject.value === ID.value) {
180
+ const objectSubject = assertion.object().subject();
181
+ if (objectSubject instanceof ARID) return objectSubject;
182
+ throw new InvalidReferenceAridError();
183
+ }
184
+ }
185
+ } catch (error) {
186
+ if (error instanceof NotReferenceEnvelopeError || error instanceof InvalidReferenceAridError || error instanceof NoIdAssertionError) throw error;
187
+ continue;
188
+ }
189
+ throw new NoIdAssertionError();
190
+ }
191
+
192
+ //#endregion
193
+ //#region src/hybrid/kv.ts
194
+ /**
195
+ * Hybrid storage layer combining Mainline DHT and IPFS.
196
+ *
197
+ * Port of hybrid/kv.rs from hubert-rust.
198
+ *
199
+ * @module
200
+ */
201
+ /**
202
+ * Hybrid storage layer combining Mainline DHT and IPFS.
203
+ *
204
+ * Automatically optimizes storage based on envelope size:
205
+ * - **Small envelopes (≤1000 bytes)**: Stored directly in DHT
206
+ * - **Large envelopes (>1000 bytes)**: Reference in DHT → actual envelope in IPFS
207
+ *
208
+ * This provides the best of both worlds:
209
+ * - Fast lookups for small messages via DHT
210
+ * - Large capacity for big messages via IPFS
211
+ * - Transparent indirection handled automatically
212
+ *
213
+ * Port of `struct HybridKv` from hybrid/kv.rs lines 59-63.
214
+ *
215
+ * # Requirements
216
+ *
217
+ * - No external daemon for DHT (embedded client)
218
+ * - Requires Kubo daemon for IPFS (http://127.0.0.1:5001)
219
+ *
220
+ * @category Hybrid Backend
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const store = await HybridKv.create("http://127.0.0.1:5001");
225
+ *
226
+ * // Small envelope → DHT only
227
+ * const arid1 = ARID.new();
228
+ * const small = Envelope.new("Small message");
229
+ * await store.put(arid1, small);
230
+ *
231
+ * // Large envelope → DHT reference + IPFS
232
+ * const arid2 = ARID.new();
233
+ * const large = Envelope.new("x".repeat(2000));
234
+ * await store.put(arid2, large);
235
+ *
236
+ * // Get works the same for both
237
+ * const retrieved1 = await store.get(arid1);
238
+ * const retrieved2 = await store.get(arid2);
239
+ * ```
240
+ */
241
+ var HybridKv = class HybridKv {
242
+ dht;
243
+ ipfs;
244
+ dhtSizeLimit;
245
+ /**
246
+ * Private constructor - use `create()` factory method.
247
+ */
248
+ constructor(dht, ipfs) {
249
+ this.dht = dht;
250
+ this.ipfs = ipfs;
251
+ this.dhtSizeLimit = 1e3;
252
+ }
253
+ /**
254
+ * Create a new Hybrid KV store with default settings.
255
+ *
256
+ * Port of `HybridKv::new()` from hybrid/kv.rs lines 75-84.
257
+ *
258
+ * @param ipfsRpcUrl - IPFS RPC endpoint (e.g., "http://127.0.0.1:5001")
259
+ */
260
+ static async create(ipfsRpcUrl) {
261
+ return new HybridKv(await MainlineDhtKv.create(), new IpfsKv(ipfsRpcUrl));
262
+ }
263
+ /**
264
+ * Set custom DHT size limit (default: 1000 bytes).
265
+ *
266
+ * Envelopes larger than this will use IPFS indirection.
267
+ *
268
+ * Port of `HybridKv::with_dht_size_limit()` from hybrid/kv.rs lines 89-92.
269
+ */
270
+ withDhtSizeLimit(limit) {
271
+ this.dhtSizeLimit = limit;
272
+ return this;
273
+ }
274
+ /**
275
+ * Set whether to pin content in IPFS (default: false).
276
+ *
277
+ * Only affects envelopes stored in IPFS (when larger than DHT limit).
278
+ *
279
+ * Port of `HybridKv::with_pin_content()` from hybrid/kv.rs lines 97-100.
280
+ */
281
+ withPinContent(pin) {
282
+ this.ipfs = this.ipfs.withPinContent(pin);
283
+ return this;
284
+ }
285
+ /**
286
+ * Check if an envelope fits in the DHT.
287
+ *
288
+ * Port of `HybridKv::fits_in_dht()` from hybrid/kv.rs lines 103-106.
289
+ *
290
+ * @internal
291
+ */
292
+ fitsInDht(envelope) {
293
+ return envelope.taggedCborData.length <= this.dhtSizeLimit;
294
+ }
295
+ /**
296
+ * Store an envelope at the given ARID.
297
+ *
298
+ * Port of `KvStore::put()` implementation from hybrid/kv.rs lines 109-168.
299
+ */
300
+ async put(arid, envelope, ttlSeconds, verbose) {
301
+ if (this.fitsInDht(envelope)) {
302
+ if (verbose) verbosePrintln(`Storing envelope in DHT (size ≤ ${this.dhtSizeLimit} bytes)`);
303
+ await this.dht.put(arid, envelope, ttlSeconds, verbose);
304
+ return `Stored in DHT at ARID: ${arid.urString()}`;
305
+ } else {
306
+ if (verbose) verbosePrintln("Envelope too large for DHT, using IPFS indirection");
307
+ const referenceArid = ARID.new();
308
+ if (verbose) verbosePrintln(`Storing actual envelope in IPFS with reference ARID: ${referenceArid.urString()}`);
309
+ await this.ipfs.put(referenceArid, envelope, ttlSeconds, verbose);
310
+ const envelopeSize = envelope.taggedCborData.length;
311
+ const reference = createReferenceEnvelope(referenceArid, envelopeSize);
312
+ if (verbose) verbosePrintln("Storing reference envelope in DHT at original ARID");
313
+ await this.dht.put(arid, reference, ttlSeconds, verbose);
314
+ return `Stored in IPFS (ref: ${referenceArid.urString()}) via DHT at ARID: ${arid.urString()}`;
315
+ }
316
+ }
317
+ /**
318
+ * Retrieve an envelope for the given ARID.
319
+ *
320
+ * Port of `KvStore::get()` implementation from hybrid/kv.rs lines 171-230.
321
+ */
322
+ async get(arid, timeoutSeconds, verbose) {
323
+ const dhtEnvelope = await this.dht.get(arid, timeoutSeconds, verbose);
324
+ if (dhtEnvelope === null) return null;
325
+ if (isReferenceEnvelope(dhtEnvelope)) {
326
+ if (verbose) verbosePrintln("Found reference envelope, fetching actual envelope from IPFS");
327
+ const referenceArid = extractReferenceArid(dhtEnvelope);
328
+ if (verbose) verbosePrintln(`Reference ARID: ${referenceArid.urString()}`);
329
+ const ipfsEnvelope = await this.ipfs.get(referenceArid, timeoutSeconds, verbose);
330
+ if (ipfsEnvelope === null) throw new ContentNotFoundError();
331
+ if (verbose) verbosePrintln("Successfully retrieved actual envelope from IPFS");
332
+ return ipfsEnvelope;
333
+ } else {
334
+ if (verbose) verbosePrintln("Envelope is not a reference, treating as direct payload");
335
+ return dhtEnvelope;
336
+ }
337
+ }
338
+ /**
339
+ * Check if an envelope exists at the given ARID.
340
+ *
341
+ * Port of `KvStore::exists()` implementation from hybrid/kv.rs lines 254-257.
342
+ */
343
+ async exists(arid) {
344
+ return await this.dht.exists(arid);
345
+ }
346
+ /**
347
+ * Destroy the hybrid store and release resources.
348
+ */
349
+ async destroy() {
350
+ await this.dht.destroy();
351
+ }
352
+ };
353
+
354
+ //#endregion
355
+ export { ContentNotFoundError as a, NoIdAssertionError as c, isReferenceEnvelope as i, NotReferenceEnvelopeError as l, createReferenceEnvelope as n, HybridError as o, extractReferenceArid as r, InvalidReferenceAridError as s, HybridKv as t };
356
+ //# sourceMappingURL=hybrid-BZhumygj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-BZhumygj.mjs","names":[],"sources":["../src/hybrid/error.ts","../src/hybrid/reference.ts","../src/hybrid/kv.ts"],"sourcesContent":["/**\n * Hybrid-specific errors.\n *\n * Port of hybrid/error.rs from hubert-rust.\n *\n * @module\n */\n\nimport { HubertError } from \"../error.js\";\n\n/**\n * Base class for Hybrid-specific errors.\n *\n * @category Hybrid\n */\nexport class HybridError extends HubertError {\n constructor(message: string) {\n super(message);\n this.name = \"HybridError\";\n }\n}\n\n/**\n * Referenced IPFS content not found.\n *\n * Port of `Error::ContentNotFound` from hybrid/error.rs line 4-5.\n *\n * @category Hybrid\n */\nexport class ContentNotFoundError extends HybridError {\n constructor() {\n super(\"Referenced IPFS content not found\");\n this.name = \"ContentNotFoundError\";\n }\n}\n\n/**\n * Not a reference envelope.\n *\n * Port of `Error::NotReferenceEnvelope` from hybrid/error.rs line 7-8.\n *\n * @category Hybrid\n */\nexport class NotReferenceEnvelopeError extends HybridError {\n constructor() {\n super(\"Not a reference envelope\");\n this.name = \"NotReferenceEnvelopeError\";\n }\n}\n\n/**\n * Invalid ARID in reference envelope.\n *\n * Port of `Error::InvalidReferenceArid` from hybrid/error.rs line 10-11.\n *\n * @category Hybrid\n */\nexport class InvalidReferenceAridError extends HybridError {\n constructor() {\n super(\"Invalid ARID in reference envelope\");\n this.name = \"InvalidReferenceAridError\";\n }\n}\n\n/**\n * No id assertion found in reference envelope.\n *\n * Port of `Error::NoIdAssertion` from hybrid/error.rs line 13-14.\n *\n * @category Hybrid\n */\nexport class NoIdAssertionError extends HybridError {\n constructor() {\n super(\"No id assertion found in reference envelope\");\n this.name = \"NoIdAssertionError\";\n }\n}\n","/**\n * Reference envelope utilities for hybrid storage.\n *\n * Port of hybrid/reference.rs from hubert-rust.\n *\n * @module\n */\n\nimport { ARID } from \"@bcts/components\";\nimport { Envelope } from \"@bcts/envelope\";\nimport { KnownValue } from \"@bcts/known-values\";\n\nimport {\n InvalidReferenceAridError,\n NoIdAssertionError,\n NotReferenceEnvelopeError,\n} from \"./error.js\";\n\n/**\n * Known value for dereferenceVia predicate.\n * @internal\n */\nconst DEREFERENCE_VIA = new KnownValue(8);\n\n/**\n * Known value for id predicate.\n * @internal\n */\nconst ID = new KnownValue(2);\n\n/**\n * Creates a reference envelope that points to content stored in IPFS.\n *\n * Reference envelopes are small envelopes stored in the DHT that contain\n * a pointer to the actual envelope stored in IPFS. This allows the hybrid\n * storage layer to transparently handle large envelopes that exceed the\n * DHT size limit.\n *\n * Port of `create_reference_envelope()` from hybrid/reference.rs lines 31-39.\n *\n * # Format\n *\n * ```text\n * '' [\n * 'dereferenceVia': \"ipfs\",\n * 'id': <ARID>,\n * \"size\": <number>\n * ]\n * ```\n *\n * @param referenceArid - The ARID used to look up the actual envelope in IPFS\n * @param actualSize - Size of the actual envelope in bytes (for diagnostics)\n * @returns A reference envelope that can be stored in the DHT\n *\n * @category Hybrid\n */\nexport function createReferenceEnvelope(referenceArid: ARID, actualSize: number): Envelope {\n return Envelope.unit()\n .addAssertion(DEREFERENCE_VIA, \"ipfs\")\n .addAssertion(ID, referenceArid)\n .addAssertion(\"size\", actualSize);\n}\n\n/**\n * Checks if an envelope is a reference envelope.\n *\n * A reference envelope contains `dereferenceVia: \"ipfs\"` and an `id` assertion.\n *\n * Port of `is_reference_envelope()` from hybrid/reference.rs lines 53-91.\n *\n * @param envelope - The envelope to check\n * @returns `true` if this is a reference envelope, `false` otherwise\n *\n * @category Hybrid\n */\nexport function isReferenceEnvelope(envelope: Envelope): boolean {\n // Check if subject is the unit value\n if (!envelope.isSubjectUnit) {\n return false;\n }\n\n const assertions = envelope.assertions();\n\n // Check for dereferenceVia: \"ipfs\" assertion\n let hasDereferenceVia = false;\n let hasId = false;\n\n for (const assertion of assertions) {\n try {\n const predicate = assertion.predicate();\n\n // Check if predicate is a known value\n const predicateSubject = predicate.subject();\n if (predicateSubject instanceof KnownValue) {\n const kv = predicateSubject;\n\n // Check for dereferenceVia\n if (kv.value === DEREFERENCE_VIA.value) {\n const object = assertion.object();\n const objectSubject = object.subject();\n if (typeof objectSubject === \"string\" && objectSubject === \"ipfs\") {\n hasDereferenceVia = true;\n }\n }\n\n // Check for id\n if (kv.value === ID.value) {\n hasId = true;\n }\n }\n } catch {\n // Skip assertions that can't be parsed\n continue;\n }\n }\n\n return hasDereferenceVia && hasId;\n}\n\n/**\n * Extracts the reference ARID from a reference envelope.\n *\n * Port of `extract_reference_arid()` from hybrid/reference.rs lines 104-129.\n *\n * @param envelope - The reference envelope\n * @returns The reference ARID\n * @throws {NotReferenceEnvelopeError} If the envelope is not a reference envelope\n * @throws {InvalidReferenceAridError} If the ARID cannot be extracted\n * @throws {NoIdAssertionError} If no id assertion is found\n *\n * @category Hybrid\n */\nexport function extractReferenceArid(envelope: Envelope): ARID {\n if (!isReferenceEnvelope(envelope)) {\n throw new NotReferenceEnvelopeError();\n }\n\n const assertions = envelope.assertions();\n\n // Find the id assertion and extract the ARID\n for (const assertion of assertions) {\n try {\n const predicate = assertion.predicate();\n const predicateSubject = predicate.subject();\n\n if (predicateSubject instanceof KnownValue) {\n const kv = predicateSubject;\n\n // Check for id\n if (kv.value === ID.value) {\n const object = assertion.object();\n const objectSubject = object.subject();\n\n if (objectSubject instanceof ARID) {\n return objectSubject;\n }\n\n // Try to extract ARID from CBOR\n throw new InvalidReferenceAridError();\n }\n }\n } catch (error) {\n if (\n error instanceof NotReferenceEnvelopeError ||\n error instanceof InvalidReferenceAridError ||\n error instanceof NoIdAssertionError\n ) {\n throw error;\n }\n // Continue searching\n continue;\n }\n }\n\n throw new NoIdAssertionError();\n}\n","/**\n * Hybrid storage layer combining Mainline DHT and IPFS.\n *\n * Port of hybrid/kv.rs from hubert-rust.\n *\n * @module\n */\n\nimport { ARID } from \"@bcts/components\";\nimport { type Envelope } from \"@bcts/envelope\";\n\nimport { type KvStore } from \"../kv-store.js\";\nimport { verbosePrintln } from \"../logging.js\";\nimport { IpfsKv } from \"../ipfs/kv.js\";\nimport { MainlineDhtKv } from \"../mainline/kv.js\";\nimport { ContentNotFoundError } from \"./error.js\";\nimport { createReferenceEnvelope, extractReferenceArid, isReferenceEnvelope } from \"./reference.js\";\n\n/**\n * Hybrid storage layer combining Mainline DHT and IPFS.\n *\n * Automatically optimizes storage based on envelope size:\n * - **Small envelopes (≤1000 bytes)**: Stored directly in DHT\n * - **Large envelopes (>1000 bytes)**: Reference in DHT → actual envelope in IPFS\n *\n * This provides the best of both worlds:\n * - Fast lookups for small messages via DHT\n * - Large capacity for big messages via IPFS\n * - Transparent indirection handled automatically\n *\n * Port of `struct HybridKv` from hybrid/kv.rs lines 59-63.\n *\n * # Requirements\n *\n * - No external daemon for DHT (embedded client)\n * - Requires Kubo daemon for IPFS (http://127.0.0.1:5001)\n *\n * @category Hybrid Backend\n *\n * @example\n * ```typescript\n * const store = await HybridKv.create(\"http://127.0.0.1:5001\");\n *\n * // Small envelope → DHT only\n * const arid1 = ARID.new();\n * const small = Envelope.new(\"Small message\");\n * await store.put(arid1, small);\n *\n * // Large envelope → DHT reference + IPFS\n * const arid2 = ARID.new();\n * const large = Envelope.new(\"x\".repeat(2000));\n * await store.put(arid2, large);\n *\n * // Get works the same for both\n * const retrieved1 = await store.get(arid1);\n * const retrieved2 = await store.get(arid2);\n * ```\n */\nexport class HybridKv implements KvStore {\n private readonly dht: MainlineDhtKv;\n private ipfs: IpfsKv;\n private dhtSizeLimit: number;\n\n /**\n * Private constructor - use `create()` factory method.\n */\n private constructor(dht: MainlineDhtKv, ipfs: IpfsKv) {\n this.dht = dht;\n this.ipfs = ipfs;\n this.dhtSizeLimit = 1000; // Conservative DHT limit\n }\n\n /**\n * Create a new Hybrid KV store with default settings.\n *\n * Port of `HybridKv::new()` from hybrid/kv.rs lines 75-84.\n *\n * @param ipfsRpcUrl - IPFS RPC endpoint (e.g., \"http://127.0.0.1:5001\")\n */\n static async create(ipfsRpcUrl: string): Promise<HybridKv> {\n const dht = await MainlineDhtKv.create();\n const ipfs = new IpfsKv(ipfsRpcUrl);\n\n return new HybridKv(dht, ipfs);\n }\n\n /**\n * Set custom DHT size limit (default: 1000 bytes).\n *\n * Envelopes larger than this will use IPFS indirection.\n *\n * Port of `HybridKv::with_dht_size_limit()` from hybrid/kv.rs lines 89-92.\n */\n withDhtSizeLimit(limit: number): this {\n this.dhtSizeLimit = limit;\n return this;\n }\n\n /**\n * Set whether to pin content in IPFS (default: false).\n *\n * Only affects envelopes stored in IPFS (when larger than DHT limit).\n *\n * Port of `HybridKv::with_pin_content()` from hybrid/kv.rs lines 97-100.\n */\n withPinContent(pin: boolean): this {\n this.ipfs = this.ipfs.withPinContent(pin);\n return this;\n }\n\n /**\n * Check if an envelope fits in the DHT.\n *\n * Port of `HybridKv::fits_in_dht()` from hybrid/kv.rs lines 103-106.\n *\n * @internal\n */\n private fitsInDht(envelope: Envelope): boolean {\n const serialized = envelope.taggedCborData;\n return serialized.length <= this.dhtSizeLimit;\n }\n\n /**\n * Store an envelope at the given ARID.\n *\n * Port of `KvStore::put()` implementation from hybrid/kv.rs lines 109-168.\n */\n async put(\n arid: ARID,\n envelope: Envelope,\n ttlSeconds?: number,\n verbose?: boolean,\n ): Promise<string> {\n // Check if it fits in DHT\n if (this.fitsInDht(envelope)) {\n // Store directly in DHT (DHT handles obfuscation)\n if (verbose) {\n verbosePrintln(`Storing envelope in DHT (size ≤ ${this.dhtSizeLimit} bytes)`);\n }\n await this.dht.put(arid, envelope, ttlSeconds, verbose);\n return `Stored in DHT at ARID: ${arid.urString()}`;\n } else {\n // Use IPFS with DHT reference\n if (verbose) {\n verbosePrintln(\"Envelope too large for DHT, using IPFS indirection\");\n }\n\n // 1. Store actual envelope in IPFS with a new ARID\n // (IPFS handles obfuscation with reference_arid)\n const referenceArid = ARID.new();\n if (verbose) {\n verbosePrintln(\n `Storing actual envelope in IPFS with reference ARID: ${referenceArid.urString()}`,\n );\n }\n await this.ipfs.put(referenceArid, envelope, ttlSeconds, verbose);\n\n // 2. Create reference envelope\n const envelopeSize = envelope.taggedCborData.length;\n const reference = createReferenceEnvelope(referenceArid, envelopeSize);\n\n // 3. Store reference envelope in DHT at original ARID\n // (DHT handles obfuscation with original arid)\n if (verbose) {\n verbosePrintln(\"Storing reference envelope in DHT at original ARID\");\n }\n await this.dht.put(arid, reference, ttlSeconds, verbose);\n\n return `Stored in IPFS (ref: ${referenceArid.urString()}) via DHT at ARID: ${arid.urString()}`;\n }\n }\n\n /**\n * Retrieve an envelope for the given ARID.\n *\n * Port of `KvStore::get()` implementation from hybrid/kv.rs lines 171-230.\n */\n async get(arid: ARID, timeoutSeconds?: number, verbose?: boolean): Promise<Envelope | null> {\n // 1. Try to get from DHT (DHT handles deobfuscation)\n const dhtEnvelope = await this.dht.get(arid, timeoutSeconds, verbose);\n\n if (dhtEnvelope === null) {\n return null;\n }\n\n // 2. Check if the envelope is a reference envelope\n if (isReferenceEnvelope(dhtEnvelope)) {\n if (verbose) {\n verbosePrintln(\"Found reference envelope, fetching actual envelope from IPFS\");\n }\n\n // 3. Extract reference ARID\n const referenceArid = extractReferenceArid(dhtEnvelope);\n\n if (verbose) {\n verbosePrintln(`Reference ARID: ${referenceArid.urString()}`);\n }\n\n // 4. Retrieve actual envelope from IPFS\n // (IPFS handles deobfuscation with reference_arid)\n const ipfsEnvelope = await this.ipfs.get(referenceArid, timeoutSeconds, verbose);\n\n if (ipfsEnvelope === null) {\n throw new ContentNotFoundError();\n }\n\n if (verbose) {\n verbosePrintln(\"Successfully retrieved actual envelope from IPFS\");\n }\n return ipfsEnvelope;\n } else {\n // Not a reference envelope, return it directly\n if (verbose) {\n verbosePrintln(\"Envelope is not a reference, treating as direct payload\");\n }\n return dhtEnvelope;\n }\n }\n\n /**\n * Check if an envelope exists at the given ARID.\n *\n * Port of `KvStore::exists()` implementation from hybrid/kv.rs lines 254-257.\n */\n async exists(arid: ARID): Promise<boolean> {\n // Check DHT only (references count as existing)\n return await this.dht.exists(arid);\n }\n\n /**\n * Destroy the hybrid store and release resources.\n */\n async destroy(): Promise<void> {\n await this.dht.destroy();\n }\n}\n"],"mappings":"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"}