@book.dev/sdk 3.5.0 → 3.6.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 (44) hide show
  1. package/dist/backup.d.ts +75 -1
  2. package/dist/backup.js +8 -1
  3. package/dist/backup.js.map +1 -1
  4. package/dist/client.d.ts +134 -2
  5. package/dist/client.js +171 -0
  6. package/dist/client.js.map +1 -1
  7. package/dist/csv.d.ts +49 -0
  8. package/dist/csv.js +116 -0
  9. package/dist/csv.js.map +1 -0
  10. package/dist/importAssets.d.ts +10 -0
  11. package/dist/importAssets.js +17 -0
  12. package/dist/importAssets.js.map +1 -1
  13. package/dist/index.d.ts +10 -4
  14. package/dist/index.js +9 -3
  15. package/dist/index.js.map +1 -1
  16. package/dist/ledger.d.ts +772 -0
  17. package/dist/ledger.js +493 -0
  18. package/dist/ledger.js.map +1 -0
  19. package/dist/ledgerBeancount.d.ts +117 -0
  20. package/dist/ledgerBeancount.js +309 -0
  21. package/dist/ledgerBeancount.js.map +1 -0
  22. package/dist/ledgerBeancountFixture.d.ts +61 -0
  23. package/dist/ledgerBeancountFixture.js +459 -0
  24. package/dist/ledgerBeancountFixture.js.map +1 -0
  25. package/dist/ledgerCsv.d.ts +76 -0
  26. package/dist/ledgerCsv.js +197 -0
  27. package/dist/ledgerCsv.js.map +1 -0
  28. package/dist/money.d.ts +179 -0
  29. package/dist/money.js +283 -0
  30. package/dist/money.js.map +1 -0
  31. package/dist/notionImport.d.ts +5 -3
  32. package/dist/notionImport.js +6 -56
  33. package/dist/notionImport.js.map +1 -1
  34. package/dist/plugins.d.ts +24 -0
  35. package/dist/plugins.js +25 -0
  36. package/dist/plugins.js.map +1 -1
  37. package/dist/provenance.d.ts +17 -0
  38. package/dist/provenance.js.map +1 -1
  39. package/dist/routes.d.ts +121 -0
  40. package/dist/routes.js +122 -0
  41. package/dist/routes.js.map +1 -1
  42. package/dist/types.d.ts +8 -0
  43. package/dist/types.js.map +1 -1
  44. package/package.json +1 -1
package/dist/backup.d.ts CHANGED
@@ -11,7 +11,60 @@
11
11
  */
12
12
  import type { StoredPage } from './types';
13
13
  import type { StoredDatabase } from './database';
