@brass-build/client 0.4.1 → 0.5.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 (323) hide show
  1. package/AGENTS.md +124 -45
  2. package/CHANGELOG.md +80 -4
  3. package/README.md +666 -179
  4. package/dist/api-fetch.d.ts +8 -0
  5. package/dist/api-fetch.d.ts.map +1 -1
  6. package/dist/api-fetch.js +35 -8
  7. package/dist/api-fetch.js.map +1 -1
  8. package/dist/batched-get.d.ts +11 -0
  9. package/dist/batched-get.d.ts.map +1 -0
  10. package/dist/batched-get.js +92 -0
  11. package/dist/batched-get.js.map +1 -0
  12. package/dist/call-service.d.ts +4 -0
  13. package/dist/call-service.d.ts.map +1 -1
  14. package/dist/call-service.js +66 -3
  15. package/dist/call-service.js.map +1 -1
  16. package/dist/client.d.ts +48 -2
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +918 -115
  19. package/dist/client.js.map +1 -1
  20. package/dist/document-checkpoints.d.ts +25 -2
  21. package/dist/document-checkpoints.d.ts.map +1 -1
  22. package/dist/document-checkpoints.js +258 -61
  23. package/dist/document-checkpoints.js.map +1 -1
  24. package/dist/document-commands.d.ts.map +1 -1
  25. package/dist/document-commands.js +5 -3
  26. package/dist/document-commands.js.map +1 -1
  27. package/dist/document-contents.d.ts +10 -0
  28. package/dist/document-contents.d.ts.map +1 -1
  29. package/dist/document-contents.js +23 -1
  30. package/dist/document-contents.js.map +1 -1
  31. package/dist/document-set-subscription.d.ts +56 -0
  32. package/dist/document-set-subscription.d.ts.map +1 -0
  33. package/dist/document-set-subscription.js +240 -0
  34. package/dist/document-set-subscription.js.map +1 -0
  35. package/dist/document-signals.d.ts +6 -0
  36. package/dist/document-signals.d.ts.map +1 -1
  37. package/dist/document-signals.js +31 -1
  38. package/dist/document-signals.js.map +1 -1
  39. package/dist/document-streams.d.ts +36 -2
  40. package/dist/document-streams.d.ts.map +1 -1
  41. package/dist/document-streams.js +69 -18
  42. package/dist/document-streams.js.map +1 -1
  43. package/dist/document-thumbnail-capture.d.ts +12 -2
  44. package/dist/document-thumbnail-capture.d.ts.map +1 -1
  45. package/dist/document-thumbnail-capture.js +103 -27
  46. package/dist/document-thumbnail-capture.js.map +1 -1
  47. package/dist/documents-namespace.d.ts +92 -10
  48. package/dist/documents-namespace.d.ts.map +1 -1
  49. package/dist/documents-namespace.js +285 -39
  50. package/dist/documents-namespace.js.map +1 -1
  51. package/dist/documents.d.ts +173 -8
  52. package/dist/documents.d.ts.map +1 -1
  53. package/dist/documents.js +1778 -310
  54. package/dist/documents.js.map +1 -1
  55. package/dist/errors.d.ts +3 -3
  56. package/dist/errors.d.ts.map +1 -1
  57. package/dist/errors.js.map +1 -1
  58. package/dist/fetch-retry.d.ts +1 -0
  59. package/dist/fetch-retry.d.ts.map +1 -1
  60. package/dist/fetch-retry.js +2 -0
  61. package/dist/fetch-retry.js.map +1 -1
  62. package/dist/file-choice.d.ts +2 -0
  63. package/dist/file-choice.d.ts.map +1 -0
  64. package/dist/file-choice.js +27 -0
  65. package/dist/file-choice.js.map +1 -0
  66. package/dist/guest-link.d.ts +18 -1
  67. package/dist/guest-link.d.ts.map +1 -1
  68. package/dist/guest-link.js +329 -1
  69. package/dist/guest-link.js.map +1 -1
  70. package/dist/handle-types.d.ts +67 -5
  71. package/dist/handle-types.d.ts.map +1 -1
  72. package/dist/handles.d.ts +15 -19
  73. package/dist/handles.d.ts.map +1 -1
  74. package/dist/handles.js +231 -76
  75. package/dist/handles.js.map +1 -1
  76. package/dist/hosted-ui.d.ts +61 -0
  77. package/dist/hosted-ui.d.ts.map +1 -1
  78. package/dist/hosted-ui.js +94 -42
  79. package/dist/hosted-ui.js.map +1 -1
  80. package/dist/import-contract.d.ts +74 -0
  81. package/dist/import-contract.d.ts.map +1 -0
  82. package/dist/import-contract.js +163 -0
  83. package/dist/import-contract.js.map +1 -0
  84. package/dist/import-guest.d.ts +9 -0
  85. package/dist/import-guest.d.ts.map +1 -0
  86. package/dist/import-guest.js +155 -0
  87. package/dist/import-guest.js.map +1 -0
  88. package/dist/import-report.d.ts +17 -0
  89. package/dist/import-report.d.ts.map +1 -0
  90. package/dist/import-report.js +7 -0
  91. package/dist/import-report.js.map +1 -0
  92. package/dist/importer-wait.d.ts +2 -0
  93. package/dist/importer-wait.d.ts.map +1 -0
  94. package/dist/importer-wait.js +5 -0
  95. package/dist/importer-wait.js.map +1 -0
  96. package/dist/importer.d.ts +18 -4
  97. package/dist/importer.d.ts.map +1 -1
  98. package/dist/importer.js +322 -105
  99. package/dist/importer.js.map +1 -1
  100. package/dist/in-development.d.ts +2 -0
  101. package/dist/in-development.d.ts.map +1 -0
  102. package/dist/in-development.js +10 -0
  103. package/dist/in-development.js.map +1 -0
  104. package/dist/index.d.ts +16 -8
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +24 -8
  107. package/dist/index.js.map +1 -1
  108. package/dist/init-keys.d.ts +1 -1
  109. package/dist/init-keys.d.ts.map +1 -1
  110. package/dist/init-keys.js +6 -1
  111. package/dist/init-keys.js.map +1 -1
  112. package/dist/input-documents.d.ts +67 -0
  113. package/dist/input-documents.d.ts.map +1 -0
  114. package/dist/input-documents.js +693 -0
  115. package/dist/input-documents.js.map +1 -0
  116. package/dist/inputs-status.d.ts +80 -0
  117. package/dist/inputs-status.d.ts.map +1 -0
  118. package/dist/inputs-status.js +460 -0
  119. package/dist/inputs-status.js.map +1 -0
  120. package/dist/inputs.d.ts +2 -0
  121. package/dist/inputs.d.ts.map +1 -0
  122. package/dist/inputs.js +793 -0
  123. package/dist/inputs.js.map +1 -0
  124. package/dist/internal.d.ts +6 -1
  125. package/dist/internal.d.ts.map +1 -1
  126. package/dist/internal.js +4 -1
  127. package/dist/internal.js.map +1 -1
  128. package/dist/join-content.d.ts +33 -0
  129. package/dist/join-content.d.ts.map +1 -0
  130. package/dist/join-content.js +114 -0
  131. package/dist/join-content.js.map +1 -0
  132. package/dist/join-stamp.d.ts +3 -0
  133. package/dist/join-stamp.d.ts.map +1 -0
  134. package/dist/join-stamp.js +12 -0
  135. package/dist/join-stamp.js.map +1 -0
  136. package/dist/manifest-drift.d.ts +9 -0
  137. package/dist/manifest-drift.d.ts.map +1 -0
  138. package/dist/manifest-drift.js +46 -0
  139. package/dist/manifest-drift.js.map +1 -0
  140. package/dist/open-document.d.ts +109 -2
  141. package/dist/open-document.d.ts.map +1 -1
  142. package/dist/open-document.js +1822 -811
  143. package/dist/open-document.js.map +1 -1
  144. package/dist/page-restore.d.ts +12 -0
  145. package/dist/page-restore.d.ts.map +1 -0
  146. package/dist/page-restore.js +23 -0
  147. package/dist/page-restore.js.map +1 -0
  148. package/dist/placement.d.ts +13 -0
  149. package/dist/placement.d.ts.map +1 -0
  150. package/dist/placement.js +34 -0
  151. package/dist/placement.js.map +1 -0
  152. package/dist/presence.d.ts +1 -0
  153. package/dist/presence.d.ts.map +1 -1
  154. package/dist/presence.js +16 -4
  155. package/dist/presence.js.map +1 -1
  156. package/dist/read-only-document.d.ts +87 -0
  157. package/dist/read-only-document.d.ts.map +1 -0
  158. package/dist/read-only-document.js +4 -0
  159. package/dist/read-only-document.js.map +1 -0
  160. package/dist/record-paging.d.ts +63 -6
  161. package/dist/record-paging.d.ts.map +1 -1
  162. package/dist/record-paging.js +122 -29
  163. package/dist/record-paging.js.map +1 -1
  164. package/dist/references.d.ts +40 -0
  165. package/dist/references.d.ts.map +1 -0
  166. package/dist/references.js +135 -0
  167. package/dist/references.js.map +1 -0
  168. package/dist/request-lane.d.ts +5 -0
  169. package/dist/request-lane.d.ts.map +1 -0
  170. package/dist/request-lane.js +48 -0
  171. package/dist/request-lane.js.map +1 -0
  172. package/dist/sealed-arrow.d.ts +170 -0
  173. package/dist/sealed-arrow.d.ts.map +1 -0
  174. package/dist/sealed-arrow.js +769 -0
  175. package/dist/sealed-arrow.js.map +1 -0
  176. package/dist/sealed-cache.d.ts +21 -0
  177. package/dist/sealed-cache.d.ts.map +1 -0
  178. package/dist/sealed-cache.js +247 -0
  179. package/dist/sealed-cache.js.map +1 -0
  180. package/dist/sealed-encode.d.ts +87 -0
  181. package/dist/sealed-encode.d.ts.map +1 -0
  182. package/dist/sealed-encode.js +876 -0
  183. package/dist/sealed-encode.js.map +1 -0
  184. package/dist/sealed-handles.d.ts +45 -0
  185. package/dist/sealed-handles.d.ts.map +1 -0
  186. package/dist/sealed-handles.js +408 -0
  187. package/dist/sealed-handles.js.map +1 -0
  188. package/dist/sealed-store.d.ts +242 -0
  189. package/dist/sealed-store.d.ts.map +1 -0
  190. package/dist/sealed-store.js +1155 -0
  191. package/dist/sealed-store.js.map +1 -0
  192. package/dist/serializer.d.ts +6 -15
  193. package/dist/serializer.d.ts.map +1 -1
  194. package/dist/session-toolbar.d.ts +20 -5
  195. package/dist/session-toolbar.d.ts.map +1 -1
  196. package/dist/session-toolbar.js +419 -67
  197. package/dist/session-toolbar.js.map +1 -1
  198. package/dist/session.d.ts +38 -1
  199. package/dist/session.d.ts.map +1 -1
  200. package/dist/session.js +562 -79
  201. package/dist/session.js.map +1 -1
  202. package/dist/sha256.d.ts +3 -0
  203. package/dist/sha256.d.ts.map +1 -0
  204. package/dist/sha256.js +74 -0
  205. package/dist/sha256.js.map +1 -0
  206. package/dist/stable-handles.d.ts +19 -0
  207. package/dist/stable-handles.d.ts.map +1 -0
  208. package/dist/stable-handles.js +193 -0
  209. package/dist/stable-handles.js.map +1 -0
  210. package/dist/staged-seal.d.ts +2 -0
  211. package/dist/staged-seal.d.ts.map +1 -0
  212. package/dist/staged-seal.js +76 -0
  213. package/dist/staged-seal.js.map +1 -0
  214. package/dist/subscriber-delivery.d.ts +8 -0
  215. package/dist/subscriber-delivery.d.ts.map +1 -0
  216. package/dist/subscriber-delivery.js +31 -0
  217. package/dist/subscriber-delivery.js.map +1 -0
  218. package/dist/tail-op-import.d.ts +10 -0
  219. package/dist/tail-op-import.d.ts.map +1 -0
  220. package/dist/tail-op-import.js +33 -0
  221. package/dist/tail-op-import.js.map +1 -0
  222. package/dist/topic-lifecycle.d.ts +6 -0
  223. package/dist/topic-lifecycle.d.ts.map +1 -1
  224. package/dist/topic-lifecycle.js +32 -2
  225. package/dist/topic-lifecycle.js.map +1 -1
  226. package/dist/trial-replace-dialog.d.ts +10 -0
  227. package/dist/trial-replace-dialog.d.ts.map +1 -0
  228. package/dist/trial-replace-dialog.js +153 -0
  229. package/dist/trial-replace-dialog.js.map +1 -0
  230. package/dist/types.d.ts +8 -1
  231. package/dist/types.d.ts.map +1 -1
  232. package/dist/undo.d.ts +60 -5
  233. package/dist/undo.d.ts.map +1 -1
  234. package/dist/undo.js +344 -75
  235. package/dist/undo.js.map +1 -1
  236. package/dist/version-bar.d.ts +1 -0
  237. package/dist/version-bar.d.ts.map +1 -1
  238. package/dist/version-bar.js +2 -1
  239. package/dist/version-bar.js.map +1 -1
  240. package/dist/version.d.ts +2 -2
  241. package/dist/version.js +1 -1
  242. package/dist/ws-client.d.ts +21 -0
  243. package/dist/ws-client.d.ts.map +1 -1
  244. package/dist/ws-client.js +192 -8
  245. package/dist/ws-client.js.map +1 -1
  246. package/dist/ws-frame-limit.d.ts +1 -0
  247. package/dist/ws-frame-limit.d.ts.map +1 -1
  248. package/dist/ws-frame-limit.js.map +1 -1
  249. package/docs/app-manifest.md +121 -17
  250. package/docs/custom-chrome.md +24 -16
  251. package/docs/hosted-ui-advanced.md +38 -6
  252. package/docs/imported-documents.md +104 -51
  253. package/docs/presence.md +16 -4
  254. package/docs/schemas.md +8 -5
  255. package/docs/services.md +6 -6
  256. package/docs/streams.md +139 -150
  257. package/docs/text-fields.md +26 -19
  258. package/package.json +5 -2
  259. package/src/api-fetch.ts +53 -3
  260. package/src/auth-flow.ts +1 -1
  261. package/src/batched-get.ts +117 -0
  262. package/src/call-service.ts +78 -3
  263. package/src/client.ts +1022 -112
  264. package/src/document-checkpoints.ts +307 -65
  265. package/src/document-commands.ts +5 -5
  266. package/src/document-contents.ts +27 -1
  267. package/src/document-set-subscription.ts +274 -0
  268. package/src/document-signals.ts +47 -5
  269. package/src/document-streams.ts +85 -16
  270. package/src/document-thumbnail-capture.ts +118 -32
  271. package/src/documents-namespace.ts +539 -84
  272. package/src/documents.ts +2017 -348
  273. package/src/errors.ts +17 -6
  274. package/src/fetch-retry.ts +5 -0
  275. package/src/file-choice.ts +26 -0
  276. package/src/guest-link.ts +362 -1
  277. package/src/handle-types.ts +73 -47
  278. package/src/handles.ts +253 -83
  279. package/src/hosted-ui.ts +211 -50
  280. package/src/import-contract.ts +227 -0
  281. package/src/import-guest.ts +193 -0
  282. package/src/import-report.ts +24 -0
  283. package/src/importer-wait.ts +4 -0
  284. package/src/importer.ts +386 -131
  285. package/src/in-development.ts +10 -0
  286. package/src/index.ts +76 -16
  287. package/src/init-keys.ts +6 -1
  288. package/src/input-documents.ts +819 -0
  289. package/src/inputs-status.ts +598 -0
  290. package/src/inputs.ts +1090 -0
  291. package/src/internal.ts +7 -0
  292. package/src/join-content.ts +135 -0
  293. package/src/join-stamp.ts +12 -0
  294. package/src/manifest-drift.ts +54 -0
  295. package/src/open-document.ts +2346 -930
  296. package/src/page-restore.ts +38 -0
  297. package/src/placement.ts +54 -0
  298. package/src/presence.ts +21 -5
  299. package/src/read-only-document.ts +133 -0
  300. package/src/record-paging.ts +147 -25
  301. package/src/references.ts +207 -0
  302. package/src/request-lane.ts +59 -0
  303. package/src/sealed-arrow.ts +843 -0
  304. package/src/sealed-cache.ts +267 -0
  305. package/src/sealed-encode.ts +1015 -0
  306. package/src/sealed-handles.ts +486 -0
  307. package/src/sealed-store.ts +1330 -0
  308. package/src/serializer.ts +8 -20
  309. package/src/session-toolbar.ts +463 -72
  310. package/src/session.ts +740 -99
  311. package/src/sha256.ts +65 -0
  312. package/src/stable-handles.ts +235 -0
  313. package/src/staged-seal.ts +90 -0
  314. package/src/subscriber-delivery.ts +37 -0
  315. package/src/tail-op-import.ts +30 -0
  316. package/src/topic-lifecycle.ts +38 -2
  317. package/src/trial-replace-dialog.ts +163 -0
  318. package/src/types.ts +6 -6
  319. package/src/undo.ts +386 -73
  320. package/src/version-bar.ts +5 -1
  321. package/src/version.ts +1 -1
  322. package/src/ws-client.ts +208 -16
  323. package/src/ws-frame-limit.ts +1 -0
