@pacspace-io/sdk 0.2.0 → 0.3.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.
@@ -3,6 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BalanceResource = void 0;
4
4
  const polling_1 = require("../utils/polling");
5
5
  const submission_1 = require("./submission");
6
+ const toRecordStatus = (status) => {
7
+ const normalized = String(status ?? '').toUpperCase();
8
+ if (normalized === 'PROCESSING')
9
+ return 'PROCESSING';
10
+ if (normalized === 'VERIFIED')
11
+ return 'VERIFIED';
12
+ if (normalized === 'FAILED')
13
+ return 'FAILED';
14
+ return 'QUEUED';
15
+ };
16
+ const toCheckpointType = (value) => {
17
+ return String(value) === 'recordId' ? 'recordId' : 'proofRoot';
18
+ };
19
+ const toApiCheckpointType = (value) => {
20
+ if (!value)
21
+ return undefined;
22
+ return value;
23
+ };
6
24
  /**
7
25
  * PacSpace Balance API resource.
8
26
  *
@@ -56,7 +74,8 @@ class BalanceResource {
56
74
  body.referenceId = referenceId;
57
75
  if (metadata !== undefined)
58
76
  body.metadata = metadata;
59
- return this.client.post('/api/v1/balance/delta', body, requestOptions);
77
+ const response = await this.client.post('/api/v1/balance/delta', body, requestOptions);
78
+ return this.normalizeEmitResponse(response);
60
79
  }
61
80
  /**
62
81
  * Record a delta and wait for it to be verified.
@@ -87,42 +106,45 @@ class BalanceResource {
87
106
  signal,
88
107
  });
89
108
  // If already terminal, return immediately
90
- if (['ANCHORED', 'VERIFIED', 'FAILED'].includes(initial.status)) {
109
+ if (['VERIFIED', 'FAILED'].includes(initial.status)) {
91
110
  return initial;
92
111
  }
93
- // Poll for terminal status using the balance delta endpoint
94
- return (0, polling_1.pollUntilTerminal)(() => this.client.get(`/api/v1/balance/delta/${initial.anchorId}`, { signal }), { timeout, pollInterval, signal });
112
+ // Poll for terminal status using the balance delta endpoint, then merge
113
+ // the latest status with the original emit metadata.
114
+ const terminalStatus = await (0, polling_1.pollUntilTerminal)(() => this.deltaStatus(initial.recordId, { signal }), { timeout, pollInterval, signal });
115
+ return this.mergeEmitWithStatus(initial, terminalStatus);
95
116
  }
96
117
  /**
97
118
  * Check the status of a previously emitted delta.
98
119
  *
99
- * @param anchorId - The anchor ID returned from emit.
120
+ * @param recordId - The record ID returned from emit.
100
121
  * @param options - Request overrides.
101
122
  * @returns Delta status with receipt and transaction details.
102
123
  *
103
124
  * @example
104
125
  * ```typescript
105
- * const status = await pac.balance.deltaStatus('anc_abc123');
106
- * console.log(status.status); // 'QUEUED', 'ANCHORED', etc.
126
+ * const status = await pac.balance.deltaStatus('rec_abc123');
127
+ * console.log(status.status); // 'QUEUED', 'PROCESSING', etc.
107
128
  * ```
108
129
  */
