crypto-aio 0.1.0 → 0.2.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 (163) hide show
  1. package/CHANGELOG.md +151 -1
  2. package/README.md +32 -12
  3. package/dist/adapters/evm/builder.js +64 -4
  4. package/dist/adapters/evm/builder.js.map +1 -1
  5. package/dist/adapters/evm/decode.js +6 -1
  6. package/dist/adapters/evm/decode.js.map +1 -1
  7. package/dist/adapters/evm/ethers-client.js +4 -0
  8. package/dist/adapters/evm/ethers-client.js.map +1 -1
  9. package/dist/adapters/evm/fees.d.ts +24 -0
  10. package/dist/adapters/evm/fees.js +47 -4
  11. package/dist/adapters/evm/fees.js.map +1 -1
  12. package/dist/adapters/evm/index.d.ts +1 -1
  13. package/dist/adapters/evm/index.js +2 -1
  14. package/dist/adapters/evm/index.js.map +1 -1
  15. package/dist/adapters/evm/network.d.ts +5 -0
  16. package/dist/adapters/evm/network.js +21 -2
  17. package/dist/adapters/evm/network.js.map +1 -1
  18. package/dist/adapters/evm/plugin.d.ts +5 -6
  19. package/dist/adapters/evm/plugin.js +50 -2
  20. package/dist/adapters/evm/plugin.js.map +1 -1
  21. package/dist/adapters/evm/proofs.js +17 -5
  22. package/dist/adapters/evm/proofs.js.map +1 -1
  23. package/dist/adapters/evm/reader.d.ts +18 -0
  24. package/dist/adapters/evm/reader.js +34 -0
  25. package/dist/adapters/evm/reader.js.map +1 -1
  26. package/dist/adapters/evm/tokens.d.ts +3 -0
  27. package/dist/adapters/evm/tokens.js.map +1 -1
  28. package/dist/adapters/evm/types.d.ts +10 -0
  29. package/dist/adapters/evm/types.js.map +1 -1
  30. package/dist/adapters/evm/web3-client.d.ts +6 -3
  31. package/dist/adapters/evm/web3-client.js +19 -2
  32. package/dist/adapters/evm/web3-client.js.map +1 -1
  33. package/dist/adapters/solana/builder.js +29 -18
  34. package/dist/adapters/solana/builder.js.map +1 -1
  35. package/dist/adapters/solana/fees.d.ts +12 -3
  36. package/dist/adapters/solana/fees.js +13 -4
  37. package/dist/adapters/solana/fees.js.map +1 -1
  38. package/dist/adapters/solana/message.d.ts +15 -0
  39. package/dist/adapters/solana/message.js +120 -0
  40. package/dist/adapters/solana/message.js.map +1 -0
  41. package/dist/adapters/solana/plugin.js +4 -1
  42. package/dist/adapters/solana/plugin.js.map +1 -1
  43. package/dist/adapters/solana/types.d.ts +8 -5
  44. package/dist/adapters/solana/types.js.map +1 -1
  45. package/dist/adapters/solana/web3.js +10 -32
  46. package/dist/adapters/solana/web3.js.map +1 -1
  47. package/dist/adapters/ton/proofs.js +18 -76
  48. package/dist/adapters/ton/proofs.js.map +1 -1
  49. package/dist/adapters/ton/reader.d.ts +45 -4
  50. package/dist/adapters/ton/reader.js +200 -5
  51. package/dist/adapters/ton/reader.js.map +1 -1
  52. package/dist/adapters/ton/trace.d.ts +26 -0
  53. package/dist/adapters/ton/trace.js +35 -9
  54. package/dist/adapters/ton/trace.js.map +1 -1
  55. package/dist/adapters/tron/abi.d.ts +2 -0
  56. package/dist/adapters/tron/abi.js +2 -0
  57. package/dist/adapters/tron/abi.js.map +1 -1
  58. package/dist/adapters/tron/builder.js +20 -4
  59. package/dist/adapters/tron/builder.js.map +1 -1
  60. package/dist/adapters/tron/decode.d.ts +2 -0
  61. package/dist/adapters/tron/decode.js +1 -1
  62. package/dist/adapters/tron/decode.js.map +1 -1
  63. package/dist/adapters/tron/proofs.js +1 -0
  64. package/dist/adapters/tron/proofs.js.map +1 -1
  65. package/dist/adapters/tron/reader.d.ts +10 -0
  66. package/dist/adapters/tron/reader.js +17 -0
  67. package/dist/adapters/tron/reader.js.map +1 -1
  68. package/dist/adapters/tron/tokens.d.ts +2 -0
  69. package/dist/adapters/tron/tokens.js.map +1 -1
  70. package/dist/adapters/utxo/builder.js +1 -1
  71. package/dist/adapters/utxo/builder.js.map +1 -1
  72. package/dist/adapters/utxo/chains.js +2 -1
  73. package/dist/adapters/utxo/chains.js.map +1 -1
  74. package/dist/adapters/utxo/context.d.ts +6 -0
  75. package/dist/adapters/utxo/context.js +8 -1
  76. package/dist/adapters/utxo/context.js.map +1 -1
  77. package/dist/adapters/utxo/decode.d.ts +8 -0
  78. package/dist/adapters/utxo/decode.js +36 -0
  79. package/dist/adapters/utxo/decode.js.map +1 -1
  80. package/dist/adapters/utxo/driver.js +5 -0
  81. package/dist/adapters/utxo/driver.js.map +1 -1
  82. package/dist/adapters/utxo/esplora.d.ts +23 -2
  83. package/dist/adapters/utxo/esplora.js +49 -2
  84. package/dist/adapters/utxo/esplora.js.map +1 -1
  85. package/dist/adapters/utxo/proofs.d.ts +15 -6
  86. package/dist/adapters/utxo/proofs.js +185 -47
  87. package/dist/adapters/utxo/proofs.js.map +1 -1
  88. package/dist/adapters/utxo/rawblock.d.ts +46 -0
  89. package/dist/adapters/utxo/rawblock.js +159 -0
  90. package/dist/adapters/utxo/rawblock.js.map +1 -0
  91. package/dist/adapters/utxo/rawtx.d.ts +14 -0
  92. package/dist/adapters/utxo/rawtx.js +44 -14
  93. package/dist/adapters/utxo/rawtx.js.map +1 -1
  94. package/dist/core/blockchain/handle.d.ts +6 -27
  95. package/dist/core/blockchain/handle.js +112 -112
  96. package/dist/core/blockchain/handle.js.map +1 -1
  97. package/dist/core/config/types.d.ts +15 -0
  98. package/dist/core/config/types.js.map +1 -1
  99. package/dist/core/container/container.d.ts +1 -8
  100. package/dist/core/container/container.js +42 -8
  101. package/dist/core/container/container.js.map +1 -1
  102. package/dist/core/driver/types.d.ts +34 -1
  103. package/dist/core/driver/types.js.map +1 -1
  104. package/dist/core/errors/codes.d.ts +10 -0
  105. package/dist/core/errors/codes.js +4 -0
  106. package/dist/core/errors/codes.js.map +1 -1
  107. package/dist/core/lifecycle/engine-rules.d.ts +3 -4
  108. package/dist/core/lifecycle/engine-rules.js +16 -6
  109. package/dist/core/lifecycle/engine-rules.js.map +1 -1
  110. package/dist/core/lifecycle/engine.d.ts +2 -0
  111. package/dist/core/lifecycle/engine.js +19 -1
  112. package/dist/core/lifecycle/engine.js.map +1 -1
  113. package/dist/core/lifecycle/evaluate.js +9 -4
  114. package/dist/core/lifecycle/evaluate.js.map +1 -1
  115. package/dist/core/lifecycle/monitor.js +16 -6
  116. package/dist/core/lifecycle/monitor.js.map +1 -1
  117. package/dist/core/lifecycle/reconcile.d.ts +1 -1
  118. package/dist/core/lifecycle/reconcile.js +1 -1
  119. package/dist/core/lifecycle/reconcile.js.map +1 -1
  120. package/dist/core/lifecycle/workers.d.ts +9 -3
  121. package/dist/core/lifecycle/workers.js +32 -14
  122. package/dist/core/lifecycle/workers.js.map +1 -1
  123. package/dist/core/model/ids.d.ts +1 -0
  124. package/dist/core/model/ids.js +1 -0
  125. package/dist/core/model/ids.js.map +1 -1
  126. package/dist/core/model/ordering.d.ts +8 -0
  127. package/dist/core/model/ordering.js.map +1 -1
  128. package/dist/core/model/transaction.d.ts +7 -0
  129. package/dist/core/model/transaction.js.map +1 -1
  130. package/dist/core/observe/scanner.js +3 -0
  131. package/dist/core/observe/scanner.js.map +1 -1
  132. package/dist/core/signing/wallet.js +8 -0
  133. package/dist/core/signing/wallet.js.map +1 -1
  134. package/dist/core/store/format.d.ts +20 -0
  135. package/dist/core/store/format.js +86 -0
  136. package/dist/core/store/format.js.map +1 -0
  137. package/dist/core/store/memory.js +2 -2
  138. package/dist/core/store/memory.js.map +1 -1
  139. package/dist/core/store/types.d.ts +15 -3
  140. package/dist/core/store/types.js +2 -0
  141. package/dist/core/store/types.js.map +1 -1
  142. package/dist/core/transport/http-transport.js +21 -13
  143. package/dist/core/transport/http-transport.js.map +1 -1
  144. package/dist/core/transport/types.d.ts +16 -1
  145. package/dist/core/transport/types.js.map +1 -1
  146. package/dist/core/util/json.d.ts +7 -1
  147. package/dist/core/util/json.js +13 -4
  148. package/dist/core/util/json.js.map +1 -1
  149. package/dist/core/util/names.d.ts +6 -0
  150. package/dist/core/util/names.js +10 -0
  151. package/dist/core/util/names.js.map +1 -1
  152. package/dist/index.d.ts +2 -1
  153. package/dist/index.js +4 -2
  154. package/dist/index.js.map +1 -1
  155. package/dist/testing/contracts/operations.js +20 -1
  156. package/dist/testing/contracts/operations.js.map +1 -1
  157. package/dist/testing/env.d.ts +5 -0
  158. package/dist/testing/env.js +1 -1
  159. package/dist/testing/env.js.map +1 -1
  160. package/dist/testing/faulty-store.d.ts +2 -0
  161. package/dist/testing/faulty-store.js +5 -0
  162. package/dist/testing/faulty-store.js.map +1 -1
  163. package/package.json +17 -7
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OPERATION_FORMAT = void 0;
4
+ exports.formatOf = formatOf;
5
+ exports.formatGuard = formatGuard;
6
+ const error_1 = require("../errors/error");
7
+ /**
8
+ * The format of the Operation records this version writes. A record holds the stored
9
+ * intent, the family's unsigned transaction and ordering data, and its Attempts; a release
10
+ * that changes how any of them is stored or read raises this number, and keeps reading
11
+ * every earlier format. Records written before formats existed (0.1.0) carry none and are
12
+ * format 1.
13
+ */
14
+ exports.OPERATION_FORMAT = 1;
15
+ /** The format of `record`: a record without one (0.1.0) is format 1. */
16
+ function formatOf(record) {
17
+ return record.format === undefined ? 1 : record.format;
18
+ }
19
+ function supported(record) {
20
+ const format = formatOf(record);
21
+ return Number.isSafeInteger(format) && format >= 1 && format <= exports.OPERATION_FORMAT;
22
+ }
23
+ function unsupported(record) {
24
+ const format = record.format;
25
+ return new error_1.StateError('RECORD_FORMAT_UNSUPPORTED', `operation '${record.id}' was stored in record format ${String(format)}, newer than ` +
26
+ `this crypto-aio reads (${exports.OPERATION_FORMAT}): upgrade crypto-aio to handle it`, { context: { operationId: record.id } });
27
+ }
28
+ /** `record` with its format filled in, or `RECORD_FORMAT_UNSUPPORTED` for a newer one. */
29
+ function checked(record) {
30
+ if (!supported(record))
31
+ throw unsupported(record);
32
+ return record.format === undefined ? { ...record, format: 1 } : record;
33
+ }
34
+ function checkedOrNull(record) {
35
+ return record === null ? null : checked(record);
36
+ }
37
+ /**
38
+ * The Operation store as the engine, the workers and `aio.operations` read it: every
39
+ * record it returns is in a format this version reads. A record a newer version wrote, as
40
+ * in a rolling upgrade, is never read as an older format: a read of it throws
41
+ * `RECORD_FORMAT_UNSUPPORTED`, and a worker pass releases its claim and leaves it to a
42
+ * process that reads it.
43
+ */
44
+ function formatGuard(store, log) {
45
+ const guarded = {
46
+ create: async (operation) => {
47
+ const result = await store.create(operation);
48
+ return { created: result.created, record: checked(result.record) };
49
+ },
50
+ get: async (namespace, id) => checkedOrNull(await store.get(namespace, id)),
51
+ getByKey: async (namespace, key) => checkedOrNull(await store.getByKey(namespace, key)),
52
+ findByRef: async (namespace, ref) => checkedOrNull(await store.findByRef(namespace, ref)),
53
+ update: async (namespace, id, patch, expectedVersion, fence) => checked(await store.update(namespace, id, patch, expectedVersion, fence)),
54
+ appendAttempt: async (namespace, id, attempt, patch, expectedVersion, fence) => checked(await store.appendAttempt(namespace, id, attempt, patch, expectedVersion, fence)),
55
+ getObservation: (attemptId) => store.getObservation(attemptId),
56
+ putObservation: (observation, expectedVersion) => store.putObservation(observation, expectedVersion),
57
+ claimDue: async (namespace, workerId, now, leaseMs, limit) => {
58
+ const claimed = await store.claimDue(namespace, workerId, now, leaseMs, limit);
59
+ const kept = [];
60
+ for (const record of claimed) {
61
+ if (supported(record)) {
62
+ kept.push(checked(record));
63
+ continue;
64
+ }
65
+ log.warn('skipped an operation stored in a newer record format', {
66
+ operationId: record.id,
67
+ format: record.format,
68
+ });
69
+ if (record.claim) {
70
+ await store
71
+ .releaseClaim(namespace, record.id, { claimToken: record.claim.token })
72
+ .catch(() => undefined);
73
+ }
74
+ }
75
+ return kept;
76
+ },
77
+ releaseClaim: (namespace, id, fence) => store.releaseClaim(namespace, id, fence),
78
+ list: async (filter) => (await store.list(filter)).map(checked),
79
+ };
80
+ if (store.purge) {
81
+ const purge = store.purge.bind(store);
82
+ return { ...guarded, purge: (filter) => purge(filter) };
83
+ }
84
+ return guarded;
85
+ }
86
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/core/store/format.ts"],"names":[],"mappings":";;;AAcA,4BAEC;AAkCD,kCAgDC;AAlGD,2CAA6C;AAI7C;;;;;;GAMG;AACU,QAAA,gBAAgB,GAAG,CAAC,CAAC;AAElC,wEAAwE;AACxE,SAAgB,QAAQ,CAAC,MAAuB;IAC9C,OAAQ,MAA+B,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;AACnF,CAAC;AAED,SAAS,SAAS,CAAC,MAAuB;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,wBAAgB,CAAC;AACnF,CAAC;AAED,SAAS,WAAW,CAAC,MAAuB;IAC1C,MAAM,MAAM,GAAI,MAA+B,CAAC,MAAM,CAAC;IACvD,OAAO,IAAI,kBAAU,CACnB,2BAA2B,EAC3B,cAAc,MAAM,CAAC,EAAE,iCAAiC,MAAM,CAAC,MAAM,CAAC,eAAe;QACnF,0BAA0B,wBAAgB,oCAAoC,EAChF,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,CACxC,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,SAAS,OAAO,CAAC,MAAuB;IACtC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACzE,CAAC;AAED,SAAS,aAAa,CAAC,MAA8B;IACnD,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,KAAqB,EAAE,GAAW;IAC5D,MAAM,OAAO,GAAmB;QAC9B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACrE,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC3E,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CACjC,aAAa,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACrD,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAClC,aAAa,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACtD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,CAC7D,OAAO,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;QAC3E,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,CAC7E,OAAO,CACL,MAAM,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CACjF;QACH,cAAc,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC;QAC9D,cAAc,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,EAAE,CAC/C,KAAK,CAAC,cAAc,CAAC,WAAW,EAAE,eAAe,CAAC;QACpD,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC3D,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/E,MAAM,IAAI,GAAsB,EAAE,CAAC;YACnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3B,SAAS;gBACX,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,sDAAsD,EAAE;oBAC/D,WAAW,EAAE,MAAM,CAAC,EAAE;oBACtB,MAAM,EAAG,MAA+B,CAAC,MAAM;iBAChD,CAAC,CAAC;gBACH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;oBACjB,MAAM,KAAK;yBACR,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;yBACtE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,YAAY,EAAE,CAAC,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,EAAE,KAAK,CAAC;QAChF,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;KAChE,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import { StateError } from '../errors/error';\nimport type { Logger } from '../events/logger';\nimport type { OperationRecord, OperationStore } from './types';\n\n/**\n * The format of the Operation records this version writes. A record holds the stored\n * intent, the family's unsigned transaction and ordering data, and its Attempts; a release\n * that changes how any of them is stored or read raises this number, and keeps reading\n * every earlier format. Records written before formats existed (0.1.0) carry none and are\n * format 1.\n */\nexport const OPERATION_FORMAT = 1;\n\n/** The format of `record`: a record without one (0.1.0) is format 1. */\nexport function formatOf(record: OperationRecord): number {\n return (record as { format?: unknown }).format === undefined ? 1 : record.format;\n}\n\nfunction supported(record: OperationRecord): boolean {\n const format = formatOf(record);\n return Number.isSafeInteger(format) && format >= 1 && format <= OPERATION_FORMAT;\n}\n\nfunction unsupported(record: OperationRecord): StateError {\n const format = (record as { format?: unknown }).format;\n return new StateError(\n 'RECORD_FORMAT_UNSUPPORTED',\n `operation '${record.id}' was stored in record format ${String(format)}, newer than ` +\n `this crypto-aio reads (${OPERATION_FORMAT}): upgrade crypto-aio to handle it`,\n { context: { operationId: record.id } },\n );\n}\n\n/** `record` with its format filled in, or `RECORD_FORMAT_UNSUPPORTED` for a newer one. */\nfunction checked(record: OperationRecord): OperationRecord {\n if (!supported(record)) throw unsupported(record);\n return record.format === undefined ? { ...record, format: 1 } : record;\n}\n\nfunction checkedOrNull(record: OperationRecord | null): OperationRecord | null {\n return record === null ? null : checked(record);\n}\n\n/**\n * The Operation store as the engine, the workers and `aio.operations` read it: every\n * record it returns is in a format this version reads. A record a newer version wrote, as\n * in a rolling upgrade, is never read as an older format: a read of it throws\n * `RECORD_FORMAT_UNSUPPORTED`, and a worker pass releases its claim and leaves it to a\n * process that reads it.\n */\nexport function formatGuard(store: OperationStore, log: Logger): OperationStore {\n const guarded: OperationStore = {\n create: async (operation) => {\n const result = await store.create(operation);\n return { created: result.created, record: checked(result.record) };\n },\n get: async (namespace, id) => checkedOrNull(await store.get(namespace, id)),\n getByKey: async (namespace, key) =>\n checkedOrNull(await store.getByKey(namespace, key)),\n findByRef: async (namespace, ref) =>\n checkedOrNull(await store.findByRef(namespace, ref)),\n update: async (namespace, id, patch, expectedVersion, fence) =>\n checked(await store.update(namespace, id, patch, expectedVersion, fence)),\n appendAttempt: async (namespace, id, attempt, patch, expectedVersion, fence) =>\n checked(\n await store.appendAttempt(namespace, id, attempt, patch, expectedVersion, fence),\n ),\n getObservation: (attemptId) => store.getObservation(attemptId),\n putObservation: (observation, expectedVersion) =>\n store.putObservation(observation, expectedVersion),\n claimDue: async (namespace, workerId, now, leaseMs, limit) => {\n const claimed = await store.claimDue(namespace, workerId, now, leaseMs, limit);\n const kept: OperationRecord[] = [];\n for (const record of claimed) {\n if (supported(record)) {\n kept.push(checked(record));\n continue;\n }\n log.warn('skipped an operation stored in a newer record format', {\n operationId: record.id,\n format: (record as { format?: unknown }).format,\n });\n if (record.claim) {\n await store\n .releaseClaim(namespace, record.id, { claimToken: record.claim.token })\n .catch(() => undefined);\n }\n }\n return kept;\n },\n releaseClaim: (namespace, id, fence) => store.releaseClaim(namespace, id, fence),\n list: async (filter) => (await store.list(filter)).map(checked),\n };\n if (store.purge) {\n const purge = store.purge.bind(store);\n return { ...guarded, purge: (filter) => purge(filter) };\n }\n return guarded;\n}\n"]}
@@ -277,7 +277,7 @@ class MemoryOperationStore {
277
277
  const found = [...this.#records.values()]
278
278
  .filter((r) => matches(r, filter))
279
279
  .sort((a, b) => a.createdAt - b.createdAt);
280
- return (0, clone_1.clone)(filter.limit === undefined ? found : found.slice(0, filter.limit));
280
+ return (0, clone_1.clone)(filter.limit === undefined ? found : found.slice(0, Math.max(0, filter.limit)));
281
281
  }
282
282
  async purge(filter) {
283
283
  // As `list` does: the oldest matches first, and at most `limit` of them.
@@ -287,7 +287,7 @@ class MemoryOperationStore {
287
287
  let removed = 0;
288
288
  for (const record of filter.limit === undefined
289
289
  ? found
290
- : found.slice(0, filter.limit)) {
290
+ : found.slice(0, Math.max(0, filter.limit))) {
291
291
  this.#records.delete(compositeKey(record.namespace, record.id));
292
292
  this.#keys.delete(compositeKey(record.namespace, record.idempotencyKey));
293
293
  for (const attempt of record.attempts)
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/core/store/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import { StateError } from '../errors/error';\nimport { systemClock, type Clock } from '../util/clock';\nimport { clone } from './clone';\nimport { CLEARABLE_FIELDS, isTerminal, OPERATION_PATCH_KEYS } from './types';\nimport type {\n AttemptObservation,\n AttemptRecord,\n CreateResult,\n CursorStore,\n Fence,\n Lease,\n LockManager,\n NewOperation,\n OperationFilter,\n OperationPatch,\n OperationRecord,\n OperationStore,\n ScanCursor,\n SequenceState,\n SequenceStore,\n Stores,\n StoredCursor,\n} from './types';\n\nexport class MemoryLockManager implements LockManager {\n readonly #held = new Map<string, Lease>();\n readonly #tokens = new Map<string, bigint>();\n\n constructor(private readonly clock: Clock = systemClock) {}\n\n async acquire(key: string, owner: string, ttlMs: number): Promise<Lease | null> {\n const now = this.clock.now();\n const current = this.#held.get(key);\n if (current && current.expiresAt > now) return null;\n const token = (this.#tokens.get(key) ?? 0n) + 1n;\n this.#tokens.set(key, token);\n const lease: Lease = Object.freeze({ key, owner, token, expiresAt: now + ttlMs });\n this.#held.set(key, lease);\n return lease;\n }\n\n async renew(lease: Lease, ttlMs: number): Promise<Lease | null> {\n const now = this.clock.now();\n const current = this.#held.get(lease.key);\n if (!current || current.token !== lease.token || current.owner !== lease.owner)\n return null;\n if (current.expiresAt <= now) return null;\n const renewed: Lease = Object.freeze({ ...current, expiresAt: now + ttlMs });\n this.#held.set(lease.key, renewed);\n return renewed;\n }\n\n async release(lease: Lease): Promise<void> {\n const current = this.#held.get(lease.key);\n if (current && current.token === lease.token && current.owner === lease.owner) {\n this.#held.delete(lease.key);\n }\n }\n}\n\n// Sequence and cursor error messages omit the key: it embeds a wallet address, and\n// error messages reach logs.\nexport class MemorySequenceStore implements SequenceStore {\n readonly #states = new Map<string, SequenceState>();\n\n async get(key: string): Promise<SequenceState | null> {\n const state = this.#states.get(key);\n return state ? clone(state) : null;\n }\n\n async put(\n key: string,\n state: Omit<SequenceState, 'version'>,\n expectedVersion: number | null,\n ): Promise<void> {\n const current = this.#states.get(key);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError('VERSION_CONFLICT', 'the sequence was modified concurrently');\n }\n if (current && state.fence < current.fence) {\n throw new StateError('FENCING', 'stale fencing token for the sequence');\n }\n this.#states.set(key, clone({ ...state, version: (current?.version ?? 0) + 1 }));\n }\n}\n\nexport class MemoryCursorStore implements CursorStore {\n readonly #cursors = new Map<string, StoredCursor>();\n\n async get(key: string): Promise<StoredCursor | null> {\n const stored = this.#cursors.get(key);\n return stored ? clone(stored) : null;\n }\n\n async put(\n key: string,\n cursor: ScanCursor,\n expectedVersion: number | null,\n ): Promise<number> {\n const current = this.#cursors.get(key);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError('VERSION_CONFLICT', 'the cursor was modified concurrently');\n }\n const version = (current?.version ?? 0) + 1;\n this.#cursors.set(key, clone({ cursor, version }));\n return version;\n }\n}\n\nconst compositeKey = (namespace: string, id: string): string => `${namespace}\\u0000${id}`;\n\nconst OPERATION_PATCH_KEY_SET: ReadonlySet<string> = new Set(OPERATION_PATCH_KEYS);\nconst CLEARABLE_FIELD_SET: ReadonlySet<string> = new Set(CLEARABLE_FIELDS);\n\n/**\n * Validates a pre-read snapshot of a patch's own keys, so a spread-typed patch (e.g.\n * `{ ...record, state }` forced through `as unknown as OperationPatch`) cannot write\n * or clear a field outside the writable whitelist — `attempts`, `claim`, `id`,\n * `namespace`, `idempotencyKey`, `intentHash`, `context`, `kind`, `version`,\n * `createdAt` and `updatedAt` are never reachable through `update`/`appendAttempt`.\n */\nfunction assertValidPatch(\n entries: readonly (readonly [string, unknown])[],\n clearList: readonly string[],\n): void {\n for (const [key] of entries) {\n if (key !== 'clear' && !OPERATION_PATCH_KEY_SET.has(key)) {\n throw new StateError('INVALID_TRANSITION', `unsupported patch field '${key}'`);\n }\n }\n for (const key of clearList) {\n if (!CLEARABLE_FIELD_SET.has(key)) {\n throw new StateError('INVALID_TRANSITION', `unsupported patch field '${key}'`);\n }\n }\n}\n\nfunction applyPatch(\n current: OperationRecord,\n patch: OperationPatch,\n now: number,\n): OperationRecord {\n // Read the patch's own keys exactly once: the same snapshot is both validated and\n // applied, so an accessor (a getter or Proxy trap) on the patch object can't change\n // what keys are visible between validation and application.\n const entries = Object.entries(patch) as readonly (readonly [string, unknown])[];\n // The caller's `clear` list is read once too, so what is validated is what is cleared.\n // Only an array is a list; a number, an object or a Set would not survive a serialized\n // store as a list either, so it is refused rather than read as `[]`.\n const clear = entries.find(([key]) => key === 'clear')?.[1];\n if (clear !== undefined && !Array.isArray(clear)) {\n throw new StateError('INVALID_TRANSITION', \"unsupported patch field 'clear'\");\n }\n const clearList: readonly string[] = clear === undefined ? [] : Array.from(clear);\n assertValidPatch(entries, clearList);\n const next: Record<string, unknown> = { ...current };\n for (const [key, value] of entries) {\n if (key === 'clear') continue;\n if (value !== undefined) next[key] = clone(value);\n }\n for (const key of clearList) delete next[key];\n next.version = current.version + 1;\n next.updatedAt = now;\n return next as unknown as OperationRecord;\n}\n\nfunction matches(record: OperationRecord, filter: OperationFilter): boolean {\n return (\n record.namespace === filter.namespace &&\n (filter.chain === undefined || record.context.chain === filter.chain) &&\n (filter.network === undefined || record.context.network === filter.network) &&\n (filter.from === undefined || record.intent.from === filter.from) &&\n (filter.states === undefined || filter.states.includes(record.state))\n );\n}\n\nexport class MemoryOperationStore implements OperationStore {\n readonly #records = new Map<string, OperationRecord>();\n readonly #keys = new Map<string, string>();\n readonly #observations = new Map<string, AttemptObservation>();\n #claims = 0n;\n\n constructor(private readonly clock: Clock = systemClock) {}\n\n async create(operation: NewOperation): Promise<CreateResult> {\n for (const key of ['claim', 'version', 'createdAt', 'updatedAt']) {\n if (Object.prototype.hasOwnProperty.call(operation, key)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `unsupported operation field '${key}'`,\n );\n }\n }\n const keyIndex = compositeKey(operation.namespace, operation.idempotencyKey);\n const existingId = this.#keys.get(keyIndex);\n if (existingId !== undefined) {\n return {\n created: false,\n record: clone(this.#require(operation.namespace, existingId)),\n };\n }\n const recordKey = compositeKey(operation.namespace, operation.id);\n if (this.#records.has(recordKey)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `operation '${operation.id}' already exists`,\n );\n }\n const now = this.clock.now();\n const record = {\n ...clone(operation),\n version: 1,\n createdAt: now,\n updatedAt: now,\n } as OperationRecord;\n this.#records.set(recordKey, record);\n this.#keys.set(keyIndex, operation.id);\n return { created: true, record: clone(record) };\n }\n\n async get(namespace: string, id: string): Promise<OperationRecord | null> {\n const record = this.#records.get(compositeKey(namespace, id));\n return record ? clone(record) : null;\n }\n\n async getByKey(\n namespace: string,\n idempotencyKey: string,\n ): Promise<OperationRecord | null> {\n const id = this.#keys.get(compositeKey(namespace, idempotencyKey));\n return id !== undefined ? this.get(namespace, id) : null;\n }\n\n async findByRef(\n namespace: string,\n refOrTxHash: string,\n ): Promise<OperationRecord | null> {\n for (const record of this.#records.values()) {\n if (\n record.namespace === namespace &&\n record.attempts.some((a) => a.ref.id === refOrTxHash)\n ) {\n return clone(record);\n }\n }\n for (const observation of this.#observations.values()) {\n if (observation.txHash !== refOrTxHash) continue;\n const record = this.#records.get(compositeKey(namespace, observation.operationId));\n // The observed attempt must actually belong to this record: two namespaces can\n // otherwise share an operation id and resolve the tx hash to the wrong operation.\n if (record && record.attempts.some((a) => a.id === observation.attemptId)) {\n return clone(record);\n }\n }\n return null;\n }\n\n async update(\n namespace: string,\n id: string,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord> {\n const current = this.#check(namespace, id, expectedVersion, fence);\n const next = applyPatch(current, patch, this.clock.now());\n this.#records.set(compositeKey(namespace, id), next);\n return clone(next);\n }\n\n async appendAttempt(\n namespace: string,\n id: string,\n attempt: AttemptRecord,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord> {\n const current = this.#check(namespace, id, expectedVersion, fence);\n if (current.attempts.some((a) => a.id === attempt.id)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `attempt '${attempt.id}' already exists`,\n );\n }\n const withAttempt: OperationRecord = {\n ...current,\n attempts: [...current.attempts, clone(attempt)],\n activeAttemptId: attempt.id,\n };\n const next = applyPatch(withAttempt, patch, this.clock.now());\n this.#records.set(compositeKey(namespace, id), next);\n return clone(next);\n }\n\n async getObservation(attemptId: string): Promise<AttemptObservation | null> {\n const observation = this.#observations.get(attemptId);\n return observation ? clone(observation) : null;\n }\n\n async putObservation(\n observation: Omit<AttemptObservation, 'version'>,\n expectedVersion: number | null,\n ): Promise<AttemptObservation> {\n const current = this.#observations.get(observation.attemptId);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError(\n 'VERSION_CONFLICT',\n `observation '${observation.attemptId}' was modified concurrently`,\n );\n }\n const next: AttemptObservation = {\n ...clone(observation),\n version: (current?.version ?? 0) + 1,\n };\n this.#observations.set(observation.attemptId, next);\n return clone(next);\n }\n\n async claimDue(\n namespace: string,\n workerId: string,\n now: number,\n leaseMs: number,\n limit: number,\n ): Promise<OperationRecord[]> {\n if (limit <= 0) return [];\n const due = [...this.#records.values()]\n .filter(\n (r) =>\n r.namespace === namespace &&\n !isTerminal(r.state) &&\n r.nextCheckAt !== undefined &&\n r.nextCheckAt <= now &&\n (!r.claim || r.claim.until <= now),\n )\n .sort(\n (a, b) =>\n (a.nextCheckAt as number) - (b.nextCheckAt as number) ||\n a.createdAt - b.createdAt,\n )\n .slice(0, limit);\n return due.map((record) => {\n this.#claims += 1n;\n const next: OperationRecord = {\n ...record,\n claim: { workerId, token: this.#claims.toString(), until: now + leaseMs },\n version: record.version + 1,\n updatedAt: this.clock.now(),\n };\n this.#records.set(compositeKey(namespace, record.id), next);\n return clone(next);\n });\n }\n\n async releaseClaim(namespace: string, id: string, fence: Fence): Promise<void> {\n const current = this.#require(namespace, id);\n if (current.claim?.token !== fence.claimToken) {\n throw new StateError('FENCING', `claim on operation '${id}' is no longer held`);\n }\n const { claim: _claim, ...rest } = current;\n this.#records.set(compositeKey(namespace, id), {\n ...rest,\n version: current.version + 1,\n updatedAt: this.clock.now(),\n });\n }\n\n async list(filter: OperationFilter): Promise<OperationRecord[]> {\n const found = [...this.#records.values()]\n .filter((r) => matches(r, filter))\n .sort((a, b) => a.createdAt - b.createdAt);\n return clone(filter.limit === undefined ? found : found.slice(0, filter.limit));\n }\n\n async purge(filter: OperationFilter): Promise<number> {\n // As `list` does: the oldest matches first, and at most `limit` of them.\n const found = [...this.#records.values()]\n .filter((r) => matches(r, filter))\n .sort((a, b) => a.createdAt - b.createdAt);\n let removed = 0;\n for (const record of filter.limit === undefined\n ? found\n : found.slice(0, filter.limit)) {\n this.#records.delete(compositeKey(record.namespace, record.id));\n this.#keys.delete(compositeKey(record.namespace, record.idempotencyKey));\n for (const attempt of record.attempts) this.#observations.delete(attempt.id);\n removed += 1;\n }\n return removed;\n }\n\n #require(namespace: string, id: string): OperationRecord {\n const record = this.#records.get(compositeKey(namespace, id));\n if (!record) throw new StateError('NOT_FOUND', `operation '${id}' not found`);\n return record;\n }\n\n #check(\n namespace: string,\n id: string,\n expectedVersion: number,\n fence?: Fence,\n ): OperationRecord {\n const record = this.#require(namespace, id);\n if (record.version !== expectedVersion) {\n throw new StateError(\n 'VERSION_CONFLICT',\n `operation '${id}' was modified concurrently (expected v${expectedVersion}, found v${record.version})`,\n );\n }\n if (fence && record.claim?.token !== fence.claimToken) {\n throw new StateError('FENCING', `claim on operation '${id}' is no longer held`);\n }\n return record;\n }\n}\n\nexport function createMemoryStores(clock: Clock = systemClock): Stores {\n return {\n operations: new MemoryOperationStore(clock),\n locks: new MemoryLockManager(clock),\n sequences: new MemorySequenceStore(),\n cursors: new MemoryCursorStore(),\n };\n}\n"]}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/core/store/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import { StateError } from '../errors/error';\nimport { systemClock, type Clock } from '../util/clock';\nimport { clone } from './clone';\nimport { CLEARABLE_FIELDS, isTerminal, OPERATION_PATCH_KEYS } from './types';\nimport type {\n AttemptObservation,\n AttemptRecord,\n CreateResult,\n CursorStore,\n Fence,\n Lease,\n LockManager,\n NewOperation,\n OperationFilter,\n OperationPatch,\n OperationRecord,\n OperationStore,\n ScanCursor,\n SequenceState,\n SequenceStore,\n Stores,\n StoredCursor,\n} from './types';\n\nexport class MemoryLockManager implements LockManager {\n readonly #held = new Map<string, Lease>();\n readonly #tokens = new Map<string, bigint>();\n\n constructor(private readonly clock: Clock = systemClock) {}\n\n async acquire(key: string, owner: string, ttlMs: number): Promise<Lease | null> {\n const now = this.clock.now();\n const current = this.#held.get(key);\n if (current && current.expiresAt > now) return null;\n const token = (this.#tokens.get(key) ?? 0n) + 1n;\n this.#tokens.set(key, token);\n const lease: Lease = Object.freeze({ key, owner, token, expiresAt: now + ttlMs });\n this.#held.set(key, lease);\n return lease;\n }\n\n async renew(lease: Lease, ttlMs: number): Promise<Lease | null> {\n const now = this.clock.now();\n const current = this.#held.get(lease.key);\n if (!current || current.token !== lease.token || current.owner !== lease.owner)\n return null;\n if (current.expiresAt <= now) return null;\n const renewed: Lease = Object.freeze({ ...current, expiresAt: now + ttlMs });\n this.#held.set(lease.key, renewed);\n return renewed;\n }\n\n async release(lease: Lease): Promise<void> {\n const current = this.#held.get(lease.key);\n if (current && current.token === lease.token && current.owner === lease.owner) {\n this.#held.delete(lease.key);\n }\n }\n}\n\n// Sequence and cursor error messages omit the key: it embeds a wallet address, and\n// error messages reach logs.\nexport class MemorySequenceStore implements SequenceStore {\n readonly #states = new Map<string, SequenceState>();\n\n async get(key: string): Promise<SequenceState | null> {\n const state = this.#states.get(key);\n return state ? clone(state) : null;\n }\n\n async put(\n key: string,\n state: Omit<SequenceState, 'version'>,\n expectedVersion: number | null,\n ): Promise<void> {\n const current = this.#states.get(key);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError('VERSION_CONFLICT', 'the sequence was modified concurrently');\n }\n if (current && state.fence < current.fence) {\n throw new StateError('FENCING', 'stale fencing token for the sequence');\n }\n this.#states.set(key, clone({ ...state, version: (current?.version ?? 0) + 1 }));\n }\n}\n\nexport class MemoryCursorStore implements CursorStore {\n readonly #cursors = new Map<string, StoredCursor>();\n\n async get(key: string): Promise<StoredCursor | null> {\n const stored = this.#cursors.get(key);\n return stored ? clone(stored) : null;\n }\n\n async put(\n key: string,\n cursor: ScanCursor,\n expectedVersion: number | null,\n ): Promise<number> {\n const current = this.#cursors.get(key);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError('VERSION_CONFLICT', 'the cursor was modified concurrently');\n }\n const version = (current?.version ?? 0) + 1;\n this.#cursors.set(key, clone({ cursor, version }));\n return version;\n }\n}\n\nconst compositeKey = (namespace: string, id: string): string => `${namespace}\\u0000${id}`;\n\nconst OPERATION_PATCH_KEY_SET: ReadonlySet<string> = new Set(OPERATION_PATCH_KEYS);\nconst CLEARABLE_FIELD_SET: ReadonlySet<string> = new Set(CLEARABLE_FIELDS);\n\n/**\n * Validates a pre-read snapshot of a patch's own keys, so a spread-typed patch (e.g.\n * `{ ...record, state }` forced through `as unknown as OperationPatch`) cannot write\n * or clear a field outside the writable whitelist — `attempts`, `claim`, `id`,\n * `namespace`, `idempotencyKey`, `intentHash`, `context`, `kind`, `version`,\n * `createdAt` and `updatedAt` are never reachable through `update`/`appendAttempt`.\n */\nfunction assertValidPatch(\n entries: readonly (readonly [string, unknown])[],\n clearList: readonly string[],\n): void {\n for (const [key] of entries) {\n if (key !== 'clear' && !OPERATION_PATCH_KEY_SET.has(key)) {\n throw new StateError('INVALID_TRANSITION', `unsupported patch field '${key}'`);\n }\n }\n for (const key of clearList) {\n if (!CLEARABLE_FIELD_SET.has(key)) {\n throw new StateError('INVALID_TRANSITION', `unsupported patch field '${key}'`);\n }\n }\n}\n\nfunction applyPatch(\n current: OperationRecord,\n patch: OperationPatch,\n now: number,\n): OperationRecord {\n // Read the patch's own keys exactly once: the same snapshot is both validated and\n // applied, so an accessor (a getter or Proxy trap) on the patch object can't change\n // what keys are visible between validation and application.\n const entries = Object.entries(patch) as readonly (readonly [string, unknown])[];\n // The caller's `clear` list is read once too, so what is validated is what is cleared.\n // Only an array is a list; a number, an object or a Set would not survive a serialized\n // store as a list either, so it is refused rather than read as `[]`.\n const clear = entries.find(([key]) => key === 'clear')?.[1];\n if (clear !== undefined && !Array.isArray(clear)) {\n throw new StateError('INVALID_TRANSITION', \"unsupported patch field 'clear'\");\n }\n const clearList: readonly string[] = clear === undefined ? [] : Array.from(clear);\n assertValidPatch(entries, clearList);\n const next: Record<string, unknown> = { ...current };\n for (const [key, value] of entries) {\n if (key === 'clear') continue;\n if (value !== undefined) next[key] = clone(value);\n }\n for (const key of clearList) delete next[key];\n next.version = current.version + 1;\n next.updatedAt = now;\n return next as unknown as OperationRecord;\n}\n\nfunction matches(record: OperationRecord, filter: OperationFilter): boolean {\n return (\n record.namespace === filter.namespace &&\n (filter.chain === undefined || record.context.chain === filter.chain) &&\n (filter.network === undefined || record.context.network === filter.network) &&\n (filter.from === undefined || record.intent.from === filter.from) &&\n (filter.states === undefined || filter.states.includes(record.state))\n );\n}\n\nexport class MemoryOperationStore implements OperationStore {\n readonly #records = new Map<string, OperationRecord>();\n readonly #keys = new Map<string, string>();\n readonly #observations = new Map<string, AttemptObservation>();\n #claims = 0n;\n\n constructor(private readonly clock: Clock = systemClock) {}\n\n async create(operation: NewOperation): Promise<CreateResult> {\n for (const key of ['claim', 'version', 'createdAt', 'updatedAt']) {\n if (Object.prototype.hasOwnProperty.call(operation, key)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `unsupported operation field '${key}'`,\n );\n }\n }\n const keyIndex = compositeKey(operation.namespace, operation.idempotencyKey);\n const existingId = this.#keys.get(keyIndex);\n if (existingId !== undefined) {\n return {\n created: false,\n record: clone(this.#require(operation.namespace, existingId)),\n };\n }\n const recordKey = compositeKey(operation.namespace, operation.id);\n if (this.#records.has(recordKey)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `operation '${operation.id}' already exists`,\n );\n }\n const now = this.clock.now();\n const record = {\n ...clone(operation),\n version: 1,\n createdAt: now,\n updatedAt: now,\n } as OperationRecord;\n this.#records.set(recordKey, record);\n this.#keys.set(keyIndex, operation.id);\n return { created: true, record: clone(record) };\n }\n\n async get(namespace: string, id: string): Promise<OperationRecord | null> {\n const record = this.#records.get(compositeKey(namespace, id));\n return record ? clone(record) : null;\n }\n\n async getByKey(\n namespace: string,\n idempotencyKey: string,\n ): Promise<OperationRecord | null> {\n const id = this.#keys.get(compositeKey(namespace, idempotencyKey));\n return id !== undefined ? this.get(namespace, id) : null;\n }\n\n async findByRef(\n namespace: string,\n refOrTxHash: string,\n ): Promise<OperationRecord | null> {\n for (const record of this.#records.values()) {\n if (\n record.namespace === namespace &&\n record.attempts.some((a) => a.ref.id === refOrTxHash)\n ) {\n return clone(record);\n }\n }\n for (const observation of this.#observations.values()) {\n if (observation.txHash !== refOrTxHash) continue;\n const record = this.#records.get(compositeKey(namespace, observation.operationId));\n // The observed attempt must actually belong to this record: two namespaces can\n // otherwise share an operation id and resolve the tx hash to the wrong operation.\n if (record && record.attempts.some((a) => a.id === observation.attemptId)) {\n return clone(record);\n }\n }\n return null;\n }\n\n async update(\n namespace: string,\n id: string,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord> {\n const current = this.#check(namespace, id, expectedVersion, fence);\n const next = applyPatch(current, patch, this.clock.now());\n this.#records.set(compositeKey(namespace, id), next);\n return clone(next);\n }\n\n async appendAttempt(\n namespace: string,\n id: string,\n attempt: AttemptRecord,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord> {\n const current = this.#check(namespace, id, expectedVersion, fence);\n if (current.attempts.some((a) => a.id === attempt.id)) {\n throw new StateError(\n 'INVALID_TRANSITION',\n `attempt '${attempt.id}' already exists`,\n );\n }\n const withAttempt: OperationRecord = {\n ...current,\n attempts: [...current.attempts, clone(attempt)],\n activeAttemptId: attempt.id,\n };\n const next = applyPatch(withAttempt, patch, this.clock.now());\n this.#records.set(compositeKey(namespace, id), next);\n return clone(next);\n }\n\n async getObservation(attemptId: string): Promise<AttemptObservation | null> {\n const observation = this.#observations.get(attemptId);\n return observation ? clone(observation) : null;\n }\n\n async putObservation(\n observation: Omit<AttemptObservation, 'version'>,\n expectedVersion: number | null,\n ): Promise<AttemptObservation> {\n const current = this.#observations.get(observation.attemptId);\n if ((current?.version ?? null) !== expectedVersion) {\n throw new StateError(\n 'VERSION_CONFLICT',\n `observation '${observation.attemptId}' was modified concurrently`,\n );\n }\n const next: AttemptObservation = {\n ...clone(observation),\n version: (current?.version ?? 0) + 1,\n };\n this.#observations.set(observation.attemptId, next);\n return clone(next);\n }\n\n async claimDue(\n namespace: string,\n workerId: string,\n now: number,\n leaseMs: number,\n limit: number,\n ): Promise<OperationRecord[]> {\n if (limit <= 0) return [];\n const due = [...this.#records.values()]\n .filter(\n (r) =>\n r.namespace === namespace &&\n !isTerminal(r.state) &&\n r.nextCheckAt !== undefined &&\n r.nextCheckAt <= now &&\n (!r.claim || r.claim.until <= now),\n )\n .sort(\n (a, b) =>\n (a.nextCheckAt as number) - (b.nextCheckAt as number) ||\n a.createdAt - b.createdAt,\n )\n .slice(0, limit);\n return due.map((record) => {\n this.#claims += 1n;\n const next: OperationRecord = {\n ...record,\n claim: { workerId, token: this.#claims.toString(), until: now + leaseMs },\n version: record.version + 1,\n updatedAt: this.clock.now(),\n };\n this.#records.set(compositeKey(namespace, record.id), next);\n return clone(next);\n });\n }\n\n async releaseClaim(namespace: string, id: string, fence: Fence): Promise<void> {\n const current = this.#require(namespace, id);\n if (current.claim?.token !== fence.claimToken) {\n throw new StateError('FENCING', `claim on operation '${id}' is no longer held`);\n }\n const { claim: _claim, ...rest } = current;\n this.#records.set(compositeKey(namespace, id), {\n ...rest,\n version: current.version + 1,\n updatedAt: this.clock.now(),\n });\n }\n\n async list(filter: OperationFilter): Promise<OperationRecord[]> {\n const found = [...this.#records.values()]\n .filter((r) => matches(r, filter))\n .sort((a, b) => a.createdAt - b.createdAt);\n return clone(\n filter.limit === undefined ? found : found.slice(0, Math.max(0, filter.limit)),\n );\n }\n\n async purge(filter: OperationFilter): Promise<number> {\n // As `list` does: the oldest matches first, and at most `limit` of them.\n const found = [...this.#records.values()]\n .filter((r) => matches(r, filter))\n .sort((a, b) => a.createdAt - b.createdAt);\n let removed = 0;\n for (const record of filter.limit === undefined\n ? found\n : found.slice(0, Math.max(0, filter.limit))) {\n this.#records.delete(compositeKey(record.namespace, record.id));\n this.#keys.delete(compositeKey(record.namespace, record.idempotencyKey));\n for (const attempt of record.attempts) this.#observations.delete(attempt.id);\n removed += 1;\n }\n return removed;\n }\n\n #require(namespace: string, id: string): OperationRecord {\n const record = this.#records.get(compositeKey(namespace, id));\n if (!record) throw new StateError('NOT_FOUND', `operation '${id}' not found`);\n return record;\n }\n\n #check(\n namespace: string,\n id: string,\n expectedVersion: number,\n fence?: Fence,\n ): OperationRecord {\n const record = this.#require(namespace, id);\n if (record.version !== expectedVersion) {\n throw new StateError(\n 'VERSION_CONFLICT',\n `operation '${id}' was modified concurrently (expected v${expectedVersion}, found v${record.version})`,\n );\n }\n if (fence && record.claim?.token !== fence.claimToken) {\n throw new StateError('FENCING', `claim on operation '${id}' is no longer held`);\n }\n return record;\n }\n}\n\nexport function createMemoryStores(clock: Clock = systemClock): Stores {\n return {\n operations: new MemoryOperationStore(clock),\n locks: new MemoryLockManager(clock),\n sequences: new MemorySequenceStore(),\n cursors: new MemoryCursorStore(),\n };\n}\n"]}
@@ -2,7 +2,7 @@ import type { SerializedError } from '../errors/error';
2
2
  import type { FeeEstimateDraft } from '../model/fee';
3
3
  import type { StoredIntent } from '../model/intent';
4
4
  import type { OrderingData } from '../model/ordering';
5
- import type { AttemptRef, Evidence, RawTx, TxState, UnsignedTx } from '../model/transaction';
5
+ import type { AttemptRef, Evidence, RawTx, TxFailure, TxState, UnsignedTx } from '../model/transaction';
6
6
  import type { SignatureBundle, SignerTicket } from '../signing/types';
7
7
  export type OperationState = 'created' | 'prepared' | 'awaiting-signature' | 'signed' | 'submitted' | 'stalled' | 'included' | 'final' | 'failed' | 'expired' | 'abandoned';
8
8
  export declare const TERMINAL_STATES: readonly OperationState[];
@@ -47,6 +47,8 @@ export interface AttemptObservation {
47
47
  readonly lastSeenAt?: number;
48
48
  readonly lastBroadcastAt?: number;
49
49
  readonly reason?: string;
50
+ /** With `state: 'failed'`: what failed (absent: `'reverted'`). */
51
+ readonly failure?: TxFailure;
50
52
  readonly replacedBy?: string;
51
53
  readonly version: number;
52
54
  }
@@ -58,6 +60,11 @@ export interface OperationClaim {
58
60
  }
59
61
  export interface OperationRecord {
60
62
  readonly id: string;
63
+ /**
64
+ * The record format the Operation was stored in (`OPERATION_FORMAT` when it was created).
65
+ * A store keeps it as it keeps every other field. Records of 0.1.0 have none: format 1.
66
+ */
67
+ readonly format: number;
61
68
  readonly namespace: string;
62
69
  readonly idempotencyKey: string;
63
70
  readonly intentHash: string;
@@ -131,8 +138,13 @@ export interface CreateResult {
131
138
  * a field.
132
139
  * - `appendAttempt` is atomic with its accompanying patch, never rewrites a
133
140
  * previously stored `AttemptRecord`, and rejects a duplicate attempt id. Attempt ids
134
- * are globally unique across every Operation and namespace (observations are keyed
135
- * by attempt id alone).
141
+ * are globally unique across every Operation and namespace: the core makes each one
142
+ * random, and observations are keyed by attempt id alone, so a store may also refuse
143
+ * (`INVALID_TRANSITION`) an id that another Operation holds.
144
+ * - Time: `createdAt`, `updatedAt` and claim expiry are stamped with the store's clock,
145
+ * which the core compares with its own (`claimDue` gets the worker's `now`). Give a
146
+ * durable store the container's clock, or a wall clock the processes share, as the
147
+ * memory stores take theirs.
136
148
  * - `claimDue` and `list` are scoped by namespace: they never return a record of another
137
149
  * namespace, even one with the same id.
138
150
  * - `claimDue` returns only non-terminal operations with `nextCheckAt` set and `<=
@@ -61,6 +61,7 @@ const _assertClearableIsWritable = true;
61
61
  exports.DATA_CLASSIFICATION = Object.freeze({
62
62
  operation: Object.freeze({
63
63
  id: 'operational',
64
+ format: 'operational',
64
65
  namespace: 'operational',
65
66
  idempotencyKey: 'sensitive',
66
67
  intentHash: 'operational',
@@ -110,6 +111,7 @@ exports.DATA_CLASSIFICATION = Object.freeze({
110
111
  lastBroadcastAt: 'operational',
111
112
  // A node's refusal or rejection text can carry addresses or amounts.
112
113
  reason: 'sensitive',
114
+ failure: 'operational',
113
115
  replacedBy: 'operational',
114
116
  version: 'operational',
115
117
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/store/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SerializedError } from '../errors/error';\nimport type { FeeEstimateDraft } from '../model/fee';\nimport type { StoredIntent } from '../model/intent';\nimport type { OrderingData } from '../model/ordering';\nimport type {\n AttemptRef,\n Evidence,\n RawTx,\n TxState,\n UnsignedTx,\n} from '../model/transaction';\nimport type { SignatureBundle, SignerTicket } from '../signing/types';\n\nexport type OperationState =\n | 'created'\n | 'prepared'\n | 'awaiting-signature'\n | 'signed'\n | 'submitted'\n | 'stalled'\n | 'included'\n | 'final'\n | 'failed'\n | 'expired'\n | 'abandoned';\n\n// The exported tables in this module are frozen: they are shared by every caller.\n\nexport const TERMINAL_STATES: readonly OperationState[] = Object.freeze([\n 'final',\n 'failed',\n 'expired',\n 'abandoned',\n]);\n\nexport const NON_TERMINAL_STATES: readonly OperationState[] = Object.freeze([\n 'created',\n 'prepared',\n 'awaiting-signature',\n 'signed',\n 'submitted',\n 'stalled',\n 'included',\n]);\n\n/** A private copy taken at load, so `isTerminal` never reads an exported value. */\nconst TERMINAL: ReadonlySet<OperationState> = new Set(TERMINAL_STATES);\n\nexport function isTerminal(state: OperationState): boolean {\n return TERMINAL.has(state);\n}\n\n/** Frozen at Operation creation; later handle or config changes never affect it. */\nexport interface ExecutionContext {\n readonly chain: string;\n readonly network: string;\n readonly library: string;\n /** Provider names, or `inline:<hash>` for inline configs (never secrets). */\n readonly providers: readonly string[];\n readonly indexers: readonly string[];\n readonly wallet: string;\n readonly signer?: string;\n readonly configHash: string;\n}\n\nexport type AttemptPurpose = 'original' | 'replacement' | 'cancel' | 'rebuild';\n\n/** One concrete signed transaction. Immutable after insertion. */\nexport interface AttemptRecord {\n readonly id: string;\n readonly ref: AttemptRef;\n readonly raw: RawTx;\n readonly ordering: OrderingData;\n readonly fee: FeeEstimateDraft;\n readonly unsigned: UnsignedTx;\n readonly purpose: AttemptPurpose;\n readonly supersedes?: string;\n readonly createdAt: number;\n}\n\n/** Mutable, versioned view of what the chain says about an Attempt. */\nexport interface AttemptObservation {\n readonly attemptId: string;\n readonly operationId: string;\n readonly state: TxState;\n readonly evidence: Evidence;\n readonly txHash?: string;\n readonly blockHash?: string;\n readonly blockHeight?: bigint;\n readonly confirmations: number;\n readonly firstSeenAt?: number;\n readonly lastSeenAt?: number;\n readonly lastBroadcastAt?: number;\n readonly reason?: string;\n readonly replacedBy?: string;\n readonly version: number;\n}\n\nexport interface OperationClaim {\n readonly workerId: string;\n /** Strictly increasing per store (decimal string of a bigint). */\n readonly token: string;\n readonly until: number;\n}\n\nexport interface OperationRecord {\n readonly id: string;\n readonly namespace: string;\n readonly idempotencyKey: string;\n readonly intentHash: string;\n readonly context: ExecutionContext;\n readonly kind: 'transfer';\n readonly state: OperationState;\n readonly outcome?: 'executed' | 'cancelled';\n readonly intent: StoredIntent;\n readonly unsigned?: UnsignedTx;\n readonly reservation?: OrderingData;\n readonly signerTickets?: readonly SignerTicket[];\n readonly partialSignatures?: readonly SignatureBundle[];\n readonly attempts: readonly AttemptRecord[];\n readonly activeAttemptId?: string;\n readonly ambiguous?: boolean;\n readonly version: number;\n readonly claim?: OperationClaim;\n readonly error?: SerializedError;\n readonly createdAt: number;\n readonly updatedAt: number;\n readonly nextCheckAt?: number;\n}\n\nexport type NewOperation = Omit<\n OperationRecord,\n 'version' | 'createdAt' | 'updatedAt' | 'claim'\n>;\n\nexport const CLEARABLE_FIELDS = Object.freeze([\n 'outcome',\n 'unsigned',\n 'reservation',\n 'signerTickets',\n 'partialSignatures',\n 'activeAttemptId',\n 'ambiguous',\n 'error',\n 'nextCheckAt',\n] as const);\n\n/** The only fields an `OperationPatch` may `clear`; never store-owned state or `state`. */\nexport type ClearableField = (typeof CLEARABLE_FIELDS)[number];\n\nexport interface OperationPatch {\n readonly state?: OperationState;\n readonly outcome?: 'executed' | 'cancelled';\n readonly unsigned?: UnsignedTx;\n readonly reservation?: OrderingData;\n readonly signerTickets?: readonly SignerTicket[];\n readonly partialSignatures?: readonly SignatureBundle[];\n readonly activeAttemptId?: string;\n readonly ambiguous?: boolean;\n readonly error?: SerializedError;\n readonly nextCheckAt?: number;\n /** Fields to delete; applied after the values above. */\n readonly clear?: readonly ClearableField[];\n}\n\n/** Runtime whitelist of writable `OperationPatch` fields (everything but `clear`). */\nexport const OPERATION_PATCH_KEYS = Object.freeze([\n 'state',\n 'outcome',\n 'unsigned',\n 'reservation',\n 'signerTickets',\n 'partialSignatures',\n 'activeAttemptId',\n 'ambiguous',\n 'error',\n 'nextCheckAt',\n] as const);\n\n// Compile-time check, in both directions: OPERATION_PATCH_KEYS must list exactly the\n// writable (non-'clear') fields of OperationPatch, so a future field added to one but\n// not the other fails the build instead of silently opening a hole in `update`.\ntype PatchFieldKeys = Exclude<keyof OperationPatch, 'clear'>;\ntype ListedPatchKeys = (typeof OPERATION_PATCH_KEYS)[number];\ntype AssertNoMissingPatchKeys = PatchFieldKeys extends ListedPatchKeys ? true : never;\ntype AssertNoExtraPatchKeys = ListedPatchKeys extends PatchFieldKeys ? true : never;\nconst _assertNoMissingPatchKeys: AssertNoMissingPatchKeys = true;\nconst _assertNoExtraPatchKeys: AssertNoExtraPatchKeys = true;\n\n// Compile-time check: every CLEARABLE_FIELDS entry must be a writable patch field\n// other than 'state' (which is written but never cleared). Fails the build if\n// 'attempts', 'claim', or any other non-writable field is ever added to\n// CLEARABLE_FIELDS, since that would let `clear` reach into store-owned state.\ntype AssertClearableIsWritable =\n ClearableField extends Exclude<ListedPatchKeys, 'state'> ? true : never;\nconst _assertClearableIsWritable: AssertClearableIsWritable = true;\n\nexport interface Fence {\n readonly claimToken: string;\n}\n\nexport interface OperationFilter {\n readonly namespace: string;\n readonly chain?: string;\n readonly network?: string;\n readonly from?: string;\n readonly states?: readonly OperationState[];\n readonly limit?: number;\n}\n\nexport interface CreateResult {\n readonly created: boolean;\n readonly record: OperationRecord;\n}\n\n/**\n * Durable Operation storage. Required guarantees (verified by the contract suite),\n * binding on every implementation (memory, Redis, Postgres, ...):\n * - `create` is create-if-absent on `(namespace, idempotencyKey)`: a second `create`\n * for an existing key returns the stored record unchanged, regardless of\n * `intentHash` — this store never performs the idempotency-conflict check itself;\n * the calling engine does.\n * - `update`/`appendAttempt` are version-checked (compare-and-set on\n * `expectedVersion`) and optionally fenced by a claim token. Both are restricted to\n * the same runtime patch whitelist (`OPERATION_PATCH_KEYS`), and `clear` may only\n * name `CLEARABLE_FIELDS`; any other key anywhere in the patch rejects the whole\n * call with `INVALID_TRANSITION` before any mutation. An explicit `undefined` value\n * for a writable field is a no-op (the stored value survives); only `clear` removes\n * a field.\n * - `appendAttempt` is atomic with its accompanying patch, never rewrites a\n * previously stored `AttemptRecord`, and rejects a duplicate attempt id. Attempt ids\n * are globally unique across every Operation and namespace (observations are keyed\n * by attempt id alone).\n * - `claimDue` and `list` are scoped by namespace: they never return a record of another\n * namespace, even one with the same id.\n * - `claimDue` returns only non-terminal operations with `nextCheckAt` set and `<=\n * now`, ordered by `(nextCheckAt, createdAt)` with a stable tie-break for equal\n * values, and returns `[]` for `limit <= 0`. A claimed Operation is excluded until\n * its `claim.until <= now`; then another worker may take it over. Claim tokens are\n * strictly increasing per store; fenced writes are rejected after a takeover.\n * - `list` returns matches in creation order.\n * - `ClearableField` is restricted on purpose: `clear` can never remove store-owned or\n * identity fields (`attempts`, `claim`, `version`, `id`, ...) or `state`. An\n * implementation reads the caller's `clear` list once, so the list it validates is\n * the list it applies.\n * - A key set to `undefined`, in an observation or a patch, is never persisted as a value\n * such as `null`. In a patch, the stored field keeps its value. In an\n * observation, which replaces the whole record, the field reads back `undefined` (never\n * `null`).\n */\nexport interface OperationStore {\n create(operation: NewOperation): Promise<CreateResult>;\n get(namespace: string, id: string): Promise<OperationRecord | null>;\n getByKey(namespace: string, idempotencyKey: string): Promise<OperationRecord | null>;\n /**\n * Finds by an Attempt ref id or by an observed canonical tx hash. The engine's\n * AttemptRef guard, which refuses an Attempt whose ref another Operation of the\n * namespace holds, relies on this read being read-your-writes consistent across every\n * process that shares the store: it must see any `appendAttempt` that another store\n * instance committed before it was called (no read replica, no eventually consistent\n * index).\n */\n findByRef(namespace: string, refOrTxHash: string): Promise<OperationRecord | null>;\n /**\n * Compare-and-set on `expectedVersion`, optionally fenced. Every successful update bumps\n * version, even when the patch changes no field (the engine fences stale writers\n * with such a no-effect update).\n */\n update(\n namespace: string,\n id: string,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord>;\n appendAttempt(\n namespace: string,\n id: string,\n attempt: AttemptRecord,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord>;\n getObservation(attemptId: string): Promise<AttemptObservation | null>;\n /**\n * Stores `observation` as the whole new record, version-checked. An optional field left\n * out of it or set to `undefined` (a cleared `reason`, `blockHash` or `blockHeight`)\n * reads back `undefined` (never `null`) afterwards. A store must replace the record, never\n * merge fields into the old one. The monitor relies on this to clear stale values.\n */\n putObservation(\n observation: Omit<AttemptObservation, 'version'>,\n expectedVersion: number | null,\n ): Promise<AttemptObservation>;\n /** Claims non-terminal operations whose `nextCheckAt` is set and <= now (unscheduled ones are never due). */\n claimDue(\n namespace: string,\n workerId: string,\n now: number,\n leaseMs: number,\n limit: number,\n ): Promise<OperationRecord[]>;\n releaseClaim(namespace: string, id: string, fence: Fence): Promise<void>;\n list(filter: OperationFilter): Promise<OperationRecord[]>;\n purge?(filter: OperationFilter): Promise<number>;\n}\n\nexport interface Lease {\n readonly key: string;\n readonly owner: string;\n /** Strictly increasing per key across all acquisitions. */\n readonly token: bigint;\n readonly expiresAt: number;\n}\n\nexport interface LockManager {\n acquire(key: string, owner: string, ttlMs: number): Promise<Lease | null>;\n renew(lease: Lease, ttlMs: number): Promise<Lease | null>;\n release(lease: Lease): Promise<void>;\n}\n\nexport interface SequenceState {\n readonly next: bigint;\n readonly released: readonly bigint[];\n readonly fence: bigint;\n readonly version: number;\n}\n\nexport interface SequenceStore {\n get(key: string): Promise<SequenceState | null>;\n /** Rejects on version mismatch (`VERSION_CONFLICT`) or when `fence` < stored fence (`FENCING`). */\n put(\n key: string,\n state: Omit<SequenceState, 'version'>,\n expectedVersion: number | null,\n ): Promise<void>;\n}\n\nexport interface ScanCursor {\n readonly height: bigint;\n readonly hash: string;\n readonly recent: readonly { readonly height: bigint; readonly hash: string }[];\n}\n\nexport interface StoredCursor {\n readonly cursor: ScanCursor;\n readonly version: number;\n}\n\nexport interface CursorStore {\n get(key: string): Promise<StoredCursor | null>;\n /** Returns the new version. */\n put(key: string, cursor: ScanCursor, expectedVersion: number | null): Promise<number>;\n}\n\nexport interface Stores {\n readonly operations: OperationStore;\n readonly locks: LockManager;\n readonly sequences: SequenceStore;\n readonly cursors: CursorStore;\n}\n\nexport type DataClass =\n 'secret' | 'sensitive' | 'sensitive-until-broadcast' | 'operational';\n\n/**\n * Field classification so backing stores can apply encryption and retention per field.\n *\n * `operation.reservation` and `attempt.ordering` are `sensitive` because an `inputs`\n * ordering lists UTXO outpoints, which tie a wallet to its coins. A nonce or seqno value\n * on its own is an operational identifier, like an Operation id: the `nonce.allocated`\n * and `nonce.gap` events carry it, and logs may too.\n */\nexport const DATA_CLASSIFICATION: {\n readonly operation: Readonly<Record<keyof OperationRecord, DataClass>>;\n readonly attempt: Readonly<Record<keyof AttemptRecord, DataClass>>;\n readonly observation: Readonly<Record<keyof AttemptObservation, DataClass>>;\n} = Object.freeze({\n operation: Object.freeze({\n id: 'operational',\n namespace: 'operational',\n idempotencyKey: 'sensitive',\n intentHash: 'operational',\n context: 'sensitive',\n kind: 'operational',\n state: 'operational',\n outcome: 'operational',\n intent: 'sensitive',\n unsigned: 'sensitive',\n // `inputs` orderings carry UTXO outpoints; nonce and seqno values are operational.\n reservation: 'sensitive',\n signerTickets: 'sensitive',\n partialSignatures: 'sensitive',\n attempts: 'sensitive-until-broadcast',\n activeAttemptId: 'operational',\n ambiguous: 'operational',\n version: 'operational',\n claim: 'operational',\n error: 'sensitive',\n createdAt: 'operational',\n updatedAt: 'operational',\n nextCheckAt: 'operational',\n }),\n attempt: Object.freeze({\n id: 'operational',\n ref: 'sensitive-until-broadcast',\n raw: 'sensitive-until-broadcast',\n // As `operation.reservation`.\n ordering: 'sensitive',\n fee: 'sensitive',\n unsigned: 'sensitive',\n purpose: 'operational',\n supersedes: 'operational',\n createdAt: 'operational',\n }),\n observation: Object.freeze({\n attemptId: 'operational',\n operationId: 'operational',\n state: 'operational',\n evidence: 'operational',\n txHash: 'operational',\n blockHash: 'operational',\n blockHeight: 'operational',\n confirmations: 'operational',\n firstSeenAt: 'operational',\n lastSeenAt: 'operational',\n lastBroadcastAt: 'operational',\n // A node's refusal or rejection text can carry addresses or amounts.\n reason: 'sensitive',\n replacedBy: 'operational',\n version: 'operational',\n }),\n});\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/store/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SerializedError } from '../errors/error';\nimport type { FeeEstimateDraft } from '../model/fee';\nimport type { StoredIntent } from '../model/intent';\nimport type { OrderingData } from '../model/ordering';\nimport type {\n AttemptRef,\n Evidence,\n RawTx,\n TxFailure,\n TxState,\n UnsignedTx,\n} from '../model/transaction';\nimport type { SignatureBundle, SignerTicket } from '../signing/types';\n\nexport type OperationState =\n | 'created'\n | 'prepared'\n | 'awaiting-signature'\n | 'signed'\n | 'submitted'\n | 'stalled'\n | 'included'\n | 'final'\n | 'failed'\n | 'expired'\n | 'abandoned';\n\n// The exported tables in this module are frozen: they are shared by every caller.\n\nexport const TERMINAL_STATES: readonly OperationState[] = Object.freeze([\n 'final',\n 'failed',\n 'expired',\n 'abandoned',\n]);\n\nexport const NON_TERMINAL_STATES: readonly OperationState[] = Object.freeze([\n 'created',\n 'prepared',\n 'awaiting-signature',\n 'signed',\n 'submitted',\n 'stalled',\n 'included',\n]);\n\n/** A private copy taken at load, so `isTerminal` never reads an exported value. */\nconst TERMINAL: ReadonlySet<OperationState> = new Set(TERMINAL_STATES);\n\nexport function isTerminal(state: OperationState): boolean {\n return TERMINAL.has(state);\n}\n\n/** Frozen at Operation creation; later handle or config changes never affect it. */\nexport interface ExecutionContext {\n readonly chain: string;\n readonly network: string;\n readonly library: string;\n /** Provider names, or `inline:<hash>` for inline configs (never secrets). */\n readonly providers: readonly string[];\n readonly indexers: readonly string[];\n readonly wallet: string;\n readonly signer?: string;\n readonly configHash: string;\n}\n\nexport type AttemptPurpose = 'original' | 'replacement' | 'cancel' | 'rebuild';\n\n/** One concrete signed transaction. Immutable after insertion. */\nexport interface AttemptRecord {\n readonly id: string;\n readonly ref: AttemptRef;\n readonly raw: RawTx;\n readonly ordering: OrderingData;\n readonly fee: FeeEstimateDraft;\n readonly unsigned: UnsignedTx;\n readonly purpose: AttemptPurpose;\n readonly supersedes?: string;\n readonly createdAt: number;\n}\n\n/** Mutable, versioned view of what the chain says about an Attempt. */\nexport interface AttemptObservation {\n readonly attemptId: string;\n readonly operationId: string;\n readonly state: TxState;\n readonly evidence: Evidence;\n readonly txHash?: string;\n readonly blockHash?: string;\n readonly blockHeight?: bigint;\n readonly confirmations: number;\n readonly firstSeenAt?: number;\n readonly lastSeenAt?: number;\n readonly lastBroadcastAt?: number;\n readonly reason?: string;\n /** With `state: 'failed'`: what failed (absent: `'reverted'`). */\n readonly failure?: TxFailure;\n readonly replacedBy?: string;\n readonly version: number;\n}\n\nexport interface OperationClaim {\n readonly workerId: string;\n /** Strictly increasing per store (decimal string of a bigint). */\n readonly token: string;\n readonly until: number;\n}\n\nexport interface OperationRecord {\n readonly id: string;\n /**\n * The record format the Operation was stored in (`OPERATION_FORMAT` when it was created).\n * A store keeps it as it keeps every other field. Records of 0.1.0 have none: format 1.\n */\n readonly format: number;\n readonly namespace: string;\n readonly idempotencyKey: string;\n readonly intentHash: string;\n readonly context: ExecutionContext;\n readonly kind: 'transfer';\n readonly state: OperationState;\n readonly outcome?: 'executed' | 'cancelled';\n readonly intent: StoredIntent;\n readonly unsigned?: UnsignedTx;\n readonly reservation?: OrderingData;\n readonly signerTickets?: readonly SignerTicket[];\n readonly partialSignatures?: readonly SignatureBundle[];\n readonly attempts: readonly AttemptRecord[];\n readonly activeAttemptId?: string;\n readonly ambiguous?: boolean;\n readonly version: number;\n readonly claim?: OperationClaim;\n readonly error?: SerializedError;\n readonly createdAt: number;\n readonly updatedAt: number;\n readonly nextCheckAt?: number;\n}\n\nexport type NewOperation = Omit<\n OperationRecord,\n 'version' | 'createdAt' | 'updatedAt' | 'claim'\n>;\n\nexport const CLEARABLE_FIELDS = Object.freeze([\n 'outcome',\n 'unsigned',\n 'reservation',\n 'signerTickets',\n 'partialSignatures',\n 'activeAttemptId',\n 'ambiguous',\n 'error',\n 'nextCheckAt',\n] as const);\n\n/** The only fields an `OperationPatch` may `clear`; never store-owned state or `state`. */\nexport type ClearableField = (typeof CLEARABLE_FIELDS)[number];\n\nexport interface OperationPatch {\n readonly state?: OperationState;\n readonly outcome?: 'executed' | 'cancelled';\n readonly unsigned?: UnsignedTx;\n readonly reservation?: OrderingData;\n readonly signerTickets?: readonly SignerTicket[];\n readonly partialSignatures?: readonly SignatureBundle[];\n readonly activeAttemptId?: string;\n readonly ambiguous?: boolean;\n readonly error?: SerializedError;\n readonly nextCheckAt?: number;\n /** Fields to delete; applied after the values above. */\n readonly clear?: readonly ClearableField[];\n}\n\n/** Runtime whitelist of writable `OperationPatch` fields (everything but `clear`). */\nexport const OPERATION_PATCH_KEYS = Object.freeze([\n 'state',\n 'outcome',\n 'unsigned',\n 'reservation',\n 'signerTickets',\n 'partialSignatures',\n 'activeAttemptId',\n 'ambiguous',\n 'error',\n 'nextCheckAt',\n] as const);\n\n// Compile-time check, in both directions: OPERATION_PATCH_KEYS must list exactly the\n// writable (non-'clear') fields of OperationPatch, so a future field added to one but\n// not the other fails the build instead of silently opening a hole in `update`.\ntype PatchFieldKeys = Exclude<keyof OperationPatch, 'clear'>;\ntype ListedPatchKeys = (typeof OPERATION_PATCH_KEYS)[number];\ntype AssertNoMissingPatchKeys = PatchFieldKeys extends ListedPatchKeys ? true : never;\ntype AssertNoExtraPatchKeys = ListedPatchKeys extends PatchFieldKeys ? true : never;\nconst _assertNoMissingPatchKeys: AssertNoMissingPatchKeys = true;\nconst _assertNoExtraPatchKeys: AssertNoExtraPatchKeys = true;\n\n// Compile-time check: every CLEARABLE_FIELDS entry must be a writable patch field\n// other than 'state' (which is written but never cleared). Fails the build if\n// 'attempts', 'claim', or any other non-writable field is ever added to\n// CLEARABLE_FIELDS, since that would let `clear` reach into store-owned state.\ntype AssertClearableIsWritable =\n ClearableField extends Exclude<ListedPatchKeys, 'state'> ? true : never;\nconst _assertClearableIsWritable: AssertClearableIsWritable = true;\n\nexport interface Fence {\n readonly claimToken: string;\n}\n\nexport interface OperationFilter {\n readonly namespace: string;\n readonly chain?: string;\n readonly network?: string;\n readonly from?: string;\n readonly states?: readonly OperationState[];\n readonly limit?: number;\n}\n\nexport interface CreateResult {\n readonly created: boolean;\n readonly record: OperationRecord;\n}\n\n/**\n * Durable Operation storage. Required guarantees (verified by the contract suite),\n * binding on every implementation (memory, Redis, Postgres, ...):\n * - `create` is create-if-absent on `(namespace, idempotencyKey)`: a second `create`\n * for an existing key returns the stored record unchanged, regardless of\n * `intentHash` — this store never performs the idempotency-conflict check itself;\n * the calling engine does.\n * - `update`/`appendAttempt` are version-checked (compare-and-set on\n * `expectedVersion`) and optionally fenced by a claim token. Both are restricted to\n * the same runtime patch whitelist (`OPERATION_PATCH_KEYS`), and `clear` may only\n * name `CLEARABLE_FIELDS`; any other key anywhere in the patch rejects the whole\n * call with `INVALID_TRANSITION` before any mutation. An explicit `undefined` value\n * for a writable field is a no-op (the stored value survives); only `clear` removes\n * a field.\n * - `appendAttempt` is atomic with its accompanying patch, never rewrites a\n * previously stored `AttemptRecord`, and rejects a duplicate attempt id. Attempt ids\n * are globally unique across every Operation and namespace: the core makes each one\n * random, and observations are keyed by attempt id alone, so a store may also refuse\n * (`INVALID_TRANSITION`) an id that another Operation holds.\n * - Time: `createdAt`, `updatedAt` and claim expiry are stamped with the store's clock,\n * which the core compares with its own (`claimDue` gets the worker's `now`). Give a\n * durable store the container's clock, or a wall clock the processes share, as the\n * memory stores take theirs.\n * - `claimDue` and `list` are scoped by namespace: they never return a record of another\n * namespace, even one with the same id.\n * - `claimDue` returns only non-terminal operations with `nextCheckAt` set and `<=\n * now`, ordered by `(nextCheckAt, createdAt)` with a stable tie-break for equal\n * values, and returns `[]` for `limit <= 0`. A claimed Operation is excluded until\n * its `claim.until <= now`; then another worker may take it over. Claim tokens are\n * strictly increasing per store; fenced writes are rejected after a takeover.\n * - `list` returns matches in creation order.\n * - `ClearableField` is restricted on purpose: `clear` can never remove store-owned or\n * identity fields (`attempts`, `claim`, `version`, `id`, ...) or `state`. An\n * implementation reads the caller's `clear` list once, so the list it validates is\n * the list it applies.\n * - A key set to `undefined`, in an observation or a patch, is never persisted as a value\n * such as `null`. In a patch, the stored field keeps its value. In an\n * observation, which replaces the whole record, the field reads back `undefined` (never\n * `null`).\n */\nexport interface OperationStore {\n create(operation: NewOperation): Promise<CreateResult>;\n get(namespace: string, id: string): Promise<OperationRecord | null>;\n getByKey(namespace: string, idempotencyKey: string): Promise<OperationRecord | null>;\n /**\n * Finds by an Attempt ref id or by an observed canonical tx hash. The engine's\n * AttemptRef guard, which refuses an Attempt whose ref another Operation of the\n * namespace holds, relies on this read being read-your-writes consistent across every\n * process that shares the store: it must see any `appendAttempt` that another store\n * instance committed before it was called (no read replica, no eventually consistent\n * index).\n */\n findByRef(namespace: string, refOrTxHash: string): Promise<OperationRecord | null>;\n /**\n * Compare-and-set on `expectedVersion`, optionally fenced. Every successful update bumps\n * version, even when the patch changes no field (the engine fences stale writers\n * with such a no-effect update).\n */\n update(\n namespace: string,\n id: string,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord>;\n appendAttempt(\n namespace: string,\n id: string,\n attempt: AttemptRecord,\n patch: OperationPatch,\n expectedVersion: number,\n fence?: Fence,\n ): Promise<OperationRecord>;\n getObservation(attemptId: string): Promise<AttemptObservation | null>;\n /**\n * Stores `observation` as the whole new record, version-checked. An optional field left\n * out of it or set to `undefined` (a cleared `reason`, `blockHash` or `blockHeight`)\n * reads back `undefined` (never `null`) afterwards. A store must replace the record, never\n * merge fields into the old one. The monitor relies on this to clear stale values.\n */\n putObservation(\n observation: Omit<AttemptObservation, 'version'>,\n expectedVersion: number | null,\n ): Promise<AttemptObservation>;\n /** Claims non-terminal operations whose `nextCheckAt` is set and <= now (unscheduled ones are never due). */\n claimDue(\n namespace: string,\n workerId: string,\n now: number,\n leaseMs: number,\n limit: number,\n ): Promise<OperationRecord[]>;\n releaseClaim(namespace: string, id: string, fence: Fence): Promise<void>;\n list(filter: OperationFilter): Promise<OperationRecord[]>;\n purge?(filter: OperationFilter): Promise<number>;\n}\n\nexport interface Lease {\n readonly key: string;\n readonly owner: string;\n /** Strictly increasing per key across all acquisitions. */\n readonly token: bigint;\n readonly expiresAt: number;\n}\n\nexport interface LockManager {\n acquire(key: string, owner: string, ttlMs: number): Promise<Lease | null>;\n renew(lease: Lease, ttlMs: number): Promise<Lease | null>;\n release(lease: Lease): Promise<void>;\n}\n\nexport interface SequenceState {\n readonly next: bigint;\n readonly released: readonly bigint[];\n readonly fence: bigint;\n readonly version: number;\n}\n\nexport interface SequenceStore {\n get(key: string): Promise<SequenceState | null>;\n /** Rejects on version mismatch (`VERSION_CONFLICT`) or when `fence` < stored fence (`FENCING`). */\n put(\n key: string,\n state: Omit<SequenceState, 'version'>,\n expectedVersion: number | null,\n ): Promise<void>;\n}\n\nexport interface ScanCursor {\n readonly height: bigint;\n readonly hash: string;\n readonly recent: readonly { readonly height: bigint; readonly hash: string }[];\n}\n\nexport interface StoredCursor {\n readonly cursor: ScanCursor;\n readonly version: number;\n}\n\nexport interface CursorStore {\n get(key: string): Promise<StoredCursor | null>;\n /** Returns the new version. */\n put(key: string, cursor: ScanCursor, expectedVersion: number | null): Promise<number>;\n}\n\nexport interface Stores {\n readonly operations: OperationStore;\n readonly locks: LockManager;\n readonly sequences: SequenceStore;\n readonly cursors: CursorStore;\n}\n\nexport type DataClass =\n 'secret' | 'sensitive' | 'sensitive-until-broadcast' | 'operational';\n\n/**\n * Field classification so backing stores can apply encryption and retention per field.\n *\n * `operation.reservation` and `attempt.ordering` are `sensitive` because an `inputs`\n * ordering lists UTXO outpoints, which tie a wallet to its coins. A nonce or seqno value\n * on its own is an operational identifier, like an Operation id: the `nonce.allocated`\n * and `nonce.gap` events carry it, and logs may too.\n */\nexport const DATA_CLASSIFICATION: {\n readonly operation: Readonly<Record<keyof OperationRecord, DataClass>>;\n readonly attempt: Readonly<Record<keyof AttemptRecord, DataClass>>;\n readonly observation: Readonly<Record<keyof AttemptObservation, DataClass>>;\n} = Object.freeze({\n operation: Object.freeze({\n id: 'operational',\n format: 'operational',\n namespace: 'operational',\n idempotencyKey: 'sensitive',\n intentHash: 'operational',\n context: 'sensitive',\n kind: 'operational',\n state: 'operational',\n outcome: 'operational',\n intent: 'sensitive',\n unsigned: 'sensitive',\n // `inputs` orderings carry UTXO outpoints; nonce and seqno values are operational.\n reservation: 'sensitive',\n signerTickets: 'sensitive',\n partialSignatures: 'sensitive',\n attempts: 'sensitive-until-broadcast',\n activeAttemptId: 'operational',\n ambiguous: 'operational',\n version: 'operational',\n claim: 'operational',\n error: 'sensitive',\n createdAt: 'operational',\n updatedAt: 'operational',\n nextCheckAt: 'operational',\n }),\n attempt: Object.freeze({\n id: 'operational',\n ref: 'sensitive-until-broadcast',\n raw: 'sensitive-until-broadcast',\n // As `operation.reservation`.\n ordering: 'sensitive',\n fee: 'sensitive',\n unsigned: 'sensitive',\n purpose: 'operational',\n supersedes: 'operational',\n createdAt: 'operational',\n }),\n observation: Object.freeze({\n attemptId: 'operational',\n operationId: 'operational',\n state: 'operational',\n evidence: 'operational',\n txHash: 'operational',\n blockHash: 'operational',\n blockHeight: 'operational',\n confirmations: 'operational',\n firstSeenAt: 'operational',\n lastSeenAt: 'operational',\n lastBroadcastAt: 'operational',\n // A node's refusal or rejection text can carry addresses or amounts.\n reason: 'sensitive',\n failure: 'operational',\n replacedBy: 'operational',\n version: 'operational',\n }),\n});\n"]}
@@ -17,6 +17,7 @@ const DEFAULTS = {
17
17
  timeoutMs: 15_000,
18
18
  maxLagBlocks: 5,
19
19
  proofQuorum: 2,
20
+ degradedProofs: false,
20
21
  failureThreshold: 5,
21
22
  openMs: 30_000,
22
23
  healthIntervalMs: 15_000,
@@ -460,9 +461,11 @@ class HttpTransport {
460
461
  ? 'half-open'
461
462
  : this.#lagging(e)
462
463
  ? 'lagging'
463
- : e.height !== undefined || e.identity === 'ok'
464
- ? 'healthy'
465
- : 'unknown';
464
+ : // With a height probe, an endpoint is healthy only once its height is
465
+ // known: a confirmed identity alone says nothing about its head.
466
+ e.height !== undefined || (e.identity === 'ok' && !this.#probes.height)
467
+ ? 'healthy'
468
+ : 'unknown';
466
469
  return {
467
470
  id: e.id,
468
471
  kind: e.kind,
@@ -585,11 +588,13 @@ class HttpTransport {
585
588
  // retryable error instead of resolving from fewer endpoints than needed.
586
589
  // Nor may lag, an unknown height, an unconfirmed identity or an open
587
590
  // breaker shrink a proof quorum (#quorumCandidates), so one liar is never alone.
588
- // Known gap: the floor of 1 lets the one endpoint still counted prove alone once
589
- // every other endpoint is out of the count (a liar, if the honest ones are down for
590
- // three health intervals). It favours liveness; raising it changes every family's
591
- // proofs.
592
- const needed = Math.max(1, Math.min(requested, counted.size));
591
+ // Nor, by default, may an endpoint that dropped out of the count (down for three
592
+ // health intervals): a proof waits for it, so the one endpoint left, which may be a
593
+ // liar, never proves alone. `degradedProofs` trades that for liveness.
594
+ const available = proof && !this.#opts.degradedProofs
595
+ ? this.#endpoints.filter((e) => e.identity !== 'mismatch').length
596
+ : counted.size;
597
+ const needed = Math.max(1, Math.min(requested, available));
593
598
  const eligible = () => {
594
599
  const now = this.#clock.now();
595
600
  return this.#quorumCandidates(purpose, proof).inRange.filter((e) => e.notBefore <= now && counted.has(e));
@@ -1031,7 +1036,7 @@ class HttpTransport {
1031
1036
  : '';
1032
1037
  const hasBody = bodyText !== undefined;
1033
1038
  const mode = request.responseType ?? 'json';
1034
- const { text, json } = await this.#exchange(endpoint, routeLabel(request.method, request.route), {
1039
+ const { text, json, bytes } = await this.#exchange(endpoint, routeLabel(request.method, request.route), {
1035
1040
  method: request.method,
1036
1041
  url: joinUrl(endpoint.url, request.path, query),
1037
1042
  headers: {
@@ -1047,7 +1052,7 @@ class HttpTransport {
1047
1052
  // Error texts name the route template, as events do, never the concrete path, which
1048
1053
  // carries addresses and transaction ids: error texts end up in logs and alerting.
1049
1054
  routeLabel(request.method, request.route), exactIntegers);
1050
- return (mode === 'text' ? text : json);
1055
+ return (mode === 'text' ? text : mode === 'bytes' ? bytes : json);
1051
1056
  }
1052
1057
  /**
1053
1058
  * The probes' single-attempt calls. Each request first takes a token from the
@@ -1092,11 +1097,13 @@ class HttpTransport {
1092
1097
  redirect: 'error',
1093
1098
  });
1094
1099
  const bytes = await this.#body(endpoint, response);
1095
- const text = new TextDecoder().decode(bytes);
1100
+ // A binary 2xx answer is returned as it came: UTF-8 decoding would corrupt it (and
1101
+ // decode a whole block for nothing). An error body is text whatever the mode.
1102
+ const text = mode === 'bytes' && response.ok ? '' : new TextDecoder().decode(bytes);
1096
1103
  this.#throwForStatus(endpoint, response);
1097
1104
  const context = this.#context(endpoint);
1098
1105
  let json;
1099
- if (mode !== 'text' || !response.ok) {
1106
+ if ((mode !== 'text' && mode !== 'bytes') || !response.ok) {
1100
1107
  try {
1101
1108
  json = text.length > 0 ? (0, json_1.parseJson)(text, exactIntegers) : null;
1102
1109
  }
@@ -1131,7 +1138,7 @@ class HttpTransport {
1131
1138
  }
1132
1139
  }
1133
1140
  this.#emitResponse(endpoint, label, started, bytes.byteLength);
1134
- return { text, json };
1141
+ return { text, json, bytes };
1135
1142
  }
1136
1143
  /**
1137
1144
  * An answer's body, at most `maxResponseBytes`. A longer one, by its declared length
@@ -1203,6 +1210,7 @@ class HttpTransport {
1203
1210
  // Credentials were rejected before any processing — excluded from ambiguity.
1204
1211
  throw new error_1.ProviderError('PROVIDER_MISCONFIGURED', `endpoint rejected the credentials (HTTP ${status})`, {
1205
1212
  context,
1213
+ details: { httpStatus: status },
1206
1214
  retryable: true,
1207
1215
  });
1208
1216
  }