@solana/codecs-data-structures 8.4.0 → 8.5.0-canary-20261006090716

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.
package/README.md CHANGED
@@ -71,6 +71,8 @@ Because the sentinel is only compared at the start of the next item slot, two in
71
71
  1. **No item may _begin_ with the sentinel's bytes.** A valid item that starts with the sentinel is indistinguishable from the terminator, so decoding would stop early at that item. The sentinel may still appear _inside_ an item, just never at its start. For example, a single `0xff` byte is a poor sentinel for a list of public keys, since roughly one key in 256 starts with `0xff`; a sentinel as wide as an item (such as the all-zero public key) avoids this because only that exact key can match the terminator.
72
72
  2. **Under `"optional"` and `"omitted"`, the sentinel must be no wider than the smallest possible item.** Otherwise a trailing region shorter than the sentinel but large enough to hold a valid item would be skipped, since decoding stops as soon as fewer bytes than the sentinel remain. This cannot arise under `"required"` because a terminator is always written.
73
73
 
74
+ Unlike the invariants above, one condition is enforced by the codec: the sentinel and `"remainder"` strategies reject zero-byte item codecs (e.g. `getStructCodec([])`), on both the encoding and decoding sides, since such items can never advance past a sentinel boundary or the end of the byte array. Creating one throws `SOLANA_ERROR__CODECS__UNEXPECTED_ZERO_FIXED_SIZE_ITEM_FOR_ARRAY_LIKE_SIZE_STRATEGY`. Zero-byte items remain valid when the collection size is explicit, such as a size prefix or a fixed number of items.
75
+
74
76
  When the size is stored as a prefix, decoding an exhausted byte array yields an empty array instead of failing. This allows arrays to be appended to existing data layouts without breaking the decoding of older data. Use the `requireSizePrefix` option to throw instead.
75
77
 
76
78
  ```ts
@@ -34,7 +34,9 @@ function getMaxSize(codec) {
34
34
  function getArrayEncoder(item, config = {}) {
35
35
  const size = config.size ?? codecsNumbers.getU32Encoder();
36
36
  assertValidSize(size);
37
- const fixedSize = computeArrayLikeCodecSize(size, getFixedSize(item));
37
+ const itemSize = getFixedSize(item);
38
+ assertItemCodecSupportsSizeStrategy(config.description ?? "array", size, itemSize);
39
+ const fixedSize = computeArrayLikeCodecSize(size, itemSize);
38
40
  const maxSize = computeArrayLikeCodecSize(size, getMaxSize(item)) ?? void 0;
39
41
  return codecsCore.createEncoder({
40
42
  ...fixedSize !== null ? { fixedSize } : {
@@ -67,6 +69,7 @@ function getArrayDecoder(item, config = {}) {
67
69
  const size = config.size ?? codecsNumbers.getU32Decoder();
68
70
  assertValidSize(size);
69
71
  const itemSize = getFixedSize(item);
72
+ assertItemCodecSupportsSizeStrategy(config.description ?? "array", size, itemSize);
70
73
  const fixedSize = computeArrayLikeCodecSize(size, itemSize);
71
74
  const maxSize = computeArrayLikeCodecSize(size, getMaxSize(item)) ?? void 0;
72
75
  return codecsCore.createDecoder({
@@ -137,6 +140,15 @@ function assertValidSize(size) {
137
140
  throw new errors.SolanaError(errors.SOLANA_ERROR__CODECS__SENTINEL_MUST_NOT_BE_EMPTY);
138
141
  }
139
142
  }
143
+ function assertItemCodecSupportsSizeStrategy(codecDescription, size, itemFixedSize) {
144
+ if (itemFixedSize !== 0) return;
145
+ if (size === "remainder" || isSentinelSize(size)) {
146
+ throw new errors.SolanaError(errors.SOLANA_ERROR__CODECS__UNEXPECTED_ZERO_FIXED_SIZE_ITEM_FOR_ARRAY_LIKE_SIZE_STRATEGY, {
147
+ codecDescription,
148
+ sizeStrategy: size === "remainder" ? "remainder" : "sentinel"
149
+ });
150
+ }
151
+ }
140
152
  function hexBytes(bytes) {
141
153
  return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
142
154
  }