14
- export declare const BACKUP_VERSION = 1;
14
+ import type { LedgerAuditEvent } from './ledger';
15
+ /**
16
+ * Version 2 (LGR-15): the bundle MAY carry a {@link LedgerBackupSection} — the
17
+ * ledger's durability surface (audit stream, settings rows, evidence assets)
18
+ * that pages/databases alone cannot express. Additive: a v1 bundle (no
19
+ * `ledger` key) still imports; a v2 bundle read by an old server imports its
20
+ * pages/databases and ignores the extra key.
21
+ */
22
+ export declare const BACKUP_VERSION = 2;
23
+ /**
24
+ * One content-addressed asset carried by a backup (LGR-15): the evidence bytes
25
+ * behind a ledger transaction's manifest. `id` IS the SHA-256 of the bytes —
26
+ * the importer re-derives it and refuses a mismatch, so a bundle can never
27
+ * plant bytes under a hash they do not answer to.
28
+ */
29
+ export interface LedgerBackupAsset {
30
+ /** Asset-store id: 64 lowercase hex chars, the SHA-256 of the bytes. */
31
+ id: string;
32
+ mime: string;
33
+ /** Byte count of the decoded content. */
34
+ size: number;
35
+ /** The raw bytes, base64-encoded (JSON cannot carry binary). */
36
+ bytesBase64: string;
37
+ /** Page ids holding an `asset_refs` edge to this asset (restored where the page exists). */
38
+ refs: string[];
39
+ }
40
+ /**
41
+ * The ledger durability surface of a backup (LGR-15). The ledger's ENTITIES
42
+ * (accounts/transactions/postings/reconciliations and their host pages) travel
43
+ * as ordinary pages/databases in the bundle; this section carries what those
44
+ * rows alone cannot restore:
45
+ *
46
+ * - `settings` — the seeded ids (`ledgerDb`), the period records
47
+ * (`ledgerPeriods`), and the entry-number sequence (`ledgerEntrySeq`),
48
+ * verbatim as stored;
49
+ * - `audit` — the FULL append-only audit stream, seq order, hashes included.
50
+ * Restored verbatim so the tamper-evidence chain survives the round trip
51
+ * (the LGR-7 verifier re-checks it against the restored rows);
52
+ * - `assets` — the evidence bytes referenced by transaction manifests, so the
53
+ * verifier's receipt re-hash check still has bytes to answer with.
54
+ *
55
+ * Restore semantics are deliberately narrow (see the server's `importBundle`):
56
+ * overwrite mode only, and ONLY into a library with no seeded ledger — a
57
+ * library that already has one keeps its LGR-3 protections and the section is
58
+ * skipped, reported via `ImportResult.ledger`.
59
+ */
60
+ export interface LedgerBackupSection {
61
+ /** Raw `settings` rows by key: `ledgerDb`, `ledgerPeriods`, `ledgerEntrySeq` (when present). */
62
+ settings: Record<string, unknown>;
63
+ /** The full audit stream, ascending `seq`, verbatim (hashes included). */
64
+ audit: LedgerAuditEvent[];
65
+ /** Evidence assets referenced by ledger transaction manifests. */
66
+ assets: LedgerBackupAsset[];
67
+ }
15
68
  export interface LibraryBackup {
16
69
  version: number;
17
70
  exportedAt: string;
@@ -19,6 +72,8 @@ export interface LibraryBackup {
19
72
  databases: StoredDatabase[];
20
73
  /** pageId → emoji icon (added client-side; ignored by the server). */
21
74
  icons?: Record<string, string>;
75
+ /** LGR-15: the ledger durability surface; absent when no ledger is seeded. */
76
+ ledger?: LedgerBackupSection;
22
77
  }
23
78
  export type ImportMode = 'copy' | 'overwrite';
24
79
  /** What the client sends to restore: the (already-selected) pages/databases + mode. */
@@ -26,7 +81,24 @@ export interface ImportRequest {
26
81
  pages: StoredPage[];
27
82
  databases: StoredDatabase[];
28
83
  mode: ImportMode;
84
+ /**
85
+ * LGR-15: the bundle's ledger section, forwarded on a full overwrite restore.
86
+ * Applied only when the target has no seeded ledger AND the selection carried
87
+ * the ledger's own pages/databases; otherwise skipped and reported.
88
+ */
89
+ ledger?: LedgerBackupSection;
29
90
  }
91
+ /**
92
+ * What became of a bundle's {@link LedgerBackupSection} on import (LGR-15).
93
+ * - `restored` — settings + audit stream + evidence assets applied;
94
+ * - `skipped-existing-ledger` — the target already has a seeded ledger, whose
95
+ * LGR-3 protections stand (restore ledger bundles into a FRESH library);
96
+ * - `skipped-copy-mode` — copy mode re-ids every page, which would sever the
97
+ * audit stream's entity references; the section only applies in overwrite;
98
+ * - `skipped-incomplete` — the page selection did not carry the ledger's own
99
+ * host pages/databases, so the section had nothing sound to attach to.
100
+ */
101
+ export type LedgerRestoreOutcome = 'restored' | 'skipped-existing-ledger' | 'skipped-copy-mode' | 'skipped-incomplete';
30
102
  export interface ImportResult {
31
103
  /** New pages created (copy mode, or overwrite of a not-yet-existing id). */
32
104
  created: number;
@@ -36,6 +108,8 @@ export interface ImportResult {
36
108
  renamed: number;
37
109
  /** old page id → new page id (copy mode; identity in overwrite). */
38
110
  idMap: Record<string, string>;
111
+ /** LGR-15: outcome of the bundle's ledger section; absent when none was sent. */
112
+ ledger?: LedgerRestoreOutcome;
39
113
  /**
40
114
  * True when this apply was a **replay** of an already-imported bundle (ER-6):
41
115
  * the bundle's content hash matched a prior import, so nothing was written and
package/dist/backup.js CHANGED
@@ -1,4 +1,11 @@
1
- export const BACKUP_VERSION = 1;
1
+ /**
2
+ * Version 2 (LGR-15): the bundle MAY carry a {@link LedgerBackupSection} — the
3
+ * ledger's durability surface (audit stream, settings rows, evidence assets)
4
+ * that pages/databases alone cannot express. Additive: a v1 bundle (no
5
+ * `ledger` key) still imports; a v2 bundle read by an old server imports its
6
+ * pages/databases and ignores the extra key.
7
+ */
8
+ export const BACKUP_VERSION = 2;
2
9
  export const BACKUP_CADENCES = ['daily', 'weekly', 'monthly', 'yearly'];
3
10
  /** Interval of each cadence, in milliseconds. */
4
11
  export const BACKUP_CADENCE_MS = {
@@ -1 +1 @@
1
- {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -4,9 +4,10 @@ import type { AgentChatEvent, AgentChatMessage, AgentChatOptions, AiConfig, AiPr
4
4
  import type { AclLevel, AgentEditsMode, AgentEditsPolicy, Member, MemberRole, MemberStatus, PageAcl, PageGraph, PageInput, PageMeta, PageVersionMeta, PageVisibility, StoredPage, StoredPageVersion } from './types';
5
5
  import type { InstanceConfig, InstanceInfo, StoredEdit } from './provenance';
6
6
  import type { AgentTokenMeta, AgentTokenScope } from './identity';
7
- import type { BackupCadence, BackupConfig, BackupStatus, ImportRequest, ImportResult } from './backup';
7
+ import type { BackupCadence, BackupConfig, BackupStatus, ImportRequest, ImportResult, LedgerBackupSection } from './backup';
8
8
  import type { DatabaseInput, DatabaseRow, DatabaseUpdate, RowInput, RowUpdate, StoredDatabase } from './database';
9
9
  import type { CommentInput, StoredComment, StoredSuggestion, SuggestionInput, SuggestionStatus, SuggestionUpdate } from './suggestions';
10
+ import type { LedgerAccount, LedgerAccountInput, LedgerAccountPatch, LedgerAuditEvent, LedgerClearedState, LedgerDraftInput, LedgerDraftPatch, LedgerInfo, LedgerPeriod, LedgerPeriodCloseInput, LedgerPeriodCloseResult, LedgerPeriodReopenResult, LedgerPosting, LedgerReconciliation, LedgerReconciliationInput, LedgerReconciliationPatch, LedgerReconciliationStatus, LedgerReconciliationSummary, LedgerReverseOptions, LedgerTransaction, LedgerTransactionState, LedgerVerifyReport } from './ledger';
10
11
  /** Handlers for a single page's live update stream. */
11
12
  export interface PageSubscription {
12
13
  /** A newer version of the page was saved (by anyone). */
@@ -170,10 +171,12 @@ export interface DataClient {
170
171
  * it. Resolves `true` if a live page was trashed.
171
172
  */
172
173
  deletePage(id: string): Promise<boolean>;
173
- /** Export the whole space: every live page (full data) + every database. */
174
+ /** Export the whole space: every live page (full data) + every database —
175
+ * plus the ledger durability section when a ledger is seeded (LGR-15). */
174
176
  exportLibrary(): Promise<{
175
177
  pages: StoredPage[];
176
178
  databases: StoredDatabase[];
179
+ ledger?: LedgerBackupSection;
177
180
  }>;
178
181
  /** Restore a (client-selected) set of pages/databases; see {@link ImportRequest}. */
179
182
  importLibrary(req: ImportRequest): Promise<ImportResult>;
@@ -289,6 +292,85 @@ export interface DataClient {
289
292
  reorderRows(databaseId: string, orderedIds: string[]): Promise<void>;
290
293
  /** Subscribe to a database's live row-list updates. Returns an unsubscribe fn. */
291
294
  subscribeRows(databaseId: string, onRows: (rows: DatabaseRow[]) => void): () => void;
295
+ /** Whether the ledger is initialized, and the seeded database/host ids. */
296
+ ledgerInfo(): Promise<LedgerInfo>;
297
+ /** Seed the four managed ledger databases + restricted host page (idempotent). */
298
+ ledgerInit(): Promise<LedgerInfo>;
299
+ /** List accounts (hierarchy is encoded in the colon-delimited names). */
300
+ ledgerListAccounts(): Promise<LedgerAccount[]>;
301
+ /** Create an account. `currency` defaults to `USD`. */
302
+ ledgerCreateAccount(input: LedgerAccountInput): Promise<LedgerAccount>;
303
+ /** Fetch one account, or `null` when it does not exist. */
304
+ ledgerGetAccount(id: string): Promise<LedgerAccount | null>;
305
+ /** Rename / close / reopen an account. Closing rejects at nonzero posted balance. */
306
+ ledgerUpdateAccount(id: string, patch: LedgerAccountPatch): Promise<LedgerAccount>;
307
+ /** List transactions with their postings (`state` filters; `limit` caps). */
308
+ ledgerListTransactions(opts?: {
309
+ state?: LedgerTransactionState;
310
+ limit?: number;
311
+ }): Promise<LedgerTransaction[]>;
312
+ /** Fetch one transaction (with postings), or `null` when it does not exist. */
313
+ ledgerGetTransaction(id: string): Promise<LedgerTransaction | null>;
314
+ /** Create a DRAFT transaction (with postings). Drafts are freely mutable. */
315
+ ledgerCreateDraft(input: LedgerDraftInput): Promise<LedgerTransaction>;
316
+ /** Update a DRAFT (posted/void transactions are immutable — typed rejection). */
317
+ ledgerUpdateDraft(id: string, patch: LedgerDraftPatch): Promise<LedgerTransaction>;
318
+ /** Delete a DRAFT and its postings (permanent, audited). Posted/void reject. */
319
+ ledgerDeleteDraft(id: string): Promise<boolean>;
320
+ /** Post a draft atomically (validates all invariants; assigns the entry number). */
321
+ ledgerPostTransaction(id: string): Promise<LedgerTransaction>;
322
+ /** Atomically create + post the reversing entry and void the original. */
323
+ ledgerReverseTransaction(id: string, opts?: LedgerReverseOptions): Promise<LedgerTransaction>;
324
+ /** Flip a posting between `pending`/`cleared` (`reconciled` is locked, LGR-11). */
325
+ ledgerSetPostingCleared(postingId: string, cleared: LedgerClearedState): Promise<LedgerPosting>;
326
+ /** List reconciliations, newest statement first. Filters are ANDed. */
327
+ ledgerListReconciliations(opts?: {
328
+ accountId?: string;
329
+ status?: LedgerReconciliationStatus;
330
+ }): Promise<LedgerReconciliation[]>;
331
+ /** One reconciliation with its live cleared balance + difference, or `null`. */
332
+ ledgerGetReconciliation(id: string): Promise<LedgerReconciliationSummary | null>;
333
+ /** START a reconciliation. Rejects `reconciliation-exists` if one is open. */
334
+ ledgerStartReconciliation(input: LedgerReconciliationInput): Promise<LedgerReconciliation>;
335
+ /** AMEND an OPEN reconciliation's statement date/balance (LGR-22) — the fix
336
+ * for a mistyped target, which no amount of ticking can reach zero. Touches
337
+ * no posting; returns the summary with the difference recomputed. */
338
+ ledgerAmendReconciliation(id: string, patch: LedgerReconciliationPatch): Promise<LedgerReconciliationSummary>;
339
+ /** ABANDON an OPEN reconciliation (LGR-22): end it without balancing it and
340
+ * without posting anything. Terminal, audited, posting-neutral — every tick
341
+ * keeps its cleared state, and the account is free to start a new one. */
342
+ ledgerAbandonReconciliation(id: string): Promise<LedgerReconciliation>;
343
+ /** Match (`cleared`) or unmatch (`pending`) one posting inside an OPEN one. */
344
+ ledgerToggleReconciliationPosting(id: string, postingId: string, cleared: 'pending' | 'cleared'): Promise<LedgerReconciliationSummary>;
345
+ /** FINISH — only at a difference of exactly 0; freezes the matched postings. */
346
+ ledgerFinishReconciliation(id: string): Promise<LedgerReconciliationSummary>;
347
+ /** REOPEN a finished reconciliation (explicit, audited); unfreezes its postings. */
348
+ ledgerReopenReconciliation(id: string): Promise<LedgerReconciliationSummary>;
349
+ /** Every period record — closed AND reopened history (LGR-12). */
350
+ ledgerListPeriods(): Promise<LedgerPeriod[]>;
351
+ /** CLOSE a period: closing entry + date-range lock; warns (never blocks) on
352
+ * open reconciliations. Store-enforced — `period-closed` rejections for any
353
+ * posting/reversal dated inside the range hold over both transports. */
354
+ ledgerClosePeriod(input: LedgerPeriodCloseInput): Promise<LedgerPeriodCloseResult>;
355
+ /** REOPEN a closed period (explicit, audited): voids the closing entry via a
356
+ * reversal and restores postability for the range. */
357
+ ledgerReopenPeriod(id: string): Promise<LedgerPeriodReopenResult>;
358
+ /** Read the append-only audit log, newest first (`before` = seq cursor). */
359
+ ledgerListAudit(opts?: {
360
+ limit?: number;
361
+ before?: number;
362
+ }): Promise<LedgerAuditEvent[]>;
363
+ /** The whole ledger as the canonical postings CSV (LGR-7) — byte-stable:
364
+ * same data ⇒ identical bytes over BOTH transports. */
365
+ ledgerExportCsv(): Promise<string>;
366
+ /** The whole ledger as a Beancount journal (LGR-13) — byte-stable like the
367
+ * CSV, built from the same read model; `bean-check`/Fava re-verify it with
368
+ * an independent implementation. */
369
+ ledgerExportBeancount(): Promise<string>;
370
+ /** The independent invariant verifier's report (LGR-7). Admin-gated over
371
+ * HTTP (the report names entity ids across the whole book); the local
372
+ * single-user store answers directly. */
373
+ ledgerVerify(): Promise<LedgerVerifyReport>;
292
374
  /** List a page's suggestions, newest first. `status` filters (e.g. only open). */
293
375
  listSuggestions(pageId: string, status?: SuggestionStatus): Promise<StoredSuggestion[]>;
294
376
  /** Persist a new suggestion (status defaults to `open`). */
@@ -554,6 +636,7 @@ export declare class HttpDataClient implements DataClient {
554
636
  exportLibrary(): Promise<{
555
637
  pages: StoredPage[];
556
638
  databases: StoredDatabase[];
639
+ ledger?: LedgerBackupSection;
557
640
  }>;
558
641
  importLibrary(req: ImportRequest): Promise<ImportResult>;
559
642
  listTrash(): Promise<PageMeta[]>;
@@ -619,6 +702,55 @@ export declare class HttpDataClient implements DataClient {
619
702
  updateRow(databaseId: string, rowId: string, patch: RowUpdate): Promise<DatabaseRow>;
620
703
  reorderRows(databaseId: string, orderedIds: string[]): Promise<void>;
621
704
  subscribeRows(databaseId: string, onRows: (rows: DatabaseRow[]) => void): () => void;
705
+ /**
706
+ * Like {@link request}, but re-materializes the server's `{error, code}` body
707
+ * into a typed {@link LedgerError} — so a caller catches the SAME error class
708
+ * over HTTP as it does against the in-process {@link PageStore} (local mode).
709
+ */
710
+ private ledgerRequest;
711
+ /** Re-materialize a non-2xx ledger response into a typed {@link LedgerError}. */
712
+ private throwLedgerError;
713
+ ledgerInfo(): Promise<LedgerInfo>;
714
+ ledgerInit(): Promise<LedgerInfo>;
715
+ ledgerListAccounts(): Promise<LedgerAccount[]>;
716
+ ledgerCreateAccount(input: LedgerAccountInput): Promise<LedgerAccount>;
717
+ ledgerGetAccount(id: string): Promise<LedgerAccount | null>;
718
+ ledgerUpdateAccount(id: string, patch: LedgerAccountPatch): Promise<LedgerAccount>;
719
+ ledgerListTransactions(opts?: {
720
+ state?: LedgerTransactionState;
721
+ limit?: number;
722
+ }): Promise<LedgerTransaction[]>;
723
+ ledgerGetTransaction(id: string): Promise<LedgerTransaction | null>;
724
+ ledgerCreateDraft(input: LedgerDraftInput): Promise<LedgerTransaction>;
725
+ ledgerUpdateDraft(id: string, patch: LedgerDraftPatch): Promise<LedgerTransaction>;
726
+ ledgerDeleteDraft(id: string): Promise<boolean>;
727
+ ledgerPostTransaction(id: string): Promise<LedgerTransaction>;
728
+ ledgerReverseTransaction(id: string, opts?: LedgerReverseOptions): Promise<LedgerTransaction>;
729
+ ledgerSetPostingCleared(postingId: string, cleared: LedgerClearedState): Promise<LedgerPosting>;
730
+ ledgerListReconciliations(opts?: {
731
+ accountId?: string;
732
+ status?: LedgerReconciliationStatus;
733
+ }): Promise<LedgerReconciliation[]>;
734
+ ledgerGetReconciliation(id: string): Promise<LedgerReconciliationSummary | null>;
735
+ ledgerStartReconciliation(input: LedgerReconciliationInput): Promise<LedgerReconciliation>;
736
+ ledgerAmendReconciliation(id: string, patch: LedgerReconciliationPatch): Promise<LedgerReconciliationSummary>;
737
+ ledgerAbandonReconciliation(id: string): Promise<LedgerReconciliation>;
738
+ ledgerToggleReconciliationPosting(id: string, postingId: string, cleared: 'pending' | 'cleared'): Promise<LedgerReconciliationSummary>;
739
+ ledgerFinishReconciliation(id: string): Promise<LedgerReconciliationSummary>;
740
+ ledgerReopenReconciliation(id: string): Promise<LedgerReconciliationSummary>;
741
+ ledgerListPeriods(): Promise<LedgerPeriod[]>;
742
+ ledgerClosePeriod(input: LedgerPeriodCloseInput): Promise<LedgerPeriodCloseResult>;
743
+ ledgerReopenPeriod(id: string): Promise<LedgerPeriodReopenResult>;
744
+ ledgerListAudit(opts?: {
745
+ limit?: number;
746
+ before?: number;
747
+ }): Promise<LedgerAuditEvent[]>;
748
+ /** Canonical postings CSV (LGR-7) — a ledger read that is text, not JSON. */
749
+ ledgerExportCsv(): Promise<string>;
750
+ /** Beancount journal (LGR-13) — text like the CSV, same error mapping. */
751
+ ledgerExportBeancount(): Promise<string>;
752
+ /** The independent verifier's report (admin-gated server-side). */
753
+ ledgerVerify(): Promise<LedgerVerifyReport>;
622
754
  listSuggestions(pageId: string, status?: SuggestionStatus): Promise<StoredSuggestion[]>;
623
755
  createSuggestion(input: SuggestionInput): Promise<StoredSuggestion>;
624
756
  updateSuggestion(id: string, patch: SuggestionUpdate): Promise<StoredSuggestion>;
package/dist/client.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { API } from './routes';
2
+ import { LEDGER_ERROR_CODES, LedgerError } from './ledger';
2
3
  /**
3
4
  * The global `fetch`, wrapped so it's safe to store on an object and call as a
4
5
  * property/method. WebKit (the desktop WKWebView) throws "Can only call
@@ -779,6 +780,176 @@ export class HttpDataClient {
779
780
  subscribeRows(databaseId, onRows) {
780
781
  return this.liveStream().onRows(databaseId, onRows);
781
782
  }
783
+ // ── Ledger: server-enforced double-entry accounting (LGR-3) ──────────────────
784
+ /**
785
+ * Like {@link request}, but re-materializes the server's `{error, code}` body
786
+ * into a typed {@link LedgerError} — so a caller catches the SAME error class
787
+ * over HTTP as it does against the in-process {@link PageStore} (local mode).
788
+ */
789
+ async ledgerRequest(method, path, body) {
790
+ const res = await this.authFetch(`${this.baseUrl}${path}`, {
791
+ method,
792
+ headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
793
+ body: body === undefined ? undefined : JSON.stringify(body),
794
+ cache: 'no-store',
795
+ });
796
+ if (!res.ok)
797
+ return this.throwLedgerError(res);
798
+ if (res.status === 204)
799
+ return undefined;
800
+ return (await res.json());
801
+ }
802
+ /** Re-materialize a non-2xx ledger response into a typed {@link LedgerError}. */
803
+ async throwLedgerError(res) {
804
+ let data = null;
805
+ try {
806
+ data = (await res.json());
807
+ }
808
+ catch {
809
+ data = null;
810
+ }
811
+ if (data?.code && LEDGER_ERROR_CODES.includes(data.code)) {
812
+ throw new LedgerError(data.code, data.error ?? `ledger request failed (${res.status})`);
813
+ }
814
+ throw new Error(`OpenBook request failed (${res.status} ${res.statusText})${data?.error ? `: ${data.error}` : ''}`);
815
+ }
816
+ ledgerInfo() {
817
+ return this.ledgerRequest('GET', API.ledger);
818
+ }
819
+ ledgerInit() {
820
+ return this.ledgerRequest('POST', API.ledger);
821
+ }
822
+ ledgerListAccounts() {
823
+ return this.ledgerRequest('GET', API.ledgerAccounts);
824
+ }
825
+ ledgerCreateAccount(input) {
826
+ return this.ledgerRequest('POST', API.ledgerAccounts, input);
827
+ }
828
+ async ledgerGetAccount(id) {
829
+ try {
830
+ return await this.ledgerRequest('GET', API.ledgerAccount(id));
831
+ }
832
+ catch (err) {
833
+ if (err instanceof LedgerError && err.code === 'not-found')
834
+ return null;
835
+ throw err;
836
+ }
837
+ }
838
+ ledgerUpdateAccount(id, patch) {
839
+ return this.ledgerRequest('PATCH', API.ledgerAccount(id), patch);
840
+ }
841
+ ledgerListTransactions(opts) {
842
+ const params = new URLSearchParams();
843
+ if (opts?.state)
844
+ params.set('state', opts.state);
845
+ if (opts?.limit != null)
846
+ params.set('limit', String(opts.limit));
847
+ const query = params.toString();
848
+ return this.ledgerRequest('GET', `${API.ledgerTransactions}${query ? `?${query}` : ''}`);
849
+ }
850
+ async ledgerGetTransaction(id) {
851
+ try {
852
+ return await this.ledgerRequest('GET', API.ledgerTransaction(id));
853
+ }
854
+ catch (err) {
855
+ if (err instanceof LedgerError && err.code === 'not-found')
856
+ return null;
857
+ throw err;
858
+ }
859
+ }
860
+ ledgerCreateDraft(input) {
861
+ return this.ledgerRequest('POST', API.ledgerTransactions, input);
862
+ }
863
+ ledgerUpdateDraft(id, patch) {
864
+ return this.ledgerRequest('PATCH', API.ledgerTransaction(id), patch);
865
+ }
866
+ async ledgerDeleteDraft(id) {
867
+ await this.ledgerRequest('DELETE', API.ledgerTransaction(id));
868
+ return true;
869
+ }
870
+ ledgerPostTransaction(id) {
871
+ return this.ledgerRequest('POST', API.ledgerTransactionPost(id));
872
+ }
873
+ ledgerReverseTransaction(id, opts) {
874
+ return this.ledgerRequest('POST', API.ledgerTransactionReverse(id), opts ?? {});
875
+ }
876
+ ledgerSetPostingCleared(postingId, cleared) {
877
+ return this.ledgerRequest('PUT', API.ledgerPostingCleared(postingId), { cleared });
878
+ }
879
+ // ── Statement reconciliation (LGR-11) ───────────────────────────────────────
880
+ ledgerListReconciliations(opts) {
881
+ const params = new URLSearchParams();
882
+ if (opts?.accountId != null)
883
+ params.set('accountId', opts.accountId);
884
+ if (opts?.status != null)
885
+ params.set('status', opts.status);
886
+ const query = params.toString();
887
+ return this.ledgerRequest('GET', `${API.ledgerReconciliations}${query ? `?${query}` : ''}`);
888
+ }
889
+ async ledgerGetReconciliation(id) {
890
+ try {
891
+ return await this.ledgerRequest('GET', API.ledgerReconciliation(id));
892
+ }
893
+ catch (err) {
894
+ if (err instanceof LedgerError && err.code === 'not-found')
895
+ return null;
896
+ throw err;
897
+ }
898
+ }
899
+ ledgerStartReconciliation(input) {
900
+ return this.ledgerRequest('POST', API.ledgerReconciliations, input);
901
+ }
902
+ ledgerAmendReconciliation(id, patch) {
903
+ return this.ledgerRequest('PATCH', API.ledgerReconciliation(id), patch);
904
+ }
905
+ ledgerAbandonReconciliation(id) {
906
+ return this.ledgerRequest('POST', API.ledgerReconciliationAbandon(id));
907
+ }
908
+ ledgerToggleReconciliationPosting(id, postingId, cleared) {
909
+ return this.ledgerRequest('PUT', API.ledgerReconciliationPosting(id, postingId), { cleared });
910
+ }
911
+ ledgerFinishReconciliation(id) {
912
+ return this.ledgerRequest('POST', API.ledgerReconciliationFinish(id));
913
+ }
914
+ ledgerReopenReconciliation(id) {
915
+ return this.ledgerRequest('POST', API.ledgerReconciliationReopen(id));
916
+ }
917
+ ledgerListPeriods() {
918
+ return this.ledgerRequest('GET', API.ledgerPeriods);
919
+ }
920
+ ledgerClosePeriod(input) {
921
+ return this.ledgerRequest('POST', API.ledgerPeriods, input);
922
+ }
923
+ ledgerReopenPeriod(id) {
924
+ return this.ledgerRequest('POST', API.ledgerPeriodReopen(id));
925
+ }
926
+ ledgerListAudit(opts) {
927
+ const params = new URLSearchParams();
928
+ if (opts?.limit != null)
929
+ params.set('limit', String(opts.limit));
930
+ if (opts?.before != null)
931
+ params.set('before', String(opts.before));
932
+ const query = params.toString();
933
+ return this.ledgerRequest('GET', `${API.ledgerAudit}${query ? `?${query}` : ''}`);
934
+ }
935
+ /** Canonical postings CSV (LGR-7) — a ledger read that is text, not JSON. */
936
+ async ledgerExportCsv() {
937
+ const res = await this.authFetch(`${this.baseUrl}${API.ledgerExportCsv}`, { cache: 'no-store' });
938
+ if (!res.ok)
939
+ return this.throwLedgerError(res);
940
+ return res.text();
941
+ }
942
+ /** Beancount journal (LGR-13) — text like the CSV, same error mapping. */
943
+ async ledgerExportBeancount() {
944
+ const res = await this.authFetch(`${this.baseUrl}${API.ledgerExportBeancount}`, { cache: 'no-store' });
945
+ if (!res.ok)
946
+ return this.throwLedgerError(res);
947
+ return res.text();
948
+ }
949
+ /** The independent verifier's report (admin-gated server-side). */
950
+ async ledgerVerify() {
951
+ return this.ledgerRequest('GET', API.ledgerVerify);
952
+ }
782
953
  // ── Suggestions + comments (the review layer) ────────────────────────────────
783
954
  async listSuggestions(pageId, status) {
784
955
  const path = status ? `${API.suggestions(pageId)}?status=${encodeURIComponent(status)}` : API.suggestions(pageId);