@@ -0,0 +1,1155 @@
1
+ // The FROZEN form: a stream's content as plain bytes, with no CRDT engine on
2
+ // either side of it.
3
+ //
4
+ // Content is JSON in the shape the stream's schema describes: each top-level
5
+ // entry is a root, and a root is a map (an object whose entries are its
6
+ // records), a list (an array), or a text (a string). An ordered map is two
7
+ // roots, the map and the list of its keys. That is exactly what an importer
8
+ // returns and what `doc.map`, `doc.list`, `doc.orderedMap` and `doc.text` read
9
+ // back, so a stream nobody has edited is fully determined by it. A CRDT
10
+ // snapshot of the same content pays for per-container state, a version vector
11
+ // and an op history describing no edits, and every reader pays an engine to
12
+ // get the content back; this holds the content and nothing else.
13
+ //
14
+ // It lives beside `record-paging.ts` because the published `@brass-build/client`
15
+ // ships unbundled and can only import published packages and its own relative
16
+ // files, so the frame and the one decoder every reader uses are defined once,
17
+ // here. Deliberately loro-free.
18
+ //
19
+ // The stored frame (version 4, below) holds every map as typed Arrow tables
20
+ // and the stream as one ZSTD frame (`sealed-arrow.ts` reads the tables,
21
+ // `sealed-encode.ts` writes them). Version 5 is the same tables gzipped, the
22
+ // staging form a producer without ZSTD uploads. Content can also be held in
23
+ // memory with the same roots (`SealedContent.fromJSON`), so a reader never
24
+ // asks which it holds. A join serves the stored frame only to a caller whose
25
+ // `accept` names it (`SEALED_ACCEPT`).
26
+ //
27
+ // A list whose items are exactly a map's keys (an ordered map's order list) is
28
+ // not stored: the map is stored in the list's order and the list is read back
29
+ // as the map's keys in stored order.
30
+ import { HEADER_KEY, META_KEY, ORDER_KEY, RECORDS_KEY, SCHEMA_VERSION_KEY, recordValueOf, } from './record-paging.js';
31
+ import { Gunzip } from 'fflate';
32
+ /**
33
+ * The peer every conversion of frozen content into a CRDT authors as.
34
+ *
35
+ * Pinned, so one frozen stream produces one document: two sides that convert
36
+ * the same content independently write the same ops, and their later edits
37
+ * merge rather than doubling every record.
38
+ *
39
+ * A live editor never authors as this peer. It keeps the one its own document
40
+ * was opened with, so every op it writes after a conversion is its own.
41
+ */
42
+ export const CONVERSION_PEER_ID = '1';
43
+ /** `BRST`. Four bytes so a reader handed the wrong object fails on its first
44
+ * word rather than on a length field it read out of a compressed run. */
45
+ const MAGIC = 0x42_52_53_54;
46
+ /** Named roots, every map as typed Arrow tables, the whole stream one ZSTD
47
+ * frame. What the platform stores; only its encoder writes it. */
48
+ export const VERSION_ARROW = 4;
49
+ /** Frame 4's body under gzip rather than ZSTD: what a producer without a ZSTD
50
+ * compressor (a browser) uploads. Staging only; the seal stores it as frame
51
+ * 4, so no reader is ever handed it. */
52
+ export const VERSION_ARROW_STAGED = 5;
53
+ /** The newest frame this build reads. */
54
+ export const NEWEST_VERSION = VERSION_ARROW;
55
+ /** The join `accept` token naming the stored frames this build decodes. */
56
+ export const SEALED_ACCEPT = `sealed@${VERSION_ARROW}`;
57
+ // Magic (u32), version (u8), reserved (3 bytes, zero), then 8 reserved bytes
58
+ // before the compressed body.
59
+ const PROLOGUE_BYTES = 16;
60
+ /** The kind a JSON value would be stored as, or null for a value no root can
61
+ * hold. */
62
+ export function sealedRootKindOf(value) {
63
+ if (typeof value === 'string')
64
+ return 'text';
65
+ if (Array.isArray(value))
66
+ return 'list';
67
+ if (value !== null && typeof value === 'object')
68
+ return 'map';
69
+ return null;
70
+ }
71
+ // --- Encoding ---------------------------------------------------------------
72
+ /** Whether a value is a record (`{ type, params }` and nothing else). */
73
+ function isRecordShaped(value) {
74
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
75
+ return false;
76
+ }
77
+ const o = value;
78
+ let count = 0;
79
+ for (const key in o) {
80
+ if (!Object.prototype.hasOwnProperty.call(o, key))
81
+ continue;
82
+ count += 1;
83
+ if (key !== 'type' && key !== 'params')
84
+ return false;
85
+ }
86
+ return count === 2 && typeof o['type'] === 'string';
87
+ }
88
+ /** A value split the way a record store reports it. */
89
+ export function sealedRecordOf(value) {
90
+ return isRecordShaped(value)
91
+ ? { tag: value.type, body: value.params }
92
+ : { tag: null, body: value };
93
+ }
94
+ function entriesFromTriples(records) {
95
+ return {
96
+ keys: records.map((r) => r[0]),
97
+ value: (i) => recordValueOf(records[i][1], records[i][2]),
98
+ release: (i) => {
99
+ records[i][2] = undefined;
100
+ },
101
+ };
102
+ }
103
+ function entriesFromObject(object, keys) {
104
+ return {
105
+ keys,
106
+ value: (i) => object[keys[i]],
107
+ release: (i) => {
108
+ object[keys[i]] = undefined;
109
+ },
110
+ };
111
+ }
112
+ /**
113
+ * The roots `content` is stored as, in its order: an ordered map's key list
114
+ * folded into the map it orders, and every map's entries in stored order.
115
+ *
116
+ * Throws for a root that is not a map, a list or a string.
117
+ *
118
+ * @internal
119
+ */
120
+ export function planSealedContent(content) {
121
+ const maps = new Map();
122
+ const lists = [];
123
+ for (const name of Object.keys(content)) {
124
+ const value = content[name];
125
+ const kind = sealedRootKindOf(value);
126
+ if (kind === null) {
127
+ throw new TypeError(`Content root "${name}" is neither an object, an array, nor a string.`);
128
+ }
129
+ if (kind === 'map')
130
+ maps.set(name, value);
131
+ else if (kind === 'list')
132
+ lists.push({ name, items: value });
133
+ }
134
+ // An ordered map: a list that is exactly a map's keys orders that map and
135
+ // is not stored. One list per map, first come.
136
+ const orderOf = new Map();
137
+ const listKeysOf = new Map();
138
+ for (const { name, items } of lists) {
139
+ for (const [mapName, object] of maps) {
140
+ if (orderOf.has(mapName))
141
+ continue;
142
+ if (isKeyPermutation(items, object)) {
143
+ orderOf.set(mapName, items);
144
+ listKeysOf.set(name, mapName);
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ const roots = [];
150
+ for (const name of Object.keys(content)) {
151
+ const value = content[name];
152
+ const kind = sealedRootKindOf(value);
153
+ if (kind === 'map') {
154
+ const object = value;
155
+ const keys = orderOf.get(name) ?? ownDefinedKeys(object);
156
+ roots.push({ kind: 'map', name, entries: entriesFromObject(object, keys) });
157
+ }
158
+ else if (kind === 'list') {
159
+ const mapName = listKeysOf.get(name);
160
+ roots.push(mapName !== undefined
161
+ ? { kind: 'list', name, keysOf: mapName }
162
+ : { kind: 'list', name, items: value });
163
+ }
164
+ else {
165
+ roots.push({ kind: 'text', name, text: value });
166
+ }
167
+ }
168
+ return roots;
169
+ }
170
+ /**
171
+ * The roots a record store handed over as triples is stored as.
172
+ *
173
+ * @internal
174
+ */
175
+ export function planSealedStore(source) {
176
+ return [
177
+ {
178
+ kind: 'map',
179
+ name: META_KEY,
180
+ entries: entriesFromObject({ [SCHEMA_VERSION_KEY]: source.schemaVersion }, [
181
+ SCHEMA_VERSION_KEY,
182
+ ]),
183
+ },
184
+ {
185
+ kind: 'list',
186
+ name: HEADER_KEY,
187
+ items: source.header.map((e) => recordValueOf(e.tag, e.body)),
188
+ },
189
+ { kind: 'map', name: RECORDS_KEY, entries: entriesFromTriples(source.records) },
190
+ { kind: 'list', name: ORDER_KEY, keysOf: RECORDS_KEY },
191
+ ];
192
+ }
193
+ /** The keys of an object whose value is not `undefined`, which JSON would
194
+ * drop and which `release` leaves behind. */
195
+ function ownDefinedKeys(object) {
196
+ return Object.keys(object).filter((k) => object[k] !== undefined);
197
+ }
198
+ function isKeyPermutation(items, object) {
199
+ const keys = ownDefinedKeys(object);
200
+ if (keys.length !== items.length)
201
+ return false;
202
+ const seen = new Set();
203
+ for (const item of items) {
204
+ if (typeof item !== 'string' || seen.has(item))
205
+ return false;
206
+ if (!Object.prototype.hasOwnProperty.call(object, item))
207
+ return false;
208
+ if (object[item] === undefined)
209
+ return false;
210
+ seen.add(item);
211
+ }
212
+ return true;
213
+ }
214
+ // --- Decoding ---------------------------------------------------------------
215
+ /** Whether a byte run opens as frozen content THIS reader can read. Cheap
216
+ * enough to ask of any object, since it reads the first five bytes. */
217
+ export function isSealedStore(bytes) {
218
+ const version = frameVersion(bytes);
219
+ return version === VERSION_ARROW || version === VERSION_ARROW_STAGED;
220
+ }
221
+ /** The frame version stamped on frozen content, or null for bytes that are
222
+ * not frozen content. */
223
+ export function frameVersion(bytes) {
224
+ if (bytes.byteLength < PROLOGUE_BYTES)
225
+ return null;
226
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
227
+ return view.getUint32(0) === MAGIC ? view.getUint8(4) : null;
228
+ }
229
+ /** Frozen content that could not be read. The message names the content
230
+ * rather than the field that failed, because every way this throws means the
231
+ * bytes are not content this reader can open. */
232
+ export class SealedStoreError extends Error {
233
+ underlying;
234
+ constructor(message,
235
+ /** The error the decoder raised, where this wraps one. */
236
+ underlying) {
237
+ super(message);
238
+ this.underlying = underlying;
239
+ }
240
+ }
241
+ /** `out[key] = value`, as an own property even where the key is `__proto__`. */
242
+ function setOwn(out, key, value) {
243
+ if (key === '__proto__') {
244
+ Object.defineProperty(out, key, { value, enumerable: true, writable: true, configurable: true });
245
+ }
246
+ else {
247
+ out[key] = value;
248
+ }
249
+ }
250
+ /** A map root held in memory: its keys in stored order and each value's JSON
251
+ * text, parsed afresh on every read. */
252
+ class SealedJsonMap {
253
+ _keys;
254
+ _lines;
255
+ _index = null;
256
+ constructor(_keys, _lines) {
257
+ this._keys = _keys;
258
+ this._lines = _lines;
259
+ }
260
+ size() {
261
+ return this._keys.length;
262
+ }
263
+ has(key) {
264
+ return this._at(key) >= 0;
265
+ }
266
+ get(key) {
267
+ const at = this._at(key);
268
+ return at < 0 ? undefined : JSON.parse(this._lines[at]);
269
+ }
270
+ keys() {
271
+ return [...this._keys];
272
+ }
273
+ forEach(visit) {
274
+ for (let i = 0; i < this._keys.length; i += 1)
275
+ visit(this._keys[i], JSON.parse(this._lines[i]));
276
+ }
277
+ *entries() {
278
+ for (let i = 0; i < this._keys.length; i += 1) {
279
+ yield [this._keys[i], JSON.parse(this._lines[i])];
280
+ }
281
+ }
282
+ valueBytes() {
283
+ let bytes = 0;
284
+ for (const line of this._lines)
285
+ bytes += line.length;
286
+ return bytes;
287
+ }
288
+ toJSON() {
289
+ const out = {};
290
+ this.forEach((key, value) => setOwn(out, key, value));
291
+ return out;
292
+ }
293
+ releaseDecoded() { }
294
+ _at(key) {
295
+ if (this._index === null) {
296
+ this._index = new Map();
297
+ for (let i = 0; i < this._keys.length; i += 1)
298
+ this._index.set(this._keys[i], i);
299
+ }
300
+ return this._index.get(key) ?? -1;
301
+ }
302
+ }
303
+ /** A map's entries as `SealedJsonMap` holds them. A key repeated keeps its
304
+ * first entry. CONSUMES the values. */
305
+ function jsonMapOf(entries) {
306
+ const keys = [];
307
+ const lines = [];
308
+ const seen = new Set();
309
+ for (let i = 0; i < entries.keys.length; i += 1) {
310
+ const key = entries.keys[i];
311
+ if (!seen.has(key)) {
312
+ seen.add(key);
313
+ keys.push(key);
314
+ lines.push(JSON.stringify(entries.value(i)) ?? 'null');
315
+ }
316
+ entries.release(i);
317
+ }
318
+ return new SealedJsonMap(keys, lines);
319
+ }
320
+ /** Content held in memory, from the roots it is stored as. */
321
+ function contentOfPlan(plan, builtBy) {
322
+ const roots = new Map();
323
+ for (const root of plan) {
324
+ if (root.kind === 'map')
325
+ roots.set(root.name, { kind: 'map', map: jsonMapOf(root.entries) });
326
+ else if (root.kind === 'list') {
327
+ roots.set(root.name, 'keysOf' in root
328
+ ? { kind: 'list', keysOf: root.keysOf }
329
+ : { kind: 'list', items: JSON.parse(listJson(root.items)) });
330
+ }
331
+ else
332
+ roots.set(root.name, { kind: 'text', value: root.text });
333
+ }
334
+ return SealedContent._of(new Uint8Array(0), roots, builtBy);
335
+ }
336
+ /** A list's items as one JSON array, an `undefined` item read as null. */
337
+ function listJson(items) {
338
+ return '[' + items.map((item) => JSON.stringify(item === undefined ? null : item) ?? 'null').join(',') + ']';
339
+ }
340
+ /**
341
+ * Decoded frozen content: the frame read and every root located, or content
342
+ * held in memory (`fromJSON`, `fromRecords`) with the same roots, so a reader
343
+ * never asks which one it holds.
344
+ */
345
+ export class SealedContent {
346
+ _bytes;
347
+ _roots;
348
+ _builtBy;
349
+ constructor(_bytes, _roots, _builtBy) {
350
+ this._bytes = _bytes;
351
+ this._roots = _roots;
352
+ this._builtBy = _builtBy;
353
+ }
354
+ /** Read the frame. Throws `SealedStoreError` for bytes that are not frozen
355
+ * content, or whose frame does not fit inside them. */
356
+ static decode(bytes) {
357
+ const version = frameVersion(bytes);
358
+ if (version === null)
359
+ throw new SealedStoreError('Not frozen content');
360
+ if (version === VERSION_ARROW || version === VERSION_ARROW_STAGED) {
361
+ return decodeArrowFrame(bytes, version);
362
+ }
363
+ throw new SealedStoreError(`Frozen content version ${version}, which this build reads up to ` +
364
+ `${NEWEST_VERSION}`);
365
+ }
366
+ /**
367
+ * Content held in memory, as `decode` reads the same content stored.
368
+ * CONSUMES the map values, as `sealedTablesOfImport` does. Throws for a
369
+ * root that is not a map, a list or a string.
370
+ */
371
+ static fromJSON(content, builtBy) {
372
+ return contentOfPlan(planSealedContent(content), builtBy ?? null);
373
+ }
374
+ /** A record store handed over as triples, held in memory. CONSUMES
375
+ * `source.records`. */
376
+ static fromRecords(source) {
377
+ return contentOfPlan(planSealedStore(source), source.builtBy ?? null);
378
+ }
379
+ /** @internal */
380
+ static fromPlan(plan, builtBy) {
381
+ return contentOfPlan(plan, builtBy);
382
+ }
383
+ /** @internal */
384
+ static _of(bytes, roots, builtBy) {
385
+ return new SealedContent(bytes, roots, builtBy);
386
+ }
387
+ /** The content's own size, which is what it costs to hold and transfer. */
388
+ get byteLength() {
389
+ return this._bytes.byteLength;
390
+ }
391
+ /** The importer that produced the content, or null for content that names
392
+ * none. */
393
+ builtBy() {
394
+ return this._builtBy;
395
+ }
396
+ rootNames() {
397
+ return [...this._roots.keys()];
398
+ }
399
+ kindOf(name) {
400
+ return this._roots.get(name)?.kind;
401
+ }
402
+ /** A map root as a lazy reader, or null when the content holds no such
403
+ * map. */
404
+ mapRoot(name) {
405
+ const root = this._roots.get(name);
406
+ return root?.kind === 'map' ? root.map : null;
407
+ }
408
+ /** The map a list root is the key walk of (an ordered map's order list), or
409
+ * null for a list stored with items of its own. */
410
+ keyWalkOf(name) {
411
+ const root = this._roots.get(name);
412
+ return root?.kind === 'list' && 'keysOf' in root ? root.keysOf : null;
413
+ }
414
+ /** Every entry of a map root, in stored order, without building the
415
+ * object. */
416
+ forEachMapEntry(name, visit) {
417
+ this.mapRoot(name)?.forEach(visit);
418
+ }
419
+ /** A map root as an object, or null when the content holds no such map. */
420
+ map(name) {
421
+ return this.mapRoot(name)?.toJSON() ?? null;
422
+ }
423
+ /** A list root's items, or null when the content holds no such list. */
424
+ list(name) {
425
+ const root = this._roots.get(name);
426
+ if (root?.kind !== 'list')
427
+ return null;
428
+ if ('items' in root)
429
+ return [...root.items];
430
+ return [...(this.mapRoot(root.keysOf)?.keys() ?? [])];
431
+ }
432
+ /** A text root, or null when the content holds no such text. */
433
+ text(name) {
434
+ const root = this._roots.get(name);
435
+ return root?.kind === 'text' ? root.value : null;
436
+ }
437
+ /** The whole content as the JSON it was encoded from. */
438
+ toJSON() {
439
+ const out = {};
440
+ for (const name of this._roots.keys()) {
441
+ const kind = this.kindOf(name);
442
+ if (kind === 'map')
443
+ setOwn(out, name, this.map(name));
444
+ else if (kind === 'list')
445
+ setOwn(out, name, this.list(name));
446
+ else if (kind === 'text')
447
+ setOwn(out, name, this.text(name));
448
+ }
449
+ return out;
450
+ }
451
+ }
452
+ /**
453
+ * The most a frame-4 or staging-frame body expands to. The encoder refuses to
454
+ * write tables past it and the decoder refuses a body that would expand past
455
+ * it, so every stored line is one every reader decodes, and a body an
456
+ * uploader shaped to expand further is refused before it is decompressed.
457
+ * The densest import measured (2.85M IFC geometry records) expands to 68 MB,
458
+ * and a 128 MiB CSV of single-digit cells to about 285 MB.
459
+ */
460
+ export const SEALED_INNER_MAX_BYTES = 256 * 1024 * 1024;
461
+ /** Content whose tables would expand past `SEALED_INNER_MAX_BYTES`. */
462
+ export class SealedContentTooLargeError extends SealedStoreError {
463
+ constructor() {
464
+ super(`Sealed content expands past ${SEALED_INNER_MAX_BYTES} bytes`);
465
+ this.name = 'SealedContentTooLargeError';
466
+ }
467
+ }
468
+ const ZSTD_MAGIC = 0xfd2fb528;
469
+ const ZSTD_BLOCK_MAX = 128 * 1024;
470
+ /**
471
+ * The most a ZSTD stream can write or allocate, read from its frame and block
472
+ * headers without decompressing it: per frame its window, and its declared
473
+ * content size, which a decoder holds the frame's output to. A frame that
474
+ * declares none is bounded by its blocks: each raw or RLE block's stated size
475
+ * plus the format's 128 KiB ceiling per compressed block, which overstates a
476
+ * level-19 frame (its blocks average a few KB). Both encoders here declare the
477
+ * size. Throws on a stream that is not ZSTD frames end to end.
478
+ */
479
+ export function zstdOutputBound(bytes) {
480
+ let at = 0;
481
+ let output = 0;
482
+ let largest = 0;
483
+ const need = (n) => {
484
+ if (at + n > bytes.length)
485
+ throw new RangeError('zstd stream is truncated');
486
+ };
487
+ const le = (n) => {
488
+ need(n);
489
+ let v = 0;
490
+ for (let i = n - 1; i >= 0; i -= 1)
491
+ v = v * 256 + bytes[at + i];
492
+ at += n;
493
+ return v;
494
+ };
495
+ while (at < bytes.length) {
496
+ const magic = le(4);
497
+ if ((magic & 0xfffffff0) >>> 0 === 0x184d2a50) {
498
+ const size = le(4);
499
+ need(size);
500
+ at += size;
501
+ continue;
502
+ }
503
+ if (magic !== ZSTD_MAGIC)
504
+ throw new RangeError('not a zstd frame');
505
+ const fhd = le(1);
506
+ if (fhd & 8)
507
+ throw new RangeError('zstd frame header sets a reserved bit');
508
+ const fcsFlag = fhd >> 6;
509
+ const single = (fhd >> 5) & 1;
510
+ const checksum = (fhd >> 2) & 1;
511
+ let window = 0;
512
+ if (single === 0) {
513
+ const wd = le(1);
514
+ const base = 2 ** (10 + (wd >> 3));
515
+ window = base + (base / 8) * (wd & 7);
516
+ }
517
+ le([0, 1, 2, 4][fhd & 3]);
518
+ const fcsBytes = fcsFlag === 0 ? single : 2 ** fcsFlag;
519
+ const fcs = fcsBytes === 0 ? 0 : le(fcsBytes) + (fcsBytes === 2 ? 256 : 0);
520
+ largest = Math.max(largest, window, fcs);
521
+ let blocks = 0;
522
+ for (;;) {
523
+ const header = le(3);
524
+ const type = (header >> 1) & 3;
525
+ const size = header >>> 3;
526
+ if (type === 0) {
527
+ need(size);
528
+ at += size;
529
+ blocks += size;
530
+ }
531
+ else if (type === 1) {
532
+ need(1);
533
+ at += 1;
534
+ blocks += size;
535
+ }
536
+ else if (type === 2) {
537
+ need(size);
538
+ at += size;
539
+ blocks += ZSTD_BLOCK_MAX;
540
+ }
541
+ else {
542
+ throw new RangeError('zstd block type is reserved');
543
+ }
544
+ if (header & 1)
545
+ break;
546
+ }
547
+ output += fcsBytes === 0 ? blocks : fcs;
548
+ if (checksum)
549
+ le(4);
550
+ }
551
+ return Math.max(output, largest);
552
+ }
553
+ /** A ZSTD body decompressed, refused before it is decompressed where its
554
+ * headers allow more than `max`. */
555
+ function unzstdCapped(unzstd, body, max) {
556
+ let bound;
557
+ try {
558
+ bound = zstdOutputBound(body);
559
+ }
560
+ catch (cause) {
561
+ throw new SealedStoreError('Frozen content is unreadable', cause);
562
+ }
563
+ if (bound > max)
564
+ throw new SealedContentTooLargeError();
565
+ return unzstd(body, max);
566
+ }
567
+ /** A gzip body inflated a slice at a time, refused once it passes `max`.
568
+ * A slice inflates to at most 1032 times its own size. */
569
+ function gunzipStreamed(body, max) {
570
+ const chunks = [];
571
+ let total = 0;
572
+ const inflate = new Gunzip((chunk) => {
573
+ total += chunk.byteLength;
574
+ if (total > max)
575
+ throw new SealedContentTooLargeError();
576
+ chunks.push(chunk);
577
+ });
578
+ const step = 16 * 1024;
579
+ for (let at = 0; at < body.length; at += step) {
580
+ inflate.push(body.subarray(at, at + step), at + step >= body.length);
581
+ }
582
+ const out = new Uint8Array(total);
583
+ let at = 0;
584
+ for (const chunk of chunks) {
585
+ out.set(chunk, at);
586
+ at += chunk.byteLength;
587
+ }
588
+ return out;
589
+ }
590
+ let sealedCodec = null;
591
+ let sealedCodecLoading = null;
592
+ /** Whether decoding these bytes needs `loadSealedCodec` first. */
593
+ export function needsSealedCodec(bytes) {
594
+ const version = frameVersion(bytes);
595
+ return (version === VERSION_ARROW || version === VERSION_ARROW_STAGED) && sealedCodec === null;
596
+ }
597
+ /** Node's error for output past `maxOutputLength`. */
598
+ function overMax(err) {
599
+ return err?.code === 'ERR_BUFFER_TOO_LARGE';
600
+ }
601
+ /** Load what a frame-4 decode needs. Under a Node that carries zstd, the
602
+ * built-in decompresses; anywhere else, `fzstd`. */
603
+ export function loadSealedCodec() {
604
+ if (sealedCodec !== null)
605
+ return Promise.resolve();
606
+ sealedCodecLoading ??= (async () => {
607
+ let native;
608
+ try {
609
+ const proc = globalThis.process;
610
+ native = proc?.getBuiltinModule?.('node:zlib');
611
+ }
612
+ catch {
613
+ native = undefined;
614
+ }
615
+ const nativeUnzstd = native?.zstdDecompressSync?.bind(native);
616
+ const nativeGunzip = native?.gunzipSync?.bind(native);
617
+ const [arrow, unzstd, reader] = await Promise.all([
618
+ import('apache-arrow'),
619
+ nativeUnzstd !== undefined
620
+ ? Promise.resolve((bytes, max) => {
621
+ try {
622
+ return new Uint8Array(nativeUnzstd(bytes, { maxOutputLength: max }));
623
+ }
624
+ catch (err) {
625
+ if (overMax(err))
626
+ throw new SealedContentTooLargeError();
627
+ throw err;
628
+ }
629
+ })
630
+ : // The frame headers bound what `fzstd` allocates and writes
631
+ // (`unzstdCapped` checks them first), so it runs uncapped.
632
+ import('fzstd').then((m) => (bytes) => m.decompress(bytes)),
633
+ import('./sealed-arrow.js'),
634
+ ]);
635
+ const gunzip = nativeGunzip !== undefined
636
+ ? (bytes, max) => {
637
+ try {
638
+ return new Uint8Array(nativeGunzip(bytes, { maxOutputLength: max }));
639
+ }
640
+ catch (err) {
641
+ if (overMax(err))
642
+ throw new SealedContentTooLargeError();
643
+ throw err;
644
+ }
645
+ }
646
+ : gunzipStreamed;
647
+ sealedCodec = { arrow: arrow, unzstd, gunzip, reader };
648
+ })().catch((err) => {
649
+ sealedCodecLoading = null;
650
+ throw err;
651
+ });
652
+ return sealedCodecLoading;
653
+ }
654
+ /** Decode frozen bytes, loading the frame-4 codec first when they need it.
655
+ * A frame-4 decode awaits `pause` before each table it parses, so a large
656
+ * decode leaves the page responsive. */
657
+ export async function decodeSealedContent(bytes, pause = slicedYield()) {
658
+ if (needsSealedCodec(bytes))
659
+ await loadSealedCodec();
660
+ const version = frameVersion(bytes);
661
+ if (version !== VERSION_ARROW && version !== VERSION_ARROW_STAGED)
662
+ return SealedContent.decode(bytes);
663
+ const steps = arrowFrameSteps(bytes, version);
664
+ for (let step = steps.next();; step = steps.next()) {
665
+ if (step.done === true)
666
+ return step.value;
667
+ await pause();
668
+ }
669
+ }
670
+ /** Work between yields. A yield costs a few milliseconds on a phone, and a
671
+ * frame holds hundreds of tables, so yielding before every one adds up. */
672
+ const YIELD_SLICE_MS = 25;
673
+ /** A pause that hands the event loop back once `sliceMs` of work has run
674
+ * since the last time it did. */
675
+ export function slicedYield(sliceMs = YIELD_SLICE_MS) {
676
+ let since = performance.now();
677
+ return async () => {
678
+ if (performance.now() - since < sliceMs)
679
+ return;
680
+ await yieldToEventLoop();
681
+ since = performance.now();
682
+ };
683
+ }
684
+ /** Hands the event loop back for one turn: `scheduler.yield` where the
685
+ * platform has it, else a message, which no timer clamp delays. */
686
+ export function yieldToEventLoop() {
687
+ const scheduler = globalThis.scheduler;
688
+ if (typeof scheduler?.yield === 'function')
689
+ return scheduler.yield();
690
+ if (typeof MessageChannel === 'function') {
691
+ return new Promise((resolve) => {
692
+ const channel = new MessageChannel();
693
+ channel.port1.onmessage = () => {
694
+ channel.port1.close();
695
+ resolve();
696
+ };
697
+ channel.port2.postMessage(null);
698
+ });
699
+ }
700
+ return new Promise((resolve) => setTimeout(resolve, 0));
701
+ }
702
+ /** A frame-4 object around its ZSTD-compressed parts, or with
703
+ * `VERSION_ARROW_STAGED`, the staging frame around its gzipped parts. */
704
+ export function arrowFrameBytes(compressed, version = VERSION_ARROW) {
705
+ const out = new Uint8Array(PROLOGUE_BYTES + compressed.byteLength);
706
+ const view = new DataView(out.buffer);
707
+ view.setUint32(0, MAGIC);
708
+ view.setUint8(4, version);
709
+ out.set(compressed, PROLOGUE_BYTES);
710
+ return out;
711
+ }
712
+ /** A frame-4 or staging-frame object's compressed parts. */
713
+ export function arrowFrameBody(bytes) {
714
+ return bytes.subarray(PROLOGUE_BYTES);
715
+ }
716
+ /**
717
+ * A frame-4 or staging-frame object's parts, decompressed. Throws
718
+ * `SealedContentTooLargeError` for a body that expands past
719
+ * `SEALED_INNER_MAX_BYTES`, and needs `loadSealedCodec` first.
720
+ */
721
+ export function arrowFrameInner(bytes) {
722
+ const version = frameVersion(bytes);
723
+ if (sealedCodec === null) {
724
+ throw new SealedStoreError(`Frozen content version ${version} needs its codec loaded first`);
725
+ }
726
+ const body = bytes.subarray(PROLOGUE_BYTES);
727
+ try {
728
+ return version === VERSION_ARROW_STAGED
729
+ ? sealedCodec.gunzip(body, SEALED_INNER_MAX_BYTES)
730
+ : unzstdCapped(sealedCodec.unzstd, body, SEALED_INNER_MAX_BYTES);
731
+ }
732
+ catch (err) {
733
+ if (err instanceof SealedStoreError)
734
+ throw err;
735
+ throw new SealedStoreError('Frozen content is unreadable', err);
736
+ }
737
+ }
738
+ /** The manifest part's name; table parts are named by their position. */
739
+ export const ARROW_MANIFEST_PART = 'manifest';
740
+ function decodeArrowFrame(bytes, version) {
741
+ const steps = arrowFrameSteps(bytes, version);
742
+ for (let step = steps.next();; step = steps.next())
743
+ if (step.done === true)
744
+ return step.value;
745
+ }
746
+ /** A frame-4 decode, stepping before each table it parses. */
747
+ function* arrowFrameSteps(bytes, version) {
748
+ if (sealedCodec === null) {
749
+ throw new SealedStoreError(`Frozen content version ${version} needs its codec loaded first`);
750
+ }
751
+ const codec = sealedCodec;
752
+ const inner = arrowFrameInner(bytes);
753
+ let parts;
754
+ try {
755
+ parts = codec.reader.unpackParts(inner);
756
+ }
757
+ catch (cause) {
758
+ throw new SealedStoreError('Frozen content is unreadable', cause);
759
+ }
760
+ const [manifestName, manifestBytes] = parts[0] ?? [];
761
+ if (manifestName !== ARROW_MANIFEST_PART || manifestBytes === undefined) {
762
+ throw new SealedStoreError('Frozen content descriptor is unreadable');
763
+ }
764
+ let manifest;
765
+ try {
766
+ manifest = JSON.parse(new TextDecoder().decode(manifestBytes));
767
+ }
768
+ catch (cause) {
769
+ throw new SealedStoreError('Frozen content descriptor is unreadable', cause);
770
+ }
771
+ if (!Array.isArray(manifest) || !Array.isArray(manifest[1])) {
772
+ throw new SealedStoreError('Frozen content descriptor is unreadable');
773
+ }
774
+ const tablesOf = new Map();
775
+ for (const [, ipc] of parts.slice(1)) {
776
+ yield;
777
+ let table;
778
+ let meta;
779
+ try {
780
+ codec.reader.checkIpcFile(ipc);
781
+ table = codec.arrow.tableFromIPC(ipc);
782
+ meta = JSON.parse(table.schema.metadata.get(codec.reader.TABLE_META_KEY) ?? '');
783
+ for (const batch of table.batches)
784
+ codec.reader.checkTableData(batch.data);
785
+ }
786
+ catch (cause) {
787
+ throw new SealedStoreError('Frozen table is unreadable', cause);
788
+ }
789
+ let list = tablesOf.get(meta.root);
790
+ if (list === undefined)
791
+ tablesOf.set(meta.root, (list = []));
792
+ for (const batch of table.batches)
793
+ list.push({ meta, data: batch.data });
794
+ }
795
+ const by = manifest[0];
796
+ const builtBy = Array.isArray(by) && typeof by[0] === 'string' && typeof by[1] === 'string'
797
+ ? { id: by[0], version: by[1] }
798
+ : null;
799
+ const roots = new Map();
800
+ for (const d of manifest[1]) {
801
+ if (!Array.isArray(d) || typeof d[1] !== 'string') {
802
+ throw new SealedStoreError('Frozen content descriptor is unreadable');
803
+ }
804
+ const name = d[1];
805
+ if (d[0] === 'map') {
806
+ roots.set(name, { kind: 'map', map: new SealedArrowMap(codec.reader, tablesOf.get(name) ?? []) });
807
+ }
808
+ else if (d[0] === 'list') {
809
+ if (typeof d[3] === 'string')
810
+ roots.set(name, { kind: 'list', keysOf: d[3] });
811
+ else if (Array.isArray(d[2]))
812
+ roots.set(name, { kind: 'list', items: d[2] });
813
+ else
814
+ throw new SealedStoreError('Frozen content descriptor is unreadable');
815
+ }
816
+ else if (d[0] === 'text' && typeof d[2] === 'string') {
817
+ roots.set(name, { kind: 'text', value: d[2] });
818
+ }
819
+ else {
820
+ throw new SealedStoreError(`Frozen content root "${name}" has an unknown kind`);
821
+ }
822
+ }
823
+ return SealedContent._of(bytes, roots, builtBy);
824
+ }
825
+ /** A row's value, with any failure to build it read as unreadable content. */
826
+ function readValue(t, row) {
827
+ try {
828
+ return t.value(row);
829
+ }
830
+ catch (cause) {
831
+ throw new SealedStoreError('Frozen table is unreadable', cause);
832
+ }
833
+ }
834
+ /** A row's key, with any failure to read it as a string read as unreadable
835
+ * content. */
836
+ function readKey(t, row) {
837
+ let key;
838
+ try {
839
+ key = t.key(row);
840
+ }
841
+ catch (cause) {
842
+ throw new SealedStoreError('Frozen table is unreadable', cause);
843
+ }
844
+ if (typeof key !== 'string')
845
+ throw new SealedStoreError('Frozen table is unreadable');
846
+ return key;
847
+ }
848
+ /**
849
+ * Records a hash per key as a walk meets them, in position order, and throws
850
+ * from `finish` if two keys were the same: the hashes are sorted, and only
851
+ * where two agree are the keys themselves rebuilt and compared. Kept to a
852
+ * typed array, so the check costs one sort of four bytes a key.
853
+ */
854
+ class KeyCheck {
855
+ _keyAt;
856
+ _hashes;
857
+ constructor(count, _keyAt) {
858
+ this._keyAt = _keyAt;
859
+ this._hashes = new Uint32Array(count);
860
+ }
861
+ add(key, at) {
862
+ this._hashes[at] = keyHash(key);
863
+ }
864
+ finish() {
865
+ const sorted = this._hashes.slice().sort();
866
+ for (let i = 1; i < sorted.length; i += 1) {
867
+ if (sorted[i] !== sorted[i - 1])
868
+ continue;
869
+ const h = sorted[i];
870
+ const seen = new Set();
871
+ for (let at = 0; at < this._hashes.length; at += 1) {
872
+ if (this._hashes[at] !== h)
873
+ continue;
874
+ const key = this._keyAt(at);
875
+ if (seen.has(key))
876
+ throw new SealedStoreError('Frozen table is unreadable');
877
+ seen.add(key);
878
+ }
879
+ while (i + 1 < sorted.length && sorted[i + 1] === h)
880
+ i += 1;
881
+ }
882
+ }
883
+ }
884
+ function keyHash(key) {
885
+ let h = 0x811c9dc5;
886
+ for (let i = 0; i < key.length; i += 1)
887
+ h = Math.imul(h ^ key.charCodeAt(i), 0x01000193);
888
+ return h >>> 0;
889
+ }
890
+ /** Keyed reads a frame-4 map answers by scanning its key columns before it
891
+ * builds an index of every key. A scan touches only typed arrays and costs
892
+ * milliseconds at a million records; the index costs a string and a map
893
+ * entry per record, which a few keyed reads never repay. */
894
+ const SCANNED_LOOKUPS = 64;
895
+ /** A version 4 map root. Its tables are prepared on first use; a value is
896
+ * built when it is read, so a keyed read builds the rows it asks for. Until a
897
+ * walk prepares every table, a keyed read prepares only the table it hits,
898
+ * decoding its dictionaries' entries as they are read. */
899
+ class SealedArrowMap {
900
+ _reader;
901
+ _stored;
902
+ _count;
903
+ _cx;
904
+ _ready;
905
+ _lazyCx = null;
906
+ _lazyReady = null;
907
+ _tables = null;
908
+ _tableAt = null;
909
+ _rowAt = null;
910
+ _keys = null;
911
+ _index = null;
912
+ _lookups = 0;
913
+ /** Whether a whole walk has found every key distinct. */
914
+ _unique = false;
915
+ constructor(_reader, _stored) {
916
+ this._reader = _reader;
917
+ this._stored = _stored;
918
+ let count = 0;
919
+ for (const t of _stored)
920
+ count += t.data.length;
921
+ this._count = count;
922
+ this._cx = new _reader.ReadContext();
923
+ this._ready = Array.from({ length: _stored.length });
924
+ }
925
+ size() {
926
+ return this._count;
927
+ }
928
+ has(key) {
929
+ return this._find(key) !== null;
930
+ }
931
+ get(key) {
932
+ const found = this._find(key);
933
+ return found === null ? undefined : readValue(this._keyedTable(found[0]), found[1]);
934
+ }
935
+ keys() {
936
+ if (this._keys === null) {
937
+ this._order();
938
+ const check = this._unique ? null : new KeyCheck(this._count, (at) => this._keyAt(at));
939
+ const keys = Array.from({ length: this._count });
940
+ for (let at = 0; at < this._count; at += 1) {
941
+ const key = this._keyAt(at);
942
+ check?.add(key, at);
943
+ keys[at] = key;
944
+ }
945
+ check?.finish();
946
+ this._unique = true;
947
+ this._keys = keys;
948
+ }
949
+ return this._keys;
950
+ }
951
+ forEach(visit) {
952
+ for (const [key, value] of this.entries())
953
+ visit(key, value);
954
+ }
955
+ *entries() {
956
+ this._order();
957
+ const tables = this._tables;
958
+ const tableAt = this._tableAt;
959
+ const rowAt = this._rowAt;
960
+ // A repeated key is reported once the walk has met both.
961
+ const check = this._unique ? null : new KeyCheck(this._count, (at) => this._keyAt(at));
962
+ for (let at = 0; at < this._count; at += 1) {
963
+ const t = tables[tableAt[at]];
964
+ const row = rowAt[at];
965
+ const key = readKey(t, row);
966
+ check?.add(key, at);
967
+ yield [key, readValue(t, row)];
968
+ }
969
+ check?.finish();
970
+ this._unique = true;
971
+ }
972
+ valueBytes() {
973
+ let bytes = 0;
974
+ for (const [, value] of this.entries())
975
+ bytes += (JSON.stringify(value) ?? 'null').length;
976
+ return bytes;
977
+ }
978
+ toJSON() {
979
+ const out = {};
980
+ for (const [key, value] of this.entries())
981
+ setOwn(out, key, value);
982
+ return out;
983
+ }
984
+ releaseDecoded() {
985
+ this._keys = null;
986
+ this._index = null;
987
+ this._lookups = 0;
988
+ }
989
+ /** The table and row holding `key`. */
990
+ _find(key) {
991
+ if (this._index === null && this._lookups >= SCANNED_LOOKUPS) {
992
+ const keys = this.keys();
993
+ const index = new Map();
994
+ for (let at = 0; at < keys.length; at += 1)
995
+ index.set(keys[at], at);
996
+ this._index = index;
997
+ }
998
+ if (this._index !== null) {
999
+ const at = this._index.get(key);
1000
+ return at === undefined ? null : [this._tableAt[at], this._rowAt[at]];
1001
+ }
1002
+ this._lookups += 1;
1003
+ const cx = this._tables === null ? this._lazy() : this._cx;
1004
+ const matches = [];
1005
+ for (let ti = 0; ti < this._stored.length; ti += 1) {
1006
+ const t = this._stored[ti];
1007
+ let rows;
1008
+ try {
1009
+ rows = this._reader.rowsWithKey(t.meta, t.data.children[0], key, cx);
1010
+ }
1011
+ catch (cause) {
1012
+ throw new SealedStoreError('Frozen table is unreadable', cause);
1013
+ }
1014
+ for (const row of rows)
1015
+ matches.push([ti, row]);
1016
+ }
1017
+ // The encoder writes each key once, so tables repeating one are not its.
1018
+ if (matches.length > 1)
1019
+ throw new SealedStoreError('Frozen table is unreadable');
1020
+ return matches[0] ?? null;
1021
+ }
1022
+ _keyAt(at) {
1023
+ return readKey(this._tables[this._tableAt[at]], this._rowAt[at]);
1024
+ }
1025
+ _lazy() {
1026
+ return (this._lazyCx ??= new this._reader.ReadContext(true));
1027
+ }
1028
+ /** A table for a keyed read: the walk's where it is prepared, else one
1029
+ * prepared lazily on its own. */
1030
+ _keyedTable(ti) {
1031
+ const ready = this._ready[ti];
1032
+ if (ready !== undefined)
1033
+ return ready;
1034
+ const lazy = (this._lazyReady ??= Array.from({ length: this._stored.length }));
1035
+ let t = lazy[ti];
1036
+ if (t === undefined) {
1037
+ const stored = this._stored[ti];
1038
+ try {
1039
+ t = this._reader.readyTable(stored.meta, stored.data, this._lazy());
1040
+ }
1041
+ catch (cause) {
1042
+ throw new SealedStoreError('Frozen table is unreadable', cause);
1043
+ }
1044
+ lazy[ti] = t;
1045
+ }
1046
+ return t;
1047
+ }
1048
+ _table(ti) {
1049
+ let t = this._ready[ti];
1050
+ if (t === undefined) {
1051
+ const stored = this._stored[ti];
1052
+ try {
1053
+ t = this._reader.readyTable(stored.meta, stored.data, this._cx);
1054
+ }
1055
+ catch (cause) {
1056
+ throw new SealedStoreError('Frozen table is unreadable', cause);
1057
+ }
1058
+ this._ready[ti] = t;
1059
+ }
1060
+ return t;
1061
+ }
1062
+ /** Each position's table and row. Every position in the map is held by
1063
+ * exactly one row, or the tables do not describe the map. */
1064
+ _order() {
1065
+ if (this._tables !== null)
1066
+ return;
1067
+ const tables = this._stored.map((_, ti) => this._table(ti));
1068
+ const tableAt = new Int32Array(this._count).fill(-1);
1069
+ const rowAt = new Int32Array(this._count);
1070
+ tables.forEach((t, ti) => {
1071
+ const positions = t.positions;
1072
+ for (let row = 0; row < positions.length; row += 1) {
1073
+ const at = positions[row];
1074
+ if (!(at >= 0 && at < this._count) || tableAt[at] !== -1) {
1075
+ throw new SealedStoreError('Frozen table is unreadable');
1076
+ }
1077
+ tableAt[at] = ti;
1078
+ rowAt[at] = row;
1079
+ }
1080
+ });
1081
+ this._tableAt = tableAt;
1082
+ this._rowAt = rowAt;
1083
+ this._tables = tables;
1084
+ this._lazyCx = null;
1085
+ this._lazyReady = null;
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Frozen content read as a record store: the `instances` map, its order, the
1090
+ * schema version and the header. Every server-side reader that walks records
1091
+ * (an exporter, a view) reads through this, and it answers for any frozen
1092
+ * content: a root the content does not hold reads as empty.
1093
+ */
1094
+ export class SealedStore {
1095
+ _content;
1096
+ _records;
1097
+ constructor(_content) {
1098
+ this._content = _content;
1099
+ this._records = _content.mapRoot(RECORDS_KEY);
1100
+ }
1101
+ static decode(bytes) {
1102
+ return new SealedStore(SealedContent.decode(bytes));
1103
+ }
1104
+ static of(content) {
1105
+ return new SealedStore(content);
1106
+ }
1107
+ /** The general form this view reads. */
1108
+ get content() {
1109
+ return this._content;
1110
+ }
1111
+ get byteLength() {
1112
+ return this._content.byteLength;
1113
+ }
1114
+ /** The schema version, the `meta` root's one key. */
1115
+ schemaVersion() {
1116
+ const v = this._content.mapRoot(META_KEY)?.get(SCHEMA_VERSION_KEY);
1117
+ return typeof v === 'string' ? v : '';
1118
+ }
1119
+ /** The header entries in the record shape, in order. */
1120
+ header() {
1121
+ const out = [];
1122
+ for (const item of this._content.list(HEADER_KEY) ?? []) {
1123
+ if (isRecordShaped(item))
1124
+ out.push({ tag: item.type, body: item.params });
1125
+ }
1126
+ return out;
1127
+ }
1128
+ builtBy() {
1129
+ return this._content.builtBy();
1130
+ }
1131
+ keys() {
1132
+ return this._records?.keys() ?? [];
1133
+ }
1134
+ recordCount() {
1135
+ return this._records?.size() ?? 0;
1136
+ }
1137
+ has(key) {
1138
+ return this._records?.has(key) ?? false;
1139
+ }
1140
+ get(key) {
1141
+ if (this._records === null || !this._records.has(key))
1142
+ return undefined;
1143
+ return sealedRecordOf(this._records.get(key));
1144
+ }
1145
+ forEachRecord(visit) {
1146
+ this._records?.forEach((key, value) => {
1147
+ const record = sealedRecordOf(value);
1148
+ visit(key, record.tag, record.body);
1149
+ });
1150
+ }
1151
+ releaseDecoded() {
1152
+ this._records?.releaseDecoded();
1153
+ }
1154
+ }
1155
+ //# sourceMappingURL=sealed-store.js.map