109
- async deltaStatus(anchorId, options) {
110
- return this.client.get(`/api/v1/balance/delta/${encodeURIComponent(anchorId)}`, options);
130
+ async deltaStatus(recordId, options) {
131
+ const response = await this.client.get(`/api/v1/balance/delta/${encodeURIComponent(recordId)}`, options);
132
+ return this.normalizeDeltaStatusResponse(response);
111
133
  }
112
134
  /**
113
135
  * Alias for `deltaStatus()` with lifecycle-oriented naming.
114
136
  */
115
- async getAnchorStatus(anchorId, options) {
116
- return this.deltaStatus(anchorId, options);
137
+ async getRecordStatus(recordId, options) {
138
+ return this.deltaStatus(recordId, options);
117
139
  }
118
140
  /**
119
- * Poll anchor lifecycle until it reaches a terminal status.
141
+ * Poll record lifecycle until it reaches a terminal status.
120
142
  *
121
- * Terminal statuses are `ANCHORED`, `VERIFIED`, and `FAILED`.
143
+ * Terminal statuses are `VERIFIED` and `FAILED`.
122
144
  */
123
- async waitForAnchored(anchorId, options) {
145
+ async waitForVerified(recordId, options) {
124
146
  const { timeout = 60000, pollInterval = 2000, signal, ...requestOptions } = options ?? {};
125
- return (0, polling_1.pollUntilTerminal)(() => this.getAnchorStatus(anchorId, {
147
+ return (0, polling_1.pollUntilTerminal)(() => this.getRecordStatus(recordId, {
126
148
  ...requestOptions,
127
149
  signal,
128
150
  }), { timeout, pollInterval, signal });
@@ -149,7 +171,8 @@ class BalanceResource {
149
171
  params.set('limit', String(limit));
150
172
  const query = params.toString();
151
173
  const path = `/api/v1/balance/gaps/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
152
- return this.client.get(path, requestOptions);
174
+ const response = await this.client.get(path, requestOptions);
175
+ return this.normalizeSequenceGapsResponse(response);
153
176
  }
154
177
  /**
155
178
  * Record multiple deltas in a single batch request.
@@ -172,7 +195,8 @@ class BalanceResource {
172
195
  */
173
196
  async emitBatch(deltas, options) {
174
197
  const { ...requestOptions } = options ?? {};
175
- return this.client.post('/api/v1/balance/delta/batch', { deltas }, requestOptions);
198
+ const response = await this.client.post('/api/v1/balance/delta/batch', { deltas }, requestOptions);
199
+ return this.normalizeBulkEmitResponse(response);
176
200
  }
177
201
  /**
178
202
  * Queue one tenant-produced summary for SDK-managed submission.
@@ -241,7 +265,7 @@ class BalanceResource {
241
265
  * ```
242
266
  */
243
267
  async derive(customerId, options) {
244
- const { startingBalance, startingCheckpoint, startingCheckpointType, limit, offset, ...requestOptions } = options ?? {};
268
+ const { startingBalance, startingCheckpoint, startingCheckpointType, limit, offset, period, timePreset, startDate, endDate, ...requestOptions } = options ?? {};
245
269
  // Build query string
246
270
  const params = new URLSearchParams();
247
271
  if (startingBalance !== undefined) {
@@ -250,8 +274,9 @@ class BalanceResource {
250
274
  if (startingCheckpoint !== undefined) {
251
275
  params.set('startingCheckpoint', startingCheckpoint);
252
276
  }
253
- if (startingCheckpointType !== undefined) {
254
- params.set('startingCheckpointType', startingCheckpointType);
277
+ const apiCheckpointType = toApiCheckpointType(startingCheckpointType);
278
+ if (apiCheckpointType !== undefined) {
279
+ params.set('startingCheckpointType', apiCheckpointType);
255
280
  }
256
281
  if (limit !== undefined) {
257
282
  params.set('limit', String(limit));
@@ -259,9 +284,60 @@ class BalanceResource {
259
284
  if (offset !== undefined) {
260
285
  params.set('offset', String(offset));
261
286
  }
287
+ if (period !== undefined) {
288
+ params.set('period', period);
289
+ }
290
+ if (timePreset !== undefined) {
291
+ params.set('timePreset', timePreset);
292
+ }
293
+ if (startDate !== undefined) {
294
+ params.set('startDate', startDate);
295
+ }
296
+ if (endDate !== undefined) {
297
+ params.set('endDate', endDate);
298
+ }
262
299
  const query = params.toString();
263
300
  const path = `/api/v1/balance/derive/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
264
- return this.client.get(path, requestOptions);
301
+ const response = await this.client.get(path, requestOptions);
302
+ return this.normalizeDeriveResponse(response);
303
+ }
304
+ /**
305
+ * Convenience helper for deriving the current UTC month.
306
+ */
307
+ async deriveCurrentMonth(customerId, options) {
308
+ return this.derive(customerId, {
309
+ ...options,
310
+ timePreset: 'current_month',
311
+ });
312
+ }
313
+ /**
314
+ * Convenience helper for deriving a specific UTC month (`YYYY-MM`).
315
+ */
316
+ async deriveForPeriod(customerId, period, options) {
317
+ return this.derive(customerId, {
318
+ ...options,
319
+ period,
320
+ });
321
+ }
322
+ /**
323
+ * Convenience helper for deriving a rolling window of recent UTC months.
324
+ *
325
+ * For example, `months = 3` resolves from the start of month N-2 through the
326
+ * end of the current UTC month.
327
+ */
328
+ async deriveMonthsBack(customerId, months, options) {
329
+ if (!Number.isInteger(months) || months < 1) {
330
+ throw new TypeError('months must be a positive integer');
331
+ }
332
+ const now = new Date();
333
+ const endExclusive = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
334
+ const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (months - 1), 1));
335
+ return this.derive(customerId, {
336
+ ...options,
337
+ timePreset: 'custom',
338
+ startDate: start.toISOString(),
339
+ endDate: endExclusive.toISOString(),
340
+ });
265
341
  }
266
342
  // -------------------------------------------------------------------------
267
343
  // Compare
@@ -291,7 +367,7 @@ class BalanceResource {
291
367
  * ```
292
368
  */
293
369
  async compare(customerId, balances, options) {
294
- const { startingBalance = 0, startingCheckpoint, startingCheckpointType, ...requestOptions } = options ?? {};
370
+ const { startingBalance = 0, startingCheckpoint, startingCheckpointType, period, timePreset, startDate, endDate, ...requestOptions } = options ?? {};
295
371
  const body = {
296
372
  customerId,
297
373
  yourBalance: balances.yours,
@@ -301,10 +377,24 @@ class BalanceResource {
301
377
  if (startingCheckpoint !== undefined) {
302
378
  body.startingCheckpoint = startingCheckpoint;
303
379
  }
304
- if (startingCheckpointType !== undefined) {
305
- body.startingCheckpointType = startingCheckpointType;
380
+ const apiCheckpointType = toApiCheckpointType(startingCheckpointType);
381
+ if (apiCheckpointType !== undefined) {
382
+ body.startingCheckpointType = apiCheckpointType;
383
+ }
384
+ if (period !== undefined) {
385
+ body.period = period;
386
+ }
387
+ if (timePreset !== undefined) {
388
+ body.timePreset = timePreset;
389
+ }
390
+ if (startDate !== undefined) {
391
+ body.startDate = startDate;
392
+ }
393
+ if (endDate !== undefined) {
394
+ body.endDate = endDate;
306
395
  }
307
- return this.client.post('/api/v1/balance/compare', body, requestOptions);
396
+ const response = await this.client.post('/api/v1/balance/compare', body, requestOptions);
397
+ return this.normalizeCompareResponse(response);
308
398
  }
309
399
  // -------------------------------------------------------------------------
310
400
  // Receipt
@@ -314,7 +404,7 @@ class BalanceResource {
314
404
  *
315
405
  * When `options.period` is provided, returns a period-specific receipt (proof root,
316
406
  * verifyUrl, verificationReference) suitable for sharing on invoices. When
317
- * omitted, returns the full ledger receipt with all verified deltas.
407
+ * omitted, returns the full record receipt with all verified deltas.
318
408
  *
319
409
  * @param customerId - The customer account to generate a receipt for.
320
410
  * @param options - Optional period (YYYY-MM) and request overrides.
@@ -327,7 +417,7 @@ class BalanceResource {
327
417
  * console.log(receipt.proofRoot); // Include on invoice
328
418
  * console.log(receipt.verifyUrl); // Share with counterparty
329
419
  *
330
- * // Full ledger receipt
420
+ * // Full record receipt
331
421
  * const full = await pac.balance.receipt('cust_123');
332
422
  * console.log(full.finalBalance);
333
423
  * ```
@@ -343,7 +433,8 @@ class BalanceResource {
343
433
  }
344
434
  const { period: _period, timePreset: _timePreset, startDate: _startDate, endDate: _endDate, ...requestOptions } = opts;
345
435
  const hasOverrides = Object.keys(requestOptions).length > 0;
346
- return this.client.get(`/api/v1/balance/receipt/${encodeURIComponent(customerId)}`, hasOverrides ? requestOptions : undefined);
436
+ const response = await this.client.get(`/api/v1/balance/receipt/${encodeURIComponent(customerId)}`, hasOverrides ? requestOptions : undefined);
437
+ return this.normalizeReceiptResponse(response);
347
438
  }
348
439
  /**
349
440
  * @internal
@@ -362,7 +453,8 @@ class BalanceResource {
362
453
  params.set('endDate', endDate);
363
454
  const query = params.toString();
364
455
  const path = `/api/v1/balance/invoice-proof/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
365
- return this.client.get(path, requestOptions);
456
+ const response = await this.client.get(path, requestOptions);
457
+ return this.normalizeInvoiceProofResponse(response);
366
458
  }
367
459
  // -------------------------------------------------------------------------
368
460
  // Checkpoint
@@ -376,14 +468,14 @@ class BalanceResource {
376
468
  *
377
469
  * @param customerId - Customer to checkpoint (omit for all customers).
378
470
  * @param options - Period (YYYY-MM) and request overrides.
379
- * @returns Checkpoint details with Merkle root and status.
471
+ * @returns Checkpoint details with proof root and status.
380
472
  *
381
473
  * @example
382
474
  * ```typescript
383
475
  * const checkpoint = await pac.balance.checkpoint('cust_123', {
384
476
  * period: '2026-02',
385
477
  * });
386
- * console.log(checkpoint.merkleRoot); // Include in your invoice
478
+ * console.log(checkpoint.proofRoot); // Include in your invoice
387
479
  * ```
388
480
  */
389
481
  async checkpoint(customerId, options) {
@@ -403,7 +495,8 @@ class BalanceResource {
403
495
  body.mode = mode;
404
496
  if (fingerprints !== undefined)
405
497
  body.fingerprints = fingerprints;
406
- return this.client.post('/api/v1/balance/checkpoint', body, requestOptions);
498
+ const response = await this.client.post('/api/v1/balance/checkpoint', body, requestOptions);
499
+ return this.normalizeCheckpointResponse(response);
407
500
  }
408
501
  // -------------------------------------------------------------------------
409
502
  // List Checkpoints
@@ -444,7 +537,8 @@ class BalanceResource {
444
537
  params.set('offset', String(offset));
445
538
  const query = params.toString();
446
539
  const path = `/api/v1/balance/checkpoints${query ? `?${query}` : ''}`;
447
- return this.client.get(path, requestOptions);
540
+ const response = await this.client.get(path, requestOptions);
541
+ return this.normalizeListCheckpointsResponse(response);
448
542
  }
449
543
  // -------------------------------------------------------------------------
450
544
  // Webhook Deliveries
@@ -494,13 +588,13 @@ class BalanceResource {
494
588
  // Customer Ledgers
495
589
  // -------------------------------------------------------------------------
496
590
  /**
497
- * List all customer ledgers for your account.
591
+ * List all customer balance records for your account.
498
592
  *
499
593
  * Each unique customerId passed to emit() automatically creates an
500
- * isolated ledger. This method returns a paginated list of all ledgers.
594
+ * isolated customer record. This method returns a paginated list.
501
595
  *
502
596
  * @param options - Search, pagination, and request overrides.
503
- * @returns Paginated list of customer ledgers.
597
+ * @returns Paginated list of customer records.
504
598
  *
505
599
  * @example
506
600
  * ```typescript
@@ -521,36 +615,511 @@ class BalanceResource {
521
615
  params.set('page', String(page));
522
616
  const query = params.toString();
523
617
  const path = `/api/v1/balance/customers${query ? `?${query}` : ''}`;
524
- return this.client.get(path, requestOptions);
618
+ const response = await this.client.get(path, requestOptions);
619
+ return this.normalizeListCustomersResponse(response);
525
620
  }
526
621
  /**
527
- * Get the full ledger detail for a specific customer.
622
+ * Get the full record detail for a specific customer.
528
623
  *
529
- * Returns the derived ledger identifier, computed balance, delta count,
530
- * and latest checkpoint. The ledger identifier is deterministic --
531
- * the same customerId always produces the same identifier.
624
+ * Returns the derived customer reference, computed balance, delta count,
625
+ * and latest checkpoint. The customer reference is deterministic.
532
626
  *
533
627
  * @param customerId - The customer to look up.
534
628
  * @param options - Activity pagination and request overrides.
535
- * @returns Customer ledger detail including balance and identifiers.
629
+ * @returns Customer detail including balance and references.
536
630
  *
537
631
  * @example
538
632
  * ```typescript
539
- * const ledger = await pac.balance.customer('cust_001');
540
- * console.log(ledger.ledgerIdentifier); // 0xA1b2...Ef34
541
- * console.log(ledger.computedBalance); // 4500.00
633
+ * const customer = await pac.balance.customer('cust_001');
634
+ * console.log(customer.customerReference); // 0xA1b2...Ef34
635
+ * console.log(customer.computedBalance); // 4500.00
542
636
  * ```
543
637
  */
544
638
  async customer(customerId, options) {
545
- const { deltaPage, deltaLimit, ...requestOptions } = options ?? {};
639
+ const { deltaPage, deltaLimit, period, ...requestOptions } = options ?? {};
546
640
  const params = new URLSearchParams();
547
641
  if (deltaPage !== undefined)
548
642
  params.set('deltaPage', String(deltaPage));
549
643
  if (deltaLimit !== undefined)
550
644
  params.set('deltaLimit', String(deltaLimit));
645
+ if (period !== undefined)
646
+ params.set('period', period);
551
647
  const query = params.toString();
552
648
  const path = `/api/v1/balance/customers/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
553
- return this.client.get(path, requestOptions);
649
+ const response = await this.client.get(path, requestOptions);
650
+ return this.normalizeCustomerDetailResponse(response);
651
+ }
652
+ normalizeEmitResponse(data) {
653
+ const proofRoot = String(data.proofRoot ?? data.receiptId ?? '');
654
+ return {
655
+ recordId: String(data.recordId ?? ''),
656
+ customerId: String(data.customerId ?? ''),
657
+ delta: Number(data.delta ?? 0),
658
+ sequenceNumber: data.sequenceNumber == null ? undefined : String(data.sequenceNumber),
659
+ status: toRecordStatus(data.status),
660
+ receiptId: String(data.receiptId ?? proofRoot),
661
+ proofRoot,
662
+ itemHashes: Array.isArray(data.itemHashes)
663
+ ? data.itemHashes.map((value) => String(value))
664
+ : [],
665
+ estimatedVerifiedDeltas: Number(data.estimatedVerifiedDeltas ?? 0),
666
+ estimatedCredits: Number(data.estimatedCredits ?? 0),
667
+ receivedAt: String(data.receivedAt ?? ''),
668
+ message: String(data.message ?? ''),
669
+ };
670
+ }
671
+ mergeEmitWithStatus(initial, status) {
672
+ const firstDelta = status.deltas[0];
673
+ const customerId = status.customerId ?? firstDelta?.customerId ?? initial.customerId;
674
+ const delta = status.delta ?? firstDelta?.delta ?? initial.delta;
675
+ const sequenceNumber = firstDelta?.sequenceNumber ?? initial.sequenceNumber;
676
+ return {
677
+ ...initial,
678
+ recordId: status.recordId || initial.recordId,
679
+ customerId: customerId == null ? initial.customerId : String(customerId),
680
+ delta: delta == null ? initial.delta : Number(delta),
681
+ sequenceNumber: sequenceNumber ?? undefined,
682
+ status: status.status,
683
+ receiptId: status.receiptId ?? initial.receiptId,
684
+ proofRoot: status.receiptId ?? initial.proofRoot,
685
+ receivedAt: status.receivedAt || initial.receivedAt,
686
+ message: status.status === 'VERIFIED'
687
+ ? 'Delta verified.'
688
+ : status.status === 'FAILED'
689
+ ? 'Delta verification failed.'
690
+ : initial.message,
691
+ };
692
+ }
693
+ normalizeDeltaStatusResponse(data) {
694
+ const deltas = Array.isArray(data.deltas)
695
+ ? data.deltas.map((entry) => {
696
+ const item = entry;
697
+ return {
698
+ customerId: item.customerId == null ? null : String(item.customerId),
699
+ delta: item.delta == null ? null : Number(item.delta),
700
+ sequenceNumber: item.sequenceNumber == null ? null : String(item.sequenceNumber),
701
+ };
702
+ })
703
+ : [];
704
+ return {
705
+ recordId: String(data.recordId ?? ''),
706
+ status: toRecordStatus(data.status),
707
+ deltaCount: Number(data.deltaCount ?? deltas.length),
708
+ customerId: data.customerId == null ? null : String(data.customerId),
709
+ delta: data.delta == null ? null : Number(data.delta),
710
+ deltas,
711
+ receiptId: data.receiptId == null ? null : String(data.receiptId),
712
+ verificationReference: data.verificationReference == null ? null : String(data.verificationReference),
713
+ receivedAt: String(data.receivedAt ?? ''),
714
+ verifiedAt: data.verifiedAt == null ? null : String(data.verifiedAt),
715
+ };
716
+ }
717
+ normalizeDeriveResponse(data) {
718
+ const deltas = Array.isArray(data.deltas)
719
+ ? data.deltas.map((entry) => {
720
+ const item = entry;
721
+ const proofRoot = String(item.proofRoot ?? item.receiptId ?? '');
722
+ return {
723
+ recordId: String(item.recordId ?? ''),
724
+ itemHash: String(item.itemHash ?? ''),
725
+ proofRoot,
726
+ receiptId: String(item.receiptId ?? proofRoot),
727
+ sequenceNumber: item.sequenceNumber == null ? null : String(item.sequenceNumber),
728
+ delta: Number(item.delta ?? 0),
729
+ reason: item.reason == null ? null : String(item.reason),
730
+ referenceId: item.referenceId == null ? null : String(item.referenceId),
731
+ window: String(item.window ?? ''),
732
+ declaredTimestamp: item.declaredTimestamp == null
733
+ ? null
734
+ : String(item.declaredTimestamp),
735
+ blockTimestamp: item.blockTimestamp == null ? null : String(item.blockTimestamp),
736
+ dataPurged: Boolean(item.dataPurged),
737
+ verified: true,
738
+ };
739
+ })
740
+ : [];
741
+ const verificationProof = (data.verificationProof ??
742
+ {});
743
+ const windowSummaries = Array.isArray(data.windowSummaries)
744
+ ? data.windowSummaries.map((entry) => {
745
+ const item = entry;
746
+ return {
747
+ window: String(item.window ?? ''),
748
+ deltasCount: Number(item.deltasCount ?? 0),
749
+ netDelta: Number(item.netDelta ?? 0),
750
+ firstBlockTimestamp: item.firstBlockTimestamp == null
751
+ ? null
752
+ : String(item.firstBlockTimestamp),
753
+ lastBlockTimestamp: item.lastBlockTimestamp == null
754
+ ? null
755
+ : String(item.lastBlockTimestamp),
756
+ };
757
+ })
758
+ : [];
759
+ const scope = (data.scope ?? null);
760
+ const monthlyBreakdown = Array.isArray(data.monthlyBreakdown)
761
+ ? data.monthlyBreakdown.map((entry) => {
762
+ const item = entry;
763
+ const checkpoint = (item.checkpoint ?? null);
764
+ return {
765
+ period: String(item.period ?? ''),
766
+ periodStart: String(item.periodStart ?? ''),
767
+ periodEndExclusive: String(item.periodEndExclusive ?? ''),
768
+ deltaCount: Number(item.deltaCount ?? 0),
769
+ netDelta: Number(item.netDelta ?? 0),
770
+ runningBalance: Number(item.runningBalance ?? 0),
771
+ checkpoint: checkpoint
772
+ ? {
773
+ checkpointId: String(checkpoint.checkpointId ?? ''),
774
+ proofRoot: checkpoint.proofRoot == null
775
+ ? null
776
+ : String(checkpoint.proofRoot),
777
+ status: toRecordStatus(checkpoint.status),
778
+ }
779
+ : null,
780
+ };
781
+ })
782
+ : undefined;
783
+ const pagination = (data.pagination ?? {});
784
+ const hint = data._hint;
785
+ return {
786
+ customerId: String(data.customerId ?? ''),
787
+ startingBalance: Number(data.startingBalance ?? 0),
788
+ startingCheckpoint: String(data.startingCheckpoint ?? 'genesis'),
789
+ startingCheckpointType: toCheckpointType(data.startingCheckpointType),
790
+ deltasCount: Number(data.deltasCount ?? deltas.length),
791
+ computedBalance: Number(data.computedBalance ?? 0),
792
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
793
+ latestReceiptId: data.latestReceiptId == null ? null : String(data.latestReceiptId),
794
+ deltas,
795
+ windowSummaries,
796
+ verificationProof: {
797
+ proofRoot: verificationProof.proofRoot == null
798
+ ? null
799
+ : String(verificationProof.proofRoot),
800
+ message: String(verificationProof.message ?? ''),
801
+ },
802
+ scope: scope
803
+ ? {
804
+ kind: String(scope.kind ?? 'period') === 'custom'
805
+ ? 'custom'
806
+ : String(scope.kind ?? 'period') === 'preset'
807
+ ? 'preset'
808
+ : 'period',
809
+ periodId: scope.periodId == null ? null : String(scope.periodId),
810
+ preset: scope.preset == null
811
+ ? null
812
+ : String(scope.preset),
813
+ startDate: String(scope.startDate ?? ''),
814
+ endDateExclusive: String(scope.endDateExclusive ?? ''),
815
+ label: String(scope.label ?? ''),
816
+ }
817
+ : null,
818
+ monthlyBreakdown,
819
+ cumulativeBalance: data.cumulativeBalance == null
820
+ ? undefined
821
+ : Number(data.cumulativeBalance),
822
+ pagination: {
823
+ total: Number(pagination.total ?? deltas.length),
824
+ limit: Number(pagination.limit ?? deltas.length),
825
+ offset: Number(pagination.offset ?? 0),
826
+ },
827
+ _hint: hint == null
828
+ ? undefined
829
+ : {
830
+ message: String(hint.message ?? ''),
831
+ recommendation: String(hint.recommendation ?? ''),
832
+ docsUrl: String(hint.docsUrl ?? ''),
833
+ },
834
+ };
835
+ }
836
+ normalizeCompareResponse(data) {
837
+ const proof = (data.proof ?? {});
838
+ const scope = (data.scope ?? null);
839
+ const monthlyBreakdown = Array.isArray(data.monthlyBreakdown)
840
+ ? data.monthlyBreakdown.map((entry) => {
841
+ const item = entry;
842
+ const checkpoint = (item.checkpoint ?? null);
843
+ return {
844
+ period: String(item.period ?? ''),
845
+ periodStart: String(item.periodStart ?? ''),
846
+ periodEndExclusive: String(item.periodEndExclusive ?? ''),
847
+ deltaCount: Number(item.deltaCount ?? 0),
848
+ netDelta: Number(item.netDelta ?? 0),
849
+ runningBalance: Number(item.runningBalance ?? 0),
850
+ checkpoint: checkpoint
851
+ ? {
852
+ checkpointId: String(checkpoint.checkpointId ?? ''),
853
+ proofRoot: checkpoint.proofRoot == null
854
+ ? null
855
+ : String(checkpoint.proofRoot),
856
+ status: toRecordStatus(checkpoint.status),
857
+ }
858
+ : null,
859
+ };
860
+ })
861
+ : undefined;
862
+ return {
863
+ customerId: String(data.customerId ?? ''),
864
+ yourBalance: Number(data.yourBalance ?? 0),
865
+ theirBalance: Number(data.theirBalance ?? 0),
866
+ neutralBalance: Number(data.neutralBalance ?? 0),
867
+ matchesYours: Boolean(data.matchesYours),
868
+ matchesTheirs: Boolean(data.matchesTheirs),
869
+ deltasVerified: Number(data.deltasVerified ?? 0),
870
+ discrepancyReport: data.discrepancyReport ?? null,
871
+ proof: {
872
+ proofRoot: proof.proofRoot == null ? null : String(proof.proofRoot),
873
+ latestCheckpoint: proof.latestCheckpoint == null ? null : String(proof.latestCheckpoint),
874
+ windowSummaries: proof.windowSummaries ??
875
+ [],
876
+ },
877
+ scope: scope
878
+ ? {
879
+ kind: String(scope.kind ?? 'period') === 'custom'
880
+ ? 'custom'
881
+ : String(scope.kind ?? 'period') === 'preset'
882
+ ? 'preset'
883
+ : 'period',
884
+ periodId: scope.periodId == null ? null : String(scope.periodId),
885
+ preset: scope.preset == null
886
+ ? null
887
+ : String(scope.preset),
888
+ startDate: String(scope.startDate ?? ''),
889
+ endDateExclusive: String(scope.endDateExclusive ?? ''),
890
+ label: String(scope.label ?? ''),
891
+ }
892
+ : null,
893
+ monthlyBreakdown,
894
+ cumulativeBalance: data.cumulativeBalance == null
895
+ ? undefined
896
+ : Number(data.cumulativeBalance),
897
+ };
898
+ }
899
+ normalizeReceiptResponse(data) {
900
+ const verification = (data.verification ?? {});
901
+ return {
902
+ customerId: String(data.customerId ?? ''),
903
+ generatedAt: String(data.generatedAt ?? ''),
904
+ deltasCount: Number(data.deltasCount ?? 0),
905
+ finalBalance: Number(data.finalBalance ?? 0),
906
+ proofRoot: data.proofRoot == null ? null : String(data.proofRoot),
907
+ receiptId: data.receiptId == null ? null : String(data.receiptId),
908
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
909
+ deltas: data.deltas ?? [],
910
+ windowSummaries: data.windowSummaries ?? [],
911
+ verification: {
912
+ message: String(verification.message ?? ''),
913
+ itemHashes: Array.isArray(verification.itemHashes)
914
+ ? verification.itemHashes.map((value) => String(value))
915
+ : [],
916
+ },
917
+ };
918
+ }
919
+ normalizeInvoiceProofResponse(data) {
920
+ return {
921
+ customerId: String(data.customerId ?? ''),
922
+ period: String(data.period ?? ''),
923
+ proofRoot: String(data.proofRoot ?? ''),
924
+ verificationReference: data.verificationReference == null
925
+ ? null
926
+ : String(data.verificationReference),
927
+ verificationExplorerUrl: data.verificationExplorerUrl == null
928
+ ? null
929
+ : String(data.verificationExplorerUrl),
930
+ verifyUrl: data.verifyUrl == null ? null : String(data.verifyUrl),
931
+ deltaCount: Number(data.deltaCount ?? 0),
932
+ startingBalance: Number(data.startingBalance ?? 0),
933
+ finalBalance: Number(data.finalBalance ?? 0),
934
+ windowSummary: data.windowSummary ??
935
+ {
936
+ window: '',
937
+ deltasCount: 0,
938
+ netDelta: 0,
939
+ firstDeltaAt: null,
940
+ lastDeltaAt: null,
941
+ },
942
+ deltas: data.deltas ?? [],
943
+ verification: data.verification ??
944
+ {
945
+ proofRoot: null,
946
+ totalBalance: 0,
947
+ periodBalance: 0,
948
+ },
949
+ };
950
+ }
951
+ normalizeCheckpointResponse(data) {
952
+ return {
953
+ checkpointId: String(data.checkpointId ?? ''),
954
+ period: String(data.period ?? ''),
955
+ proofRoot: String(data.proofRoot ?? ''),
956
+ scope: data.scope,
957
+ mode: data.mode,
958
+ deltaCount: Number(data.deltaCount ?? 0),
959
+ selectedFingerprintCount: data.selectedFingerprintCount == null
960
+ ? undefined
961
+ : Number(data.selectedFingerprintCount),
962
+ eligibleFingerprintCount: data.eligibleFingerprintCount == null
963
+ ? undefined
964
+ : Number(data.eligibleFingerprintCount),
965
+ selectedFingerprints: Array.isArray(data.selectedFingerprints)
966
+ ? data.selectedFingerprints.map((value) => String(value))
967
+ : undefined,
968
+ fromIndex: Number(data.fromIndex ?? 0),
969
+ toIndex: Number(data.toIndex ?? 0),
970
+ customerId: String(data.customerId ?? ''),
971
+ status: toRecordStatus(data.status),
972
+ message: String(data.message ?? ''),
973
+ };
974
+ }
975
+ normalizeListCheckpointsResponse(data) {
976
+ const checkpoints = Array.isArray(data.checkpoints)
977
+ ? data.checkpoints.map((entry) => {
978
+ const item = entry;
979
+ return {
980
+ checkpointId: String(item.checkpointId ?? ''),
981
+ period: String(item.period ?? ''),
982
+ proofRoot: item.proofRoot == null ? null : String(item.proofRoot),
983
+ scope: item.scope,
984
+ mode: item.mode,
985
+ deltaCount: Number(item.deltaCount ?? 0),
986
+ selectedFingerprintCount: item.selectedFingerprintCount == null
987
+ ? undefined
988
+ : Number(item.selectedFingerprintCount),
989
+ eligibleFingerprintCount: item.eligibleFingerprintCount == null
990
+ ? undefined
991
+ : Number(item.eligibleFingerprintCount),
992
+ customerId: String(item.customerId ?? ''),
993
+ status: toRecordStatus(item.status),
994
+ verificationReference: item.verificationReference == null
995
+ ? null
996
+ : String(item.verificationReference),
997
+ verifiedAt: item.verifiedAt == null ? null : String(item.verifiedAt),
998
+ receivedAt: String(item.receivedAt ?? ''),
999
+ };
1000
+ })
1001
+ : [];
1002
+ const pagination = (data.pagination ?? {});
1003
+ return {
1004
+ checkpoints,
1005
+ pagination: {
1006
+ total: Number(pagination.total ?? checkpoints.length),
1007
+ limit: Number(pagination.limit ?? checkpoints.length),
1008
+ offset: Number(pagination.offset ?? 0),
1009
+ },
1010
+ };
1011
+ }
1012
+ normalizeSequenceGapsResponse(data) {
1013
+ const gaps = Array.isArray(data.gaps)
1014
+ ? data.gaps.map((entry) => {
1015
+ const item = entry;
1016
+ return {
1017
+ startSequence: String(item.startSequence ?? ''),
1018
+ endSequence: String(item.endSequence ?? ''),
1019
+ missingCount: String(item.missingCount ?? '0'),
1020
+ };
1021
+ })
1022
+ : [];
1023
+ return {
1024
+ customerId: String(data.customerId ?? ''),
1025
+ customerReference: String(data.customerReference ?? ''),
1026
+ customerPartition: String(data.customerPartition ?? ''),
1027
+ nextSequence: String(data.nextSequence ?? '0'),
1028
+ rangeStart: String(data.rangeStart ?? '0'),
1029
+ rangeEnd: String(data.rangeEnd ?? '0'),
1030
+ gaps,
1031
+ hasGaps: Boolean(data.hasGaps),
1032
+ missingRanges: Number(data.missingRanges ?? gaps.length),
1033
+ missingCountInPage: String(data.missingCountInPage ?? '0'),
1034
+ truncated: Boolean(data.truncated),
1035
+ };
1036
+ }
1037
+ normalizeBulkEmitResponse(data) {
1038
+ const results = Array.isArray(data.results)
1039
+ ? data.results.map((entry) => {
1040
+ const item = entry;
1041
+ return {
1042
+ index: Number(item.index ?? 0),
1043
+ customerId: String(item.customerId ?? ''),
1044
+ delta: Number(item.delta ?? 0),
1045
+ recordId: item.recordId == null ? undefined : String(item.recordId),
1046
+ itemHash: item.itemHash == null ? undefined : String(item.itemHash),
1047
+ receiptId: item.receiptId == null ? undefined : String(item.receiptId),
1048
+ referenceId: item.referenceId == null ? undefined : String(item.referenceId),
1049
+ sequenceNumber: item.sequenceNumber == null
1050
+ ? undefined
1051
+ : String(item.sequenceNumber),
1052
+ idempotent: item.idempotent == null ? undefined : Boolean(item.idempotent),
1053
+ status: String(item.status ?? ''),
1054
+ error: item.error == null ? undefined : String(item.error),
1055
+ };
1056
+ })
1057
+ : [];
1058
+ return {
1059
+ totalQueued: Number(data.totalQueued ?? results.length),
1060
+ totalFailed: Number(data.totalFailed ?? 0),
1061
+ results,
1062
+ _efficiency: data._efficiency,
1063
+ };
1064
+ }
1065
+ normalizeListCustomersResponse(data) {
1066
+ const customers = Array.isArray(data.customers)
1067
+ ? data.customers.map((entry) => {
1068
+ const item = entry;
1069
+ return {
1070
+ customerId: String(item.customerId ?? ''),
1071
+ customerReference: String(item.customerReference ?? ''),
1072
+ totalDeltas: Number(item.totalDeltas ?? 0),
1073
+ firstDeltaAt: item.firstDeltaAt == null ? null : String(item.firstDeltaAt),
1074
+ lastDeltaAt: item.lastDeltaAt == null ? null : String(item.lastDeltaAt),
1075
+ };
1076
+ })
1077
+ : [];
1078
+ const pagination = (data.pagination ?? {});
1079
+ return {
1080
+ customers,
1081
+ pagination: {
1082
+ total: Number(pagination.total ?? customers.length),
1083
+ page: Number(pagination.page ?? 1),
1084
+ limit: Number(pagination.limit ?? customers.length),
1085
+ totalPages: Number(pagination.totalPages ?? 1),
1086
+ },
1087
+ };
1088
+ }
1089
+ normalizeCustomerDetailResponse(data) {
1090
+ const period = (data.period ?? null);
1091
+ return {
1092
+ customerId: String(data.customerId ?? ''),
1093
+ customerReference: String(data.customerReference ?? ''),
1094
+ totalDeltas: Number(data.totalDeltas ?? 0),
1095
+ firstDeltaAt: data.firstDeltaAt == null ? null : String(data.firstDeltaAt),
1096
+ lastDeltaAt: data.lastDeltaAt == null ? null : String(data.lastDeltaAt),
1097
+ customerPartition: String(data.customerPartition ?? ''),
1098
+ computedBalance: Number(data.computedBalance ?? 0),
1099
+ period: period
1100
+ ? {
1101
+ id: String(period.id ?? ''),
1102
+ startDate: String(period.startDate ?? ''),
1103
+ endDateExclusive: String(period.endDateExclusive ?? ''),
1104
+ label: String(period.label ?? ''),
1105
+ }
1106
+ : undefined,
1107
+ mostRecentActivePeriod: data.mostRecentActivePeriod == null
1108
+ ? null
1109
+ : String(data.mostRecentActivePeriod),
1110
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
1111
+ privacyNote: String(data.privacyNote ?? ''),
1112
+ recentActivity: data.recentActivity ??
1113
+ {
1114
+ items: [],
1115
+ pagination: {
1116
+ total: 0,
1117
+ page: 1,
1118
+ limit: 20,
1119
+ totalPages: 1,
1120
+ },
1121
+ },
1122
+ };
554
1123
  }
555
1124
  // -------------------------------------------------------------------------
556
1125
  // Invoice Proof (deprecated — use receipt with period)