@hearthlog/runtime 0.0.0-stage → 0.1.0

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 (120) hide show
  1. package/LICENSE +235 -0
  2. package/package.json +51 -3
  3. package/src/access/session.ts +223 -0
  4. package/src/access/user-session.ts +61 -0
  5. package/src/ai/decisions.ts +89 -0
  6. package/src/calendar/draft.ts +84 -0
  7. package/src/calendar/feeds.ts +91 -0
  8. package/src/calendar/groups.ts +198 -0
  9. package/src/calendar/logic.ts +141 -0
  10. package/src/calendar/preferences.ts +40 -0
  11. package/src/calendar/weeks.ts +56 -0
  12. package/src/commands/index.ts +184 -0
  13. package/src/commands/local-commit.ts +214 -0
  14. package/src/commands/standard.ts +8 -0
  15. package/src/commands/validation.ts +206 -0
  16. package/src/db/bootstrap.ts +72 -0
  17. package/src/db/calendar.ts +638 -0
  18. package/src/db/dynamic-ddl.ts +127 -0
  19. package/src/db/entities.ts +208 -0
  20. package/src/db/history.ts +194 -0
  21. package/src/db/index.ts +1240 -0
  22. package/src/db/kv.ts +33 -0
  23. package/src/db/local-statistics.ts +106 -0
  24. package/src/db/module-projections.ts +46 -0
  25. package/src/db/raw-browser.ts +361 -0
  26. package/src/db/read-repositories.ts +29 -0
  27. package/src/db/read-session.ts +47 -0
  28. package/src/db/schema-version.ts +76 -0
  29. package/src/db/session-context.ts +33 -0
  30. package/src/event/code-export.ts +65 -0
  31. package/src/event/dynamic-entities.ts +503 -0
  32. package/src/event/dynamic-materializer.ts +288 -0
  33. package/src/event/handlers.ts +490 -0
  34. package/src/event/index.ts +53 -0
  35. package/src/event/meta-entity-types.ts +165 -0
  36. package/src/event/projection-builder.ts +384 -0
  37. package/src/event/queries.ts +126 -0
  38. package/src/event/rebuild.ts +176 -0
  39. package/src/event/reference-lookup.ts +93 -0
  40. package/src/event/registry.ts +158 -0
  41. package/src/event/schema-effects.ts +45 -0
  42. package/src/event/types.ts +20 -0
  43. package/src/history/action-boundary.ts +18 -0
  44. package/src/history/action-detail.ts +23 -0
  45. package/src/history/change-comparison.ts +29 -0
  46. package/src/history/change-display.ts +41 -0
  47. package/src/history/contracts.ts +63 -0
  48. package/src/history/history.ts +372 -0
  49. package/src/history/legacy-replay.ts +69 -0
  50. package/src/history/legacy-url.ts +9 -0
  51. package/src/history/lineage.ts +123 -0
  52. package/src/history/log-format.ts +17 -0
  53. package/src/history/log-map.ts +110 -0
  54. package/src/history/navigation.ts +14 -0
  55. package/src/history/point-snapshot.ts +229 -0
  56. package/src/history/timeline.ts +215 -0
  57. package/src/history/url.ts +18 -0
  58. package/src/ledger/README.md +118 -0
  59. package/src/ledger/admin-client.ts +616 -0
  60. package/src/ledger/client.ts +10 -0
  61. package/src/ledger/config.ts +117 -0
  62. package/src/ledger/entity-id.ts +115 -0
  63. package/src/ledger/errors.ts +144 -0
  64. package/src/ledger/http-client.ts +590 -0
  65. package/src/ledger/index.ts +67 -0
  66. package/src/ledger/socket.ts +400 -0
  67. package/src/ledger/types.ts +202 -0
  68. package/src/ledger/version-tracker-singleton.ts +12 -0
  69. package/src/ledger/version-tracker.ts +259 -0
  70. package/src/log-management.ts +123 -0
  71. package/src/log-validation.ts +19 -0
  72. package/src/native/reminders.ts +2 -0
  73. package/src/native/shell.svelte.ts +136 -0
  74. package/src/native/shell.ts +102 -0
  75. package/src/pwa/registration.ts +169 -0
  76. package/src/recovery/local-recovery.ts +59 -0
  77. package/src/recovery/reset-sequence.ts +13 -0
  78. package/src/runtime/clock.ts +43 -0
  79. package/src/runtime/kept-page-data.svelte.ts +42 -0
  80. package/src/runtime/page-data.svelte.ts +160 -0
  81. package/src/runtime/phone-header.svelte.ts +17 -0
  82. package/src/runtime/read-coordinator.ts +103 -0
  83. package/src/runtime/refresh.ts +15 -0
  84. package/src/runtime/start-page.ts +56 -0
  85. package/src/runtime/view.ts +83 -0
  86. package/src/runtime/workspace.ts +62 -0
  87. package/src/storage/indexeddb-statistics.ts +153 -0
  88. package/src/store/attention.svelte.ts +51 -0
  89. package/src/store/conflict-merge.ts +169 -0
  90. package/src/store/entity-sync-state.svelte.ts +36 -0
  91. package/src/store/event-log.svelte.ts +109 -0
  92. package/src/store/events.svelte.ts +14 -0
  93. package/src/store/local-context.ts +93 -0
  94. package/src/store/local-store.svelte.ts +58 -0
  95. package/src/store/log-version.svelte.ts +35 -0
  96. package/src/store/logs.svelte.ts +60 -0
  97. package/src/store/moved.svelte.ts +43 -0
  98. package/src/store/offline-readiness.svelte.ts +188 -0
  99. package/src/store/outbox-request.ts +61 -0
  100. package/src/store/outbox.ts +426 -0
  101. package/src/store/pending-work-transfer.ts +49 -0
  102. package/src/store/pending-work.ts +143 -0
  103. package/src/store/sync-coordinator.ts +170 -0
  104. package/src/store/sync-invalidation.ts +10 -0
  105. package/src/store/sync-issues.ts +406 -0
  106. package/src/store/sync-progress.ts +54 -0
  107. package/src/store/sync-state.ts +127 -0
  108. package/src/store/sync.svelte.ts +1010 -0
  109. package/src/store/tab-coordination.ts +164 -0
  110. package/src/testing/e2e-seed.ts +40 -0
  111. package/src/testing/local-database.ts +120 -0
  112. package/src/testing/projection-database.ts +105 -0
  113. package/src/testing/test-entities.ts +28 -0
  114. package/src/theme/appearance.svelte.ts +111 -0
  115. package/src/utils/address.ts +5 -0
  116. package/src/utils/async.ts +30 -0
  117. package/src/utils/long-press.ts +70 -0
  118. package/src/utils/perf-dataset.ts +405 -0
  119. package/src/utils/perf.ts +627 -0
  120. package/README.md +0 -3
