@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,912 @@
1
+ const require_chunk = require('./chunk-CbDLau6x.cjs');
2
+ const require_logging = require('./logging-qc9uMgil.cjs');
3
+ let better_sqlite3 = require("better-sqlite3");
4
+ better_sqlite3 = require_chunk.__toESM(better_sqlite3);
5
+ let fs = require("fs");
6
+ fs = require_chunk.__toESM(fs);
7
+ let path = require("path");
8
+ path = require_chunk.__toESM(path);
9
+ let fastify = require("fastify");
10
+ fastify = require_chunk.__toESM(fastify);
11
+ let _bcts_components = require("@bcts/components");
12
+ let _bcts_envelope = require("@bcts/envelope");
13
+
14
+ //#region src/server/error.ts
15
+ /**
16
+ * Server-specific error types.
17
+ *
18
+ * Port of server/error.rs from hubert-rust.
19
+ *
20
+ * @module
21
+ */
22
+ /**
23
+ * Base error class for server errors.
24
+ *
25
+ * @category Server Errors
26
+ */
27
+ var ServerError = class extends require_logging.HubertError {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "ServerError";
31
+ }
32
+ };
33
+ /**
34
+ * General server error.
35
+ *
36
+ * Port of `Error::General(String)` from server/error.rs line 4.
37
+ *
38
+ * @category Server Errors
39
+ */
40
+ var ServerGeneralError = class extends ServerError {
41
+ constructor(message) {
42
+ super(`Server error: ${message}`);
43
+ this.name = "ServerGeneralError";
44
+ }
45
+ };
46
+ /**
47
+ * Network error during server communication.
48
+ *
49
+ * Port of `Error::NetworkError(String)` from server/error.rs line 7.
50
+ *
51
+ * @category Server Errors
52
+ */
53
+ var ServerNetworkError = class extends ServerError {
54
+ constructor(message) {
55
+ super(`Network error: ${message}`);
56
+ this.name = "ServerNetworkError";
57
+ }
58
+ };
59
+ /**
60
+ * Parse error during data handling.
61
+ *
62
+ * Port of `Error::ParseError(String)` from server/error.rs line 10.
63
+ *
64
+ * @category Server Errors
65
+ */
66
+ var ServerParseError = class extends ServerError {
67
+ constructor(message) {
68
+ super(`Parse error: ${message}`);
69
+ this.name = "ServerParseError";
70
+ }
71
+ };
72
+ /**
73
+ * SQLite database error.
74
+ *
75
+ * Port of `Error::Sqlite(e)` from server/error.rs line 19.
76
+ *
77
+ * @category Server Errors
78
+ */
79
+ var SqliteError = class extends ServerError {
80
+ /** The underlying error */
81
+ cause;
82
+ constructor(message, cause) {
83
+ super(`SQLite error: ${message}`);
84
+ this.name = "SqliteError";
85
+ if (cause !== void 0) this.cause = cause;
86
+ }
87
+ };
88
+
89
+ //#endregion
90
+ //#region src/server/memory-kv.ts
91
+ /**
92
+ * In-memory key-value store for Gordian Envelopes.
93
+ *
94
+ * Provides volatile storage with TTL support and automatic cleanup of
95
+ * expired entries.
96
+ *
97
+ * Port of `struct MemoryKv` from server/memory_kv.rs lines 14-21.
98
+ *
99
+ * @category Server Backend
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const store = new MemoryKv();
104
+ * const arid = ARID.new();
105
+ * const envelope = Envelope.new("Hello, Memory!");
106
+ *
107
+ * await store.put(arid, envelope, 3600); // 1 hour TTL
108
+ * const result = await store.get(arid);
109
+ * ```
110
+ */
111
+ var MemoryKv = class {
112
+ storage;
113
+ /**
114
+ * Create a new in-memory key-value store.
115
+ *
116
+ * Port of `MemoryKv::new()` from server/memory_kv.rs lines 29-33.
117
+ */
118
+ constructor() {
119
+ this.storage = /* @__PURE__ */ new Map();
120
+ }
121
+ /**
122
+ * Check if an ARID exists and is not expired.
123
+ *
124
+ * Port of `check_exists()` from server/memory_kv.rs lines 36-53.
125
+ *
126
+ * @internal
127
+ */
128
+ checkExists(arid) {
129
+ const key = arid.urString();
130
+ const entry = this.storage.get(key);
131
+ if (entry) {
132
+ if (entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt) {
133
+ this.storage.delete(key);
134
+ return false;
135
+ }
136
+ return true;
137
+ }
138
+ return false;
139
+ }
140
+ /**
141
+ * Store an envelope at the given ARID.
142
+ *
143
+ * Port of `KvStore::put()` implementation from server/memory_kv.rs lines 62-102.
144
+ */
145
+ async put(arid, envelope, ttlSeconds, verbose) {
146
+ const key = arid.urString();
147
+ if (this.storage.has(key)) {
148
+ if (verbose) require_logging.verbosePrintln(`PUT ${key} ALREADY_EXISTS`);
149
+ throw new require_logging.AlreadyExistsError(key);
150
+ }
151
+ const expiresAt = ttlSeconds !== void 0 ? Date.now() + ttlSeconds * 1e3 : void 0;
152
+ const entry = { envelopeCbor: envelope.taggedCborData() };
153
+ if (expiresAt !== void 0) entry.expiresAt = expiresAt;
154
+ this.storage.set(key, entry);
155
+ if (verbose) require_logging.verbosePrintln(`PUT ${key}${ttlSeconds !== void 0 ? ` (TTL ${ttlSeconds}s)` : ""} OK (Memory)`);
156
+ return "Stored in memory";
157
+ }
158
+ /**
159
+ * Retrieve an envelope for the given ARID.
160
+ *
161
+ * Port of `KvStore::get()` implementation from server/memory_kv.rs lines 104-181.
162
+ */
163
+ async get(arid, timeoutSeconds, verbose) {
164
+ const timeout = timeoutSeconds ?? 30;
165
+ const start = Date.now();
166
+ let firstAttempt = true;
167
+ const key = arid.urString();
168
+ const { EnvelopeDecoder } = await import("@bcts/envelope");
169
+ while (true) {
170
+ const entry = this.storage.get(key);
171
+ if (entry) {
172
+ if (entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt) {
173
+ this.storage.delete(key);
174
+ if (verbose) require_logging.verbosePrintln(`GET ${key} EXPIRED`);
175
+ return null;
176
+ }
177
+ try {
178
+ const envelope = EnvelopeDecoder.tryFromCborData(entry.envelopeCbor);
179
+ if (verbose) require_logging.verbosePrintln(`GET ${key} OK (Memory)`);
180
+ return envelope;
181
+ } catch {
182
+ return null;
183
+ }
184
+ }
185
+ if ((Date.now() - start) / 1e3 >= timeout) {
186
+ if (verbose) require_logging.verbosePrintln(`GET ${key} NOT_FOUND (timeout after ${timeout}s)`);
187
+ return null;
188
+ }
189
+ if (firstAttempt && verbose) {
190
+ require_logging.verbosePrintln(`Polling for ${key} (timeout: ${timeout}s)`);
191
+ firstAttempt = false;
192
+ } else if (verbose) process.stdout.write(".");
193
+ await new Promise((resolve) => setTimeout(resolve, 500));
194
+ }
195
+ }
196
+ /**
197
+ * Check if an envelope exists at the given ARID.
198
+ *
199
+ * Port of `KvStore::exists()` implementation from server/memory_kv.rs lines 183-186.
200
+ */
201
+ async exists(arid) {
202
+ return this.checkExists(arid);
203
+ }
204
+ };
205
+
206
+ //#endregion
207
+ //#region src/server/sqlite-kv.ts
208
+ /**
209
+ * SQLite-backed key-value store for Gordian Envelopes.
210
+ *
211
+ * Port of server/sqlite_kv.rs from hubert-rust.
212
+ *
213
+ * @module
214
+ */
215
+ /**
216
+ * SQLite-backed key-value store for Gordian Envelopes.
217
+ *
218
+ * Provides persistent storage with TTL support and automatic cleanup of
219
+ * expired entries.
220
+ *
221
+ * Port of `struct SqliteKv` from server/sqlite_kv.rs lines 16-24.
222
+ *
223
+ * @category Server Backend
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * const store = new SqliteKv("./hubert.db");
228
+ * const arid = ARID.new();
229
+ * const envelope = Envelope.new("Hello, SQLite!");
230
+ *
231
+ * await store.put(arid, envelope, 3600); // 1 hour TTL
232
+ * const result = await store.get(arid);
233
+ *
234
+ * // Cleanup when done
235
+ * store.close();
236
+ * ```
237
+ */
238
+ var SqliteKv = class {
239
+ db;
240
+ dbPath;
241
+ cleanupInterval = null;
242
+ /**
243
+ * Create a new SQLite-backed key-value store.
244
+ *
245
+ * Port of `SqliteKv::new()` from server/sqlite_kv.rs lines 26-67.
246
+ *
247
+ * @param dbPath - Path to the SQLite database file. Will be created if it doesn't exist.
248
+ * @throws {SqliteError} If database initialization fails
249
+ */
250
+ constructor(dbPath) {
251
+ this.dbPath = dbPath;
252
+ const parentDir = path.dirname(dbPath);
253
+ if (parentDir && parentDir !== "." && !fs.existsSync(parentDir)) fs.mkdirSync(parentDir, { recursive: true });
254
+ try {
255
+ this.db = new better_sqlite3.default(dbPath);
256
+ this.db.exec(`
257
+ CREATE TABLE IF NOT EXISTS hubert_store (
258
+ arid TEXT PRIMARY KEY,
259
+ envelope TEXT NOT NULL,
260
+ expires_at INTEGER
261
+ );
262
+ CREATE INDEX IF NOT EXISTS idx_expires_at ON hubert_store(expires_at);
263
+ `);
264
+ } catch (error) {
265
+ throw new SqliteError(`Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
266
+ }
267
+ this.startCleanupTask();
268
+ }
269
+ /**
270
+ * Start a background task that prunes expired entries every minute.
271
+ *
272
+ * Port of `start_cleanup_task()` from server/sqlite_kv.rs lines 70-126.
273
+ *
274
+ * @internal
275
+ */
276
+ startCleanupTask() {
277
+ this.cleanupInterval = setInterval(() => {
278
+ const now = Math.floor(Date.now() / 1e3);
279
+ try {
280
+ const arids = this.db.prepare("SELECT arid FROM hubert_store WHERE expires_at IS NOT NULL AND expires_at <= ?").all(now).map((row) => row.arid);
281
+ if (arids.length > 0) {
282
+ this.db.prepare("DELETE FROM hubert_store WHERE expires_at IS NOT NULL AND expires_at <= ?").run(now);
283
+ const count = arids.length;
284
+ const aridList = arids.join(" ");
285
+ require_logging.verbosePrintln(`Pruned ${count} expired ${count === 1 ? "entry" : "entries"}: ${aridList}`);
286
+ }
287
+ } catch {}
288
+ }, 60 * 1e3);
289
+ }
290
+ /**
291
+ * Check if an ARID exists and is not expired.
292
+ *
293
+ * Port of `check_exists()` from server/sqlite_kv.rs lines 129-170.
294
+ *
295
+ * @internal
296
+ */
297
+ checkExists(arid) {
298
+ const aridStr = arid.urString();
299
+ const now = Math.floor(Date.now() / 1e3);
300
+ try {
301
+ const row = this.db.prepare("SELECT expires_at FROM hubert_store WHERE arid = ?").get(aridStr);
302
+ if (row) {
303
+ if (row.expires_at !== null && now >= row.expires_at) {
304
+ this.db.prepare("DELETE FROM hubert_store WHERE arid = ?").run(aridStr);
305
+ return false;
306
+ }
307
+ return true;
308
+ }
309
+ return false;
310
+ } catch {
311
+ return false;
312
+ }
313
+ }
314
+ /**
315
+ * Store an envelope at the given ARID.
316
+ *
317
+ * Port of `KvStore::put()` implementation from server/sqlite_kv.rs lines 175-236.
318
+ */
319
+ async put(arid, envelope, ttlSeconds, verbose) {
320
+ if (this.checkExists(arid)) {
321
+ if (verbose) require_logging.verbosePrintln(`PUT ${arid.urString()} ALREADY_EXISTS`);
322
+ throw new require_logging.AlreadyExistsError(arid.urString());
323
+ }
324
+ const aridStr = arid.urString();
325
+ const envelopeStr = envelope.urString();
326
+ const expiresAt = ttlSeconds !== void 0 ? Math.floor(Date.now() / 1e3) + ttlSeconds : null;
327
+ try {
328
+ this.db.prepare("INSERT INTO hubert_store (arid, envelope, expires_at) VALUES (?, ?, ?)").run(aridStr, envelopeStr, expiresAt);
329
+ if (verbose) require_logging.verbosePrintln(`PUT ${aridStr}${ttlSeconds !== void 0 ? ` (TTL ${ttlSeconds}s)` : ""} OK (SQLite: ${this.dbPath})`);
330
+ return `Stored in SQLite: ${this.dbPath}`;
331
+ } catch (error) {
332
+ throw new SqliteError(`Failed to insert: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
333
+ }
334
+ }
335
+ /**
336
+ * Retrieve an envelope for the given ARID.
337
+ *
338
+ * Port of `KvStore::get()` implementation from server/sqlite_kv.rs lines 238-354.
339
+ */
340
+ async get(arid, timeoutSeconds, verbose) {
341
+ const timeout = timeoutSeconds ?? 30;
342
+ const start = Date.now();
343
+ let firstAttempt = true;
344
+ const aridStr = arid.urString();
345
+ const { Envelope } = await import("@bcts/envelope");
346
+ while (true) {
347
+ const now = Math.floor(Date.now() / 1e3);
348
+ try {
349
+ const row = this.db.prepare("SELECT envelope, expires_at FROM hubert_store WHERE arid = ?").get(aridStr);
350
+ if (row) {
351
+ if (row.expires_at !== null && now >= row.expires_at) {
352
+ this.db.prepare("DELETE FROM hubert_store WHERE arid = ?").run(aridStr);
353
+ if (verbose) require_logging.verbosePrintln(`GET ${aridStr} EXPIRED`);
354
+ return null;
355
+ }
356
+ const envelope = Envelope.fromUrString(row.envelope);
357
+ if (verbose) require_logging.verbosePrintln(`GET ${aridStr} OK (SQLite: ${this.dbPath})`);
358
+ return envelope;
359
+ }
360
+ } catch {}
361
+ if ((Date.now() - start) / 1e3 >= timeout) {
362
+ if (verbose) require_logging.verbosePrintln(`GET ${aridStr} NOT_FOUND (timeout after ${timeout}s)`);
363
+ return null;
364
+ }
365
+ if (firstAttempt && verbose) {
366
+ require_logging.verbosePrintln(`Polling for ${aridStr} (timeout: ${timeout}s)`);
367
+ firstAttempt = false;
368
+ } else if (verbose) process.stdout.write(".");
369
+ await new Promise((resolve) => setTimeout(resolve, 500));
370
+ }
371
+ }
372
+ /**
373
+ * Check if an envelope exists at the given ARID.
374
+ *
375
+ * Port of `KvStore::exists()` implementation from server/sqlite_kv.rs lines 356-359.
376
+ */
377
+ async exists(arid) {
378
+ return this.checkExists(arid);
379
+ }
380
+ /**
381
+ * Close the database connection and stop the cleanup task.
382
+ */
383
+ close() {
384
+ if (this.cleanupInterval) {
385
+ clearInterval(this.cleanupInterval);
386
+ this.cleanupInterval = null;
387
+ }
388
+ this.db.close();
389
+ }
390
+ };
391
+
392
+ //#endregion
393
+ //#region src/server/server-kv.ts
394
+ /**
395
+ * Create a new in-memory server KV store.
396
+ *
397
+ * Port of `ServerKv::memory()` from server/server_kv.rs line 19.
398
+ *
399
+ * @returns A new MemoryKv instance
400
+ * @category Server Backend
401
+ */
402
+ function createMemoryKv() {
403
+ return new MemoryKv();
404
+ }
405
+ /**
406
+ * Create a new SQLite-backed server KV store.
407
+ *
408
+ * Port of `ServerKv::sqlite()` from server/server_kv.rs line 22.
409
+ *
410
+ * @param store - The SqliteKv instance to use
411
+ * @returns The same SqliteKv instance
412
+ * @category Server Backend
413
+ */
414
+ function createSqliteKv(store) {
415
+ return store;
416
+ }
417
+ /**
418
+ * Synchronously put an envelope into the store.
419
+ *
420
+ * This function wraps the async KvStore trait implementation for use
421
+ * in synchronous HTTP handlers.
422
+ *
423
+ * Port of `put_sync()` from server/server_kv.rs lines 27-52.
424
+ *
425
+ * @param store - The KvStore to use
426
+ * @param arid - The ARID to store at
427
+ * @param envelope - The envelope to store
428
+ * @param ttlSeconds - TTL in seconds
429
+ * @returns Promise that resolves when storage is complete
430
+ * @internal
431
+ */
432
+ async function putSync(store, arid, envelope, ttlSeconds) {
433
+ await store.put(arid, envelope, ttlSeconds, false);
434
+ }
435
+ /**
436
+ * Synchronously get an envelope from the store.
437
+ *
438
+ * This function wraps the async KvStore trait implementation for use
439
+ * in synchronous HTTP handlers.
440
+ *
441
+ * Port of `get_sync()` from server/server_kv.rs lines 58-71.
442
+ *
443
+ * @param store - The KvStore to use
444
+ * @param arid - The ARID to retrieve
445
+ * @returns The envelope if found, or null
446
+ * @internal
447
+ */
448
+ async function getSync(store, arid) {
449
+ return await store.get(arid, 0, false);
450
+ }
451
+
452
+ //#endregion
453
+ //#region src/server/server.ts
454
+ /**
455
+ * Hubert HTTP server implementation.
456
+ *
457
+ * Port of server/server.rs from hubert-rust.
458
+ *
459
+ * @module
460
+ */
461
+ /**
462
+ * Package version for health endpoint.
463
+ */
464
+ const VERSION = "1.0.0-alpha.1";
465
+ /**
466
+ * Default server configuration.
467
+ *
468
+ * Port of `impl Default for ServerConfig` from server/server.rs lines 32-40.
469
+ */
470
+ const defaultServerConfig = {
471
+ port: 45678,
472
+ maxTtl: 86400,
473
+ verbose: false
474
+ };
475
+ /**
476
+ * Shared server state.
477
+ *
478
+ * Port of `struct ServerState` from server/server.rs lines 43-47.
479
+ *
480
+ * @internal
481
+ */
482
+ var ServerState = class {
483
+ storage;
484
+ config;
485
+ constructor(config, storage) {
486
+ this.storage = storage;
487
+ this.config = config;
488
+ }
489
+ /**
490
+ * Put an envelope into storage.
491
+ *
492
+ * Port of `ServerState::put()` from server/server.rs lines 54-101.
493
+ */
494
+ async put(arid, envelope, requestedTtl, clientIp) {
495
+ const maxTtl = this.config.maxTtl;
496
+ let ttl;
497
+ if (requestedTtl !== void 0) ttl = requestedTtl > maxTtl ? maxTtl : requestedTtl;
498
+ else ttl = maxTtl;
499
+ try {
500
+ await putSync(this.storage, arid, envelope, ttl);
501
+ if (this.config.verbose) require_logging.verbosePrintln(`${clientIp ? `${clientIp}: ` : ""}PUT ${arid.urString()} (TTL ${ttl}s) OK`);
502
+ } catch (error) {
503
+ if (this.config.verbose) {
504
+ const ipStr = clientIp ? `${clientIp}: ` : "";
505
+ const errorMsg = error instanceof Error ? error.message : String(error);
506
+ require_logging.verbosePrintln(`${ipStr}PUT ${arid.urString()} (TTL ${ttl}s) ERROR: ${errorMsg}`);
507
+ }
508
+ throw error;
509
+ }
510
+ }
511
+ /**
512
+ * Get an envelope from storage.
513
+ *
514
+ * Port of `ServerState::get()` from server/server.rs lines 103-126.
515
+ */
516
+ async get(arid, clientIp) {
517
+ const result = await getSync(this.storage, arid);
518
+ if (this.config.verbose) {
519
+ const ipStr = clientIp ? `${clientIp}: ` : "";
520
+ const status = result ? "OK" : "NOT_FOUND";
521
+ require_logging.verbosePrintln(`${ipStr}GET ${arid.urString()} ${status}`);
522
+ }
523
+ return result;
524
+ }
525
+ };
526
+ /**
527
+ * Hubert HTTP server.
528
+ *
529
+ * Port of `struct Server` from server/server.rs lines 128-133.
530
+ *
531
+ * @category Server
532
+ *
533
+ * @example
534
+ * ```typescript
535
+ * const server = Server.newMemory({ port: 8080, maxTtl: 3600, verbose: true });
536
+ * await server.run();
537
+ * ```
538
+ */
539
+ var Server = class Server {
540
+ config;
541
+ state;
542
+ fastify;
543
+ /**
544
+ * Create a new server with the given configuration and storage backend.
545
+ *
546
+ * Port of `Server::new()` from server/server.rs lines 135-139.
547
+ */
548
+ constructor(config, storage) {
549
+ this.config = config;
550
+ this.state = new ServerState(config, storage);
551
+ this.fastify = (0, fastify.default)({ logger: false });
552
+ this.setupRoutes();
553
+ }
554
+ /**
555
+ * Create a new server with in-memory storage.
556
+ *
557
+ * Port of `Server::new_memory()` from server/server.rs lines 142-144.
558
+ */
559
+ static newMemory(config = {}) {
560
+ return new Server({
561
+ ...defaultServerConfig,
562
+ ...config
563
+ }, new MemoryKv());
564
+ }
565
+ /**
566
+ * Create a new server with SQLite storage.
567
+ *
568
+ * Port of `Server::new_sqlite()` from server/server.rs lines 147-149.
569
+ */
570
+ static newSqlite(config = {}, storage) {
571
+ return new Server({
572
+ ...defaultServerConfig,
573
+ ...config
574
+ }, storage);
575
+ }
576
+ /**
577
+ * Setup HTTP routes.
578
+ * @internal
579
+ */
580
+ setupRoutes() {
581
+ this.fastify.get("/health", this.handleHealth.bind(this));
582
+ this.fastify.post("/put", this.handlePut.bind(this));
583
+ this.fastify.post("/get", this.handleGet.bind(this));
584
+ }
585
+ /**
586
+ * Handle health check requests.
587
+ *
588
+ * Port of `handle_health()` from server/server.rs lines 179-187.
589
+ */
590
+ async handleHealth(_request, reply) {
591
+ reply.send({
592
+ server: "hubert",
593
+ version: VERSION,
594
+ status: "ok"
595
+ });
596
+ }
597
+ /**
598
+ * Handle PUT requests.
599
+ *
600
+ * Port of `handle_put()` from server/server.rs lines 195-238.
601
+ */
602
+ async handlePut(request, reply) {
603
+ try {
604
+ const bodyStr = request.body;
605
+ if (typeof bodyStr !== "string") {
606
+ reply.status(400).send("Expected text body");
607
+ return;
608
+ }
609
+ const lines = bodyStr.split("\n");
610
+ if (lines.length < 2) {
611
+ reply.status(400).send("Expected at least 2 lines: ur:arid and ur:envelope");
612
+ return;
613
+ }
614
+ let arid;
615
+ try {
616
+ arid = _bcts_components.ARID.fromUrString(lines[0]);
617
+ } catch {
618
+ reply.status(400).send("Invalid ur:arid");
619
+ return;
620
+ }
621
+ let envelope;
622
+ try {
623
+ envelope = _bcts_envelope.Envelope.fromUrString(lines[1]);
624
+ } catch {
625
+ reply.status(400).send("Invalid ur:envelope");
626
+ return;
627
+ }
628
+ let ttl;
629
+ if (lines.length > 2 && lines[2].trim() !== "") {
630
+ const parsed = parseInt(lines[2], 10);
631
+ if (isNaN(parsed)) {
632
+ reply.status(400).send("Invalid TTL");
633
+ return;
634
+ }
635
+ ttl = parsed;
636
+ }
637
+ const clientIp = request.ip;
638
+ await this.state.put(arid, envelope, ttl, clientIp);
639
+ reply.status(200).send("OK");
640
+ } catch (error) {
641
+ if (error instanceof Error && error.name === "AlreadyExistsError") reply.status(409).send(error.message);
642
+ else reply.status(500).send(error instanceof Error ? error.message : "Internal server error");
643
+ }
644
+ }
645
+ /**
646
+ * Handle GET requests.
647
+ *
648
+ * Port of `handle_get()` from server/server.rs lines 244-269.
649
+ */
650
+ async handleGet(request, reply) {
651
+ try {
652
+ const bodyStr = request.body;
653
+ if (typeof bodyStr !== "string") {
654
+ reply.status(400).send("Expected text body");
655
+ return;
656
+ }
657
+ const aridStr = bodyStr.trim();
658
+ if (aridStr === "") {
659
+ reply.status(400).send("Expected ur:arid");
660
+ return;
661
+ }
662
+ let arid;
663
+ try {
664
+ arid = _bcts_components.ARID.fromUrString(aridStr);
665
+ } catch {
666
+ reply.status(400).send("Invalid ur:arid");
667
+ return;
668
+ }
669
+ const clientIp = request.ip;
670
+ const envelope = await this.state.get(arid, clientIp);
671
+ if (envelope) reply.status(200).send(envelope.urString());
672
+ else reply.status(404).send("Not found");
673
+ } catch (error) {
674
+ reply.status(500).send(error instanceof Error ? error.message : "Internal server error");
675
+ }
676
+ }
677
+ /**
678
+ * Run the server.
679
+ *
680
+ * Port of `Server::run()` from server/server.rs lines 152-170.
681
+ */
682
+ async run() {
683
+ const addr = `127.0.0.1:${this.config.port}`;
684
+ this.fastify.addContentTypeParser("text/plain", { parseAs: "string" }, (_request, payload, done) => {
685
+ done(null, payload);
686
+ });
687
+ this.fastify.addContentTypeParser("application/octet-stream", { parseAs: "string" }, (_request, payload, done) => {
688
+ done(null, payload);
689
+ });
690
+ await this.fastify.listen({
691
+ port: this.config.port,
692
+ host: "127.0.0.1"
693
+ });
694
+ console.log(`✓ Hubert server listening on ${addr}`);
695
+ }
696
+ /**
697
+ * Get the port the server is configured to listen on.
698
+ *
699
+ * Port of `Server::port()` from server/server.rs line 173.
700
+ */
701
+ port() {
702
+ return this.config.port;
703
+ }
704
+ /**
705
+ * Stop the server.
706
+ */
707
+ async close() {
708
+ await this.fastify.close();
709
+ }
710
+ };
711
+
712
+ //#endregion
713
+ //#region src/server/kv.ts
714
+ /**
715
+ * Server-backed key-value store using HTTP API.
716
+ *
717
+ * This implementation communicates with a Hubert server via HTTP POST requests.
718
+ *
719
+ * Port of `struct ServerKvClient` from server/kv.rs lines 6-37.
720
+ *
721
+ * @category Server Backend
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * const store = new ServerKvClient("http://127.0.0.1:45678");
726
+ * const arid = ARID.new();
727
+ * const envelope = Envelope.new("Hello, Server!");
728
+ *
729
+ * // Put envelope (write-once)
730
+ * await store.put(arid, envelope);
731
+ *
732
+ * // Get envelope with verbose logging
733
+ * const retrieved = await store.get(arid, undefined, true);
734
+ * ```
735
+ */
736
+ var ServerKvClient = class {
737
+ baseUrl;
738
+ /**
739
+ * Create a new server KV store client.
740
+ *
741
+ * Port of `ServerKvClient::new()` from server/kv.rs lines 39-46.
742
+ *
743
+ * @param baseUrl - Base URL of the Hubert server (e.g., "http://127.0.0.1:45678")
744
+ */
745
+ constructor(baseUrl) {
746
+ this.baseUrl = baseUrl;
747
+ }
748
+ /**
749
+ * Store an envelope at the given ARID.
750
+ *
751
+ * Port of `KvStore::put()` implementation from server/kv.rs lines 67-122.
752
+ */
753
+ async put(arid, envelope, ttlSeconds, verbose) {
754
+ if (verbose) require_logging.verbosePrintln("Starting server put operation");
755
+ let body;
756
+ if (ttlSeconds !== void 0) body = `${arid.urString()}\n${envelope.urString()}\n${ttlSeconds}`;
757
+ else body = `${arid.urString()}\n${envelope.urString()}`;
758
+ if (verbose) require_logging.verbosePrintln("Sending PUT request to server");
759
+ try {
760
+ const response = await fetch(`${this.baseUrl}/put`, {
761
+ method: "POST",
762
+ body,
763
+ headers: { "Content-Type": "text/plain" }
764
+ });
765
+ if (response.status === 200) {
766
+ if (verbose) require_logging.verbosePrintln("Server put operation completed");
767
+ return "Stored successfully";
768
+ } else if (response.status === 409) {
769
+ if (verbose) require_logging.verbosePrintln("Server put operation failed");
770
+ throw new require_logging.AlreadyExistsError(arid.urString());
771
+ } else {
772
+ if (verbose) require_logging.verbosePrintln("Server put operation failed");
773
+ throw new ServerGeneralError(await response.text());
774
+ }
775
+ } catch (error) {
776
+ if (error instanceof require_logging.AlreadyExistsError || error instanceof ServerGeneralError) throw error;
777
+ throw new ServerNetworkError(error instanceof Error ? error.message : String(error));
778
+ }
779
+ }
780
+ /**
781
+ * Retrieve an envelope for the given ARID.
782
+ *
783
+ * Port of `KvStore::get()` implementation from server/kv.rs lines 124-212.
784
+ */
785
+ async get(arid, timeoutSeconds, verbose) {
786
+ let printedDot = false;
787
+ if (verbose) require_logging.verbosePrintln("Starting server get operation");
788
+ const timeout = timeoutSeconds ?? 30;
789
+ const deadline = Date.now() + timeout * 1e3;
790
+ const pollInterval = 1e3;
791
+ if (verbose) require_logging.verbosePrintln("Polling server for value");
792
+ while (true) {
793
+ const body = arid.urString();
794
+ try {
795
+ const response = await fetch(`${this.baseUrl}/get`, {
796
+ method: "POST",
797
+ body,
798
+ headers: { "Content-Type": "text/plain" }
799
+ });
800
+ if (response.status === 200) {
801
+ if (verbose && printedDot) require_logging.verboseNewline();
802
+ if (verbose) require_logging.verbosePrintln("Value found on server");
803
+ const envelopeStr = await response.text();
804
+ try {
805
+ const envelope = _bcts_envelope.Envelope.fromUrString(envelopeStr);
806
+ if (verbose) require_logging.verbosePrintln("Server get operation completed");
807
+ return envelope;
808
+ } catch (error) {
809
+ throw new ServerParseError(error instanceof Error ? error.message : String(error));
810
+ }
811
+ } else if (response.status === 404) {
812
+ if (Date.now() >= deadline) {
813
+ if (verbose && printedDot) require_logging.verboseNewline();
814
+ if (verbose) require_logging.verbosePrintln("Timeout reached, value not found");
815
+ return null;
816
+ }
817
+ if (verbose) {
818
+ require_logging.verbosePrintDot();
819
+ printedDot = true;
820
+ }
821
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
822
+ } else throw new ServerGeneralError(await response.text());
823
+ } catch (error) {
824
+ if (error instanceof ServerGeneralError || error instanceof ServerParseError) throw error;
825
+ throw new ServerNetworkError(error instanceof Error ? error.message : String(error));
826
+ }
827
+ }
828
+ }
829
+ /**
830
+ * Check if an envelope exists at the given ARID.
831
+ *
832
+ * Port of `KvStore::exists()` implementation from server/kv.rs lines 214-218.
833
+ */
834
+ async exists(arid) {
835
+ return await this.get(arid, 1, false) !== null;
836
+ }
837
+ };
838
+
839
+ //#endregion
840
+ Object.defineProperty(exports, 'MemoryKv', {
841
+ enumerable: true,
842
+ get: function () {
843
+ return MemoryKv;
844
+ }
845
+ });
846
+ Object.defineProperty(exports, 'Server', {
847
+ enumerable: true,
848
+ get: function () {
849
+ return Server;
850
+ }
851
+ });
852
+ Object.defineProperty(exports, 'ServerError', {
853
+ enumerable: true,
854
+ get: function () {
855
+ return ServerError;
856
+ }
857
+ });
858
+ Object.defineProperty(exports, 'ServerGeneralError', {
859
+ enumerable: true,
860
+ get: function () {
861
+ return ServerGeneralError;
862
+ }
863
+ });
864
+ Object.defineProperty(exports, 'ServerKvClient', {
865
+ enumerable: true,
866
+ get: function () {
867
+ return ServerKvClient;
868
+ }
869
+ });
870
+ Object.defineProperty(exports, 'ServerNetworkError', {
871
+ enumerable: true,
872
+ get: function () {
873
+ return ServerNetworkError;
874
+ }
875
+ });
876
+ Object.defineProperty(exports, 'ServerParseError', {
877
+ enumerable: true,
878
+ get: function () {
879
+ return ServerParseError;
880
+ }
881
+ });
882
+ Object.defineProperty(exports, 'SqliteError', {
883
+ enumerable: true,
884
+ get: function () {
885
+ return SqliteError;
886
+ }
887
+ });
888
+ Object.defineProperty(exports, 'SqliteKv', {
889
+ enumerable: true,
890
+ get: function () {
891
+ return SqliteKv;
892
+ }
893
+ });
894
+ Object.defineProperty(exports, 'createMemoryKv', {
895
+ enumerable: true,
896
+ get: function () {
897
+ return createMemoryKv;
898
+ }
899
+ });
900
+ Object.defineProperty(exports, 'createSqliteKv', {
901
+ enumerable: true,
902
+ get: function () {
903
+ return createSqliteKv;
904
+ }
905
+ });
906
+ Object.defineProperty(exports, 'defaultServerConfig', {
907
+ enumerable: true,
908
+ get: function () {
909
+ return defaultServerConfig;
910
+ }
911
+ });
912
+ //# sourceMappingURL=server-BBNRZ30D.cjs.map