@bldrs-ai/conway 1.529.1492 → 1.534.1505

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 (58) hide show
  1. package/compiled/examples/browser-bundled.cjs +19 -19
  2. package/compiled/examples/cli-bundled.cjs +126 -44
  3. package/compiled/examples/cli-step-bundled.cjs +17 -15
  4. package/compiled/examples/validator-bundled.cjs +19 -19
  5. package/compiled/src/compat/web-ifc/ifc_api_deferred_open.test.js +120 -0
  6. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +32 -1
  7. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  8. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +77 -13
  9. package/compiled/src/ifc/ifc_scene_builder.d.ts +80 -0
  10. package/compiled/src/ifc/ifc_scene_builder.d.ts.map +1 -1
  11. package/compiled/src/ifc/ifc_scene_builder.js +119 -28
  12. package/compiled/src/ifc/ifc_spatial_skeleton.d.ts +145 -0
  13. package/compiled/src/ifc/ifc_spatial_skeleton.d.ts.map +1 -0
  14. package/compiled/src/ifc/ifc_spatial_skeleton.js +266 -0
  15. package/compiled/src/ifc/ifc_spatial_skeleton.test.d.ts +2 -0
  16. package/compiled/src/ifc/ifc_spatial_skeleton.test.d.ts.map +1 -0
  17. package/compiled/src/ifc/ifc_spatial_skeleton.test.js +199 -0
  18. package/compiled/src/ifc/ifc_stream_open.d.ts +41 -5
  19. package/compiled/src/ifc/ifc_stream_open.d.ts.map +1 -1
  20. package/compiled/src/ifc/ifc_stream_open.js +29 -2
  21. package/compiled/src/ifc/ifc_stream_open.test.js +26 -1
  22. package/compiled/src/index.d.ts +7 -3
  23. package/compiled/src/index.d.ts.map +1 -1
  24. package/compiled/src/index.js +6 -2
  25. package/compiled/src/namespace_surface.test.js +6 -1
  26. package/compiled/src/step/parsing/prefix_type_index.d.ts +124 -0
  27. package/compiled/src/step/parsing/prefix_type_index.d.ts.map +1 -0
  28. package/compiled/src/step/parsing/prefix_type_index.js +207 -0
  29. package/compiled/src/step/parsing/prefix_type_index.test.d.ts +2 -0
  30. package/compiled/src/step/parsing/prefix_type_index.test.d.ts.map +1 -0
  31. package/compiled/src/step/parsing/prefix_type_index.test.js +298 -0
  32. package/compiled/src/step/parsing/record_event.d.ts +23 -0
  33. package/compiled/src/step/parsing/record_event.d.ts.map +1 -0
  34. package/compiled/src/step/parsing/record_event.js +1 -0
  35. package/compiled/src/step/parsing/record_field_cursor.d.ts +94 -0
  36. package/compiled/src/step/parsing/record_field_cursor.d.ts.map +1 -0
  37. package/compiled/src/step/parsing/record_field_cursor.js +189 -0
  38. package/compiled/src/step/parsing/step_parser.d.ts +9 -6
  39. package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
  40. package/compiled/src/step/parsing/step_parser.js +21 -14
  41. package/compiled/src/step/parsing/streaming_index_builder.d.ts +13 -5
  42. package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -1
  43. package/compiled/src/step/parsing/streaming_index_builder.js +9 -4
  44. package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts +24 -8
  45. package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts.map +1 -1
  46. package/compiled/src/step/parsing/streaming_record_dispatcher.js +34 -13
  47. package/compiled/src/stream/index.d.ts +12 -5
  48. package/compiled/src/stream/index.d.ts.map +1 -1
  49. package/compiled/src/stream/index.js +11 -4
  50. package/compiled/src/version/version.js +1 -1
  51. package/compiled/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +1 -1
  53. package/compiled/src/step/parsing/incremental_type_index.d.ts +0 -64
  54. package/compiled/src/step/parsing/incremental_type_index.d.ts.map +0 -1
  55. package/compiled/src/step/parsing/incremental_type_index.js +0 -92
  56. package/compiled/src/step/parsing/incremental_type_index.test.d.ts +0 -2
  57. package/compiled/src/step/parsing/incremental_type_index.test.d.ts.map +0 -1
  58. package/compiled/src/step/parsing/incremental_type_index.test.js +0 -55
@@ -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.529.1492";
33
+ var versionString = "Conway v1.534.1505";
34
34
 
35
35
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
36
36
  var wasmType = "";
