@bldrs-ai/conway 1.399.1223 → 1.400.1226

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 (34) hide show
  1. package/compiled/examples/browser-bundled.cjs +177 -11
  2. package/compiled/examples/cli-bundled.cjs +177 -11
  3. package/compiled/examples/cli-step-bundled.cjs +69 -8
  4. package/compiled/examples/validator-bundled.cjs +177 -11
  5. package/compiled/src/ifc/ifc_step_model.d.ts +5 -2
  6. package/compiled/src/ifc/ifc_step_model.d.ts.map +1 -1
  7. package/compiled/src/ifc/ifc_step_model.js +5 -3
  8. package/compiled/src/ifc/ifc_step_parser.d.ts +36 -0
  9. package/compiled/src/ifc/ifc_step_parser.d.ts.map +1 -1
  10. package/compiled/src/ifc/ifc_step_parser.js +40 -0
  11. package/compiled/src/ifc/ifc_stream_model.test.d.ts +2 -0
  12. package/compiled/src/ifc/ifc_stream_model.test.d.ts.map +1 -0
  13. package/compiled/src/ifc/ifc_stream_model.test.js +70 -0
  14. package/compiled/src/parsing/parsing_buffer.d.ts +23 -0
  15. package/compiled/src/parsing/parsing_buffer.d.ts.map +1 -1
  16. package/compiled/src/parsing/parsing_buffer.js +31 -0
  17. package/compiled/src/step/parsing/byte_source.d.ts +55 -0
  18. package/compiled/src/step/parsing/byte_source.d.ts.map +1 -0
  19. package/compiled/src/step/parsing/byte_source.js +35 -0
  20. package/compiled/src/step/parsing/step_parser.d.ts +14 -0
  21. package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
  22. package/compiled/src/step/parsing/step_parser.js +41 -4
  23. package/compiled/src/step/parsing/streaming_index_builder.d.ts +60 -0
  24. package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -0
  25. package/compiled/src/step/parsing/streaming_index_builder.js +115 -0
  26. package/compiled/src/step/parsing/streaming_index_builder.test.d.ts +2 -0
  27. package/compiled/src/step/parsing/streaming_index_builder.test.d.ts.map +1 -0
  28. package/compiled/src/step/parsing/streaming_index_builder.test.js +107 -0
  29. package/compiled/src/step/step_model_base.d.ts +9 -3
  30. package/compiled/src/step/step_model_base.d.ts.map +1 -1
  31. package/compiled/src/step/step_model_base.js +9 -3
  32. package/compiled/src/version/version.js +1 -1
  33. package/compiled/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +1 -1
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
30
30
  var readline = __toESM(require("node:readline"), 1);
31
31
 
32
32
  // compiled/src/version/version.js
33
- var versionString = "Conway v1.399.1223";
33
+ var versionString = "Conway v1.400.1226";
34
34
 
35
35
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
36
36
  var wasmType = "";
@@ -1286,6 +1286,37 @@ var ParsingBuffer = class {
1286
1286
  this.buffer = buffer;
1287
1287
  this.end = endOffset;
1288
1288
  }