@@ -0,0 +1,590 @@
1
+ /**
2
+ * HTTP Client for the Ledger Service API
3
+ *
4
+ * HTTP transport with retry logic, request/response logging, realtime socket
5
+ * construction, and API error handling.
6
+ */
7
+
8
+ import { EventSocket } from "./socket.js";
9
+ import type { LedgerConfig, RetryPolicy } from "./config.js";
10
+ import type {
11
+ LogSummary,
12
+ LogListResponse,
13
+ CreateLogRequest,
14
+ CreateLogResponse,
15
+ ForkLogRequest,
16
+ LogMetadata,
17
+ AppendEventsRequest,
18
+ AppendEventsResponse,
19
+ ConflictResponse,
20
+ EventBatch,
21
+ StoredEvent,
22
+ SessionResponse,
23
+ ErrorResponse,
24
+ EntityVersionChainErrorResponse,
25
+ } from "./types.js";
26
+ import {
27
+ LedgerServiceError,
28
+ VersionConflictError,
29
+ NotFoundError,
30
+ AuthenticationError,
31
+ EntityVersionChainError,
32
+ IdempotencyMismatchError,
33
+ isLedgerServiceError,
34
+ isVersionConflictError,
35
+ isNotFoundError,
36
+ isAuthenticationError,
37
+ } from "./errors.js";
38
+ import { fetchAccessProtected } from '../access/session.js';
39
+
40
+ // Re-export errors for convenience
41
+ export {
42
+ LedgerServiceError,
43
+ VersionConflictError,
44
+ NotFoundError,
45
+ AuthenticationError,
46
+ } from "./errors.js";
47
+
48
+ /**
49
+ * Options for HTTP requests
50
+ */
51
+ interface RequestOptions {
52
+ method: "GET" | "POST" | "PUT" | "DELETE";
53
+ path: string;
54
+ body?: unknown;
55
+ queryParams?: Record<string, string | number | undefined>;
56
+ }
57
+
58
+ /**
59
+ * Client for the Ledger Service API.
60
+ */
61
+ export class LedgerClient {
62
+ private readonly config: LedgerConfig;
63
+ private readonly baseUrl: string;
64
+
65
+ constructor(config: LedgerConfig) {
66
+ this.config = config;
67
+ // Ensure baseUrl doesn't have trailing slash
68
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
69
+ }
70
+
71
+ /**
72
+ * Get the base URL for the client
73
+ */
74
+ getBaseUrl(): string {
75
+ return this.baseUrl;
76
+ }
77
+
78
+ /**
79
+ * Get the auth token for the client
80
+ */
81
+ getAuthToken(): string | undefined {
82
+ return this.config.authToken;
83
+ }
84
+
85
+ private supportsActionMetadata = false;
86
+ private capabilitiesChecked = false;
87
+ private supportsWorkspaceRestore = false;
88
+ private supportsEventExcision = false;
89
+ private sessionRequest: Promise<SessionResponse> | null = null;
90
+
91
+ async getWorkspaceRestoreSupport(): Promise<boolean> {
92
+ if (!this.capabilitiesChecked) await this.getSession();
93
+ return this.supportsWorkspaceRestore;
94
+ }
95
+
96
+ async getEventExcisionSupport(): Promise<boolean> {
97
+ if (!this.capabilitiesChecked) await this.getSession();
98
+ return this.supportsEventExcision;
99
+ }
100
+
101
+ private replayVersion(): number {
102
+ if (this.supportsEventExcision) return 2;
103
+ if (this.supportsWorkspaceRestore) return 1;
104
+ return 0;
105
+ }
106
+
107
+ async getActionMetadataSupport(): Promise<boolean> {
108
+ if (!this.capabilitiesChecked) await this.getSession();
109
+ return this.supportsActionMetadata;
110
+ }
111
+
112
+ async getSession(): Promise<SessionResponse> {
113
+ this.sessionRequest ??= this.request<SessionResponse>({ method: "GET", path: "/session" })
114
+ .then((session) => {
115
+ this.supportsActionMetadata = session.capabilities?.includes('action-metadata-v1') ?? false;
116
+ this.supportsWorkspaceRestore = session.capabilities?.includes('workspace-restore-v1') ?? false;
117
+ this.supportsEventExcision = session.capabilities?.includes('event-excision-v1') ?? false;
118
+ this.capabilitiesChecked = true;
119
+ return session;
120
+ })
121
+ .finally(() => { this.sessionRequest = null; });
122
+ return this.sessionRequest;
123
+ }
124
+
125
+ /**
126
+ * Makes an HTTP request with retry logic and error handling
127
+ */
128
+ private async request<T>(options: RequestOptions): Promise<T> {
129
+ const { method, path, body, queryParams } = options;
130
+
131
+ // Build URL with query parameters
132
+ let url = `${this.baseUrl}${path}`;
133
+ if (queryParams) {
134
+ const params = new URLSearchParams();
135
+ Object.entries(queryParams).forEach(([key, value]) => {
136
+ if (value !== undefined) {
137
+ params.append(key, String(value));
138
+ }
139
+ });
140
+ const queryString = params.toString();
141
+ if (queryString) {
142
+ url += `?${queryString}`;
143
+ }
144
+ }
145
+
146
+ // Build headers
147
+ const headers: Record<string, string> = {
148
+ "Content-Type": "application/json",
149
+
150
+ };
151
+ if (this.supportsWorkspaceRestore || this.supportsEventExcision) headers["X-Ledger-Replay-Version"] = String(this.replayVersion());
152
+ if (this.config.authToken) {
153
+ headers["Authorization"] = `Bearer ${this.config.authToken}`;
154
+ }
155
+
156
+ // Log request
157
+ this.logRequest(method, url, body);
158
+
159
+ // Execute request with retry logic
160
+ return await this.executeWithRetry(async () => {
161
+ const controller = new AbortController();
162
+ const timeoutId = setTimeout(
163
+ () => controller.abort(),
164
+ this.config.timeout,
165
+ );
166
+
167
+ try {
168
+ const response = await fetchAccessProtected(url, {
169
+ method,
170
+ headers,
171
+ body: body ? JSON.stringify(body) : undefined,
172
+ signal: controller.signal,
173
+ });
174
+
175
+ clearTimeout(timeoutId);
176
+
177
+ // Handle response
178
+ return await this.handleResponse<T>(response);
179
+ } catch (error) {
180
+ clearTimeout(timeoutId);
181
+
182
+ // Handle abort/timeout
183
+ if (error instanceof Error && error.name === "AbortError") {
184
+ throw new LedgerServiceError(
185
+ `Request timeout after ${this.config.timeout}ms`,
186
+ );
187
+ }
188
+
189
+ // Handle network errors
190
+ if (error instanceof TypeError) {
191
+ throw new LedgerServiceError(`Network error: ${error.message}`);
192
+ }
193
+
194
+ throw error;
195
+ }
196
+ }, this.config.retryPolicy);
197
+ }
198
+
199
+ /**
200
+ * Executes a function with exponential backoff retry logic
201
+ */
202
+ private async executeWithRetry<T>(
203
+ fn: () => Promise<T>,
204
+ retryPolicy: RetryPolicy,
205
+ ): Promise<T> {
206
+ let lastError: Error | undefined;
207
+ let delay = retryPolicy.initialDelayMs;
208
+
209
+ for (let attempt = 0; attempt <= retryPolicy.maxRetries; attempt++) {
210
+ try {
211
+ return await fn();
212
+ } catch (error) {
213
+ lastError = error as Error;
214
+
215
+ // Don't retry on certain errors
216
+ if (
217
+ isVersionConflictError(error) ||
218
+ isNotFoundError(error) ||
219
+ isAuthenticationError(error) ||
220
+ (isLedgerServiceError(error) &&
221
+ error.statusCode !== undefined &&
222
+ error.statusCode >= 400 &&
223
+ error.statusCode < 500)
224
+ ) {
225
+ throw error;
226
+ }
227
+
228
+ // Don't retry if this was the last attempt
229
+ if (attempt === retryPolicy.maxRetries) {
230
+ break;
231
+ }
232
+
233
+ // Log retry attempt
234
+ this.logRetry(
235
+ attempt + 1,
236
+ retryPolicy.maxRetries,
237
+ delay,
238
+ error as Error,
239
+ );
240
+
241
+ // Wait before retrying
242
+ await this.sleep(delay);
243
+
244
+ // Calculate next delay with exponential backoff
245
+ delay = Math.min(
246
+ delay * retryPolicy.backoffMultiplier,
247
+ retryPolicy.maxDelayMs,
248
+ );
249
+ }
250
+ }
251
+
252
+ throw lastError;
253
+ }
254
+
255
+ /**
256
+ * Handles HTTP response and converts to typed result or throws appropriate error
257
+ */
258
+ private async handleResponse<T>(response: Response): Promise<T> {
259
+ const contentType = response.headers.get("content-type");
260
+ const isJson = contentType?.includes("application/json");
261
+
262
+ // Handle success responses
263
+ if (response.ok) {
264
+ if (response.status === 204) {
265
+ // No content
266
+ return undefined as T;
267
+ }
268
+
269
+ const data = isJson ? await response.json() : await response.text();
270
+ this.logResponse(response.status, data);
271
+ return data as T;
272
+ }
273
+
274
+ // Handle error responses
275
+ let errorData: unknown;
276
+ try {
277
+ errorData = isJson ? await response.json() : await response.text();
278
+ } catch {
279
+ errorData = null;
280
+ }
281
+
282
+ this.logResponse(response.status, errorData);
283
+ const errorResponse = errorData && typeof errorData === "object"
284
+ ? errorData as Partial<ErrorResponse>
285
+ : null;
286
+
287
+ // Handle specific error status codes
288
+ switch (response.status) {
289
+ case 401:
290
+ case 403:
291
+ throw new AuthenticationError(
292
+ `Authentication failed: ${response.statusText}`,
293
+ response.status,
294
+ errorResponse?.code ?? "unauthorized",
295
+ );
296
+
297
+ case 404:
298
+ throw new NotFoundError(
299
+ `Resource not found: ${response.statusText}`,
300
+ errorResponse?.code ?? "not_found",
301
+ );
302
+
303
+ case 409:
304
+ if (errorResponse?.code === "entity_conflict") {
305
+ throw new VersionConflictError(
306
+ "Version conflict detected",
307
+ errorData as ConflictResponse,
308
+ );
309
+ }
310
+ if (errorResponse?.code === "invalid_entity_version_chain") {
311
+ throw new EntityVersionChainError(
312
+ errorData as EntityVersionChainErrorResponse,
313
+ );
314
+ }
315
+ if (errorResponse?.code === "idempotency_mismatch") {
316
+ throw new IdempotencyMismatchError(errorData);
317
+ }
318
+ throw new LedgerServiceError(
319
+ `Conflict: ${response.statusText}`,
320
+ 409,
321
+ errorData,
322
+ errorResponse?.code,
323
+ );
324
+
325
+ case 400:
326
+ throw new LedgerServiceError(
327
+ `Bad request: ${JSON.stringify(errorData)}`,
328
+ 400,
329
+ errorData,
330
+ errorResponse?.code,
331
+ );
332
+
333
+ case 422:
334
+ // Ledger names the offending event and key, which is the reason
335
+ // shown for a rejected batch.
336
+ throw new LedgerServiceError(
337
+ errorResponse?.message ?? `Rejected: ${response.statusText}`,
338
+ 422,
339
+ errorData,
340
+ errorResponse?.code,
341
+ );
342
+
343
+ case 500:
344
+ case 502:
345
+ case 503:
346
+ case 504:
347
+ throw new LedgerServiceError(
348
+ `Server error: ${response.statusText}`,
349
+ response.status,
350
+ errorData,
351
+ errorResponse?.code,
352
+ );
353
+
354
+ default:
355
+ throw new LedgerServiceError(
356
+ `HTTP ${response.status}: ${response.statusText}`,
357
+ response.status,
358
+ errorData,
359
+ errorResponse?.code,
360
+ );
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Logs an outgoing request
366
+ */
367
+ private logRequest(method: string, url: string, _body?: unknown): void {
368
+ if (typeof console !== "undefined") {
369
+ console.debug(`[LedgerClient] ${method} ${url}`);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Logs a response
375
+ */
376
+ private logResponse(status: number, _data: unknown): void {
377
+ if (typeof console !== "undefined") {
378
+ console.debug(`[LedgerClient] Response ${status}`);
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Logs a retry attempt
384
+ */
385
+ private logRetry(
386
+ attempt: number,
387
+ maxRetries: number,
388
+ delayMs: number,
389
+ error: Error,
390
+ ): void {
391
+ if (typeof console !== "undefined") {
392
+ console.warn(
393
+ `[LedgerClient] Retry ${attempt}/${maxRetries} after ${delayMs}ms due to:`,
394
+ error.message,
395
+ );
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Sleep utility for retry delays
401
+ */
402
+ private sleep(ms: number): Promise<void> {
403
+ return new Promise((resolve) => setTimeout(resolve, ms));
404
+ }
405
+
406
+ // ============================================================================
407
+ // Log Management Endpoints
408
+ // ============================================================================
409
+
410
+ /**
411
+ * Lists all available logs
412
+ * GET /logs
413
+ */
414
+ async listLogs(): Promise<LogSummary[]> {
415
+ const response = await this.listLogsResponse();
416
+ return response.logs;
417
+ }
418
+
419
+ async listLogsResponse(): Promise<LogListResponse> {
420
+ return await this.request<LogListResponse>({
421
+ method: "GET",
422
+ path: "/logs",
423
+ });
424
+ }
425
+
426
+ /**
427
+ * Creates a new log
428
+ * POST /logs
429
+ */
430
+ async createLog(request: CreateLogRequest): Promise<CreateLogResponse> {
431
+ return await this.request<CreateLogResponse>({
432
+ method: "POST",
433
+ path: "/logs",
434
+ body: request,
435
+ });
436
+ }
437
+
438
+ async forkLog(sourceLogId: string, request: ForkLogRequest): Promise<LogSummary> {
439
+ return await this.request<LogSummary>({
440
+ method: 'POST',
441
+ path: `/logs/${encodeURIComponent(sourceLogId)}/forks`,
442
+ body: request,
443
+ });
444
+ }
445
+
446
+ async setLogVisibility(logId: string, hidden: boolean): Promise<LogSummary> {
447
+ return await this.request<LogSummary>({
448
+ method: 'POST',
449
+ path: `/logs/${encodeURIComponent(logId)}/visibility`,
450
+ body: { hidden },
451
+ });
452
+ }
453
+
454
+ async finalizeFork(logId: string): Promise<LogSummary> {
455
+ return await this.request<LogSummary>({
456
+ method: 'POST',
457
+ path: `/logs/${encodeURIComponent(logId)}/finalize-fork`,
458
+ });
459
+ }
460
+
461
+ /**
462
+ * Gets metadata for a specific log
463
+ * GET /logs/{logId}
464
+ */
465
+ async getLog(logId: string): Promise<LogMetadata> {
466
+ return await this.request<LogMetadata>({
467
+ method: "GET",
468
+ path: `/logs/${encodeURIComponent(logId)}`,
469
+ });
470
+ }
471
+
472
+ /**
473
+ * Deletes a log
474
+ * DELETE /logs/{logId}
475
+ */
476
+ async deleteLog(logId: string): Promise<void> {
477
+ await this.request<void>({
478
+ method: "DELETE",
479
+ path: `/logs/${encodeURIComponent(logId)}`,
480
+ });
481
+ }
482
+
483
+ // ============================================================================
484
+ // Event Append Endpoint
485
+ // ============================================================================
486
+
487
+ /**
488
+ * Appends events to a log
489
+ * POST /logs/{logId}/events
490
+ *
491
+ * @throws {VersionConflictError} when entity version conflict occurs (HTTP 409)
492
+ */
493
+ async appendEvents(
494
+ logId: string,
495
+ request: AppendEventsRequest,
496
+ ): Promise<AppendEventsResponse> {
497
+ await this.getWorkspaceRestoreSupport();
498
+ return await this.request<AppendEventsResponse>({
499
+ method: "POST",
500
+ path: `/logs/${encodeURIComponent(logId)}/events`,
501
+ body: request,
502
+ });
503
+ }
504
+
505
+ // ============================================================================
506
+ // Event Query Endpoints
507
+ // ============================================================================
508
+
509
+ /**
510
+ * Gets events from a log, optionally after a specific version
511
+ * GET /logs/{logId}/events
512
+ */
513
+ async getEvents(
514
+ logId: string,
515
+ afterVersion?: number,
516
+ ): Promise<StoredEvent[]> {
517
+ const events: StoredEvent[] = [];
518
+ let cursor = afterVersion ?? 0;
519
+ while (true) {
520
+ const page = await this.getEventPage(logId, cursor);
521
+ events.push(...page);
522
+ if (page.length < 512) return events;
523
+ cursor = page[page.length - 1].version;
524
+ }
525
+ }
526
+
527
+ async getEventPage(
528
+ logId: string,
529
+ afterVersion = 0,
530
+ limit = 512,
531
+ ): Promise<StoredEvent[]> {
532
+ await this.getWorkspaceRestoreSupport();
533
+ const response = await this.request<EventBatch>({
534
+ method: "GET",
535
+ path: `/logs/${encodeURIComponent(logId)}/events`,
536
+ queryParams: { afterVersion, limit },
537
+ });
538
+ return response.events;
539
+ }
540
+
541
+ // ============================================================================
542
+ // WebSocket realtime delivery
543
+ // ============================================================================
544
+
545
+ createEventSocket(logId: string, afterVersion = 0): EventSocket {
546
+ return new EventSocket({
547
+ afterVersion,
548
+ urlProvider: (cursor) =>
549
+ buildEventSocketUrl(
550
+ this.baseUrl,
551
+ logId,
552
+ cursor,
553
+ typeof window === "undefined"
554
+ ? undefined
555
+ : window.location.href,
556
+ this.supportsWorkspaceRestore ? this.replayVersion() : 0,
557
+ ),
558
+ autoReconnect: true,
559
+ });
560
+ }
561
+ }
562
+
563
+ export function buildEventSocketUrl(
564
+ baseUrl: string,
565
+ logId: string,
566
+ afterVersion: number,
567
+ browserUrl?: string,
568
+ replayVersion = 0,
569
+ ): string {
570
+ const path =
571
+ `${baseUrl.replace(/\/$/, "")}/logs/${encodeURIComponent(logId)}` +
572
+ "/events/socket";
573
+ let socket: URL;
574
+ if (baseUrl.startsWith("/")) {
575
+ if (!browserUrl) {
576
+ throw new Error(
577
+ "A browser URL is required for a relative Ledger service URL",
578
+ );
579
+ }
580
+ socket = new URL(path, new URL(browserUrl).origin);
581
+ } else {
582
+ socket = new URL(path);
583
+ }
584
+ if (socket.protocol === "https:") socket.protocol = "wss:";
585
+ else if (socket.protocol === "http:") socket.protocol = "ws:";
586
+ else throw new Error("Ledger service URL must use HTTP or HTTPS");
587
+ socket.searchParams.set("afterVersion", String(afterVersion));
588
+ if (replayVersion > 0) socket.searchParams.set("replayVersion", String(replayVersion));
589
+ return socket.toString();
590
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Ledger Service client library
3
+ *
4
+ * This module provides the main exports for interacting with the Ledger Service API.
5
+ */
6
+
7
+ // Configuration
8
+ export {
9
+ loadLedgerConfig,
10
+ type LedgerConfig,
11
+ type RetryPolicy,
12
+ ConfigValidationError,
13
+ } from "./config.js";
14
+
15
+ // Error classes and type guards
16
+ export {
17
+ LedgerServiceError,
18
+ VersionConflictError,
19
+ NotFoundError,
20
+ AuthenticationError,
21
+ EntityVersionChainError,
22
+ IdempotencyMismatchError,
23
+ isLedgerServiceError,
24
+ isVersionConflictError,
25
+ isNotFoundError,
26
+ isAuthenticationError,
27
+ } from "./errors.js";
28
+
29
+ // Ledger transport
30
+ export { LedgerClient } from "./http-client.js";
31
+ export { getLedgerClient } from './client.js';
32
+ export {
33
+ LedgerAdminClient,
34
+ LedgerAdminError,
35
+ LedgerAdminUnauthorizedError,
36
+ type AdminAppListResponse,
37
+ type AdminAppSummary,
38
+ type AdminAppTokenResponse,
39
+ type AdminErrorResponse,
40
+ type AdminSession,
41
+ } from "./admin-client.js";
42
+
43
+ // Types
44
+ export * from "./types.js";
45
+
46
+ // Entity ID utilities
47
+ export {
48
+ extractEntityId,
49
+ extractEntityIds,
50
+ groupEventsByEntity,
51
+ } from "./entity-id.js";
52
+
53
+ // WebSocket realtime delivery
54
+ export {
55
+ EventSocket,
56
+ createEventSocket,
57
+ SocketState,
58
+ SocketProtocolError,
59
+ LEDGER_SOCKET_PROTOCOL,
60
+ type EventSocketConfig,
61
+ type SocketFrame,
62
+ type SocketUrlProvider,
63
+ type SocketResyncReason,
64
+ } from "./socket.js";
65
+
66
+ // Version tracking
67
+ export { EntityVersionTracker } from "./version-tracker.js";