@gscdump/lakehouse 7.7.0 → 7.8.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 (37) hide show
  1. package/README.md +37 -2
  2. package/dist/_virtual/node_modules/.pnpm/hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/gzip.mjs +196 -0
  3. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/column.mjs +1 -1
  4. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/datapage.mjs +1 -1
  5. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/index.mjs +1 -1
  6. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/indexes.mjs +1 -1
  7. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/metadata.mjs +1 -1
  8. package/dist/_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs +1 -1
  9. package/dist/_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/avro/avro.read.mjs +3 -3
  10. package/dist/_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/fetch.mjs +5 -39
  11. package/dist/_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/read.mjs +2 -2
  12. package/dist/_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/fetch.d.mts +1 -35
  13. package/dist/_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/index.d.mts +1 -1
  14. package/dist/catalog.d.mts +11 -3
  15. package/dist/catalog.mjs +24 -9
  16. package/dist/dataset.d.mts +4 -0
  17. package/dist/dataset.mjs +4 -1
  18. package/dist/index.d.mts +3 -1
  19. package/dist/manifest-read-limits.d.mts +2 -0
  20. package/dist/manifest-read-limits.mjs +26 -0
  21. package/dist/resolver-byte-cache.mjs +85 -0
  22. package/package.json +1 -1
  23. package/dist/_virtual/node_modules/.pnpm/hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/gzip.mjs +0 -186
  24. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.bitreader.mjs +0 -0
  25. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.blocks.mjs +0 -0
  26. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.context.mjs +0 -0
  27. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.contextmap.mjs +0 -0
  28. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.dictionary.mjs +0 -0
  29. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.huffman.mjs +0 -0
  30. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.mjs +0 -0
  31. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.prefix.mjs +0 -0
  32. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.streams.mjs +0 -0
  33. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/brotli.transform.mjs +0 -0
  34. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/compressors.mjs +0 -0
  35. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/gzip.huffman.mjs +0 -0
  36. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/index.mjs +0 -0
  37. /package/dist/_virtual/node_modules/.pnpm/{hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d → hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5}/node_modules/hyparquet-compressors/src/lz4.mjs +0 -0
package/README.md CHANGED
@@ -49,8 +49,8 @@ Raw Icebird table creation and append functions live under `unsafe-raw` for pack
49
49
 
50
50
  `icebergManifests` on `@gscdump/lakehouse/unsafe-raw` accepts an optional
51
51
  `readLimits: ManifestReadLimits`. Omission preserves the existing reader and
52
- caller-owned manifest cache behavior. No higher-level file resolver or host
53
- automatically enables these limits.
52
+ caller-owned manifest cache behavior. No file resolver or host automatically
53
+ enables these limits.
54
54
 