@@ -2626,10 +2626,12 @@ var StepParser = class extends StepHeaderParser {
2626
2626
  * buffer; the callback may rebase the buffer's window in place. Must be
2627
2627
  * synchronous on this driver — a returned Promise is a caller bug.
2628
2628
  * @param onRecordIndexed Called as each top-level record is indexed, with
2629
- * its localID, expressID and typeID (0 for external-mapping records) — the
2630
- * seam for incremental semantic consumers (type index, roots registry,
2631
- * names skeleton). Must be synchronous and cheap; expensive work belongs on
2632
- * a demand queue, not the parse path.
2629
+ * its localID, expressID, typeID (0 for external-mapping records) and the
2630
+ * record's bytes as an un-materialised `(buffer, byteOffset, byteLength)`
2631
+ * window view — the seam for incremental semantic consumers (type index,
2632
+ * roots registry, names skeleton). See {@link RecordEventHandler} for the
2633
+ * window-lifetime rule. Must be synchronous and cheap; expensive work
2634
+ * belongs on a demand queue, not the parse path.
2633
2635
  * @param onProgress Optional byte-cursor progress callback.
2634
2636
  * @return {BlockParseResult} The parsing result, including the index and result enum.
2635
2637
  */
@@ -2913,11 +2915,6 @@ var StepParser = class extends StepHeaderParser {
2913
2915
  if (!charws(SEMICOLON)) {
2914
2916
  return syntaxError();
2915
2917
  }
2916
- {
2917
- const recordLen = input2.address - startElement2;
2918
- onRecordIndexed?.(topLevelCount, expressID, 0, input2.buffer.subarray(input2.cursor - recordLen, input2.cursor));
2919
- }
2920
- ++topLevelCount;
2921
2918
  pushEntry({
2922
2919
  address: startElement2,
2923
2920
  length: input2.address - startElement2,
@@ -2927,6 +2924,11 @@ var StepParser = class extends StepHeaderParser {
2927
2924
  inlineEntities: inlineElements,
2928
2925
  multiMapping: multiElements
2929
2926
  });
2927
+ {
2928
+ const recordLen = input2.address - startElement2;
2929
+ onRecordIndexed?.(topLevelCount, expressID, 0, input2.buffer, input2.cursor - recordLen, recordLen);
2930
+ }
2931
+ ++topLevelCount;
2930
2932
  multiElements = void 0;
2931
2933
  continue;
2932
2934
  }
@@ -3006,11 +3008,6 @@ var StepParser = class extends StepHeaderParser {
3006
3008
  if (!charws(SEMICOLON)) {
3007
3009
  return syntaxError();
3008
3010
  }
3009
- {
3010
- const recordLen = input2.address - startElement;
3011
- onRecordIndexed?.(topLevelCount, expressID, foundItem, input2.buffer.subarray(input2.cursor - recordLen, input2.cursor));
3012
- }
3013
- ++topLevelCount;
3014
3011
  pushEntry({
3015
3012
  address: startElement,
3016
3013
  length: input2.address - startElement,
@@ -3018,6 +3015,11 @@ var StepParser = class extends StepHeaderParser {
3018
3015
  expressID,
3019
3016
  inlineEntities: inlineElements
3020
3017
  });
3018
+ {
3019
+ const recordLen = input2.address - startElement;
3020
+ onRecordIndexed?.(topLevelCount, expressID, foundItem, input2.buffer, input2.cursor - recordLen, recordLen);
3021
+ }
3022
+ ++topLevelCount;
3021
3023
  }
3022
3024
  return parseResult2(ParseResult.INCOMPLETE);
3023
3025
  }
@@ -72163,13 +72165,11 @@ async function buildIndexStreamingAsync(source, parser211, pool3, onRecordIndexe
72163
72165
  };
72164
72166
  }
72165
72167
  }
72166
- function buildColumnarIndexStreaming(source, parser211, pool3, onRecordIndexed) {
72167
- const sink = new ColumnarIndexSink();
72168
+ function buildColumnarIndexStreaming(source, parser211, pool3, onRecordIndexed, sink = new ColumnarIndexSink()) {
72168
72169
  const { header, result, stats } = buildIndexStreaming(source, parser211, pool3, onRecordIndexed, sink);
72169
72170
  return { header, columns: sink.finalize(), result, stats };
72170
72171
  }
72171
- async function buildColumnarIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, onProgress, yieldIntervalMs) {
72172
- const sink = new ColumnarIndexSink();
72172
+ async function buildColumnarIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, onProgress, yieldIntervalMs, sink = new ColumnarIndexSink()) {
72173
72173
  const { header, result, stats } = await buildIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, sink, onProgress, yieldIntervalMs);
72174
72174
  return { header, columns: sink.finalize(), result, stats };
72175
72175
  }
@@ -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.529.1492";
14968
+ var versionString = "Conway v1.534.1505";
14969
14969
 