1289
+ /**
1290
+ * Streaming support: repoint this buffer at a fresh window over the same
1291
+ * logical stream while keeping `address` file-absolute.
1292
+ *
1293
+ * A moving-window parse holds only a slice of the source in memory at a
1294
+ * time. Between top-level records (where the rewind stack is empty) the
1295
+ * driver slides the window forward: it copies the unconsumed tail to the
1296
+ * front of a reused buffer, appends freshly-read bytes, and calls this to
1297
+ * repoint the parser. `addressBase` is the file offset that window index 0
1298
+ * now corresponds to; we store it as a negative initial offset so
1299
+ * `address` ( = cursor − initialOffset ) stays the file-absolute value the
1300
+ * non-streaming parse would have produced.
1301
+ *
1302
+ * Must only be called with the rewind stack empty (i.e. at a record
1303
+ * boundary) — the stack holds window-relative cursors that a slide would
1304
+ * invalidate. Throws otherwise.
1305
+ *
1306
+ * @param buffer The new window buffer.
1307
+ * @param cursorInWindow The cursor position within the new window.
1308
+ * @param endInWindow The count of valid bytes in the new window.
1309
+ * @param addressBase The file offset that window index 0 maps to.
1310
+ */
1311
+ rebaseWindow(buffer, cursorInWindow, endInWindow, addressBase) {
1312
+ if (this.rewindStack_.length !== 0) {
1313
+ throw Error("rebaseWindow called mid-transaction (rewind stack non-empty)");
1314
+ }
1315
+ this.buffer = buffer;
1316
+ this.cursor_ = cursorInWindow;
1317
+ this.initialOffset_ = -addressBase;
1318
+ this.end = endInWindow;
1319
+ }
1289
1320
  /**
1290
1321
  * Construct this with a buffer, initial offset into the buffer and an end offset.
1291
1322
  *
@@ -2538,6 +2569,29 @@ var StepParser = class extends StepHeaderParser {
2538
2569
  onProgress?.(next.value);
2539
2570
  }
2540
2571
  }
2572
+ /**
2573
+ * Streaming driver over parseDataBlockIncremental: identical parse (same
2574
+ * generator body) but invokes `onRecordBoundary` at every top-level record
2575
+ * boundary so a caller feeding the parser from a moving window can slide
2576
+ * that window forward while the rewind stack is empty. See
2577
+ * streaming_index_builder.ts for the coordinator that owns the window.
2578
+ *
2579
+ * @param input The input parsing buffer, positioned at the data section.
2580
+ * @param onRecordBoundary Called at each top-level record boundary with the
2581
+ * buffer; the callback may rebase the buffer's window in place.
2582
+ * @param onProgress Optional byte-cursor progress callback.
2583
+ * @return {BlockParseResult} The parsing result, including the index and result enum.
2584
+ */
2585
+ parseDataBlockStreamed(input2, onRecordBoundary, onProgress) {
2586
+ const parser211 = this.parseDataBlockIncremental(input2, onRecordBoundary);
2587
+ while (true) {
2588
+ const next = parser211.next();
2589
+ if (next.done === true) {
2590
+ return next.value;
2591
+ }
2592
+ onProgress?.(next.value);
2593
+ }
2594
+ }
2541
2595
  /**
2542
2596
  * Cooperative variant of parseDataBlock: identical parse (same generator
2543
2597
  * body), but periodically awaits a macrotask so the event loop can run —
@@ -2575,7 +2629,7 @@ var StepParser = class extends StepHeaderParser {
2575
2629
  * @yields {number} The current byte cursor within the input buffer.
2576
2630
  * @return {BlockParseResult} The parsing result, including the index and result enum.
2577
2631
  */