55
55
  ```ts
56
56
  import { icebergManifests } from '@gscdump/lakehouse/unsafe-raw'
@@ -113,6 +113,41 @@ physically cancelled by the AsyncBuffer interface, but a first failure stops new
113
113
  dispatches and prevents late responses from being decoded. Choose compatible
114
114
  limits and validate the complete consumer path before enabling them.
115
115
 
116
+ The same optional `readLimits` can be passed to `resolveIcebergDataFiles`,
117
+ `dataset.resolveDataFiles(..., { readLimits })`, and the engine's
118
+ `listIcebergDataFiles`. These wrappers capture and validate all ten values before
119
+ catalog reads or lazy imports, then perform one bounded manifest walk. Per-object
120
+ and aggregate limits remain independent upper bounds; a smaller aggregate limit
121
+ does not expand the per-object allowance. Invalid, missing or unknown values are
122
+ rejected even when the table has no current snapshot.
123
+
124
+ In this opt-in mode, decoded manifest, resolved-file and month caches are neither
125
+ read nor populated: old cache entries cannot establish compliance with a new
126
+ policy. The existing catalog configuration, snapshot pointer and table metadata
127
+ caches remain in use, with their existing identity/freshness rules. Catalog JSON
128
+ and those metadata caches are outside the Avro allocation bound. Existing
129
+ partition, Site, search-type, date pruning and output ordering are unchanged.
130
+
131
+ This has an explicit warm-read cost. A default exact-range cache hit can avoid
132
+ all Avro reads; a default month hit can need only the manifest list. A bounded
133
+ call reads the list and every selected manifest again (subject to any injected
134
+ resolver's own byte cache).
135
+
136
+ A connection from `connectIcebergCatalog` keeps the objects it reads in a
137
+ per-connection byte cache. That cache holds at most 16 MiB of fetched bytes.
138
+ If a read takes it over 16 MiB, it evicts the least recently read object
139
+ first. It does not keep an object larger than 16 MiB, and it does not keep a
140
+ failed read. Concurrent reads of one path share one S3 request. A successful
141
+ write or delete of a path evicts that path. A bounded call reads through the uncached
142
+ S3 resolver instead, so the connection keeps none of its manifest bytes after
143
+ the call. It also does not reuse bytes that earlier unbounded calls cached. If
144
+ you replace `connection.resolver`, the bounded call uses your resolver, and your
145
+ resolver owns its retention. Each concurrent bounded call owns its own limits;
146
+ the option does not impose a connection-wide or process-wide budget. There is
147
+ no automatic fallback to cached or unbounded results after refusal. Validate
148
+ retained files and request costs before enabling a policy in a host; this does
149
+ not bound a remote SQL provider's internal manifest planner.
150
+
116
151
  ## Append retries
117
152
 
118
153
  Supply a durable `commitRetry.appendId` when a dataset append belongs to a job or receipt.
@@ -0,0 +1,196 @@
1
+ import { fixedDistance, fixedDistanceExtraBits, fixedDistanceMap, fixedLength, fixedLengthExtraBits, fixedLengthMap, huffMap } from "./gzip.huffman.mjs";
2
+ const codeLengthIndexMap = new Uint8Array([
3
+ 16,
4
+ 17,
5
+ 18,
6
+ 0,
7
+ 8,
8
+ 7,
9
+ 9,
10
+ 6,
11
+ 10,
12
+ 5,
13
+ 11,
14
+ 4,
15
+ 12,
16
+ 3,
17
+ 13,
18
+ 2,
19
+ 14,
20
+ 1,
21
+ 15
22
+ ]);
23
+ function bits(input, pos, mask) {
24
+ const o = pos / 8 | 0;
25
+ return (input[o] | input[o + 1] << 8) >> (pos & 7) & mask;
26
+ }
27
+ function bits16(d, p) {
28
+ const o = p / 8 | 0;
29
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
30
+ }
31
+ function shft(p) {
32
+ return (p + 7) / 8 | 0;
33
+ }
34
+ function gzipStart(input, i) {
35
+ if (input[i++] !== 31 || input[i++] !== 139 || input[i++] !== 8) return 0;
36
+ const flag = input[i++];
37
+ if (flag & 224) throw new Error("invalid gzip header flags");
38
+ i += 6;
39
+ if (flag & 4) i += (input[i] | input[i + 1] << 8) + 2;
40
+ for (let zs = (flag >> 3 & 1) + (flag >> 4 & 1); zs > 0; zs -= Number(!input[i++]));
41
+ return i + (flag & 2);
42
+ }
43
+ function gunzip(input, output, inputIndex = 0, outputIndex = 0, limits) {
44
+ const maximum = limits?.maxOutputBytes;
45
+ if (limits && (limits.raw !== true || !Number.isSafeInteger(maximum) || maximum < 0 || output || inputIndex !== 0 || outputIndex !== 0)) throw new RangeError("Invalid bounded raw DEFLATE options");
46
+ let out = output ?? new Uint8Array(limits ? Math.min(1024, maximum) : 1024);
47
+ if (limits && !input.length) throw new Error("unexpected EOF");
48
+ if (!(input.length - inputIndex)) return out;
49
+ const totalBits = input.length * 8;
50
+ function ensureSize(length) {
51
+ if (limits && (!Number.isSafeInteger(length) || length > maximum)) throw new RangeError("DEFLATE output byte limit exceeded");
52
+ if (!output && length > out.length) {
53
+ const old = out;
54
+ out = new Uint8Array(limits ? Math.min(maximum, Math.max(old.length * 2, length)) : Math.max(old.length * 2, length));
55
+ out.set(old);
56
+ }
57
+ }
58
+ let memberStart = inputIndex;
59
+ for (;;) {
60
+ const payloadStart = limits ? 0 : gzipStart(input, memberStart);
61
+ const isGzip = !limits && payloadStart > 0;
62
+ if (isGzip && payloadStart === input.length - 8) break;
63
+ if (isGzip && payloadStart > input.length - 8) throw new Error("unexpected EOF");
64
+ const pos = inflate(payloadStart * 8);
65
+ if (limits && Math.ceil(pos / 8) !== input.length) throw new Error("trailing raw DEFLATE bytes");
66
+ if (!isGzip || output && outputIndex >= out.length) break;
67
+ const nextBlock = Math.ceil(pos / 8) + 8;
68
+ if (nextBlock > input.length) throw new Error("unexpected EOF");
69
+ if (nextBlock === input.length) break;
70
+ if (input[nextBlock] !== 31 || input[nextBlock + 1] !== 139 || input[nextBlock + 2] !== 8) throw new Error("invalid gzip member");
71
+ memberStart = nextBlock;
72
+ }
73
+ function inflate(pos) {
74
+ let final = 0;
75
+ let lengthBits = 0;
76
+ let distBits = 0;
77
+ let lengthMap;
78
+ let distMap;
79
+ do {
80
+ if (!lengthMap) {
81
+ final = bits(input, pos, 1);
82
+ const type = bits(input, pos + 1, 3);
83
+ pos += 3;
84
+ if (!type) {
85
+ const s = shft(pos) + 4;
86
+ const l = input[s - 4] | input[s - 3] << 8;
87
+ if (limits && (s > input.length || ((input[s - 2] | input[s - 1] << 8) ^ l) !== 65535)) throw new Error("invalid stored block length");
88
+ const t = s + l;
89
+ if (t > input.length) throw new Error("unexpected EOF");
90
+ ensureSize(outputIndex + l);
91
+ out.set(input.subarray(s, t), outputIndex);
92
+ outputIndex += l;
93
+ pos = t * 8;
94
+ continue;
95
+ } else if (type === 1) {
96
+ lengthMap = fixedLengthMap;
97
+ distMap = fixedDistanceMap;
98
+ lengthBits = 9;
99
+ distBits = 5;
100
+ } else if (type === 2) {
101
+ const hLiteral = bits(input, pos, 31) + 257;
102
+ const hcLengths = bits(input, pos + 10, 15) + 4;
103
+ const tl = hLiteral + bits(input, pos + 5, 31) + 1;
104
+ pos += 14;
105
+ const lengthDistanceTree = new Uint8Array(tl);
106
+ const codeLengthTree = /* @__PURE__ */ new Uint8Array(19);
107
+ for (let i = 0; i < hcLengths; ++i) codeLengthTree[codeLengthIndexMap[i]] = bits(input, pos + i * 3, 7);
108
+ pos += hcLengths * 3;
109
+ const codeLengthBits = Math.max(...codeLengthTree);
110
+ const clbMask = (1 << codeLengthBits) - 1;
111
+ const codeLengthMap = huffMap(codeLengthTree, codeLengthBits, 1);
112
+ for (let i = 0; i < tl;) {
113
+ const r = codeLengthMap[bits(input, pos, clbMask)];
114
+ if (limits && !r) throw new Error("invalid code length");
115
+ pos += r & 15;
116
+ const symbol = r >> 4;
117
+ if (symbol < 16) lengthDistanceTree[i++] = symbol;
118
+ else {
119
+ let copy = 0;
120
+ let n = 0;
121
+ if (symbol === 16) {
122
+ if (limits && i === 0) throw new Error("invalid code length repeat");
123
+ n = 3 + bits(input, pos, 3);
124
+ pos += 2;
125
+ copy = lengthDistanceTree[i - 1];
126
+ } else if (symbol === 17) {
127
+ n = 3 + bits(input, pos, 7);
128
+ pos += 3;
129
+ } else if (symbol === 18) {
130
+ n = 11 + bits(input, pos, 127);
131
+ pos += 7;
132
+ }
133
+ if (limits && i + n > tl) throw new Error("invalid code length repeat");
134
+ while (n--) lengthDistanceTree[i++] = copy;
135
+ }
136
+ }
137
+ const lengthTree = lengthDistanceTree.subarray(0, hLiteral);
138
+ const distanceTree = lengthDistanceTree.subarray(hLiteral);
139
+ lengthBits = Math.max(...lengthTree);
140
+ distBits = Math.max(...distanceTree);
141
+ lengthMap = huffMap(lengthTree, lengthBits, 1);
142
+ distMap = huffMap(distanceTree, distBits, 1);
143
+ } else throw new Error("invalid block type");
144
+ if (pos > totalBits) throw new Error("unexpected EOF");
145
+ }
146
+ ensureSize(limits ? Math.min(maximum, outputIndex + 131072) : outputIndex + 131072);
147
+ const lms = (1 << lengthBits) - 1;
148
+ const dms = (1 << distBits) - 1;
149
+ for (;;) {
150
+ const code = lengthMap[bits16(input, pos) & lms];
151
+ const sym = code >> 4;
152
+ pos += code & 15;
153
+ if (pos > totalBits) throw new Error("unexpected EOF");
154
+ if (!code) throw new Error("invalid length/literal");
155
+ if (sym < 256) {
156
+ if (limits) ensureSize(outputIndex + 1);
157
+ out[outputIndex++] = sym;
158
+ } else if (sym === 256) {
159
+ lengthMap = void 0;
160
+ break;
161
+ } else {
162
+ let add = sym - 254;
163
+ if (sym > 264) {
164
+ const index = sym - 257;
165
+ const b = fixedLengthExtraBits[index];
166
+ add = bits(input, pos, (1 << b) - 1) + fixedLength[index];
167
+ pos += b;
168
+ }
169
+ if (!distMap) throw new Error("invalid distance map");
170
+ const d = distMap[bits16(input, pos) & dms];
171
+ const dsym = d >> 4;
172
+ if (!d) throw new Error("invalid distance");
173
+ if (limits && dsym > 29) throw new Error("invalid distance symbol");
174
+ pos += d & 15;
175
+ let dt = fixedDistance[dsym];
176
+ if (dsym > 3) {
177
+ const b = fixedDistanceExtraBits[dsym];
178
+ dt += bits16(input, pos) & (1 << b) - 1;
179
+ pos += b;
180
+ }
181
+ if (pos > totalBits) throw new Error("unexpected EOF");
182
+ const end = outputIndex + add;
183
+ if (outputIndex < dt) throw new Error("unexpected dictionary case");
184
+ ensureSize(end);
185
+ for (; outputIndex < end; outputIndex++) out[outputIndex] = out[outputIndex - dt];
186
+ }
187
+ }
188
+ if (lengthMap) final = 1;
189
+ } while (!final);
190
+ return pos;
191
+ }
192
+ if (limits && outputIndex !== out.length) return out.slice(0, outputIndex);
193
+ if (!output) return out.subarray(0, outputIndex);
194
+ return out;
195
+ }
196
+ export { gunzip };
@@ -1,5 +1,5 @@
1
- import { Encodings, PageTypes } from "./constants.mjs";
2
1
  import { convert, convertWithDictionary } from "./convert.mjs";
2
+ import { Encodings, PageTypes } from "./constants.mjs";
3
3
  import { isFlatColumn } from "./schema.mjs";
4
4
  import { deserializeTCompactProtocol } from "./thrift.mjs";
5
5
  import { assembleLists } from "./assemble.mjs";
@@ -1,8 +1,8 @@
1
+ import { snappyUncompress } from "./snappy.mjs";
1
2
  import { getMaxDefinitionLevel, getMaxRepetitionLevel } from "./schema.mjs";
2
3
  import { deltaBinaryUnpack, deltaByteArray, deltaLengthByteArray } from "./delta.mjs";
3
4
  import { byteStreamSplit, readRleBitPackedHybrid } from "./encoding.mjs";
4
5
  import { readPlain } from "./plain.mjs";
5
- import { snappyUncompress } from "./snappy.mjs";
6
6
  function readDataPage(bytes, daph, { type, element, schemaPath }) {
7
7
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
8
8
  const reader = {
@@ -1,7 +1,7 @@
1
+ import "./snappy.mjs";
1
2
  import { parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
2
3
  import "./indexes.mjs";
3
4
  import { asyncBufferFromUrl, cachedAsyncBuffer, toJson } from "./utils.mjs";
4
- import "./snappy.mjs";
5
5
  import { parquetScan } from "./scan.mjs";
6
6
  import { parquetReadObjects } from "./read.mjs";
7
7
  import "./query.mjs";
@@ -1,5 +1,5 @@
1
- import { BoundaryOrders } from "./constants.mjs";
2
1
  import { DEFAULT_PARSERS } from "./convert.mjs";
2
+ import { BoundaryOrders } from "./constants.mjs";
3
3
  import { deserializeTCompactProtocol } from "./thrift.mjs";
4
4
  import { convertMetadata } from "./metadata.mjs";
5
5
  function readColumnIndex(reader, schema, parsers = void 0) {
@@ -1,5 +1,5 @@
1
- import { CompressionCodecs, ConvertedTypes, EdgeInterpolationAlgorithms, Encodings, FieldRepetitionTypes, PageTypes, ParquetTypes } from "./constants.mjs";
2
1
  import { DEFAULT_PARSERS, parseDecimal, parseFloat16 } from "./convert.mjs";
2
+ import { CompressionCodecs, ConvertedTypes, EdgeInterpolationAlgorithms, Encodings, FieldRepetitionTypes, PageTypes, ParquetTypes } from "./constants.mjs";
3
3
  import { getSchemaPath } from "./schema.mjs";
4
4
  import { deserializeTCompactProtocol } from "./thrift.mjs";
5
5
  import { markGeoColumns } from "./geoparquet.mjs";
@@ -1,7 +1,7 @@
1
+ import "./snappy.mjs";
1
2
  import { parquetMetadataAsync, parquetSchema } from "./metadata.mjs";
2
3
  import "./indexes.mjs";
3
4
  import { asyncBufferFromUrl, cachedAsyncBuffer, toJson } from "./utils.mjs";
4
- import "./snappy.mjs";
5
5
  import { parquetScan } from "./scan.mjs";
6
6
  import { parquetReadObjects } from "./read.mjs";
7
7
  import "./index.mjs";
@@ -1,8 +1,8 @@
1
- import { parseDecimal } from "../../../../../hyparquet@1.31.1/node_modules/hyparquet/src/convert.mjs";
2
- import { gunzip } from "../../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/gzip.mjs";
3
- import "../../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/index.mjs";
4
1
  import { boundedBytes } from "./avro.budget.mjs";
5
2
  import { checkBlockEnd, readBlockEnd, readZigZag, readZigZagBigInt } from "./avro.metadata.mjs";
3
+ import { gunzip } from "../../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/gzip.mjs";
4
+ import "../../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/index.mjs";
5
+ import { parseDecimal } from "../../../../../hyparquet@1.31.1/node_modules/hyparquet/src/convert.mjs";
6
6
  const textDecoder = new TextDecoder();
7
7
  function avroRead({ reader, metadata, syncMarker, recordKind = "entries" }) {
8
8
  const budget = reader.budget;
@@ -1,13 +1,13 @@
1
+ import { avroMetadata } from "./avro/avro.metadata.mjs";
2
+ import { compressors } from "../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/compressors.mjs";
3
+ import "../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/index.mjs";
4
+ import { avroRead } from "./avro/avro.read.mjs";
1
5
  import { parquetMetadataAsync } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/metadata.mjs";
2
6
  import { asyncBufferFromUrl, cachedAsyncBuffer } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/utils.mjs";
3
7
  import { parquetReadObjects } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/read.mjs";
4
8
  import "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
5
- import { compressors } from "../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/compressors.mjs";
6
- import "../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/index.mjs";
7
9
  import { ByteWriter } from "../../../../hyparquet-writer@0.16.10_patch_hash=d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/bytewriter.mjs";
8
10
  import "../../../../hyparquet-writer@0.16.10_patch_hash=d26d002aede61ad95dd633d6557b56897cdcbc45dfc3a5d93fa0e9075ad5425f/node_modules/hyparquet-writer/src/node.mjs";
9
- import { avroMetadata } from "./avro/avro.metadata.mjs";
10
- import { avroRead } from "./avro/avro.read.mjs";
11
11
  import { sanitize } from "./utils.mjs";
12
12
  import { isDeletionVector } from "./delete.mjs";
13
13
  import { puffinReadDeletionVector } from "./puffin/puffin.mjs";
@@ -68,40 +68,6 @@ function urlResolver({ requestInit } = {}) {
68
68
  }
69
69
  };
70
70
  }
71
- function cachingResolver(base) {
72
- const cache = /* @__PURE__ */ new Map();
73
- const out = { reader(path, byteLength) {
74
- let buf = cache.get(path);
75
- if (!buf) {
76
- buf = (async () => cachedAsyncBuffer(await base.reader(path, byteLength)))();
77
- cache.set(path, buf);
78
- buf.catch(() => {
79
- if (cache.get(path) === buf) cache.delete(path);
80
- });
81
- }
82
- return buf;
83
- } };
84
- if (base.writer) {
85
- const baseWriter = base.writer;
86
- out.writer = (path, options) => {
87
- const w = baseWriter(path, options);
88
- const origFinish = w.finish.bind(w);
89
- w.finish = async function() {
90
- await origFinish();
91
- cache.delete(path);
92
- };
93
- return w;
94
- };
95
- }
96
- if (base.deleter) {
97
- const baseDeleter = base.deleter;
98
- out.deleter = async (path) => {
99
- await baseDeleter(path);
100
- cache.delete(path);
101
- };
102
- }
103
- return out;
104
- }
105
71
  function s3Lister({ requestInit } = {}) {
106
72
  return async function list(url) {
107
73
  const s3parts = s3ParseUrl(url);
@@ -299,4 +265,4 @@ async function fetchBoundedAvroObject(url, resolver, byteLength, budget, recordK
299
265
  return budget.fail(error);
300
266
  }
301
267
  }
302
- export { cachingResolver, fetchAvroRecords, fetchBoundedAvroObject, fetchDeleteMaps, readParquetMetadata, resolveText, s3Lister, s3ParseUrl, translateS3Url, urlResolver };
268
+ export { fetchAvroRecords, fetchBoundedAvroObject, fetchDeleteMaps, readParquetMetadata, resolveText, s3Lister, s3ParseUrl, translateS3Url, urlResolver };
@@ -1,3 +1,5 @@
1
+ import { compressors } from "../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/compressors.mjs";
2
+ import "../../../../hyparquet-compressors@1.1.2_patch_hash=3f8f44805c034b2563763dcd5d37050ea6b1d5d45b46ea37adc4a721e748a1c5/node_modules/hyparquet-compressors/src/index.mjs";
1
3
  import { isListLike, isMapLike } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/schema.mjs";
2
4
  import { parquetSchema } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/metadata.mjs";
3
5
  import { cachedAsyncBuffer } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/utils.mjs";
@@ -5,8 +7,6 @@ import { columnsNeededForFilter, matchFilter } from "../../../../hyparquet@1.31.
5
7
  import { parquetScan } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/scan.mjs";
6
8
  import { parquetReadObjects } from "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/read.mjs";
7
9
  import "../../../../hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
8
- import { compressors } from "../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/compressors.mjs";
9
- import "../../../../hyparquet-compressors@1.1.2_patch_hash=821c89dbb6181f0c34d9abebd844451458a4f16c04e7aa7a19abca0c09c2238d/node_modules/hyparquet-compressors/src/index.mjs";
10
10
  import { equalityMatch, sanitize } from "./utils.mjs";
11
11
  import { applicablePositionDeletes, deleteFileAppliesToDataEntry } from "./delete.mjs";
12
12
  import { readParquetMetadata } from "./fetch.mjs";
@@ -1,36 +1,2 @@
1
1
  import "../../../../hyparquet@1.31.1/node_modules/hyparquet/types/node.mjs";
2
- import { Resolver } from "../src/types.mjs";
3
- /**
4
- * Wrap a `Resolver` so reads of the same path share one HTTP fetch and
5
- * subsequent range reads share one in-memory buffer. Writes and deletes
6
- * through the same resolver invalidate the cache entry for their path on
7
- * success, so a commit pipeline (`icebergAppend` → reload metadata) sees the
8
- * new bytes without manual invalidation.
9
- *
10
- * - `reader(path)` is memoized by path. The returned buffer is passed through
11
- * `cachedAsyncBuffer` so range reads within a single file are also deduped.
12
- * `byteLength` is a fetch hint and not part of the cache key — the bytes
13
- * are the same either way.
14
- * - `writer(path).finish()` invalidates the cache entry only when the
15
- * underlying finish resolves; a rejected finish (e.g. an `If-None-Match: *`
16
- * collision returning 412/409) leaves the cached bytes intact, since the
17
- * server-side object hasn't changed.
18
- * - `deleter(path)` invalidates the cache entry on success.
19
- * - `writer` and `deleter` are omitted when the base resolver omits them, so
20
- * wrapping a read-only resolver still yields a read-only resolver.
21
- *
22
- * Iceberg writes are almost entirely create-new-path (data parquets, manifest
23
- * avros, snapshot avros, vN.metadata.json all live at fresh paths per
24
- * commit). Only `version-hint.text` and equivalent catalog-pointer files are
25
- * truly mutated in place, so path-level invalidation is sufficient to keep
26
- * single-process write-then-read pipelines consistent.
27
- *
28
- * Cross-process freshness is out of scope: a reader in process B does not
29
- * see writes committed by process A through a different cache. Use a
30
- * short-lived resolver, an external coordination layer, or wrap with TTL /
31
- * conditional-GET revalidation if you need that.
32
- *
33
- * @param {Resolver} base
34
- * @returns {Resolver}
35
- */
36
- export declare function cachingResolver(base: Resolver): Resolver;
2
+ import "../src/types.mjs";
@@ -1,9 +1,9 @@
1
1
  import { icebergManifests } from "./manifest.mjs";
2
2
  import { icebergAppend, icebergCreateTable, icebergDropTable } from "./write/write.mjs";
3
3
  import { restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable } from "./catalog/rest.mjs";
4
- import { cachingResolver } from "./fetch.mjs";
5
4
  import "./create.mjs";
6
5
  import "./catalog/file.mjs";
6
+ import "./fetch.mjs";
7
7
  import { s3SignedResolver } from "./s3.mjs";
8
8
  import "../../../../squirreling@0.16.6/node_modules/squirreling/src/index.mjs";
9
9
  import "./read.mjs";
@@ -1,10 +1,11 @@
1
- import { ManifestReadCache } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/manifest.mjs";
1
+ import { ManifestReadCache, ManifestReadLimits } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/manifest.mjs";
2
2
  import { icebergAppend } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/write/write.mjs";
3
3
  import { CatalogCache } from "./catalog-cache.mjs";
4
+ import "./manifest-read-limits.mjs";
4
5
  import { IcebergPrimitiveType, IcebergS3Config } from "./schema.mjs";
5
6
  import { PartitionValueMatch } from "./partition-prune.mjs";
6
7
  import { restCatalogConnect, restCatalogLoadTable } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/catalog/rest.mjs";
7
- import { cachingResolver } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/fetch.mjs";
8
+ import { Resolver } from "icebird/src/types.js";
8
9
  /** A field in an icebird table `Schema`. */
9
10
  export interface IcebergSchemaField {
10
11
  id: number;
@@ -61,7 +62,7 @@ export interface IcebergConnection {
61
62
  /** icebird REST catalog context, passed as `{ catalog }` to icebird write fns. */
62
63
  catalog: Awaited<ReturnType<typeof restCatalogConnect>>;
63
64
  /** icebird S3 resolver (caching-wrapped), passed as `{ resolver }` to icebird fns. */
64
- resolver: ReturnType<typeof cachingResolver>;
65
+ resolver: Resolver;
65
66
  /** The namespace the dataset's table lives under. */
66
67
  namespace: string;
67
68
  /**
@@ -259,6 +260,13 @@ export interface ResolveIcebergDataFilesOptions {
259
260
  cache?: CatalogCache;
260
261
  clock?: () => number;
261
262
  profiler?: QueryProfiler;
263
+ /**
264
+ * Opt-in Avro source/decode-work limits for one uncached manifest walk.
265
+ * Bypasses decoded manifest, month and resolved-file caches; catalog metadata
266
+ * and snapshot caching keep their existing authority and are not byte-bounded
267
+ * by this option. No default limits or fallback to unbounded decoding.
268
+ */
269
+ readLimits?: ManifestReadLimits;
262
270
  }
263
271
  /**
264
272
  * Drop the cached snapshot pointer for `(namespace, table)` so the next read
package/dist/catalog.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import { stringifyBigintSafe } from "./bigint.mjs";
2
2
  import { restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/catalog/rest.mjs";
3
- import { cachingResolver } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/fetch.mjs";
4
3
  import { icebergManifests } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/manifest.mjs";
5
4
  import { s3SignedResolver } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/src/s3.mjs";
6
5
  import { deriveAppendIdentity } from "./append-identity.mjs";
7
6
  import { cacheGet, cachePut, reportCatalogCacheError } from "./catalog-cache.mjs";
8
7
  import { toIcebergDayCount } from "./date.mjs";
8
+ import { captureManifestReadLimits } from "./manifest-read-limits.mjs";
9
9
  import { MULTI_MONTH_MANIFEST, buildManifestPartitionFilter, manifestMonthBucket } from "./partition-prune.mjs";
10
+ import { byteCappedCachingResolver } from "./resolver-byte-cache.mjs";
10
11
  const connectionReadBatch = Symbol("iceberg-connection-read-batch");
11
12
  function beginConnectionRead(conn) {
12
13
  const batch = conn[connectionReadBatch];
@@ -115,7 +116,7 @@ async function connectIcebergCatalog(config, opts = {}) {
115
116
  endpoint: config.s3.endpoint,
116
117
  pathStyle: true
117
118
  });
118
- const resolver = withVerifiedWriterByteLengths(cachingResolver(s3Resolver));
119
+ const resolver = withUncachedReader(withVerifiedWriterByteLengths(byteCappedCachingResolver(s3Resolver)), s3Resolver);
119
120
  return {
120
121
  catalog,
121
122
  resolver,
@@ -128,6 +129,17 @@ async function connectIcebergCatalog(config, opts = {}) {
128
129
  }
129
130
  };
130
131
  }
132
+ const uncachedReader = Symbol("iceberg-uncached-reader");
133
+ function withUncachedReader(resolver, base) {
134
+ return {
135
+ ...resolver,
136
+ [uncachedReader]: (path, byteLength) => base.reader(path, byteLength)
137
+ };
138
+ }
139
+ function boundedReadResolver(resolver) {
140
+ const read = resolver[uncachedReader];
141
+ return read ? { reader: read } : resolver;
142
+ }
131
143
  function withVerifiedWriterByteLengths(resolver) {
132
144
  if (!resolver.writer) return resolver;
133
145
  const baseWriter = resolver.writer;
@@ -643,12 +655,14 @@ function toListedFile(entry, matches, monthFieldName, wantedMonths, dateFieldId)
643
655
  ...dateBounds ? { dateBounds } : {}
644
656
  };
645
657
  }
646
- async function resolveViaFullWalk(conn, metadata, partitionFilter, matches, monthFieldName, wantedMonths, dateFieldId, manifestCache) {
658
+ async function resolveViaFullWalk(conn, metadata, partitionFilter, matches, monthFieldName, wantedMonths, dateFieldId, manifestCache, readLimits) {
659
+ const resolver = readLimits ? boundedReadResolver(conn.resolver) : conn.resolver;
647
660
  const manifests = await icebergManifests({
648
661
  metadata,
649
- resolver: conn.resolver,
662
+ resolver,
650
663
  partitionFilter,
651
- manifestCache
664
+ manifestCache,
665
+ readLimits
652
666
  });
653
667
  const entries = [];
654
668
  for (const m of manifests) for (const entry of m.entries) {
@@ -745,6 +759,7 @@ function resolveIcebergDataFiles(conn, opts) {
745
759
  return resolveIcebergDataFilesInBatch(conn, opts).finally(release);
746
760
  }
747
761
  async function resolveIcebergDataFilesInBatch(conn, opts) {
762
+ const readLimits = captureManifestReadLimits(opts.readLimits);
748
763
  const { namespace, table } = opts;
749
764
  const profiler = opts.profiler;
750
765
  const now = (opts.clock ?? Date.now)();
@@ -756,7 +771,7 @@ async function resolveIcebergDataFilesInBatch(conn, opts) {
756
771
  if (snapshotId == null) return [];
757
772
  const scope = conn.cacheScope ?? "";
758
773
  const filesKey = resolvedFilesKey(scope, namespace, table, snapshotId, opts.matches, opts.range);
759
- if (opts.cache) {
774
+ if (opts.cache && !readLimits) {
760
775
  const endCache = profiler?.start("iceberg.cache");
761
776
  const cached = await cacheGet(opts.cache, filesKey, now);
762
777
  endCache?.({ hit: cached !== void 0 });
@@ -778,8 +793,8 @@ async function resolveIcebergDataFilesInBatch(conn, opts) {
778
793
  const monthFieldName = monthField?.name;
779
794
  const dateFieldId = dateColumnFieldId(metadata, monthField?.sourceColumn);
780
795
  const dayWindow = rangeDayWindow(opts.range);
781
- const manifestCache = manifestReadCacheFor(conn, namespace, table, metadata);
782
- const walk = opts.cache && monthFieldName ? await resolveViaMonthCache(conn, opts.cache, metadata, partitionFilter, opts.partitionSpec, opts.matches, monthFieldName, wantedMonths, dateFieldId, scope, namespace, table, now, manifestCache) : await resolveViaFullWalk(conn, metadata, partitionFilter, opts.matches, monthFieldName, wantedMonths, dateFieldId, manifestCache);
796
+ const manifestCache = readLimits ? void 0 : manifestReadCacheFor(conn, namespace, table, metadata);
797
+ const walk = opts.cache && monthFieldName && !readLimits ? await resolveViaMonthCache(conn, opts.cache, metadata, partitionFilter, opts.partitionSpec, opts.matches, monthFieldName, wantedMonths, dateFieldId, scope, namespace, table, now, manifestCache) : await resolveViaFullWalk(conn, metadata, partitionFilter, opts.matches, monthFieldName, wantedMonths, dateFieldId, manifestCache, readLimits);
783
798
  let boundsPruned = 0;
784
799
  const out = [];
785
800
  for (const file of walk.entries) {
@@ -798,7 +813,7 @@ async function resolveIcebergDataFilesInBatch(conn, opts) {
798
813
  monthsHit: walk.monthsHit,
799
814
  manifestsWalked: walk.manifestsWalked
800
815
  });
801
- if (opts.cache) {
816
+ if (opts.cache && !readLimits) {
802
817
  const freshKey = resolvedFilesKey(scope, namespace, table, snapshotId, opts.matches, opts.range);
803
818
  await cachePut(opts.cache, freshKey, out, RESOLVED_FILES_TTL_MS, now);
804
819
  }
@@ -1,4 +1,6 @@
1
+ import { ManifestReadLimits } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/manifest.mjs";
1
2
  import { CatalogCache } from "./catalog-cache.mjs";
3
+ import "./manifest-read-limits.mjs";
2
4
  import { IcebergColumnType, IcebergPartitionField, IcebergTableSpec } from "./schema.mjs";
3
5
  import { ManifestPartitionFilter, PartitionValueMatch } from "./partition-prune.mjs";
4
6
  import { CommitRetryOptions, ConnectIcebergOptions, IcebergCatalogConfig, IcebergConnection, IcebergListedDataFile, IcebergPartitionSpec, IcebergSchema, IcebergSortOrder, IcebergTableOpResult, QueryProfiler } from "./catalog.mjs";
@@ -97,6 +99,8 @@ export interface ResolveDataFilesOptions {
97
99
  cache?: CatalogCache;
98
100
  clock?: () => number;
99
101
  profiler?: QueryProfiler;
102
+ /** Uncached Avro read limits; catalog metadata/snapshot caching is unchanged. */
103
+ readLimits?: ManifestReadLimits;
100
104
  }
101
105
  export interface IcebergDataset {
102
106
  readonly def: IcebergDatasetDef;
package/dist/dataset.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { coerceBigIntToNumber } from "./bigint.mjs";
2
+ import { captureManifestReadLimits } from "./manifest-read-limits.mjs";
2
3
  import { buildManifestPartitionFilter } from "./partition-prune.mjs";
3
4
  import { ICEBERG_TYPE_MAP } from "./schema.mjs";
4
5
  const INT32_MIN = -2147483648;
@@ -299,6 +300,7 @@ function defineIcebergDataset(def) {
299
300
  });
300
301
  }
301
302
  async function resolveDataFiles(conn, identity, range, dims, opts = {}) {
303
+ const readLimits = captureManifestReadLimits(opts.readLimits);
302
304
  const { resolveDatasetDataFiles } = await import("./dataset-runtime.mjs");
303
305
  return resolveDatasetDataFiles(conn, {
304
306
  namespace: def.namespace,
@@ -308,7 +310,8 @@ function defineIcebergDataset(def) {
308
310
  range,
309
311
  cache: opts.cache,
310
312
  clock: opts.clock,
311
- profiler: opts.profiler
313
+ profiler: opts.profiler,
314
+ readLimits
312
315
  });
313
316
  }
314
317
  return {
package/dist/index.d.mts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { bigintJsonReplacer, coerceBigIntToNumber, encodeJsonBigintSafe, stringifyBigintSafe } from "./bigint.mjs";
2
+ import { ManifestReadLimits } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/manifest.mjs";
2
3
  import { CatalogCache, CatalogCacheGetOutcome, cacheGet, cachePut } from "./catalog-cache.mjs";
4
+ import "./manifest-read-limits.mjs";
3
5
  import { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_TYPE_MAP, IcebergColumn, IcebergColumnType, IcebergPartitionField, IcebergPartitionTransform, IcebergPrimitiveType, IcebergS3Config, IcebergTableSpec, PartitionKeyEncoding } from "./schema.mjs";
4
6
  import { IcebergFieldSummary, ManifestPartitionFilter, PartitionValueMatch, buildManifestPartitionFilter } from "./partition-prune.mjs";
5
7
  import { CommitRetryOptions, ConnectIcebergOptions, IcebergCatalogConfig, IcebergConnection, IcebergDataFileDateBounds, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, QueryProfiler, ResolveIcebergDataFilesOptions, catalogCacheScope, connectIcebergCatalog, createIcebergNamespace, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles } from "./catalog.mjs";
6
8
  import { AppendBatchSource, AppendBatchesOptions, AppendBatchesResult, AppendCommitOptions, AppendResult, AppendSink, AppendSinkCloseResult, AppendSinkOptions, DatasetDim, DatasetIdentity, IcebergDataset, IcebergDatasetColumnDef, IcebergDatasetDef, IcebergDatasetLedger, ResolveDataFilesOptions, defineIcebergDataset, deriveTableSpec } from "./dataset.mjs";
7
9
  import { toIcebergDayCount } from "./date.mjs";
8
- export { type AppendBatchSource, type AppendBatchesOptions, type AppendBatchesResult, type AppendCommitOptions, type AppendResult, type AppendSink, type AppendSinkCloseResult, type AppendSinkOptions, type CatalogCache, type CatalogCacheGetOutcome, type CommitRetryOptions, type ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, type DatasetDim, type DatasetIdentity, ICEBERG_TYPE_MAP, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergDataFileDateBounds, type IcebergDataset, type IcebergDatasetColumnDef, type IcebergDatasetDef, type IcebergDatasetLedger, type IcebergFieldSummary, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableOpResult, type IcebergTableSpec, type ManifestPartitionFilter, type PartitionKeyEncoding, type PartitionValueMatch, type QueryProfiler, type ResolveDataFilesOptions, type ResolveIcebergDataFilesOptions, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, createIcebergNamespace, defineIcebergDataset, deriveTableSpec, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, stringifyBigintSafe, toIcebergDayCount };
10
+ export { type AppendBatchSource, type AppendBatchesOptions, type AppendBatchesResult, type AppendCommitOptions, type AppendResult, type AppendSink, type AppendSinkCloseResult, type AppendSinkOptions, type CatalogCache, type CatalogCacheGetOutcome, type CommitRetryOptions, type ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, type DatasetDim, type DatasetIdentity, ICEBERG_TYPE_MAP, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergDataFileDateBounds, type IcebergDataset, type IcebergDatasetColumnDef, type IcebergDatasetDef, type IcebergDatasetLedger, type IcebergFieldSummary, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableOpResult, type IcebergTableSpec, type ManifestPartitionFilter, type ManifestReadLimits, type PartitionKeyEncoding, type PartitionValueMatch, type QueryProfiler, type ResolveDataFilesOptions, type ResolveIcebergDataFilesOptions, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, createIcebergNamespace, defineIcebergDataset, deriveTableSpec, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, stringifyBigintSafe, toIcebergDayCount };
@@ -0,0 +1,2 @@
1
+ import { ManifestReadLimits } from "./_virtual/node_modules/.pnpm/icebird@0.8.30_patch_hash=1359a91da68522a736babe444880ae391f19455fbe47c346f76cec92c5c2e471/node_modules/icebird/types/manifest.mjs";
2
+ export type { ManifestReadLimits };
@@ -0,0 +1,26 @@
1
+ const fields = [
2
+ "maxObjectBytes",
3
+ "maxTotalObjectBytes",
4
+ "maxManifests",
5
+ "maxEntries",
6
+ "maxInflatedBlockBytes",
7
+ "maxTotalInflatedBytes",
8
+ "maxValueBytes",
9
+ "maxDecodedValues",
10
+ "maxHeaderBytes",
11
+ "maxSchemaDepth"
12
+ ];
13
+ function captureManifestReadLimits(options) {
14
+ if (options === void 0) return void 0;
15
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new TypeError("Invalid manifest read limits");
16
+ for (const key of Object.keys(options)) if (!fields.includes(key)) throw new TypeError(`Unknown manifest read limit: ${key}`);
17
+ const limits = {};
18
+ for (const key of fields) {
19
+ const value = options[key];
20
+ if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`Invalid manifest read limit: ${key}`);
21
+ limits[key] = value;
22
+ }
23
+ if (limits.maxSchemaDepth > 128) throw new RangeError("Manifest schema depth must not exceed 128");
24
+ return Object.freeze(limits);
25
+ }
26
+ export { captureManifestReadLimits };
@@ -0,0 +1,85 @@
1
+ import { cachedAsyncBuffer } from "./_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/utils.mjs";
2
+ import "./_virtual/node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
3
+ const CONNECTION_CACHE_MAX_BYTES = 16777216;
4
+ function byteCappedCachingResolver(base, maxBytes = CONNECTION_CACHE_MAX_BYTES) {
5
+ const entries = /* @__PURE__ */ new Map();
6
+ let totalBytes = 0;
7
+ function touch(path, entry) {
8
+ entries.delete(path);
9
+ entries.set(path, entry);
10
+ }
11
+ function evict(path, account) {
12
+ if (entries.get(path)?.account !== account) return;
13
+ entries.delete(path);
14
+ totalBytes -= account.bytes;
15
+ }
16
+ function retain(path, account, bytes) {
17
+ const entry = entries.get(path);
18
+ if (entry?.account !== account) return;
19
+ account.bytes += bytes;
20
+ totalBytes += bytes;
21
+ touch(path, entry);
22
+ if (account.bytes > maxBytes) {
23
+ evict(path, account);
24
+ return;
25
+ }
26
+ for (const [oldest, old] of entries) {
27
+ if (totalBytes <= maxBytes) break;
28
+ if (old.account.bytes > 0) evict(oldest, old.account);
29
+ }
30
+ }
31
+ function counted(path, account, file) {
32
+ return {
33
+ byteLength: file.byteLength,
34
+ slice(start, end) {
35
+ return Promise.resolve(file.slice(start, end)).then((bytes) => {
36
+ retain(path, account, bytes.byteLength);
37
+ return bytes;
38
+ }, (error) => {
39
+ evict(path, account);
40
+ throw error;
41
+ });
42
+ }
43
+ };
44
+ }
45
+ function invalidate(path) {
46
+ const entry = entries.get(path);
47
+ if (entry) evict(path, entry.account);
48
+ }
49
+ const out = { reader(path, byteLength) {
50
+ const hit = entries.get(path);
51
+ if (hit) {
52
+ touch(path, hit);
53
+ return hit.buffer;
54
+ }
55
+ const account = { bytes: 0 };
56
+ const buffer = (async () => cachedAsyncBuffer(counted(path, account, await base.reader(path, byteLength))))();
57
+ entries.set(path, {
58
+ buffer,
59
+ account
60
+ });
61
+ buffer.catch(() => evict(path, account));
62
+ return buffer;
63
+ } };
64
+ if (base.writer) {
65
+ const baseWriter = base.writer;
66
+ out.writer = (path, options) => {
67
+ const writer = baseWriter(path, options);
68
+ const finish = writer.finish.bind(writer);
69
+ writer.finish = async function() {
70
+ await finish();
71
+ invalidate(path);
72
+ };
73
+ return writer;
74
+ };
75
+ }
76
+ if (base.deleter) {
77
+ const baseDeleter = base.deleter;
78
+ out.deleter = async (path) => {
79
+ await baseDeleter(path);
80
+ invalidate(path);
81
+ };
82
+ }
83
+ return out;
84
+ }
85
+ export { CONNECTION_CACHE_MAX_BYTES, byteCappedCachingResolver };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/lakehouse",
3
3
  "type": "module",
4
- "version": "7.7.0",
4
+ "version": "7.8.0",
5
5
  "description": "Dataset-agnostic Iceberg lakehouse layer + dataset registry for R2 Data Catalog producers (ADR-0021).",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -1,186 +0,0 @@
1
- import { fixedDistance, fixedDistanceExtraBits, fixedDistanceMap, fixedLength, fixedLengthExtraBits, fixedLengthMap, huffMap } from "./gzip.huffman.mjs";
2
- const codeLengthIndexMap = new Uint8Array([
3
- 16,
4
- 17,
5
- 18,
6
- 0,
7
- 8,
8
- 7,
9
- 9,
10
- 6,
11
- 10,
12
- 5,
13
- 11,
14
- 4,
15
- 12,
16
- 3,
17
- 13,
18
- 2,
19
- 14,
20
- 1,
21
- 15
22
- ]);
23
- function bits(input, pos, mask) {
24
- const o = pos / 8 | 0;
25
- return (input[o] | input[o + 1] << 8) >> (pos & 7) & mask;
26
- }
27
- function bits16(d, p) {
28
- const o = p / 8 | 0;
29
- return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
30
- }
31
- function shft(p) {
32
- return (p + 7) / 8 | 0;
33
- }
34
- function gzipStart(input, i) {
35
- if (input[i++] !== 31 || input[i++] !== 139 || input[i++] !== 8) return 0;
36
- const flag = input[i++];
37
- i += 6;
38
- if (flag & 4) i += (input[i + 10] | input[i + 11] << 8) + 2;
39
- for (let zs = (flag >> 3 & 1) + (flag >> 4 & 1); zs > 0; zs -= Number(!input[i++]));
40
- return i + (flag & 2);
41
- }
42
- function gunzip(input, output, inputIndex = 0, outputIndex = 0, limits) {
43
- const maximum = limits?.maxOutputBytes;
44
- if (limits && (limits.raw !== true || !Number.isSafeInteger(maximum) || maximum < 0 || output || inputIndex !== 0 || outputIndex !== 0)) throw new RangeError("Invalid bounded raw DEFLATE options");
45
- let out = output ?? new Uint8Array(limits ? Math.min(1024, maximum) : 1024);
46
- if (limits && !input.length) throw new Error("unexpected EOF");
47
- if (!(input.length - inputIndex)) return out;
48
- const payloadStart = limits ? 0 : gzipStart(input, inputIndex);
49
- if (!limits && payloadStart === input.length - 8) return out;
50
- if (!limits && payloadStart > input.length - 8) throw new Error("unexpected EOF");
51
- let pos = payloadStart * 8;
52
- let final = 0;
53
- let lengthBits = 0;
54
- let distBits = 0;
55
- let lengthMap;
56
- let distMap;
57
- const totalBits = input.length * 8;
58
- function ensureSize(length) {
59
- if (limits && (!Number.isSafeInteger(length) || length > maximum)) throw new RangeError("DEFLATE output byte limit exceeded");
60
- if (!output && length > out.length) {
61
- const old = out;
62
- out = new Uint8Array(limits ? Math.min(maximum, Math.max(old.length * 2, length)) : Math.max(old.length * 2, length));
63
- out.set(old);
64
- }
65
- }
66
- do {
67
- if (!lengthMap) {
68
- final = bits(input, pos, 1);
69
- const type = bits(input, pos + 1, 3);
70
- pos += 3;
71
- if (!type) {
72
- const s = shft(pos) + 4;
73
- const l = input[s - 4] | input[s - 3] << 8;
74
- if (limits && (s > input.length || ((input[s - 2] | input[s - 1] << 8) ^ l) !== 65535)) throw new Error("invalid stored block length");
75
- const t = s + l;
76
- if (t > input.length) throw new Error("unexpected EOF");
77
- ensureSize(outputIndex + l);
78
- out.set(input.subarray(s, t), outputIndex);
79
- outputIndex += l;
80
- pos = t * 8;
81
- continue;
82
- } else if (type === 1) {
83
- lengthMap = fixedLengthMap;
84
- distMap = fixedDistanceMap;
85
- lengthBits = 9;
86
- distBits = 5;
87
- } else if (type === 2) {
88
- const hLiteral = bits(input, pos, 31) + 257;
89
- const hcLengths = bits(input, pos + 10, 15) + 4;
90
- const tl = hLiteral + bits(input, pos + 5, 31) + 1;
91
- pos += 14;
92
- const lengthDistanceTree = new Uint8Array(tl);
93
- const codeLengthTree = /* @__PURE__ */ new Uint8Array(19);
94
- for (let i = 0; i < hcLengths; ++i) codeLengthTree[codeLengthIndexMap[i]] = bits(input, pos + i * 3, 7);
95
- pos += hcLengths * 3;
96
- const codeLengthBits = Math.max(...codeLengthTree);
97
- const clbMask = (1 << codeLengthBits) - 1;
98
- const codeLengthMap = huffMap(codeLengthTree, codeLengthBits, 1);
99
- for (let i = 0; i < tl;) {
100
- const r = codeLengthMap[bits(input, pos, clbMask)];
101
- if (limits && !r) throw new Error("invalid code length");
102
- pos += r & 15;
103
- const symbol = r >> 4;
104
- if (symbol < 16) lengthDistanceTree[i++] = symbol;
105
- else {
106
- let copy = 0;
107
- let n = 0;
108
- if (symbol === 16) {
109
- if (limits && i === 0) throw new Error("invalid code length repeat");
110
- n = 3 + bits(input, pos, 3);
111
- pos += 2;
112
- copy = lengthDistanceTree[i - 1];
113
- } else if (symbol === 17) {
114
- n = 3 + bits(input, pos, 7);
115
- pos += 3;
116
- } else if (symbol === 18) {
117
- n = 11 + bits(input, pos, 127);
118
- pos += 7;
119
- }
120
- if (limits && i + n > tl) throw new Error("invalid code length repeat");
121
- while (n--) lengthDistanceTree[i++] = copy;
122
- }
123
- }
124
- const lengthTree = lengthDistanceTree.subarray(0, hLiteral);
125
- const distanceTree = lengthDistanceTree.subarray(hLiteral);
126
- lengthBits = Math.max(...lengthTree);
127
- distBits = Math.max(...distanceTree);
128
- lengthMap = huffMap(lengthTree, lengthBits, 1);
129
- distMap = huffMap(distanceTree, distBits, 1);
130
- } else throw new Error("invalid block type");
131
- if (pos > totalBits) throw new Error("unexpected EOF");
132
- }
133
- ensureSize(limits ? Math.min(maximum, outputIndex + 131072) : outputIndex + 131072);
134
- const lms = (1 << lengthBits) - 1;
135
- const dms = (1 << distBits) - 1;
136
- for (;;) {
137
- const code = lengthMap[bits16(input, pos) & lms];
138
- const sym = code >> 4;
139
- pos += code & 15;
140
- if (pos > totalBits) throw new Error("unexpected EOF");
141
- if (!code) throw new Error("invalid length/literal");
142
- if (sym < 256) {
143
- if (limits) ensureSize(outputIndex + 1);
144
- out[outputIndex++] = sym;
145
- } else if (sym === 256) {
146
- lengthMap = void 0;
147
- break;
148
- } else {
149
- let add = sym - 254;
150
- if (sym > 264) {
151
- const index = sym - 257;
152
- const b = fixedLengthExtraBits[index];
153
- add = bits(input, pos, (1 << b) - 1) + fixedLength[index];
154
- pos += b;
155
- }
156
- if (!distMap) throw new Error("invalid distance map");
157
- const d = distMap[bits16(input, pos) & dms];
158
- const dsym = d >> 4;
159
- if (!d) throw new Error("invalid distance");
160
- if (limits && dsym > 29) throw new Error("invalid distance symbol");
161
- pos += d & 15;
162
- let dt = fixedDistance[dsym];
163
- if (dsym > 3) {
164
- const b = fixedDistanceExtraBits[dsym];
165
- dt += bits16(input, pos) & (1 << b) - 1;
166
- pos += b;
167
- }
168
- if (pos > totalBits) throw new Error("unexpected EOF");
169
- const end = outputIndex + add;
170
- if (outputIndex < dt) throw new Error("unexpected dictionary case");
171
- ensureSize(end);
172
- for (; outputIndex < end; outputIndex++) out[outputIndex] = out[outputIndex - dt];
173
- }
174
- }
175
- if (lengthMap) final = 1;
176
- } while (!final);
177
- if (limits && Math.ceil(pos / 8) !== input.length) throw new Error("trailing raw DEFLATE bytes");
178
- if (!limits && outputIndex < out.length) {
179
- const nextBlock = Math.ceil(pos / 8) + 8;
180
- gunzip(input, out, nextBlock, outputIndex);
181
- }
182
- if (limits && outputIndex !== out.length) return out.slice(0, outputIndex);
183
- if (!output) return out.subarray(0, outputIndex);
184
- return out;
185
- }
186
- export { gunzip };