14970
14970
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
14971
14971
  function pThreadsAllowed() {
@@ -18462,10 +18462,12 @@ var StepParser = class extends StepHeaderParser {
18462
18462
  * buffer; the callback may rebase the buffer's window in place. Must be
18463
18463
  * synchronous on this driver — a returned Promise is a caller bug.
18464
18464
  * @param onRecordIndexed Called as each top-level record is indexed, with
18465
- * its localID, expressID and typeID (0 for external-mapping records) — the
18466
- * seam for incremental semantic consumers (type index, roots registry,
18467
- * names skeleton). Must be synchronous and cheap; expensive work belongs on
18468
- * a demand queue, not the parse path.
18465
+ * its localID, expressID, typeID (0 for external-mapping records) and the
18466
+ * record's bytes as an un-materialised `(buffer, byteOffset, byteLength)`
18467
+ * window view — the seam for incremental semantic consumers (type index,
18468
+ * roots registry, names skeleton). See {@link RecordEventHandler} for the
18469
+ * window-lifetime rule. Must be synchronous and cheap; expensive work
18470
+ * belongs on a demand queue, not the parse path.
18469
18471
  * @param onProgress Optional byte-cursor progress callback.
18470
18472
  * @return {BlockParseResult} The parsing result, including the index and result enum.
18471
18473
  */
@@ -18749,11 +18751,6 @@ var StepParser = class extends StepHeaderParser {
18749
18751
  if (!charws(SEMICOLON)) {
18750
18752
  return syntaxError();
18751
18753
  }
18752
- {
18753
- const recordLen = input.address - startElement2;
18754
- onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer.subarray(input.cursor - recordLen, input.cursor));
18755
- }
18756
- ++topLevelCount;
18757
18754
  pushEntry({
18758
18755
  address: startElement2,
18759
18756
  length: input.address - startElement2,
@@ -18763,6 +18760,11 @@ var StepParser = class extends StepHeaderParser {
18763
18760
  inlineEntities: inlineElements,
18764
18761
  multiMapping: multiElements
18765
18762
  });
18763
+ {
18764
+ const recordLen = input.address - startElement2;
18765
+ onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer, input.cursor - recordLen, recordLen);
18766
+ }
18767
+ ++topLevelCount;
18766
18768
  multiElements = void 0;
18767
18769
  continue;
18768
18770
  }
@@ -18842,11 +18844,6 @@ var StepParser = class extends StepHeaderParser {
18842
18844
  if (!charws(SEMICOLON)) {
18843
18845
  return syntaxError();
18844
18846
  }
18845
- {
18846
- const recordLen = input.address - startElement;
18847
- onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer.subarray(input.cursor - recordLen, input.cursor));
18848
- }
18849
- ++topLevelCount;
18850
18847
  pushEntry({
18851
18848
  address: startElement,
18852
18849
  length: input.address - startElement,
@@ -18854,6 +18851,11 @@ var StepParser = class extends StepHeaderParser {
18854
18851
  expressID,
18855
18852
  inlineEntities: inlineElements
18856
18853
  });
18854
+ {
18855
+ const recordLen = input.address - startElement;
18856
+ onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer, input.cursor - recordLen, recordLen);
18857
+ }
18858
+ ++topLevelCount;
18857
18859
  }
18858
18860
  return parseResult(ParseResult.INCOMPLETE);
18859
18861
  }
@@ -88131,13 +88133,11 @@ async function buildIndexStreamingAsync(source, parser210, pool3, onRecordIndexe
88131
88133
  };
88132
88134
  }
88133
88135
  }
