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