@gscdump/lakehouse 7.3.0 → 7.3.2

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.
@@ -15,12 +15,72 @@ async function hashContent(content) {
15
15
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
16
16
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
17
17
  }
18
+ function* boundedText(parts) {
19
+ const maxUnits = 65536;
20
+ let pending = "";
21
+ for (const part of parts) for (let at = 0; at < part.length;) {
22
+ const take = Math.min(maxUnits - pending.length, part.length - at);
23
+ pending += part.slice(at, at + take);
24
+ at += take;
25
+ if (pending.length === maxUnits) {
26
+ yield pending;
27
+ pending = "";
28
+ }
29
+ }
30
+ if (pending.length) yield pending;
31
+ }
32
+ async function hashFragments(parts, DigestStream) {
33
+ const digest = new DigestStream("SHA-256");
34
+ const encoder = new TextEncoderStream();
35
+ const writer = encoder.writable.getWriter();
36
+ const pipe = encoder.readable.pipeTo(digest);
37
+ const result = digest.digest;
38
+ const finished = Promise.all([pipe, result]);
39
+ finished.catch(() => {});
40
+ try {
41
+ for (const chunk of boundedText(parts)) await writer.write(chunk);
42
+ await writer.close();
43
+ const [, hash] = await finished;
44
+ return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
45
+ } catch (error) {
46
+ await writer.abort(error).catch(() => {});
47
+ await Promise.allSettled([pipe, result]);
48
+ throw error;
49
+ } finally {
50
+ writer.releaseLock();
51
+ }
52
+ }
53
+ async function hashLegacy(records, DigestStream) {
54
+ const rows = records.map((record) => Object.keys(record).sort().map((key) => `${key}=${String(record[key])}`).join("")).sort();
55
+ return DigestStream ? hashFragments(rows, DigestStream) : hashContent(rows.join(""));
56
+ }
57
+ function* framedParts(groups) {
58
+ yield "[";
59
+ let firstGroup = true;
60
+ for (const schema of Array.from(groups.keys()).sort()) {
61
+ if (!firstGroup) yield ",";
62
+ firstGroup = false;
63
+ yield "[";
64
+ yield schema;
65
+ yield ",[";
66
+ let firstRow = true;
67
+ for (const row of groups.get(schema).sort()) {
68
+ if (!firstRow) yield ",";
69
+ firstRow = false;
70
+ yield row;
71
+ }
72
+ yield "]]";
73
+ }
74
+ yield "]";
75
+ }
18
76
  async function deriveAppendIdentity(records) {
19
77
  if (!records.length) return {
20
78
  _tag: "Explicit",
21
79
  appendId: crypto.randomUUID()
22
80
  };
23
- const legacyAppendId = await hashContent(records.map((record) => Object.keys(record).sort().map((key) => `${key}=${String(record[key])}`).join("")).sort().join(""));
81
+ const available = crypto.DigestStream;
82
+ const DigestStream = typeof available === "function" && typeof TextEncoderStream === "function" ? available : void 0;
83
+ const legacyAppendId = await hashLegacy(records, DigestStream);
24
84
  const groups = /* @__PURE__ */ new Map();
25
85
  for (const record of records) {
26
86
  if (Object.getPrototypeOf(record) !== Object.prototype && Object.getPrototypeOf(record) !== null) throw new TypeError("Append records contain an unsupported value.");
@@ -31,6 +91,15 @@ async function deriveAppendIdentity(records) {
31
91
  if (group) group.push(row);
32
92
  else groups.set(schema, [row]);
33
93
  }
94
+ if (DigestStream) {
95
+ const contentHash = await hashFragments(framedParts(groups), DigestStream);
96
+ groups.clear();
97
+ return {
98
+ _tag: "Content",
99
+ appendId: `content-v2:${contentHash}`,
100
+ legacyAppendId
101
+ };
102
+ }
34
103
  const content = `[${Array.from(groups, ([schema, rows]) => `[${schema},[${rows.sort().join(",")}]]`).sort().join(",")}]`;
35
104
  groups.clear();
36
105
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/lakehouse",
3
3
  "type": "module",
4
- "version": "7.3.0",
4
+ "version": "7.3.2",
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",