88134
- function buildColumnarIndexStreaming(source, parser210, pool3, onRecordIndexed) {
88135
- const sink = new ColumnarIndexSink();
88136
+ function buildColumnarIndexStreaming(source, parser210, pool3, onRecordIndexed, sink = new ColumnarIndexSink()) {
88136
88137
  const { header, result, stats } = buildIndexStreaming(source, parser210, pool3, onRecordIndexed, sink);
88137
88138
  return { header, columns: sink.finalize(), result, stats };
88138
88139
  }
88139
- async function buildColumnarIndexStreamingAsync(source, parser210, pool3, onRecordIndexed, onProgress, yieldIntervalMs) {
88140
- const sink = new ColumnarIndexSink();
88140
+ async function buildColumnarIndexStreamingAsync(source, parser210, pool3, onRecordIndexed, onProgress, yieldIntervalMs, sink = new ColumnarIndexSink()) {
88141
88141
  const { header, result, stats } = await buildIndexStreamingAsync(source, parser210, pool3, onRecordIndexed, sink, onProgress, yieldIntervalMs);
88142
88142
  return { header, columns: sink.finalize(), result, stats };
88143
88143
  }
@@ -88456,6 +88456,7 @@ var IfcSceneBuilder = class {
88456
88456
  this.sceneLocalIdMap_ = /* @__PURE__ */ new Map();
88457
88457
  this.geometrySet_ = /* @__PURE__ */ new Set();
88458
88458
  this.sceneStack_ = [];
88459
+ this.geometryNodeVisits_ = 0;
88459
88460
  }
88460
88461
  /**
88461
88462
  *
@@ -88646,37 +88647,118 @@ var IfcSceneBuilder = class {
88646
88647
  isAllSpaces() {
88647
88648
  return this.scene_.every((node) => !(node instanceof IfcSceneGeometry) || node.isSpace);
88648
88649
  }
88650
+ *walk(includeSpaces = false) {
88651
+ for (let index = 0; index < this.scene_.length; ++index) {
88652
+ const resolved = this.resolveGeometryNode_(index, includeSpaces);
88653
+ if (resolved === void 0 || resolved[2] === void 0) {
88654
+ continue;
88655
+ }
88656
+ yield [resolved[0], resolved[1], resolved[2], resolved[3], resolved[4]];
88657
+ }
88658
+ }
88649
88659
  /**
88650
- * Walk the current scene.
88660
+ * The number of nodes in the scene array. Append-only, so a caller that
88661
+ * remembers this value has a stable cursor into the scene: everything
88662
+ * added after it sits at a higher index.
88651
88663
  *
88652
- * @yields Raw absolute matrix transform, the native absolute transform, the canonical mesh,
88653
- * @param includeSpaces
88654
- * the canonical material and the associated step element as it walks the hierarchy.
88655
- * @param walkTemporary Include temporary items.
88664
+ * @return {number} The node count.
88656
88665
  */
88657
- *walk(includeSpaces = false) {
88658
- for (const node of this.scene_) {
88659
- if (node instanceof IfcSceneGeometry && (includeSpaces || !node.isSpace)) {
88660
- const parentIndex = node.parentIndex;
88661
- const geometry = node.model.geometry?.getByLocalID(node.localID);
88662
- if (geometry === void 0) {
88663
- continue;
88664
- }
88665
- let parentNode;
88666
- if (parentIndex !== void 0) {
88667
- parentNode = this.scene_[parentIndex];
88668
- }
88669
- const material = this.materials.getMaterialByGeometryID(node.materialOverideLocalID ?? geometry.localID);
88670
- yield [
88671
- parentNode?.absoluteTransform,
88672
- parentNode?.absoluteNativeTransform,
88673
- geometry,
88674
- material !== void 0 ? material[0] : void 0,
88675
- node.relatedElementLocalId !== void 0 ? this.model.getElementByLocalID(node.relatedElementLocalId) : void 0
88676
- ];
88666
+ get nodeCount() {
88667
+ return this.scene_.length;
88668
+ }
88669
+ /**
88670
+ * How many times a node has been considered by any walk form since this
88671
+ * scene was built.
88672
+ *
88673
+ * Exists so the *complexity* of an incremental consumer can be asserted
88674
+ * rather than timed: a capture that walks only what the scene gained
88675
+ * visits each node about once for the whole load, where one that
88676
+ * re-walks from zero visits every node once per call. Output parity
88677
+ * cannot distinguish those — both deliver the same meshes — so without
88678
+ * this a regression from O(new nodes) back to O(batches x scene) passes
88679
+ * every correctness test while restoring the cost. See the batch-size
88680
+ * invariance test in ifc_api_deferred_open.test.ts.
88681
+ *
88682
+ * @return {number} The visit count.
88683
+ */
88684
+ get geometryNodeVisits() {
88685
+ return this.geometryNodeVisits_;
88686
+ }
88687
+ /**
88688
+ * Walk only the nodes added at or after `startIndex` — the incremental
88689
+ * form of {@link walk}, for a consumer draining the scene in batches.
88690
+ *
88691
+ * Unlike `walk`, this yields nodes whose geometry does NOT resolve, with
88692
+ * `undefined` in the geometry slot and the node's index last. That is
88693
+ * deliberate: a cursor-based caller passes each index exactly once, so a
88694
+ * node skipped for missing geometry would be lost forever, where the
88695
+ * whole-scene walk would have picked it up on its next pass. Geometry can
88696
+ * appear late — a product that maps geometry a release freed re-extracts
88697
+ * it — so the caller must park those indices and retry them with
88698
+ * {@link walkNode}. Yielding them is what makes that possible.
88699
+ *
88700
+ * @param startIndex First node index to consider.
88701
+ * @param includeSpaces Include space geometry.
88702
+ * @yields The walk tuple plus the node's index.
88703
+ */
88704
+ *walkFrom(startIndex, includeSpaces = false) {
88705
+ for (let index = startIndex; index < this.scene_.length; ++index) {
88706
+ const resolved = this.resolveGeometryNode_(index, includeSpaces);
88707
+ if (resolved === void 0) {
88708
+ continue;
88677
88709
  }
88710
+ yield resolved;
88678
88711
  }
88679
88712
  }
88713
+ /**
88714
+ * Resolve one node by index, for retrying a node {@link walkFrom} yielded
88715
+ * without geometry.
88716
+ *
88717
+ * @param index The node's index in the scene array.
88718
+ * @param includeSpaces Include space geometry.
88719
+ * @return {[...]|undefined} The walk tuple plus the index, or undefined
88720
+ * when the node isn't geometry this walk covers.
88721
+ */
88722
+ walkNode(index, includeSpaces = false) {
88723
+ return this.resolveGeometryNode_(index, includeSpaces);
88724
+ }
88725
+ /**
88726
+ * The shared body of walk/walkFrom/walkNode: one node in, one walk tuple
88727
+ * out. Single-sourced so the incremental walk cannot drift from the whole
88728
+ * -scene one — a drift here would show up as geometry placed differently
88729
+ * depending on batch size.
88730
+ *
88731
+ * @param index The node's index in the scene array.
88732
+ * @param includeSpaces Include space geometry.
88733
+ * @return {[...]|undefined} Undefined when the node isn't a geometry node
88734
+ * this walk covers; otherwise the tuple, whose geometry slot is undefined
88735
+ * if the geometry doesn't currently resolve.
88736
+ */
88737
+ resolveGeometryNode_(index, includeSpaces) {
88738
+ ++this.geometryNodeVisits_;
88739
+ const node = this.scene_[index];
88740
+ if (!(node instanceof IfcSceneGeometry) || !includeSpaces && node.isSpace) {
88741
+ return void 0;
88742
+ }
88743
+ const geometry = node.model.geometry?.getByLocalID(node.localID);
88744
+ if (geometry === void 0) {
88745
+ return [void 0, void 0, void 0, void 0, void 0, index];
88746
+ }
88747
+ const parentIndex = node.parentIndex;
88748
+ let parentNode;
88749
+ if (parentIndex !== void 0) {
88750
+ parentNode = this.scene_[parentIndex];
88751
+ }
88752
+ const material = this.materials.getMaterialByGeometryID(node.materialOverideLocalID ?? geometry.localID);
88753
+ return [
88754
+ parentNode?.absoluteTransform,
88755
+ parentNode?.absoluteNativeTransform,
88756
+ geometry,
88757
+ material !== void 0 ? material[0] : void 0,
88758
+ node.relatedElementLocalId !== void 0 ? this.model.getElementByLocalID(node.relatedElementLocalId) : void 0,
88759
+ index
88760
+ ];
88761
+ }
88680
88762
  /**
88681
88763
  *
88682
88764
  */
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
15943
15943
  };
15944
15944
 
15945
15945
  // compiled/src/version/version.js
15946
- var versionString = "Conway v1.529.1492";
15946
+ var versionString = "Conway v1.534.1505";
15947
15947
 
15948
15948
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
15949
15949
  function pThreadsAllowed() {
@@ -18365,10 +18365,12 @@ var StepParser = class extends StepHeaderParser {
18365
18365
  * buffer; the callback may rebase the buffer's window in place. Must be
18366
18366
  * synchronous on this driver — a returned Promise is a caller bug.
18367
18367
  * @param onRecordIndexed Called as each top-level record is indexed, with
18368
- * its localID, expressID and typeID (0 for external-mapping records) — the
18369
- * seam for incremental semantic consumers (type index, roots registry,
18370
- * names skeleton). Must be synchronous and cheap; expensive work belongs on
18371
- * a demand queue, not the parse path.
18368
+ * its localID, expressID, typeID (0 for external-mapping records) and the
18369
+ * record's bytes as an un-materialised `(buffer, byteOffset, byteLength)`
18370
+ * window view — the seam for incremental semantic consumers (type index,
18371
+ * roots registry, names skeleton). See {@link RecordEventHandler} for the
18372
+ * window-lifetime rule. Must be synchronous and cheap; expensive work
18373
+ * belongs on a demand queue, not the parse path.
18372
18374
  * @param onProgress Optional byte-cursor progress callback.
18373
18375
  * @return {BlockParseResult} The parsing result, including the index and result enum.
18374
18376
  */
@@ -18652,11 +18654,6 @@ var StepParser = class extends StepHeaderParser {
18652
18654
  if (!charws(SEMICOLON)) {
18653
18655
  return syntaxError();
18654
18656
  }
18655
- {
18656
- const recordLen = input.address - startElement2;
18657
- onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer.subarray(input.cursor - recordLen, input.cursor));
18658
- }
18659
- ++topLevelCount;
18660
18657
  pushEntry({
18661
18658
  address: startElement2,
18662
18659
  length: input.address - startElement2,
@@ -18666,6 +18663,11 @@ var StepParser = class extends StepHeaderParser {
18666
18663
  inlineEntities: inlineElements,
18667
18664
  multiMapping: multiElements
18668
18665
  });
18666
+ {
18667
+ const recordLen = input.address - startElement2;
18668
+ onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer, input.cursor - recordLen, recordLen);
18669
+ }
18670
+ ++topLevelCount;
18669
18671
  multiElements = void 0;
18670
18672
  continue;
18671
18673
  }
@@ -18745,11 +18747,6 @@ var StepParser = class extends StepHeaderParser {
18745
18747
  if (!charws(SEMICOLON)) {
18746
18748
  return syntaxError();
18747
18749
  }
18748
- {
18749
- const recordLen = input.address - startElement;
18750
- onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer.subarray(input.cursor - recordLen, input.cursor));
18751
- }
18752
- ++topLevelCount;
18753
18750
  pushEntry({
18754
18751
  address: startElement,
18755
18752
  length: input.address - startElement,
@@ -18757,6 +18754,11 @@ var StepParser = class extends StepHeaderParser {
18757
18754
  expressID,
18758
18755
  inlineEntities: inlineElements
18759
18756
  });
18757
+ {
18758
+ const recordLen = input.address - startElement;
18759
+ onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer, input.cursor - recordLen, recordLen);
18760
+ }
18761
+ ++topLevelCount;
18760
18762
  }
18761
18763
  return parseResult(ParseResult.INCOMPLETE);
18762
18764
  }
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
944
944
  var entity_types_ifc_gen_default = EntityTypesIfc;
945
945
 
946
946
  // compiled/src/version/version.js
947
- var versionString = "Conway v1.529.1492";
947
+ var versionString = "Conway v1.534.1505";
948
948
 
949
949
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
950
950
  var wasmType = "";
@@ -3540,10 +3540,12 @@ var StepParser = class extends StepHeaderParser {
3540
3540
  * buffer; the callback may rebase the buffer's window in place. Must be
3541
3541
  * synchronous on this driver — a returned Promise is a caller bug.
3542
3542
  * @param onRecordIndexed Called as each top-level record is indexed, with
3543
- * its localID, expressID and typeID (0 for external-mapping records) — the
3544
- * seam for incremental semantic consumers (type index, roots registry,
3545
- * names skeleton). Must be synchronous and cheap; expensive work belongs on
3546
- * a demand queue, not the parse path.
3543
+ * its localID, expressID, typeID (0 for external-mapping records) and the
3544
+ * record's bytes as an un-materialised `(buffer, byteOffset, byteLength)`
3545
+ * window view — the seam for incremental semantic consumers (type index,
3546
+ * roots registry, names skeleton). See {@link RecordEventHandler} for the
3547
+ * window-lifetime rule. Must be synchronous and cheap; expensive work
3548
+ * belongs on a demand queue, not the parse path.
3547
3549
  * @param onProgress Optional byte-cursor progress callback.
3548
3550
  * @return {BlockParseResult} The parsing result, including the index and result enum.
3549
3551
  */
@@ -3827,11 +3829,6 @@ var StepParser = class extends StepHeaderParser {
3827
3829
  if (!charws(SEMICOLON)) {
3828
3830
  return syntaxError();
3829
3831
  }
3830
- {
3831
- const recordLen = input.address - startElement2;
3832
- onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer.subarray(input.cursor - recordLen, input.cursor));
3833
- }
3834
- ++topLevelCount;
3835
3832
  pushEntry({
3836
3833
  address: startElement2,
3837
3834
  length: input.address - startElement2,
@@ -3841,6 +3838,11 @@ var StepParser = class extends StepHeaderParser {
3841
3838
  inlineEntities: inlineElements,
3842
3839
  multiMapping: multiElements
3843
3840
  });
3841
+ {
3842
+ const recordLen = input.address - startElement2;
3843
+ onRecordIndexed?.(topLevelCount, expressID, 0, input.buffer, input.cursor - recordLen, recordLen);
3844
+ }
3845
+ ++topLevelCount;
3844
3846
  multiElements = void 0;
3845
3847
  continue;
3846
3848
  }
@@ -3920,11 +3922,6 @@ var StepParser = class extends StepHeaderParser {
3920
3922
  if (!charws(SEMICOLON)) {
3921
3923
  return syntaxError();
3922
3924
  }
3923
- {
3924
- const recordLen = input.address - startElement;
3925
- onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer.subarray(input.cursor - recordLen, input.cursor));
3926
- }
3927
- ++topLevelCount;
3928
3925
  pushEntry({
3929
3926
  address: startElement,
3930
3927
  length: input.address - startElement,
@@ -3932,6 +3929,11 @@ var StepParser = class extends StepHeaderParser {
3932
3929
  expressID,
3933
3930
  inlineEntities: inlineElements
3934
3931
  });
3932
+ {
3933
+ const recordLen = input.address - startElement;
3934
+ onRecordIndexed?.(topLevelCount, expressID, foundItem, input.buffer, input.cursor - recordLen, recordLen);
3935
+ }
3936
+ ++topLevelCount;
3935
3937
  }
3936
3938
  return parseResult2(ParseResult.INCOMPLETE);
3937
3939
  }
@@ -72161,13 +72163,11 @@ async function buildIndexStreamingAsync(source, parser211, pool3, onRecordIndexe
72161
72163
  };
72162
72164
  }
72163
72165
  }
72164
- function buildColumnarIndexStreaming(source, parser211, pool3, onRecordIndexed) {
72165
- const sink = new ColumnarIndexSink();
72166
+ function buildColumnarIndexStreaming(source, parser211, pool3, onRecordIndexed, sink = new ColumnarIndexSink()) {
72166
72167
  const { header, result, stats } = buildIndexStreaming(source, parser211, pool3, onRecordIndexed, sink);
72167
72168
  return { header, columns: sink.finalize(), result, stats };
72168
72169
  }
72169
- async function buildColumnarIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, onProgress, yieldIntervalMs) {
72170
- const sink = new ColumnarIndexSink();
72170
+ async function buildColumnarIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, onProgress, yieldIntervalMs, sink = new ColumnarIndexSink()) {
72171
72171
  const { header, result, stats } = await buildIndexStreamingAsync(source, parser211, pool3, onRecordIndexed, sink, onProgress, yieldIntervalMs);
72172
72172
  return { header, columns: sink.finalize(), result, stats };
72173
72173
  }
@@ -277,6 +277,126 @@ describe("OpenModelStreamed + DEFER_GEOMETRY", () => {
277
277
  api4.CloseModel(classicID);
278
278
  api4.CloseModel(deferredID);
279
279
  }, 240000);
280
+ test("shared representation survives a batch boundary, emitted once", async () => {
281
+ // data/mapped_shared_representation.ifc: one IfcRepresentationMap
282
+ // mapped by two products 15 products apart, so a batch size of 4
283
+ // guarantees they land in DIFFERENT batches. The smoke corpus cannot
284
+ // produce this shape — there every sharer of a definition falls
285
+ // inside one batch — so this is the only fixture that exercises the
286
+ // delta capture across a boundary where geometry is shared.
287
+ //
288
+ // It pins the cursor capture from both directions. Lose a parked or
289
+ // suffix node and the second mapped product goes missing; re-emit a
290
+ // node the cursor should have passed (the failure a reverted cursor
291
+ // or a broken watermark produces) and instances duplicate. Both show
292
+ // up as an instance-count mismatch against classic, which is why the
293
+ // comparison is against classic rather than a hard-coded number.
294
+ const api5 = new IfcAPI();
295
+ await api5.Init();
296
+ const fixture = new Uint8Array(fs.readFileSync("data/mapped_shared_representation.ifc"));
297
+ const classicID = api5.OpenModel(fixture, SETTINGS);
298
+ const classicPlacements = [];
299
+ api5.StreamAllMeshes(classicID, (mesh) => {
300
+ for (let where = 0; where < mesh.geometries.size(); ++where) {
301
+ const placed = mesh.geometries.get(where);
302
+ classicPlacements.push(`${placed.geometryExpressID}@${[...placed.flatTransformation]
303
+ .map((value) => value.toFixed(3)).join(",")}`);
304
+ }
305
+ });
306
+ const deferredID = await api5.OpenModelStreamed(fixture, { ...SETTINGS, DEFER_GEOMETRY: true });
307
+ const pumpedPlacements = [];
308
+ let batches = 0;
309
+ for (;;) {
310
+ const { extracted, remaining } = api5.ExtractGeometryBatch(deferredID, 4, (mesh) => {
311
+ for (let where = 0; where < mesh.geometries.size(); ++where) {
312
+ const placed = mesh.geometries.get(where);
313
+ pumpedPlacements.push(`${placed.geometryExpressID}@${[...placed.flatTransformation]
314
+ .map((value) => value.toFixed(3)).join(",")}`);
315
+ }
316
+ });
317
+ ++batches;
318
+ if (remaining === 0 && extracted === 0) {
319
+ break;
320
+ }
321
+ }
322
+ // The boundary the fixture exists to create: with 16 products at
323
+ // batch 4, the two sharers cannot be in the same batch.
324
+ expect(batches).toBeGreaterThan(2);
325
+ // Exact multiset equality — placement included, so an instance
326
+ // delivered at the wrong location fails as loudly as a missing one.
327
+ expect(pumpedPlacements.slice().sort())
328
+ .toEqual(classicPlacements.slice().sort());
329
+ // Emitted once each: a duplicate would survive the multiset check
330
+ // only if classic duplicated too, but state it directly since
331
+ // re-emission is the specific failure a broken cursor produces.
332
+ expect(new Set(pumpedPlacements).size).toBe(pumpedPlacements.length);
333
+ // Both users of the shared map are present, at their own placements
334
+ // — the shared SOURCE geometry appears under two distinct transforms.
335
+ const mappedTransforms = new Set(pumpedPlacements.map((entry) => entry.split("@")[1]));
336
+ expect(mappedTransforms.size).toBe(pumpedPlacements.length);
337
+ api5.CloseModel(classicID);
338
+ api5.CloseModel(deferredID);
339
+ }, 240000);
340
+ test("delta capture visits each scene node once, whatever the batch size", async () => {
341
+ // Output parity cannot see this. The whole-scene walk this replaced
342
+ // delivered exactly the same meshes; what changed is the COST of
343
+ // delivering them — it re-resolved every node on every call, so the
344
+ // capture was O(batches x scene). A revert would pass every
345
+ // correctness assertion in this file while restoring the 1.47-2.97x
346
+ // regression the PR description measures, so the complexity gets an
347
+ // assertion of its own.
348
+ //
349
+ // Counting node visits rather than timing: deterministic, and it
350
+ // fails the same way on a fast machine as a slow one.
351
+ const api6 = new IfcAPI();
352
+ await api6.Init();
353
+ const fixture = new Uint8Array(fs.readFileSync("data/mapped_shared_representation.ifc"));
354
+ /**
355
+ * Pump a fresh deferred model to completion and report how much
356
+ * scene walking it took.
357
+ *
358
+ * @param batchSize Products per ExtractGeometryBatch call.
359
+ * @return {Promise<{visits: number, nodes: number, batches: number}>}
360
+ * Node visits, final scene size, and calls taken.
361
+ */
362
+ async function pump(batchSize) {
363
+ const modelID = await api6.OpenModelStreamed(fixture, { ...SETTINGS, DEFER_GEOMETRY: true });
364
+ let batches = 0;
365
+ for (;;) {
366
+ const { extracted, remaining } = api6.ExtractGeometryBatch(modelID, batchSize, () => { });
367
+ ++batches;
368
+ if (remaining === 0 && extracted === 0) {
369
+ break;
370
+ }
371
+ }
372
+ const scene = api6.getPassthrough(modelID)
373
+ .model[1];
374
+ // A revert that drops the counter along with the cursor would
375
+ // otherwise fail as `expected undefined to be less than 68`, which
376
+ // reads like a broken test rather than the regression it is.
377
+ expect(typeof scene?.geometryNodeVisits)
378
+ .toBe("number");
379
+ const measured = { visits: scene.geometryNodeVisits, nodes: scene.nodeCount, batches };
380
+ api6.CloseModel(modelID);
381
+ return measured;
382
+ }
383
+ const oneAtATime = await pump(1);
384
+ const allAtOnce = await pump(64);
385
+ // The fixture is 16 products, so batch 1 really does take many calls
386
+ // — otherwise this asserts nothing.
387
+ expect(oneAtATime.batches).toBeGreaterThan(8);
388
+ expect(allAtOnce.batches).toBeLessThan(oneAtATime.batches);
389
+ // The invariant: total walking is a property of the SCENE, not of how
390
+ // many calls drained it. Allowing 2x the node count leaves room for
391
+ // the whole-model drain's own pass and for parked-node retries
392
+ // (DEMAND_PARKED_NODE_RETRIES each, and nothing in this corpus parks
393
+ // a node at all), while a per-call re-walk would land near
394
+ // batches x nodes — an order of magnitude clear of the bound.
395
+ expect(oneAtATime.visits).toBeLessThan(oneAtATime.nodes * 2);
396
+ // And it must not grow when the batch shrinks, which is the specific
397
+ // shape of the regression: same scene, ~16x the calls, same work.
398
+ expect(oneAtATime.visits).toBeLessThan(allAtOnce.visits * 2);
399
+ }, 240000);
280
400
  test("ExtractGeometryBatch is a safe no-op on non-deferred models", async () => {
281
401
  const modelID = await api.OpenModelStreamed(buffer, SETTINGS);
282
402
  expect(api.ExtractGeometryBatch(modelID, 8))
@@ -75,7 +75,38 @@ export declare class IfcApiProxyIfc implements IfcApiModelPassthrough {
75
75
  * batches - the watermark makes each instance captured exactly once,
76
76
  * in the same order the classic single walk would process it.
77
77
  */
78
- private readonly demandCapturedCounts_;
78
+ /**
79
+ * How much of the scene array the delta capture has consumed. The scene
80
+ * is append-only, so this is a stable cursor: everything a batch adds
81
+ * sits above it. Replaces the per-entity walk watermark this used to
82
+ * keep, which existed only because the capture restarted its walk from
83
+ * zero every batch — an O(batches x scene) cost that dominated on
84
+ * instance-dense models (D3D: 562k instances, 1182 batches at size 64).
85
+ */
86
+ private demandSceneCursor_;
87
+ /**
88
+ * Node indices walkFrom yielded without resolvable geometry, against the
89
+ * number of times a later capture has re-checked them. Parked because a
90
+ * cursor passes each index once, where the whole-scene walk this replaced
91
+ * re-checked every node on every call. See IfcSceneBuilder.walkFrom.
92
+ *
93
+ * Bounded by DEMAND_PARKED_NODE_RETRIES because not every unresolved node
94
+ * is waiting on something: extraction can append a scene node and then
95
+ * fail to cache geometry for it (extractSweptDiskSolid returns early on a
96
+ * failed directrix; extractRepresentationItem still calls addGeometry),
97
+ * and no amount of re-checking makes a deterministic failure resolve.
98
+ * Retrying those forever would be O(batches x unresolvable) — still under
99
+ * the O(batches x scene) this replaced, but unbounded in batch count for
100
+ * no benefit. Expiring after a fixed number of attempts makes the total
101
+ * retry work O(retries x unresolvable) regardless of batch size.
102
+ *
103
+ * Measured at zero on every model available here — the 12-model smoke
104
+ * corpus, PSB, and D3D (which logs exactly the malformed-geometry errors
105
+ * that produce this case) all park nothing — so this path is a
106
+ * correctness guard rather than a hot path, and its constant is chosen to
107
+ * be generous rather than tuned.
108
+ */
109
+ private readonly demandPendingNodes_;
79
110
  /** Cursor into demandProducts_ — products before it are extracted. */
80
111
  private demandCursor_;
81
112
  /** Wall-clock split of the store-backed batch pump (profile script). */