2578
- *parseDataBlockIncremental(input2) {
2632
+ *parseDataBlockIncremental(input2, onRecordBoundary) {
2579
2633
  const indexResult = { elements: [] };
2580
2634
  const match = input2.match;
2581
2635
  const comment = () => match(commentParser2);
@@ -2622,7 +2676,7 @@ var StepParser = class extends StepHeaderParser {
2622
2676
  return syntaxError();
2623
2677
  }
2624
2678
  whitespace();
2625
- const startElement = input2.cursor;
2679
+ const startElement = input2.address;
2626
2680
  let stackDepth = 1;
2627
2681
  const savedInlineElements = inlineElements;
2628
2682
  inlineElements = void 0;
@@ -2716,6 +2770,7 @@ var StepParser = class extends StepHeaderParser {
2716
2770
  if ((++parsedElementCount & PARSE_PROGRESS_ELEMENT_MASK) === 0) {
2717
2771
  yield input2.cursor;
2718
2772
  }
2773
+ onRecordBoundary?.(input2);
2719
2774
  if (!charws(HASH2)) {
2720
2775
  if (tokenws(END_SECTION)) {
2721
2776
  return parseResult2(ParseResult.COMPLETE);
@@ -2738,7 +2793,7 @@ var StepParser = class extends StepHeaderParser {
2738
2793
  }
2739
2794
  whitespace();
2740
2795
  inlineElements = void 0;
2741
- const startElement2 = input2.cursor;
2796
+ const startElement2 = input2.address;
2742
2797
  while (!charws(CLOSE_PAREN2)) {
2743
2798
  input2.begin();
2744
2799
  const elementResult = parseInlineElement(expressID);
@@ -2769,7 +2824,7 @@ var StepParser = class extends StepHeaderParser {
2769
2824
  return syntaxError();
2770
2825
  }
2771
2826
  whitespace();
2772
- const startElement = input2.cursor;
2827
+ const startElement = input2.address;
2773
2828
  let stackDepth = 1;
2774
2829
  inlineElements = void 0;
2775
2830
  while (stackDepth > 0) {
@@ -4920,18 +4975,24 @@ var StepModelBase = class {
4920
4975
  * Construct this step model with its matching schema, a buffer to read from and an element index.
4921
4976
  *
4922
4977
  * @param schema The Step schema this is based on.
4923
- * @param buffer_ The buffer to read this from.
4978
+ * @param buffer_ The buffer to read this from. Pass `undefined` together
4979
+ * with `provider` to build a model whose source is windowed from
4980
+ * construction (e.g. a streaming open — see buildModelStreaming); the
4981
+ * synchronous read paths (geometry extraction) then require the relevant
4982
+ * ranges to be resident, exactly as after `spillSourceToExternalStore`.
4924
4983
  * @param elementIndex The element index for this, parsed or deserialized - note this takes
4925
4984
  * ownership of this array in the sense it will modify values/unfold inline elements etc.
4985
+ * @param provider Optional pre-built buffer provider. When omitted, a
4986
+ * resident provider over `buffer_` is used (the classic path).
4926
4987
  */
4927
- constructor(schema, buffer_, elementIndex) {
4988
+ constructor(schema, buffer_, elementIndex, provider) {
4928
4989
  this.schema = schema;
4929
4990
  this.buffer_ = buffer_;
4930
4991
  this.vtableBuilder_ = new StepVtableBuilder();
4931
4992
  this.descriptorCache_ = [];
4932
4993
  this.elementMemoization = true;
4933
4994
  this.nullOnErrors = true;
4934
- this.bufferProvider_ = new ResidentStepBufferProvider(buffer_);
4995
+ this.bufferProvider_ = provider ?? new ResidentStepBufferProvider(buffer_);
4935
4996
  const localElementIndex = elementIndex;
4936
4997
  let where = 0;
4937
4998
  const firstInlineElement = localElementIndex.length;
@@ -70694,11 +70755,13 @@ var IfcStepModel = class extends StepModelBase {
70694
70755
  * Construct this model given a buffer containing the data and the parsed data index on that,
70695
70756
  * adding the typeIndex on top of that.
70696
70757
  *
70697
- * @param buffer The buffer to values from.
70758
+ * @param buffer The buffer to values from, or `undefined` with `provider`
70759
+ * set for a windowed (streaming) source.
70698
70760
  * @param elementIndex The parsed index to elements in the STEP.
70761
+ * @param provider Optional pre-built buffer provider (windowed source).
70699
70762
  */
70700
- constructor(buffer, elementIndex) {
70701
- super(schema_ifc_gen_default, buffer, elementIndex);
70763
+ constructor(buffer, elementIndex, provider) {
70764
+ super(schema_ifc_gen_default, buffer, elementIndex, provider);
70702
70765
  this.externalMappingType = ifc_step_external_mapping_default;
70703
70766
  this.geometry = new IfcModelGeometry(this);
70704
70767
  this.voidGeometry = new IfcModelGeometry(this, true);
@@ -70712,7 +70775,76 @@ var IfcStepModel = class extends StepModelBase {
70712
70775
  }
70713
70776
  };
70714
70777
 
70778
+ // compiled/src/step/parsing/streaming_index_builder.js
70779
+ function buildIndexStreaming(source, parser211, pool3) {
70780
+ const fileSize = source.byteLength;
70781
+ let windowBytes = Math.max(pool3, MIN_WINDOW);
70782
+ for (; ; ) {
70783
+ const window2 = new Uint8Array(windowBytes);
70784
+ let windowStartFile = 0;
70785
+ let windowLen = source.read(0, windowBytes, window2, 0);
70786
+ let bytesRead = windowLen;
70787
+ const input2 = new ParsingBuffer(window2, 0, windowLen);
70788
+ const [header, headerResult] = parser211.parseHeader(input2);
70789
+ if (headerResult !== ParseResult.COMPLETE) {
70790
+ return {
70791
+ header,
70792
+ elements: [],
70793
+ result: headerResult,
70794
+ stats: { pool: pool3, windowBytes, slides: 0, maxRecordLen: 0, bytesRead }
70795
+ };
70796
+ }
70797
+ const slideThreshold = windowBytes >> 1;
70798
+ let slides = 0;
70799
+ let maxRecordLen = 0;
70800
+ let prevBoundaryFile = windowStartFile + input2.cursor;
70801
+ const onRecordBoundary = (buffer) => {
70802
+ const cursor = buffer.cursor;
70803
+ const recordFileStart = windowStartFile + cursor;
70804
+ const recordLen = recordFileStart - prevBoundaryFile;
70805
+ if (recordLen > maxRecordLen) {
70806
+ maxRecordLen = recordLen;
70807
+ }
70808
+ prevBoundaryFile = recordFileStart;
70809
+ if (windowStartFile + windowLen >= fileSize) {
70810
+ return;
70811
+ }
70812
+ if (cursor < slideThreshold) {
70813
+ return;
70814
+ }
70815
+ const tail = windowLen - cursor;
70816
+ window2.copyWithin(0, cursor, windowLen);
70817
+ const want = windowBytes - tail;
70818
+ const got = source.read(windowStartFile + windowLen, want, window2, tail);
70819
+ bytesRead += got;
70820
+ windowLen = tail + got;
70821
+ windowStartFile = recordFileStart;
70822
+ buffer.rebaseWindow(window2, 0, windowLen, windowStartFile);
70823
+ ++slides;
70824
+ };
70825
+ const [index, result] = parser211.parseDataBlockStreamed(input2, onRecordBoundary);
70826
+ const stoppedShort = result !== ParseResult.COMPLETE;
70827
+ const notAtEof = windowStartFile + windowLen < fileSize;
70828
+ if (stoppedShort && notAtEof) {
70829
+ windowBytes *= 2;
70830
+ continue;
70831
+ }
70832
+ const lastRecordLen = windowStartFile + input2.cursor - prevBoundaryFile;
70833
+ if (lastRecordLen > maxRecordLen) {
70834
+ maxRecordLen = lastRecordLen;
70835
+ }
70836
+ return {
70837
+ header,
70838
+ elements: index.elements,
70839
+ result,
70840
+ stats: { pool: pool3, windowBytes, slides, maxRecordLen, bytesRead }
70841
+ };
70842
+ }
70843
+ }
70844
+ var MIN_WINDOW = 4 * 1024;
70845
+
70715
70846
  // compiled/src/ifc/ifc_step_parser.js
70847
+ var DEFAULT_STREAM_POOL_BYTES = 1024 * 1024;
70716
70848
  var IfcStepParser = class extends StepParser {
70717
70849
  /**
70718
70850
  * Construct the IFC step parser.
@@ -70745,6 +70877,40 @@ var IfcStepParser = class extends StepParser {
70745
70877
  const [itemIndex, parseResult2] = await this.parseDataBlockAsync(input2, onProgress);
70746
70878
  return [parseResult2, new IfcStepModel(input2.buffer, itemIndex.elements)];
70747
70879
  }
70880
+ /**
70881
+ * Build a model by streaming the source through a bounded moving window
70882
+ * (see buildIndexStreaming / M0) rather than parsing one resident buffer,
70883
+ * then backing the model with a windowed provider over `store` — so the
70884
+ * source is never held fully resident in the JS heap.
70885
+ *
70886
+ * `source` serves the parse (synchronous windowed reads — on a worker this
70887
+ * is an OPFS sync-access handle; in node/tests a file descriptor or buffer)
70888
+ * and `store` serves the model's post-parse property access (asynchronous
70889
+ * windowed reads paged in on demand — OPFS `File.slice()` in the browser).
70890
+ * Both view the same file bytes, so the file-absolute addresses the index
70891
+ * records resolve identically through either.
70892
+ *
70893
+ * NOTE (M1 scope): this delivers the bounded-memory *parse*. Synchronous
70894
+ * geometry extraction still needs its record ranges resident — as after
70895
+ * `spillSourceToExternalStore` — so a caller that extracts geometry must
70896
+ * `ensureResident` first (demand-driven geometry is M3). Property / index
70897
+ * access works directly via the async surfaces.
70898
+ *
70899
+ * @param source Synchronous byte source feeding the streaming parse.
70900
+ * @param store Async external store backing the windowed model.
70901
+ * @param opts Optional window sizing: `pool` (parse window),
70902
+ * `chunkBytes` / `maxResidentChunks` (model window).
70903
+ * @return {[ParseResult, IfcStepModel | undefined]} The parse result and
70904
+ * the windowed model.
70905
+ */
70906
+ parseStreamToModel(source, store, opts) {
70907
+ if (store.byteLength !== source.byteLength) {
70908
+ throw new Error(`Streaming store byteLength ${store.byteLength} does not match source byteLength ${source.byteLength}`);
70909
+ }
70910
+ const { elements, result } = buildIndexStreaming(source, this, opts?.pool ?? DEFAULT_STREAM_POOL_BYTES);
70911
+ const provider = new WindowedStepBufferProvider(store, opts?.chunkBytes, opts?.maxResidentChunks);
70912
+ return [result, new IfcStepModel(void 0, elements, provider)];
70913
+ }
70748
70914
  };
70749
70915
  IfcStepParser.Instance = new IfcStepParser();
70750
70916
  var ifc_step_parser_default = IfcStepParser;
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
14965
14965
  var import_process = require("process");
14966
14966
 
14967
14967
  // compiled/src/version/version.js
14968
- var versionString = "Conway v1.399.1223";
14968
+ var versionString = "Conway v1.400.1226";
14969
14969
 
14970
14970
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
14971
14971
  function pThreadsAllowed() {
@@ -17100,6 +17100,37 @@ var ParsingBuffer = class {
17100
17100
  this.buffer = buffer;
17101
17101
  this.end = endOffset;
17102
17102
  }
17103
+ /**
17104
+ * Streaming support: repoint this buffer at a fresh window over the same
17105
+ * logical stream while keeping `address` file-absolute.
17106
+ *
17107
+ * A moving-window parse holds only a slice of the source in memory at a
17108
+ * time. Between top-level records (where the rewind stack is empty) the
17109
+ * driver slides the window forward: it copies the unconsumed tail to the
17110
+ * front of a reused buffer, appends freshly-read bytes, and calls this to
17111
+ * repoint the parser. `addressBase` is the file offset that window index 0
17112
+ * now corresponds to; we store it as a negative initial offset so
17113
+ * `address` ( = cursor − initialOffset ) stays the file-absolute value the
17114
+ * non-streaming parse would have produced.
17115
+ *
17116
+ * Must only be called with the rewind stack empty (i.e. at a record
17117
+ * boundary) — the stack holds window-relative cursors that a slide would
17118
+ * invalidate. Throws otherwise.
17119
+ *
17120
+ * @param buffer The new window buffer.
17121
+ * @param cursorInWindow The cursor position within the new window.
17122
+ * @param endInWindow The count of valid bytes in the new window.
17123
+ * @param addressBase The file offset that window index 0 maps to.
17124
+ */
17125
+ rebaseWindow(buffer, cursorInWindow, endInWindow, addressBase) {
17126
+ if (this.rewindStack_.length !== 0) {
17127
+ throw Error("rebaseWindow called mid-transaction (rewind stack non-empty)");
17128
+ }
17129
+ this.buffer = buffer;
17130
+ this.cursor_ = cursorInWindow;
17131
+ this.initialOffset_ = -addressBase;
17132
+ this.end = endInWindow;
17133
+ }
17103
17134
  /**
17104
17135
  * Construct this with a buffer, initial offset into the buffer and an end offset.
17105
17136
  *
@@ -18352,6 +18383,29 @@ var StepParser = class extends StepHeaderParser {
18352
18383
  onProgress?.(next.value);
18353
18384
  }
18354
18385
  }
18386
+ /**
18387
+ * Streaming driver over parseDataBlockIncremental: identical parse (same
18388
+ * generator body) but invokes `onRecordBoundary` at every top-level record
18389
+ * boundary so a caller feeding the parser from a moving window can slide
18390
+ * that window forward while the rewind stack is empty. See
18391
+ * streaming_index_builder.ts for the coordinator that owns the window.
18392
+ *
18393
+ * @param input The input parsing buffer, positioned at the data section.
18394
+ * @param onRecordBoundary Called at each top-level record boundary with the
18395
+ * buffer; the callback may rebase the buffer's window in place.
18396
+ * @param onProgress Optional byte-cursor progress callback.
18397
+ * @return {BlockParseResult} The parsing result, including the index and result enum.
18398
+ */
18399
+ parseDataBlockStreamed(input, onRecordBoundary, onProgress) {
18400
+ const parser210 = this.parseDataBlockIncremental(input, onRecordBoundary);
18401
+ while (true) {
18402
+ const next = parser210.next();
18403
+ if (next.done === true) {
18404
+ return next.value;
18405
+ }
18406
+ onProgress?.(next.value);
18407
+ }
18408
+ }
18355
18409
  /**
18356
18410
  * Cooperative variant of parseDataBlock: identical parse (same generator
18357
18411
  * body), but periodically awaits a macrotask so the event loop can run —
@@ -18389,7 +18443,7 @@ var StepParser = class extends StepHeaderParser {
18389
18443
  * @yields {number} The current byte cursor within the input buffer.
18390
18444
  * @return {BlockParseResult} The parsing result, including the index and result enum.
18391
18445
  */
18392
- *parseDataBlockIncremental(input) {
18446
+ *parseDataBlockIncremental(input, onRecordBoundary) {
18393
18447
  const indexResult = { elements: [] };
18394
18448
  const match = input.match;
18395
18449
  const comment = () => match(commentParser2);
@@ -18436,7 +18490,7 @@ var StepParser = class extends StepHeaderParser {
18436
18490
  return syntaxError();
18437
18491
  }
18438
18492
  whitespace();
18439
- const startElement = input.cursor;
18493
+ const startElement = input.address;
18440
18494
  let stackDepth = 1;
18441
18495
  const savedInlineElements = inlineElements;
18442
18496
  inlineElements = void 0;
@@ -18530,6 +18584,7 @@ var StepParser = class extends StepHeaderParser {
18530
18584
  if ((++parsedElementCount & PARSE_PROGRESS_ELEMENT_MASK) === 0) {
18531
18585
  yield input.cursor;
18532
18586
  }
18587
+ onRecordBoundary?.(input);
18533
18588
  if (!charws(HASH2)) {
18534
18589
  if (tokenws(END_SECTION)) {
18535
18590
  return parseResult(ParseResult.COMPLETE);
@@ -18552,7 +18607,7 @@ var StepParser = class extends StepHeaderParser {
18552
18607
  }
18553
18608
  whitespace();
18554
18609
  inlineElements = void 0;
18555
- const startElement2 = input.cursor;
18610
+ const startElement2 = input.address;
18556
18611
  while (!charws(CLOSE_PAREN2)) {
18557
18612
  input.begin();
18558
18613
  const elementResult = parseInlineElement(expressID);
@@ -18583,7 +18638,7 @@ var StepParser = class extends StepHeaderParser {
18583
18638
  return syntaxError();
18584
18639
  }
18585
18640
  whitespace();
18586
- const startElement = input.cursor;
18641
+ const startElement = input.address;
18587
18642
  let stackDepth = 1;
18588
18643
  inlineElements = void 0;
18589
18644
  while (stackDepth > 0) {
@@ -20734,18 +20789,24 @@ var StepModelBase = class {
20734
20789
  * Construct this step model with its matching schema, a buffer to read from and an element index.
20735
20790
  *
20736
20791
  * @param schema The Step schema this is based on.
20737
- * @param buffer_ The buffer to read this from.
20792
+ * @param buffer_ The buffer to read this from. Pass `undefined` together
20793
+ * with `provider` to build a model whose source is windowed from
20794
+ * construction (e.g. a streaming open — see buildModelStreaming); the
20795
+ * synchronous read paths (geometry extraction) then require the relevant
20796
+ * ranges to be resident, exactly as after `spillSourceToExternalStore`.
20738
20797
  * @param elementIndex The element index for this, parsed or deserialized - note this takes
20739
20798
  * ownership of this array in the sense it will modify values/unfold inline elements etc.
20799
+ * @param provider Optional pre-built buffer provider. When omitted, a
20800
+ * resident provider over `buffer_` is used (the classic path).
20740
20801
  */
20741
- constructor(schema, buffer_, elementIndex) {
20802
+ constructor(schema, buffer_, elementIndex, provider) {
20742
20803
  this.schema = schema;
20743
20804
  this.buffer_ = buffer_;
20744
20805
  this.vtableBuilder_ = new StepVtableBuilder();
20745
20806
  this.descriptorCache_ = [];
20746
20807
  this.elementMemoization = true;
20747
20808
  this.nullOnErrors = true;
20748
- this.bufferProvider_ = new ResidentStepBufferProvider(buffer_);
20809
+ this.bufferProvider_ = provider ?? new ResidentStepBufferProvider(buffer_);
20749
20810
  const localElementIndex = elementIndex;
20750
20811
  let where = 0;
20751
20812
  const firstInlineElement = localElementIndex.length;
@@ -86539,11 +86600,13 @@ var IfcStepModel = class extends StepModelBase {
86539
86600
  * Construct this model given a buffer containing the data and the parsed data index on that,
86540
86601
  * adding the typeIndex on top of that.
86541
86602
  *
86542
- * @param buffer The buffer to values from.
86603
+ * @param buffer The buffer to values from, or `undefined` with `provider`
86604
+ * set for a windowed (streaming) source.
86543
86605
  * @param elementIndex The parsed index to elements in the STEP.
86606
+ * @param provider Optional pre-built buffer provider (windowed source).
86544
86607
  */
86545
- constructor(buffer, elementIndex) {
86546
- super(schema_ifc_gen_default, buffer, elementIndex);
86608
+ constructor(buffer, elementIndex, provider) {
86609
+ super(schema_ifc_gen_default, buffer, elementIndex, provider);
86547
86610
  this.externalMappingType = ifc_step_external_mapping_default;
86548
86611
  this.geometry = new IfcModelGeometry(this);
86549
86612
  this.voidGeometry = new IfcModelGeometry(this, true);
@@ -86557,7 +86620,76 @@ var IfcStepModel = class extends StepModelBase {
86557
86620
  }
86558
86621
  };
86559
86622
 
86623
+ // compiled/src/step/parsing/streaming_index_builder.js
86624
+ function buildIndexStreaming(source, parser210, pool3) {
86625
+ const fileSize = source.byteLength;
86626
+ let windowBytes = Math.max(pool3, MIN_WINDOW);
86627
+ for (; ; ) {
86628
+ const window2 = new Uint8Array(windowBytes);
86629
+ let windowStartFile = 0;
86630
+ let windowLen = source.read(0, windowBytes, window2, 0);
86631
+ let bytesRead = windowLen;
86632
+ const input = new ParsingBuffer(window2, 0, windowLen);
86633
+ const [header, headerResult] = parser210.parseHeader(input);
86634
+ if (headerResult !== ParseResult.COMPLETE) {
86635
+ return {
86636
+ header,
86637
+ elements: [],
86638
+ result: headerResult,
86639
+ stats: { pool: pool3, windowBytes, slides: 0, maxRecordLen: 0, bytesRead }
86640
+ };
86641
+ }
86642
+ const slideThreshold = windowBytes >> 1;
86643
+ let slides = 0;
86644
+ let maxRecordLen = 0;
86645
+ let prevBoundaryFile = windowStartFile + input.cursor;
86646
+ const onRecordBoundary = (buffer) => {
86647
+ const cursor = buffer.cursor;
86648
+ const recordFileStart = windowStartFile + cursor;
86649
+ const recordLen = recordFileStart - prevBoundaryFile;
86650
+ if (recordLen > maxRecordLen) {
86651
+ maxRecordLen = recordLen;
86652
+ }
86653
+ prevBoundaryFile = recordFileStart;
86654
+ if (windowStartFile + windowLen >= fileSize) {
86655
+ return;
86656
+ }
86657
+ if (cursor < slideThreshold) {
86658
+ return;
86659
+ }
86660
+ const tail = windowLen - cursor;
86661
+ window2.copyWithin(0, cursor, windowLen);
86662
+ const want = windowBytes - tail;
86663
+ const got = source.read(windowStartFile + windowLen, want, window2, tail);
86664
+ bytesRead += got;
86665
+ windowLen = tail + got;
86666
+ windowStartFile = recordFileStart;
86667
+ buffer.rebaseWindow(window2, 0, windowLen, windowStartFile);
86668
+ ++slides;
86669
+ };
86670
+ const [index, result] = parser210.parseDataBlockStreamed(input, onRecordBoundary);
86671
+ const stoppedShort = result !== ParseResult.COMPLETE;
86672
+ const notAtEof = windowStartFile + windowLen < fileSize;
86673
+ if (stoppedShort && notAtEof) {
86674
+ windowBytes *= 2;
86675
+ continue;
86676
+ }
86677
+ const lastRecordLen = windowStartFile + input.cursor - prevBoundaryFile;
86678
+ if (lastRecordLen > maxRecordLen) {
86679
+ maxRecordLen = lastRecordLen;
86680
+ }
86681
+ return {
86682
+ header,
86683
+ elements: index.elements,
86684
+ result,
86685
+ stats: { pool: pool3, windowBytes, slides, maxRecordLen, bytesRead }
86686
+ };
86687
+ }
86688
+ }
86689
+ var MIN_WINDOW = 4 * 1024;
86690
+
86560
86691
  // compiled/src/ifc/ifc_step_parser.js
86692
+ var DEFAULT_STREAM_POOL_BYTES = 1024 * 1024;
86561
86693
  var IfcStepParser = class extends StepParser {
86562
86694
  /**
86563
86695
  * Construct the IFC step parser.
@@ -86590,6 +86722,40 @@ var IfcStepParser = class extends StepParser {
86590
86722
  const [itemIndex, parseResult] = await this.parseDataBlockAsync(input, onProgress);
86591
86723
  return [parseResult, new IfcStepModel(input.buffer, itemIndex.elements)];
86592
86724
  }
86725
+ /**
86726
+ * Build a model by streaming the source through a bounded moving window
86727
+ * (see buildIndexStreaming / M0) rather than parsing one resident buffer,
86728
+ * then backing the model with a windowed provider over `store` — so the
86729
+ * source is never held fully resident in the JS heap.
86730
+ *
86731
+ * `source` serves the parse (synchronous windowed reads — on a worker this
86732
+ * is an OPFS sync-access handle; in node/tests a file descriptor or buffer)
86733
+ * and `store` serves the model's post-parse property access (asynchronous
86734
+ * windowed reads paged in on demand — OPFS `File.slice()` in the browser).
86735
+ * Both view the same file bytes, so the file-absolute addresses the index
86736
+ * records resolve identically through either.
86737
+ *
86738
+ * NOTE (M1 scope): this delivers the bounded-memory *parse*. Synchronous
86739
+ * geometry extraction still needs its record ranges resident — as after
86740
+ * `spillSourceToExternalStore` — so a caller that extracts geometry must
86741
+ * `ensureResident` first (demand-driven geometry is M3). Property / index
86742
+ * access works directly via the async surfaces.
86743
+ *
86744
+ * @param source Synchronous byte source feeding the streaming parse.
86745
+ * @param store Async external store backing the windowed model.
86746
+ * @param opts Optional window sizing: `pool` (parse window),
86747
+ * `chunkBytes` / `maxResidentChunks` (model window).
86748
+ * @return {[ParseResult, IfcStepModel | undefined]} The parse result and
86749
+ * the windowed model.
86750
+ */
86751
+ parseStreamToModel(source, store, opts) {
86752
+ if (store.byteLength !== source.byteLength) {
86753
+ throw new Error(`Streaming store byteLength ${store.byteLength} does not match source byteLength ${source.byteLength}`);
86754
+ }
86755
+ const { elements, result } = buildIndexStreaming(source, this, opts?.pool ?? DEFAULT_STREAM_POOL_BYTES);
86756
+ const provider = new WindowedStepBufferProvider(store, opts?.chunkBytes, opts?.maxResidentChunks);
86757
+ return [result, new IfcStepModel(void 0, elements, provider)];
86758
+ }
86593
86759
  };
86594
86760
  IfcStepParser.Instance = new IfcStepParser();
86595
86761
  var ifc_step_parser_default = IfcStepParser;