@pacspace-io/sdk 0.1.0 → 0.2.1

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 (46) hide show
  1. package/README.md +67 -11
  2. package/dist/client.d.ts +34 -3
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +296 -21
  5. package/dist/client.js.map +1 -1
  6. package/dist/errors/index.d.ts +24 -1
  7. package/dist/errors/index.d.ts.map +1 -1
  8. package/dist/errors/index.js +44 -4
  9. package/dist/errors/index.js.map +1 -1
  10. package/dist/index.d.ts +51 -13
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +107 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/resources/balance.d.ts +221 -15
  15. package/dist/resources/balance.d.ts.map +1 -1
  16. package/dist/resources/balance.js +772 -27
  17. package/dist/resources/balance.js.map +1 -1
  18. package/dist/resources/submission.d.ts +79 -0
  19. package/dist/resources/submission.d.ts.map +1 -0
  20. package/dist/resources/submission.js +398 -0
  21. package/dist/resources/submission.js.map +1 -0
  22. package/dist/types/balance.d.ts +493 -28
  23. package/dist/types/balance.d.ts.map +1 -1
  24. package/dist/types/common.d.ts +3 -3
  25. package/dist/types/common.d.ts.map +1 -1
  26. package/dist/types/config.d.ts +53 -3
  27. package/dist/types/config.d.ts.map +1 -1
  28. package/dist/types/submission.d.ts +125 -0
  29. package/dist/types/submission.d.ts.map +1 -0
  30. package/dist/types/submission.js +3 -0
  31. package/dist/types/submission.js.map +1 -0
  32. package/dist/utils/polling.d.ts +2 -2
  33. package/dist/utils/polling.d.ts.map +1 -1
  34. package/dist/utils/polling.js +2 -6
  35. package/dist/utils/polling.js.map +1 -1
  36. package/dist/webhooks/index.d.ts +1 -1
  37. package/dist/webhooks/index.d.ts.map +1 -1
  38. package/dist/webhooks/types.d.ts +31 -31
  39. package/dist/webhooks/types.d.ts.map +1 -1
  40. package/dist/webhooks/types.js +0 -3
  41. package/dist/webhooks/types.js.map +1 -1
  42. package/dist/webhooks/verify.d.ts +5 -0
  43. package/dist/webhooks/verify.d.ts.map +1 -1
  44. package/dist/webhooks/verify.js +19 -4
  45. package/dist/webhooks/verify.js.map +1 -1
  46. package/package.json +1 -1
@@ -2,6 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BalanceResource = void 0;
4
4
  const polling_1 = require("../utils/polling");
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
+ };
5
24
  /**
6
25
  * PacSpace Balance API resource.
7
26
  *
@@ -15,8 +34,12 @@ const polling_1 = require("../utils/polling");
15
34
  */
16
35
  class BalanceResource {
17
36
  /** @internal */
18
- constructor(client) {
37
+ constructor(client, submissionOptions) {
19
38
  this.client = client;
39
+ this.submission = new submission_1.SubmissionCoordinator({
40
+ emitBatch: (deltas) => this.emitBatch(deltas),
41
+ options: submissionOptions,
42
+ });
20
43
  }
21
44
  // -------------------------------------------------------------------------
22
45
  // Emit
@@ -51,7 +74,8 @@ class BalanceResource {
51
74
  body.referenceId = referenceId;
52
75
  if (metadata !== undefined)
53
76
  body.metadata = metadata;
54
- 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);
55
79
  }
56
80
  /**
57
81
  * Record a delta and wait for it to be verified.
@@ -82,11 +106,139 @@ class BalanceResource {
82
106
  signal,
83
107
  });
84
108
  // If already terminal, return immediately
85
- if (['ANCHORED', 'VERIFIED', 'FAILED'].includes(initial.status)) {
109
+ if (['VERIFIED', 'FAILED'].includes(initial.status)) {
86
110
  return initial;
87
111
  }
88
- // Poll for terminal status using the anchor endpoint from writes controller
89
- return (0, polling_1.pollUntilTerminal)(() => this.client.get(`/api/v1/writes/${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);
116
+ }
117
+ /**
118
+ * Check the status of a previously emitted delta.
119
+ *
120
+ * @param recordId - The record ID returned from emit.
121
+ * @param options - Request overrides.
122
+ * @returns Delta status with receipt and transaction details.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const status = await pac.balance.deltaStatus('rec_abc123');
127
+ * console.log(status.status); // 'QUEUED', 'PROCESSING', etc.
128
+ * ```
129
+ */
130
+ async deltaStatus(recordId, options) {
131
+ const response = await this.client.get(`/api/v1/balance/delta/${encodeURIComponent(recordId)}`, options);
132
+ return this.normalizeDeltaStatusResponse(response);
133
+ }
134
+ /**
135
+ * Alias for `deltaStatus()` with lifecycle-oriented naming.
136
+ */
137
+ async getRecordStatus(recordId, options) {
138
+ return this.deltaStatus(recordId, options);
139
+ }
140
+ /**
141
+ * Poll record lifecycle until it reaches a terminal status.
142
+ *
143
+ * Terminal statuses are `VERIFIED` and `FAILED`.
144
+ */
145
+ async waitForVerified(recordId, options) {
146
+ const { timeout = 60000, pollInterval = 2000, signal, ...requestOptions } = options ?? {};
147
+ return (0, polling_1.pollUntilTerminal)(() => this.getRecordStatus(recordId, {
148
+ ...requestOptions,
149
+ signal,
150
+ }), { timeout, pollInterval, signal });
151
+ }
152
+ /**
153
+ * Retrieve tenant usage counters and remaining allotment.
154
+ */
155
+ async usage(options) {
156
+ return this.client.get('/api/v1/balance/usage', options);
157
+ }
158
+ /**
159
+ * Detect missing per-customer sequence ranges.
160
+ *
161
+ * Use this to identify gaps and re-emit missing deltas safely.
162
+ */
163
+ async gaps(customerId, options) {
164
+ const { fromSequence, toSequence, limit, ...requestOptions } = options ?? {};
165
+ const params = new URLSearchParams();
166
+ if (fromSequence !== undefined)
167
+ params.set('fromSequence', fromSequence);
168
+ if (toSequence !== undefined)
169
+ params.set('toSequence', toSequence);
170
+ if (limit !== undefined)
171
+ params.set('limit', String(limit));
172
+ const query = params.toString();
173
+ const path = `/api/v1/balance/gaps/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
174
+ const response = await this.client.get(path, requestOptions);
175
+ return this.normalizeSequenceGapsResponse(response);
176
+ }
177
+ /**
178
+ * Record multiple deltas in a single batch request.
179
+ *
180
+ * Ideal for end-of-day reconciliation or importing historical data.
181
+ * Up to 100 deltas per batch.
182
+ *
183
+ * @param deltas - Array of deltas to record.
184
+ * @param options - Request overrides.
185
+ * @returns Per-delta results with totals.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * const result = await pac.balance.emitBatch([
190
+ * { customerId: 'cust_123', delta: -100, reason: 'usage' },
191
+ * { customerId: 'cust_456', delta: -200, reason: 'usage' },
192
+ * ]);
193
+ * console.log(result.totalQueued); // 2
194
+ * ```
195
+ */
196
+ async emitBatch(deltas, options) {
197
+ const { ...requestOptions } = options ?? {};
198
+ const response = await this.client.post('/api/v1/balance/delta/batch', { deltas }, requestOptions);
199
+ return this.normalizeBulkEmitResponse(response);
200
+ }
201
+ /**
202
+ * Queue one tenant-produced summary for SDK-managed submission.
203
+ */
204
+ queueSummary(summary) {
205
+ return this.submission.queueSummary(summary);
206
+ }
207
+ /**
208
+ * Queue multiple tenant-produced summaries.
209
+ */
210
+ queueSummaries(summaries) {
211
+ return this.submission.queueSummaries(summaries);
212
+ }
213
+ /**
214
+ * Flush queued summaries immediately.
215
+ */
216
+ async flushSummaries(options) {
217
+ return this.submission.flushSummaries(options);
218
+ }
219
+ /**
220
+ * Start periodic summary submission scheduling.
221
+ */
222
+ startSummaryScheduler() {
223
+ this.submission.start();
224
+ }
225
+ /**
226
+ * Stop periodic summary submission scheduling.
227
+ */
228
+ stopSummaryScheduler() {
229
+ this.submission.stop();
230
+ }
231
+ /**
232
+ * Get queue/scheduler state for summary submission automation.
233
+ */
234
+ getSummaryQueueState() {
235
+ return this.submission.getQueueState();
236
+ }
237
+ /**
238
+ * Stop scheduler and flush queued summaries (best for graceful shutdown).
239
+ */
240
+ async shutdownSummaryScheduler() {
241
+ await this.submission.shutdown();
90
242
  }
91
243
  // -------------------------------------------------------------------------
92
244
  // Derive
@@ -113,7 +265,7 @@ class BalanceResource {
113
265
  * ```
114
266
  */
115
267
  async derive(customerId, options) {
116
- const { startingBalance, startingCheckpoint, startingCheckpointType, ...requestOptions } = options ?? {};
268
+ const { startingBalance, startingCheckpoint, startingCheckpointType, limit, offset, ...requestOptions } = options ?? {};
117
269
  // Build query string
118
270
  const params = new URLSearchParams();
119
271
  if (startingBalance !== undefined) {
@@ -122,12 +274,20 @@ class BalanceResource {
122
274
  if (startingCheckpoint !== undefined) {
123
275
  params.set('startingCheckpoint', startingCheckpoint);
124
276
  }
125
- if (startingCheckpointType !== undefined) {
126
- params.set('startingCheckpointType', startingCheckpointType);
277
+ const apiCheckpointType = toApiCheckpointType(startingCheckpointType);
278
+ if (apiCheckpointType !== undefined) {
279
+ params.set('startingCheckpointType', apiCheckpointType);
280
+ }
281
+ if (limit !== undefined) {
282
+ params.set('limit', String(limit));
283
+ }
284
+ if (offset !== undefined) {
285
+ params.set('offset', String(offset));
127
286
  }
128
287
  const query = params.toString();
129
288
  const path = `/api/v1/balance/derive/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
130
- return this.client.get(path, requestOptions);
289
+ const response = await this.client.get(path, requestOptions);
290
+ return this.normalizeDeriveResponse(response);
131
291
  }
132
292
  // -------------------------------------------------------------------------
133
293
  // Compare
@@ -167,10 +327,12 @@ class BalanceResource {
167
327
  if (startingCheckpoint !== undefined) {
168
328
  body.startingCheckpoint = startingCheckpoint;
169
329
  }
170
- if (startingCheckpointType !== undefined) {
171
- body.startingCheckpointType = startingCheckpointType;
330
+ const apiCheckpointType = toApiCheckpointType(startingCheckpointType);
331
+ if (apiCheckpointType !== undefined) {
332
+ body.startingCheckpointType = apiCheckpointType;
172
333
  }
173
- return this.client.post('/api/v1/balance/compare', body, requestOptions);
334
+ const response = await this.client.post('/api/v1/balance/compare', body, requestOptions);
335
+ return this.normalizeCompareResponse(response);
174
336
  }
175
337
  // -------------------------------------------------------------------------
176
338
  // Receipt
@@ -178,22 +340,59 @@ class BalanceResource {
178
340
  /**
179
341
  * Generate a verifiable receipt for a customer.
180
342
  *
181
- * Returns a human-readable receipt containing all verified deltas
182
- * and cryptographic proof data that any party can independently verify.
343
+ * When `options.period` is provided, returns a period-specific receipt (proof root,
344
+ * verifyUrl, verificationReference) suitable for sharing on invoices. When
345
+ * omitted, returns the full record receipt with all verified deltas.
183
346
  *
184
347
  * @param customerId - The customer account to generate a receipt for.
185
- * @param options - Request overrides.
186
- * @returns Receipt with verification data.
348
+ * @param options - Optional period (YYYY-MM) and request overrides.
349
+ * @returns Receipt with verification data; shape depends on whether period is set.
187
350
  *
188
351
  * @example
189
352
  * ```typescript
190
- * const receipt = await pac.balance.receipt('cust_123');
191
- * console.log(receipt.finalBalance);
192
- * console.log(receipt.verification.itemHashes);
353
+ * // Period-specific receipt (shareable proof for invoices)
354
+ * const receipt = await pac.balance.receipt('cust_123', { period: '2026-02' });
355
+ * console.log(receipt.proofRoot); // Include on invoice
356
+ * console.log(receipt.verifyUrl); // Share with counterparty
357
+ *
358
+ * // Full record receipt
359
+ * const full = await pac.balance.receipt('cust_123');
360
+ * console.log(full.finalBalance);
193
361
  * ```
194
362
  */
195
363
  async receipt(customerId, options) {
196
- return this.client.get(`/api/v1/balance/receipt/${encodeURIComponent(customerId)}`, options);
364
+ const opts = options ?? {};
365
+ const hasScopedReceipt = opts.period !== undefined ||
366
+ opts.timePreset !== undefined ||
367
+ opts.startDate !== undefined ||
368
+ opts.endDate !== undefined;
369
+ if (hasScopedReceipt) {
370
+ return this.receiptForPeriod(customerId, opts);
371
+ }
372
+ const { period: _period, timePreset: _timePreset, startDate: _startDate, endDate: _endDate, ...requestOptions } = opts;
373
+ const hasOverrides = Object.keys(requestOptions).length > 0;
374
+ const response = await this.client.get(`/api/v1/balance/receipt/${encodeURIComponent(customerId)}`, hasOverrides ? requestOptions : undefined);
375
+ return this.normalizeReceiptResponse(response);
376
+ }
377
+ /**
378
+ * @internal
379
+ * Generate a period-specific receipt (proof root, verifyUrl). Used by receipt().
380
+ */
381
+ async receiptForPeriod(customerId, options) {
382
+ const { period, timePreset, startDate, endDate, ...requestOptions } = options ?? {};
383
+ const params = new URLSearchParams();
384
+ if (period !== undefined)
385
+ params.set('period', period);
386
+ if (timePreset !== undefined)
387
+ params.set('timePreset', timePreset);
388
+ if (startDate !== undefined)
389
+ params.set('startDate', startDate);
390
+ if (endDate !== undefined)
391
+ params.set('endDate', endDate);
392
+ const query = params.toString();
393
+ const path = `/api/v1/balance/invoice-proof/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
394
+ const response = await this.client.get(path, requestOptions);
395
+ return this.normalizeInvoiceProofResponse(response);
197
396
  }
198
397
  // -------------------------------------------------------------------------
199
398
  // Checkpoint
@@ -201,30 +400,576 @@ class BalanceResource {
201
400
  /**
202
401
  * Commit a period-end checkpoint.
203
402
  *
204
- * Computes a Merkle root over all verified deltas in the billing window
205
- * and anchors it on-chain. The checkpoint hash can be included in invoices
206
- * for instant counterparty verification.
403
+ * Computes a proof root over all verified deltas in the billing window
404
+ * and records it on the public verification layer. The proof root can be
405
+ * included in invoices for instant counterparty verification.
207
406
  *
208
407
  * @param customerId - Customer to checkpoint (omit for all customers).
209
408
  * @param options - Period (YYYY-MM) and request overrides.
210
- * @returns Checkpoint details with Merkle root and status.
409
+ * @returns Checkpoint details with proof root and status.
211
410
  *
212
411
  * @example
213
412
  * ```typescript
214
413
  * const checkpoint = await pac.balance.checkpoint('cust_123', {
215
414
  * period: '2026-02',
216
415
  * });
217
- * console.log(checkpoint.merkleRoot); // Include in your invoice
416
+ * console.log(checkpoint.proofRoot); // Include in your invoice
218
417
  * ```
219
418
  */
220
419
  async checkpoint(customerId, options) {
221
- const { period, ...requestOptions } = options ?? {};
420
+ const { period, timePreset, startDate, endDate, mode, fingerprints, ...requestOptions } = options ?? {};
222
421
  const body = {};
223
422
  if (customerId !== undefined)
224
423
  body.customerId = customerId;
225
424
  if (period !== undefined)
226
425
  body.period = period;
227
- return this.client.post('/api/v1/balance/checkpoint', body, requestOptions);
426
+ if (timePreset !== undefined)
427
+ body.timePreset = timePreset;
428
+ if (startDate !== undefined)
429
+ body.startDate = startDate;
430
+ if (endDate !== undefined)
431
+ body.endDate = endDate;
432
+ if (mode !== undefined)
433
+ body.mode = mode;
434
+ if (fingerprints !== undefined)
435
+ body.fingerprints = fingerprints;
436
+ const response = await this.client.post('/api/v1/balance/checkpoint', body, requestOptions);
437
+ return this.normalizeCheckpointResponse(response);
438
+ }
439
+ // -------------------------------------------------------------------------
440
+ // List Checkpoints
441
+ // -------------------------------------------------------------------------
442
+ /**
443
+ * List committed checkpoints for your tenant.
444
+ *
445
+ * Returns a paginated list of checkpoints, optionally filtered by
446
+ * customer ID and/or billing period.
447
+ *
448
+ * @param options - Filter and pagination options.
449
+ * @returns Paginated list of checkpoints.
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * const { checkpoints } = await pac.balance.listCheckpoints({
454
+ * period: '2026-02',
455
+ * limit: 10,
456
+ * });
457
+ * ```
458
+ */
459
+ async listCheckpoints(options) {
460
+ const { customerId, period, timePreset, startDate, endDate, limit, offset, ...requestOptions } = options ?? {};
461
+ const params = new URLSearchParams();
462
+ if (customerId !== undefined)
463
+ params.set('customerId', customerId);
464
+ if (period !== undefined)
465
+ params.set('period', period);
466
+ if (timePreset !== undefined)
467
+ params.set('timePreset', timePreset);
468
+ if (startDate !== undefined)
469
+ params.set('startDate', startDate);
470
+ if (endDate !== undefined)
471
+ params.set('endDate', endDate);
472
+ if (limit !== undefined)
473
+ params.set('limit', String(limit));
474
+ if (offset !== undefined)
475
+ params.set('offset', String(offset));
476
+ const query = params.toString();
477
+ const path = `/api/v1/balance/checkpoints${query ? `?${query}` : ''}`;
478
+ const response = await this.client.get(path, requestOptions);
479
+ return this.normalizeListCheckpointsResponse(response);
480
+ }
481
+ // -------------------------------------------------------------------------
482
+ // Webhook Deliveries
483
+ // -------------------------------------------------------------------------
484
+ /**
485
+ * List webhook delivery history.
486
+ *
487
+ * @param options - Filter and pagination options.
488
+ * @returns Paginated list of webhook deliveries.
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * const { deliveries } = await pac.balance.listWebhookDeliveries({
493
+ * status: 'failed',
494
+ * limit: 10,
495
+ * });
496
+ * ```
497
+ */
498
+ async listWebhookDeliveries(options) {
499
+ const { status, limit, offset, ...requestOptions } = options ?? {};
500
+ const params = new URLSearchParams();
501
+ if (status !== undefined)
502
+ params.set('status', status);
503
+ if (limit !== undefined)
504
+ params.set('limit', String(limit));
505
+ if (offset !== undefined)
506
+ params.set('offset', String(offset));
507
+ const query = params.toString();
508
+ const path = `/api/v1/balance/webhooks${query ? `?${query}` : ''}`;
509
+ return this.client.get(path, requestOptions);
510
+ }
511
+ /**
512
+ * Retry a failed webhook delivery.
513
+ *
514
+ * @param eventId - The event ID of the failed delivery.
515
+ * @param options - Request overrides.
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * await pac.balance.retryWebhook('evt_abc123');
520
+ * ```
521
+ */
522
+ async retryWebhook(eventId, options) {
523
+ return this.client.post(`/api/v1/balance/webhooks/${encodeURIComponent(eventId)}/retry`, {}, options);
524
+ }
525
+ // -------------------------------------------------------------------------
526
+ // Customer Ledgers
527
+ // -------------------------------------------------------------------------
528
+ /**
529
+ * List all customer balance records for your account.
530
+ *
531
+ * Each unique customerId passed to emit() automatically creates an
532
+ * isolated customer record. This method returns a paginated list.
533
+ *
534
+ * @param options - Search, pagination, and request overrides.
535
+ * @returns Paginated list of customer records.
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * const { customers } = await pac.balance.customers();
540
+ * for (const c of customers) {
541
+ * console.log(c.customerId, c.totalDeltas);
542
+ * }
543
+ * ```
544
+ */
545
+ async customers(options) {
546
+ const { search, limit, page, ...requestOptions } = options ?? {};
547
+ const params = new URLSearchParams();
548
+ if (search !== undefined)
549
+ params.set('search', search);
550
+ if (limit !== undefined)
551
+ params.set('limit', String(limit));
552
+ if (page !== undefined)
553
+ params.set('page', String(page));
554
+ const query = params.toString();
555
+ const path = `/api/v1/balance/customers${query ? `?${query}` : ''}`;
556
+ const response = await this.client.get(path, requestOptions);
557
+ return this.normalizeListCustomersResponse(response);
558
+ }
559
+ /**
560
+ * Get the full record detail for a specific customer.
561
+ *
562
+ * Returns the derived customer reference, computed balance, delta count,
563
+ * and latest checkpoint. The customer reference is deterministic.
564
+ *
565
+ * @param customerId - The customer to look up.
566
+ * @param options - Activity pagination and request overrides.
567
+ * @returns Customer detail including balance and references.
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * const customer = await pac.balance.customer('cust_001');
572
+ * console.log(customer.customerReference); // 0xA1b2...Ef34
573
+ * console.log(customer.computedBalance); // 4500.00
574
+ * ```
575
+ */
576
+ async customer(customerId, options) {
577
+ const { deltaPage, deltaLimit, ...requestOptions } = options ?? {};
578
+ const params = new URLSearchParams();
579
+ if (deltaPage !== undefined)
580
+ params.set('deltaPage', String(deltaPage));
581
+ if (deltaLimit !== undefined)
582
+ params.set('deltaLimit', String(deltaLimit));
583
+ const query = params.toString();
584
+ const path = `/api/v1/balance/customers/${encodeURIComponent(customerId)}${query ? `?${query}` : ''}`;
585
+ const response = await this.client.get(path, requestOptions);
586
+ return this.normalizeCustomerDetailResponse(response);
587
+ }
588
+ normalizeEmitResponse(data) {
589
+ const proofRoot = String(data.proofRoot ?? data.receiptId ?? '');
590
+ return {
591
+ recordId: String(data.recordId ?? ''),
592
+ customerId: String(data.customerId ?? ''),
593
+ delta: Number(data.delta ?? 0),
594
+ sequenceNumber: data.sequenceNumber == null ? undefined : String(data.sequenceNumber),
595
+ status: toRecordStatus(data.status),
596
+ receiptId: String(data.receiptId ?? proofRoot),
597
+ proofRoot,
598
+ itemHashes: Array.isArray(data.itemHashes)
599
+ ? data.itemHashes.map((value) => String(value))
600
+ : [],
601
+ estimatedVerifiedDeltas: Number(data.estimatedVerifiedDeltas ?? 0),
602
+ estimatedCredits: Number(data.estimatedCredits ?? 0),
603
+ receivedAt: String(data.receivedAt ?? ''),
604
+ message: String(data.message ?? ''),
605
+ };
606
+ }
607
+ mergeEmitWithStatus(initial, status) {
608
+ const firstDelta = status.deltas[0];
609
+ const customerId = status.customerId ?? firstDelta?.customerId ?? initial.customerId;
610
+ const delta = status.delta ?? firstDelta?.delta ?? initial.delta;
611
+ const sequenceNumber = firstDelta?.sequenceNumber ?? initial.sequenceNumber;
612
+ return {
613
+ ...initial,
614
+ recordId: status.recordId || initial.recordId,
615
+ customerId: customerId == null ? initial.customerId : String(customerId),
616
+ delta: delta == null ? initial.delta : Number(delta),
617
+ sequenceNumber: sequenceNumber ?? undefined,
618
+ status: status.status,
619
+ receiptId: status.receiptId ?? initial.receiptId,
620
+ proofRoot: status.receiptId ?? initial.proofRoot,
621
+ receivedAt: status.receivedAt || initial.receivedAt,
622
+ message: status.status === 'VERIFIED'
623
+ ? 'Delta verified.'
624
+ : status.status === 'FAILED'
625
+ ? 'Delta verification failed.'
626
+ : initial.message,
627
+ };
628
+ }
629
+ normalizeDeltaStatusResponse(data) {
630
+ const deltas = Array.isArray(data.deltas)
631
+ ? data.deltas.map((entry) => {
632
+ const item = entry;
633
+ return {
634
+ customerId: item.customerId == null ? null : String(item.customerId),
635
+ delta: item.delta == null ? null : Number(item.delta),
636
+ sequenceNumber: item.sequenceNumber == null ? null : String(item.sequenceNumber),
637
+ };
638
+ })
639
+ : [];
640
+ return {
641
+ recordId: String(data.recordId ?? ''),
642
+ status: toRecordStatus(data.status),
643
+ deltaCount: Number(data.deltaCount ?? deltas.length),
644
+ customerId: data.customerId == null ? null : String(data.customerId),
645
+ delta: data.delta == null ? null : Number(data.delta),
646
+ deltas,
647
+ receiptId: data.receiptId == null ? null : String(data.receiptId),
648
+ verificationReference: data.verificationReference == null ? null : String(data.verificationReference),
649
+ receivedAt: String(data.receivedAt ?? ''),
650
+ verifiedAt: data.verifiedAt == null ? null : String(data.verifiedAt),
651
+ };
652
+ }
653
+ normalizeDeriveResponse(data) {
654
+ const deltas = Array.isArray(data.deltas)
655
+ ? data.deltas.map((entry) => {
656
+ const item = entry;
657
+ const proofRoot = String(item.proofRoot ?? item.receiptId ?? '');
658
+ return {
659
+ recordId: String(item.recordId ?? ''),
660
+ itemHash: String(item.itemHash ?? ''),
661
+ proofRoot,
662
+ receiptId: String(item.receiptId ?? proofRoot),
663
+ sequenceNumber: item.sequenceNumber == null ? null : String(item.sequenceNumber),
664
+ delta: Number(item.delta ?? 0),
665
+ reason: item.reason == null ? null : String(item.reason),
666
+ referenceId: item.referenceId == null ? null : String(item.referenceId),
667
+ window: String(item.window ?? ''),
668
+ declaredTimestamp: item.declaredTimestamp == null
669
+ ? null
670
+ : String(item.declaredTimestamp),
671
+ blockTimestamp: item.blockTimestamp == null ? null : String(item.blockTimestamp),
672
+ dataPurged: Boolean(item.dataPurged),
673
+ verified: true,
674
+ };
675
+ })
676
+ : [];
677
+ const verificationProof = (data.verificationProof ??
678
+ {});
679
+ const windowSummaries = Array.isArray(data.windowSummaries)
680
+ ? data.windowSummaries.map((entry) => {
681
+ const item = entry;
682
+ return {
683
+ window: String(item.window ?? ''),
684
+ deltasCount: Number(item.deltasCount ?? 0),
685
+ netDelta: Number(item.netDelta ?? 0),
686
+ firstBlockTimestamp: item.firstBlockTimestamp == null
687
+ ? null
688
+ : String(item.firstBlockTimestamp),
689
+ lastBlockTimestamp: item.lastBlockTimestamp == null
690
+ ? null
691
+ : String(item.lastBlockTimestamp),
692
+ };
693
+ })
694
+ : [];
695
+ const pagination = (data.pagination ?? {});
696
+ const hint = data._hint;
697
+ return {
698
+ customerId: String(data.customerId ?? ''),
699
+ startingBalance: Number(data.startingBalance ?? 0),
700
+ startingCheckpoint: String(data.startingCheckpoint ?? 'genesis'),
701
+ startingCheckpointType: toCheckpointType(data.startingCheckpointType),
702
+ deltasCount: Number(data.deltasCount ?? deltas.length),
703
+ computedBalance: Number(data.computedBalance ?? 0),
704
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
705
+ latestReceiptId: data.latestReceiptId == null ? null : String(data.latestReceiptId),
706
+ deltas,
707
+ windowSummaries,
708
+ verificationProof: {
709
+ proofRoot: verificationProof.proofRoot == null
710
+ ? null
711
+ : String(verificationProof.proofRoot),
712
+ message: String(verificationProof.message ?? ''),
713
+ },
714
+ pagination: {
715
+ total: Number(pagination.total ?? deltas.length),
716
+ limit: Number(pagination.limit ?? deltas.length),
717
+ offset: Number(pagination.offset ?? 0),
718
+ },
719
+ _hint: hint == null
720
+ ? undefined
721
+ : {
722
+ message: String(hint.message ?? ''),
723
+ recommendation: String(hint.recommendation ?? ''),
724
+ docsUrl: String(hint.docsUrl ?? ''),
725
+ },
726
+ };
727
+ }
728
+ normalizeCompareResponse(data) {
729
+ const proof = (data.proof ?? {});
730
+ return {
731
+ customerId: String(data.customerId ?? ''),
732
+ yourBalance: Number(data.yourBalance ?? 0),
733
+ theirBalance: Number(data.theirBalance ?? 0),
734
+ neutralBalance: Number(data.neutralBalance ?? 0),
735
+ matchesYours: Boolean(data.matchesYours),
736
+ matchesTheirs: Boolean(data.matchesTheirs),
737
+ deltasVerified: Number(data.deltasVerified ?? 0),
738
+ discrepancyReport: data.discrepancyReport ?? null,
739
+ proof: {
740
+ proofRoot: proof.proofRoot == null ? null : String(proof.proofRoot),
741
+ latestCheckpoint: proof.latestCheckpoint == null ? null : String(proof.latestCheckpoint),
742
+ windowSummaries: proof.windowSummaries ??
743
+ [],
744
+ },
745
+ };
746
+ }
747
+ normalizeReceiptResponse(data) {
748
+ const verification = (data.verification ?? {});
749
+ return {
750
+ customerId: String(data.customerId ?? ''),
751
+ generatedAt: String(data.generatedAt ?? ''),
752
+ deltasCount: Number(data.deltasCount ?? 0),
753
+ finalBalance: Number(data.finalBalance ?? 0),
754
+ proofRoot: data.proofRoot == null ? null : String(data.proofRoot),
755
+ receiptId: data.receiptId == null ? null : String(data.receiptId),
756
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
757
+ deltas: data.deltas ?? [],
758
+ windowSummaries: data.windowSummaries ?? [],
759
+ verification: {
760
+ message: String(verification.message ?? ''),
761
+ itemHashes: Array.isArray(verification.itemHashes)
762
+ ? verification.itemHashes.map((value) => String(value))
763
+ : [],
764
+ },
765
+ };
766
+ }
767
+ normalizeInvoiceProofResponse(data) {
768
+ return {
769
+ customerId: String(data.customerId ?? ''),
770
+ period: String(data.period ?? ''),
771
+ proofRoot: String(data.proofRoot ?? ''),
772
+ verificationReference: data.verificationReference == null
773
+ ? null
774
+ : String(data.verificationReference),
775
+ verificationExplorerUrl: data.verificationExplorerUrl == null
776
+ ? null
777
+ : String(data.verificationExplorerUrl),
778
+ verifyUrl: data.verifyUrl == null ? null : String(data.verifyUrl),
779
+ deltaCount: Number(data.deltaCount ?? 0),
780
+ startingBalance: Number(data.startingBalance ?? 0),
781
+ finalBalance: Number(data.finalBalance ?? 0),
782
+ windowSummary: data.windowSummary ??
783
+ {
784
+ window: '',
785
+ deltasCount: 0,
786
+ netDelta: 0,
787
+ firstDeltaAt: null,
788
+ lastDeltaAt: null,
789
+ },
790
+ deltas: data.deltas ?? [],
791
+ verification: data.verification ??
792
+ {
793
+ proofRoot: null,
794
+ totalBalance: 0,
795
+ periodBalance: 0,
796
+ },
797
+ };
798
+ }
799
+ normalizeCheckpointResponse(data) {
800
+ return {
801
+ checkpointId: String(data.checkpointId ?? ''),
802
+ period: String(data.period ?? ''),
803
+ proofRoot: String(data.proofRoot ?? ''),
804
+ scope: data.scope,
805
+ mode: data.mode,
806
+ deltaCount: Number(data.deltaCount ?? 0),
807
+ selectedFingerprintCount: data.selectedFingerprintCount == null
808
+ ? undefined
809
+ : Number(data.selectedFingerprintCount),
810
+ eligibleFingerprintCount: data.eligibleFingerprintCount == null
811
+ ? undefined
812
+ : Number(data.eligibleFingerprintCount),
813
+ selectedFingerprints: Array.isArray(data.selectedFingerprints)
814
+ ? data.selectedFingerprints.map((value) => String(value))
815
+ : undefined,
816
+ fromIndex: Number(data.fromIndex ?? 0),
817
+ toIndex: Number(data.toIndex ?? 0),
818
+ customerId: String(data.customerId ?? ''),
819
+ status: toRecordStatus(data.status),
820
+ message: String(data.message ?? ''),
821
+ };
822
+ }
823
+ normalizeListCheckpointsResponse(data) {
824
+ const checkpoints = Array.isArray(data.checkpoints)
825
+ ? data.checkpoints.map((entry) => {
826
+ const item = entry;
827
+ return {
828
+ checkpointId: String(item.checkpointId ?? ''),
829
+ period: String(item.period ?? ''),
830
+ proofRoot: item.proofRoot == null ? null : String(item.proofRoot),
831
+ scope: item.scope,
832
+ mode: item.mode,
833
+ deltaCount: Number(item.deltaCount ?? 0),
834
+ selectedFingerprintCount: item.selectedFingerprintCount == null
835
+ ? undefined
836
+ : Number(item.selectedFingerprintCount),
837
+ eligibleFingerprintCount: item.eligibleFingerprintCount == null
838
+ ? undefined
839
+ : Number(item.eligibleFingerprintCount),
840
+ customerId: String(item.customerId ?? ''),
841
+ status: toRecordStatus(item.status),
842
+ verificationReference: item.verificationReference == null
843
+ ? null
844
+ : String(item.verificationReference),
845
+ verifiedAt: item.verifiedAt == null ? null : String(item.verifiedAt),
846
+ receivedAt: String(item.receivedAt ?? ''),
847
+ };
848
+ })
849
+ : [];
850
+ const pagination = (data.pagination ?? {});
851
+ return {
852
+ checkpoints,
853
+ pagination: {
854
+ total: Number(pagination.total ?? checkpoints.length),
855
+ limit: Number(pagination.limit ?? checkpoints.length),
856
+ offset: Number(pagination.offset ?? 0),
857
+ },
858
+ };
859
+ }
860
+ normalizeSequenceGapsResponse(data) {
861
+ const gaps = Array.isArray(data.gaps)
862
+ ? data.gaps.map((entry) => {
863
+ const item = entry;
864
+ return {
865
+ startSequence: String(item.startSequence ?? ''),
866
+ endSequence: String(item.endSequence ?? ''),
867
+ missingCount: String(item.missingCount ?? '0'),
868
+ };
869
+ })
870
+ : [];
871
+ return {
872
+ customerId: String(data.customerId ?? ''),
873
+ customerReference: String(data.customerReference ?? ''),
874
+ customerPartition: String(data.customerPartition ?? ''),
875
+ nextSequence: String(data.nextSequence ?? '0'),
876
+ rangeStart: String(data.rangeStart ?? '0'),
877
+ rangeEnd: String(data.rangeEnd ?? '0'),
878
+ gaps,
879
+ hasGaps: Boolean(data.hasGaps),
880
+ missingRanges: Number(data.missingRanges ?? gaps.length),
881
+ missingCountInPage: String(data.missingCountInPage ?? '0'),
882
+ truncated: Boolean(data.truncated),
883
+ };
884
+ }
885
+ normalizeBulkEmitResponse(data) {
886
+ const results = Array.isArray(data.results)
887
+ ? data.results.map((entry) => {
888
+ const item = entry;
889
+ return {
890
+ index: Number(item.index ?? 0),
891
+ customerId: String(item.customerId ?? ''),
892
+ delta: Number(item.delta ?? 0),
893
+ recordId: item.recordId == null ? undefined : String(item.recordId),
894
+ itemHash: item.itemHash == null ? undefined : String(item.itemHash),
895
+ receiptId: item.receiptId == null ? undefined : String(item.receiptId),
896
+ referenceId: item.referenceId == null ? undefined : String(item.referenceId),
897
+ sequenceNumber: item.sequenceNumber == null
898
+ ? undefined
899
+ : String(item.sequenceNumber),
900
+ idempotent: item.idempotent == null ? undefined : Boolean(item.idempotent),
901
+ status: String(item.status ?? ''),
902
+ error: item.error == null ? undefined : String(item.error),
903
+ };
904
+ })
905
+ : [];
906
+ return {
907
+ totalQueued: Number(data.totalQueued ?? results.length),
908
+ totalFailed: Number(data.totalFailed ?? 0),
909
+ results,
910
+ _efficiency: data._efficiency,
911
+ };
912
+ }
913
+ normalizeListCustomersResponse(data) {
914
+ const customers = Array.isArray(data.customers)
915
+ ? data.customers.map((entry) => {
916
+ const item = entry;
917
+ return {
918
+ customerId: String(item.customerId ?? ''),
919
+ customerReference: String(item.customerReference ?? ''),
920
+ totalDeltas: Number(item.totalDeltas ?? 0),
921
+ firstDeltaAt: item.firstDeltaAt == null ? null : String(item.firstDeltaAt),
922
+ lastDeltaAt: item.lastDeltaAt == null ? null : String(item.lastDeltaAt),
923
+ };
924
+ })
925
+ : [];
926
+ const pagination = (data.pagination ?? {});
927
+ return {
928
+ customers,
929
+ pagination: {
930
+ total: Number(pagination.total ?? customers.length),
931
+ page: Number(pagination.page ?? 1),
932
+ limit: Number(pagination.limit ?? customers.length),
933
+ totalPages: Number(pagination.totalPages ?? 1),
934
+ },
935
+ };
936
+ }
937
+ normalizeCustomerDetailResponse(data) {
938
+ return {
939
+ customerId: String(data.customerId ?? ''),
940
+ customerReference: String(data.customerReference ?? ''),
941
+ totalDeltas: Number(data.totalDeltas ?? 0),
942
+ firstDeltaAt: data.firstDeltaAt == null ? null : String(data.firstDeltaAt),
943
+ lastDeltaAt: data.lastDeltaAt == null ? null : String(data.lastDeltaAt),
944
+ customerPartition: String(data.customerPartition ?? ''),
945
+ computedBalance: Number(data.computedBalance ?? 0),
946
+ latestCheckpoint: data.latestCheckpoint == null ? null : String(data.latestCheckpoint),
947
+ privacyNote: String(data.privacyNote ?? ''),
948
+ recentActivity: data.recentActivity ??
949
+ {
950
+ items: [],
951
+ pagination: {
952
+ total: 0,
953
+ page: 1,
954
+ limit: 20,
955
+ totalPages: 1,
956
+ },
957
+ },
958
+ };
959
+ }
960
+ // -------------------------------------------------------------------------
961
+ // Invoice Proof (deprecated — use receipt with period)
962
+ // -------------------------------------------------------------------------
963
+ /**
964
+ * Generate an invoice-ready receipt for a customer's billing period.
965
+ *
966
+ * @deprecated Use `receipt(customerId, { period })` instead.
967
+ * @param customerId - The customer to generate the receipt for.
968
+ * @param options - Period selection and request overrides.
969
+ * @returns Receipt with proof root, verifyUrl, and verification data.
970
+ */
971
+ async invoiceProof(customerId, options) {
972
+ return this.receiptForPeriod(customerId, options);
228
973
  }
229
974
  }
230
975
  exports.BalanceResource = BalanceResource;