@ifc-lite/export 2.8.3 → 2.8.5

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 (51) hide show
  1. package/dist/declared-property-type.d.ts +107 -0
  2. package/dist/declared-property-type.d.ts.map +1 -0
  3. package/dist/declared-property-type.js +236 -0
  4. package/dist/declared-property-type.js.map +1 -0
  5. package/dist/delta-modification-ledger.d.ts +148 -0
  6. package/dist/delta-modification-ledger.d.ts.map +1 -0
  7. package/dist/delta-modification-ledger.js +194 -0
  8. package/dist/delta-modification-ledger.js.map +1 -0
  9. package/dist/demesh-session.d.ts.map +1 -1
  10. package/dist/demesh-session.js +7 -0
  11. package/dist/demesh-session.js.map +1 -1
  12. package/dist/in-place-nomination.d.ts +65 -0
  13. package/dist/in-place-nomination.d.ts.map +1 -0
  14. package/dist/in-place-nomination.js +27 -0
  15. package/dist/in-place-nomination.js.map +1 -0
  16. package/dist/merged-exporter.js +1 -1
  17. package/dist/retype.d.ts.map +1 -1
  18. package/dist/retype.js +2 -1
  19. package/dist/retype.js.map +1 -1
  20. package/dist/select-qualification.d.ts +12 -0
  21. package/dist/select-qualification.d.ts.map +1 -1
  22. package/dist/select-qualification.js +12 -1
  23. package/dist/select-qualification.js.map +1 -1
  24. package/dist/source-ref-bounds.d.ts +53 -0
  25. package/dist/source-ref-bounds.d.ts.map +1 -0
  26. package/dist/source-ref-bounds.js +25 -0
  27. package/dist/source-ref-bounds.js.map +1 -0
  28. package/dist/step-argument-parser.d.ts +90 -0
  29. package/dist/step-argument-parser.d.ts.map +1 -0
  30. package/dist/step-argument-parser.js +207 -0
  31. package/dist/step-argument-parser.js.map +1 -0
  32. package/dist/step-exporter.d.ts +45 -4
  33. package/dist/step-exporter.d.ts.map +1 -1
  34. package/dist/step-exporter.js +465 -84
  35. package/dist/step-exporter.js.map +1 -1
  36. package/dist/step-file-assembly.d.ts +35 -0
  37. package/dist/step-file-assembly.d.ts.map +1 -0
  38. package/dist/step-file-assembly.js +102 -0
  39. package/dist/step-file-assembly.js.map +1 -0
  40. package/dist/step-serialization.d.ts +48 -48
  41. package/dist/step-serialization.d.ts.map +1 -1
  42. package/dist/step-serialization.js +90 -182
  43. package/dist/step-serialization.js.map +1 -1
  44. package/dist/type-owned-psets.d.ts +36 -0
  45. package/dist/type-owned-psets.d.ts.map +1 -1
  46. package/dist/type-owned-psets.js +41 -0
  47. package/dist/type-owned-psets.js.map +1 -1
  48. package/dist/unit-normalize.d.ts.map +1 -1
  49. package/dist/unit-normalize.js +13 -1
  50. package/dist/unit-normalize.js.map +1 -1
  51. package/package.json +7 -6
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The declared type a REGENERATED property is written back with
3
+ * (github.com/LTplus-AG/ifc-lite/issues/2482).
4
+ *
5
+ * Editing one property in a property set regenerates the WHOLE set, so every
6
+ * other property in it is re-serialized too. Those neighbours were written from
7
+ * `PropertyValueType` alone, which is a shape (string / real / integer / …) and
8
+ * not a type: the extractor collapses `IFCLABEL`, `IFCTEXT` and `IFCIDENTIFIER`
9
+ * to `String`, and every `…MEASURE` / `…RATIO` to `Real`, keeping the source
10
+ * token only in `Property.dataType`. Regenerating from the shape therefore
11
+ * rewrote `IFCTEXT` as `IFCLABEL` and `IFCLENGTHMEASURE` as `IFCREAL` — silently,
12
+ * permanently, and for properties the user never touched. On the numeric side
13
+ * the measure token IS the unit semantics, so the loss is not cosmetic.
14
+ *
15
+ * `dataType` is whatever token the source line carried, so it is NOT written
16
+ * back unconditionally. Four gates, in order:
17
+ *
18
+ * 1. **It must name a member of `IfcValue`.** `NominalValue` is declared as
19
+ * `IfcValue`, so a token outside that SELECT is not writable there —
20
+ * including the vendor and non-conformant tokens a source file can carry
21
+ * (`IFCACMEWIDGETCODE('X')` parses fine and round-trips through `dataType`).
22
+ * Membership is resolved from the schema registry rather than a hand-written
23
+ * list, so all 106 of IFC4's `IfcValue` leaves are covered and nothing else
24
+ * is. An unrecognized token falls back to the shape-derived primitive — the
25
+ * old behaviour, which is lossy but valid, rather than a token no consumer
26
+ * can resolve.
27
+ *
28
+ * 2. **Its EXPRESS base must agree with the effective `PropertyValueType`.**
29
+ * This is what decides the OTHER question #2482 raised: what wins when the
30
+ * session edited the value. `setProperty(…, valueType)` writes the effective
31
+ * type; `dataType` stays the SOURCE token. When the caller names a type in a
32
+ * different family (a `Boolean` written over an `IFCLENGTHMEASURE`), the
33
+ * caller wins and the source token is dropped. When the two agree — which is
34
+ * always the case for a property nobody edited, since the extractor derived
35
+ * both from the same token — the source token is the more specific of the
36
+ * two and wins.
37
+ *
38
+ * 3. **The VALUE must be representable in that base.** `serializeTypedMarker`
39
+ * coerces, so without this an `IFCLENGTHMEASURE` carrying a non-numeric
40
+ * value would be written as `IFCLENGTHMEASURE(NaN)`, where the shape-derived
41
+ * path writes `$`. This gate also excludes the multi-valued property kinds
42
+ * for free: an `IfcPropertyBoundedValue` is extracted as a measure `dataType`
43
+ * over a DISPLAY STRING (`'12.5 [1 – 20]'`), and a string does not fit a REAL
44
+ * base. Those kinds are regenerated as single values today — lossy, and a
45
+ * separate question — but this pass must not make them worse by wrapping a
46
+ * display string in a measure token.
47
+ *
48
+ * 4. **The VALUE must satisfy the member's own EXPRESS domain.** The base is not
49
+ * the whole type: six `IfcValue` members are CONSTRAINED defined types whose
50
+ * WHERE rule narrows the primitive they resolve to. `-1` is a fine REAL and
51
+ * not a fine `IfcPositiveLengthMeasure`; `2` is a fine REAL and not a fine
52
+ * `IfcNormalisedRatioMeasure`. Since `setProperty` performs no schema
53
+ * validation, gate 3 alone let a session edit `IFCPOSITIVELENGTHMEASURE(5.)`
54
+ * to `-1` and re-declare it `IFCPOSITIVELENGTHMEASURE(-1.)` — a line that
55
+ * parses and fails validation, where the pre-#2482 shape-derived path wrote
56
+ * a valid `IFCREAL(-1.)`. A gate whose purpose is to stop the exporter
57
+ * writing a type it cannot justify must not itself write one.
58
+ *
59
+ * A violating value does not fall all the way back to the shape-derived
60
+ * primitive. It RELAXES to the nearest ancestor that is itself an `IfcValue`
61
+ * member over the same base and carries no constraint —
62
+ * `IfcPositiveLengthMeasure` → `IfcLengthMeasure` — resolved from the
63
+ * registry's alias chain, not listed. `IFCLENGTHMEASURE(-1.)` is schema-valid
64
+ * AND keeps the unit semantics, which is the whole thing #2482 is about;
65
+ * dropping to `IFCREAL(-1.)` would re-inflict this PR's own defect on exactly
66
+ * the properties whose value went out of range.
67
+ *
68
+ * **Where the constraints come from.** `SCHEMA_REGISTRY.types` is a
69
+ * `name -> underlying type` alias map (plus STRING widths); the generator
70
+ * does not carry WHERE rules, so there is nothing to read. The six are
71
+ * therefore written out below, which is tolerable only because the set is
72
+ * CLOSED and small: they are every constrained member of `IfcValue`'s 106
73
+ * defined-type leaves, and a test walks the live registry to fail if a schema
74
+ * bump adds one this table has not heard of. String widths, which the
75
+ * registry DOES carry (`IfcLabel: 'STRING(255)'`), are deliberately not
76
+ * gated: every fallback for a string shape is itself `IFCLABEL` /
77
+ * `IFCIDENTIFIER`, so rejecting an over-long label would emit the identical
78
+ * over-long line, and rejecting an over-long `IfcText` (unbounded, therefore
79
+ * valid) would narrow it to a bounded type and make the file worse.
80
+ *
81
+ * A `null` value is left entirely to {@link serializePropertyValue}: null is
82
+ * the extractor's reading of `IFCLOGICAL(.U.)` as well as of an absent value,
83
+ * and which of those a null means is #2472's question, not this one.
84
+ */
85
+ import { PropertyValueType } from '@ifc-lite/data';
86
+ /**
87
+ * Exported for the drift test only: the members {@link CONSTRAINED_MEMBERS}
88
+ * claims to cover, so the test holds the table against the LIVE registry rather
89
+ * than against a second copy of the same list.
90
+ */
91
+ export declare const CONSTRAINED_IFC_VALUE_MEMBERS: readonly string[];
92
+ /**
93
+ * The schema-cased `IfcValue` member a regenerated property is written back
94
+ * with, else `null` for the shape-derived fallback. See the module docstring for
95
+ * the four gates. Normally this is `dataType`'s own member; for a value outside
96
+ * a CONSTRAINED member's domain it is that member's nearest unconstrained
97
+ * ancestor (gate 4).
98
+ */
99
+ export declare function declaredNominalValueType(value: unknown, type: PropertyValueType, dataType: string | undefined): string | null;
100
+ /**
101
+ * Serialize a property's `NominalValue`, honouring the type the SOURCE line
102
+ * declared when the property carries one and it survives the gates above.
103
+ * Falls back to {@link serializePropertyValue}, which derives the primitive
104
+ * from the property's shape alone.
105
+ */
106
+ export declare function serializeNominalValue(value: unknown, type: PropertyValueType, dataType: string | undefined): string;
107
+ //# sourceMappingURL=declared-property-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declared-property-type.d.ts","sourceRoot":"","sources":["../src/declared-property-type.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA0FnD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAAoC,CAAC;AAqBhG;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,IAAI,CAcf;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAIR"}
@@ -0,0 +1,236 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * The declared type a REGENERATED property is written back with
6
+ * (github.com/LTplus-AG/ifc-lite/issues/2482).
7
+ *
8
+ * Editing one property in a property set regenerates the WHOLE set, so every
9
+ * other property in it is re-serialized too. Those neighbours were written from
10
+ * `PropertyValueType` alone, which is a shape (string / real / integer / …) and
11
+ * not a type: the extractor collapses `IFCLABEL`, `IFCTEXT` and `IFCIDENTIFIER`
12
+ * to `String`, and every `…MEASURE` / `…RATIO` to `Real`, keeping the source
13
+ * token only in `Property.dataType`. Regenerating from the shape therefore
14
+ * rewrote `IFCTEXT` as `IFCLABEL` and `IFCLENGTHMEASURE` as `IFCREAL` — silently,
15
+ * permanently, and for properties the user never touched. On the numeric side
16
+ * the measure token IS the unit semantics, so the loss is not cosmetic.
17
+ *
18
+ * `dataType` is whatever token the source line carried, so it is NOT written
19
+ * back unconditionally. Four gates, in order:
20
+ *
21
+ * 1. **It must name a member of `IfcValue`.** `NominalValue` is declared as
22
+ * `IfcValue`, so a token outside that SELECT is not writable there —
23
+ * including the vendor and non-conformant tokens a source file can carry
24
+ * (`IFCACMEWIDGETCODE('X')` parses fine and round-trips through `dataType`).
25
+ * Membership is resolved from the schema registry rather than a hand-written
26
+ * list, so all 106 of IFC4's `IfcValue` leaves are covered and nothing else
27
+ * is. An unrecognized token falls back to the shape-derived primitive — the
28
+ * old behaviour, which is lossy but valid, rather than a token no consumer
29
+ * can resolve.
30
+ *
31
+ * 2. **Its EXPRESS base must agree with the effective `PropertyValueType`.**
32
+ * This is what decides the OTHER question #2482 raised: what wins when the
33
+ * session edited the value. `setProperty(…, valueType)` writes the effective
34
+ * type; `dataType` stays the SOURCE token. When the caller names a type in a
35
+ * different family (a `Boolean` written over an `IFCLENGTHMEASURE`), the
36
+ * caller wins and the source token is dropped. When the two agree — which is
37
+ * always the case for a property nobody edited, since the extractor derived
38
+ * both from the same token — the source token is the more specific of the
39
+ * two and wins.
40
+ *
41
+ * 3. **The VALUE must be representable in that base.** `serializeTypedMarker`
42
+ * coerces, so without this an `IFCLENGTHMEASURE` carrying a non-numeric
43
+ * value would be written as `IFCLENGTHMEASURE(NaN)`, where the shape-derived
44
+ * path writes `$`. This gate also excludes the multi-valued property kinds
45
+ * for free: an `IfcPropertyBoundedValue` is extracted as a measure `dataType`
46
+ * over a DISPLAY STRING (`'12.5 [1 – 20]'`), and a string does not fit a REAL
47
+ * base. Those kinds are regenerated as single values today — lossy, and a
48
+ * separate question — but this pass must not make them worse by wrapping a
49
+ * display string in a measure token.
50
+ *
51
+ * 4. **The VALUE must satisfy the member's own EXPRESS domain.** The base is not
52
+ * the whole type: six `IfcValue` members are CONSTRAINED defined types whose
53
+ * WHERE rule narrows the primitive they resolve to. `-1` is a fine REAL and
54
+ * not a fine `IfcPositiveLengthMeasure`; `2` is a fine REAL and not a fine
55
+ * `IfcNormalisedRatioMeasure`. Since `setProperty` performs no schema
56
+ * validation, gate 3 alone let a session edit `IFCPOSITIVELENGTHMEASURE(5.)`
57
+ * to `-1` and re-declare it `IFCPOSITIVELENGTHMEASURE(-1.)` — a line that
58
+ * parses and fails validation, where the pre-#2482 shape-derived path wrote
59
+ * a valid `IFCREAL(-1.)`. A gate whose purpose is to stop the exporter
60
+ * writing a type it cannot justify must not itself write one.
61
+ *
62
+ * A violating value does not fall all the way back to the shape-derived
63
+ * primitive. It RELAXES to the nearest ancestor that is itself an `IfcValue`
64
+ * member over the same base and carries no constraint —
65
+ * `IfcPositiveLengthMeasure` → `IfcLengthMeasure` — resolved from the
66
+ * registry's alias chain, not listed. `IFCLENGTHMEASURE(-1.)` is schema-valid
67
+ * AND keeps the unit semantics, which is the whole thing #2482 is about;
68
+ * dropping to `IFCREAL(-1.)` would re-inflict this PR's own defect on exactly
69
+ * the properties whose value went out of range.
70
+ *
71
+ * **Where the constraints come from.** `SCHEMA_REGISTRY.types` is a
72
+ * `name -> underlying type` alias map (plus STRING widths); the generator
73
+ * does not carry WHERE rules, so there is nothing to read. The six are
74
+ * therefore written out below, which is tolerable only because the set is
75
+ * CLOSED and small: they are every constrained member of `IfcValue`'s 106
76
+ * defined-type leaves, and a test walks the live registry to fail if a schema
77
+ * bump adds one this table has not heard of. String widths, which the
78
+ * registry DOES carry (`IfcLabel: 'STRING(255)'`), are deliberately not
79
+ * gated: every fallback for a string shape is itself `IFCLABEL` /
80
+ * `IFCIDENTIFIER`, so rejecting an over-long label would emit the identical
81
+ * over-long line, and rejecting an over-long `IfcText` (unbounded, therefore
82
+ * valid) would narrow it to a bounded type and make the file worse.
83
+ *
84
+ * A `null` value is left entirely to {@link serializePropertyValue}: null is
85
+ * the extractor's reading of `IFCLOGICAL(.U.)` as well as of an absent value,
86
+ * and which of those a null means is #2472's question, not this one.
87
+ */
88
+ import { PropertyValueType } from '@ifc-lite/data';
89
+ import { SCHEMA_REGISTRY } from '@ifc-lite/parser';
90
+ import { getSelectDefinedLeaves } from './select-qualification.js';
91
+ import { serializePropertyValue, serializeTypedMarker } from './step-serialization.js';
92
+ /** The SELECT `IfcPropertySingleValue.NominalValue` is declared as. */
93
+ const NOMINAL_VALUE_SELECT = 'IfcValue';
94
+ /** UPPERCASE STEP token → `[schema-cased type name, EXPRESS base]`. */
95
+ let nominalValueLeaves = null;
96
+ function lookupNominalValueLeaf(token) {
97
+ if (!nominalValueLeaves) {
98
+ nominalValueLeaves = new Map();
99
+ for (const [type, base] of getSelectDefinedLeaves(NOMINAL_VALUE_SELECT)) {
100
+ nominalValueLeaves.set(type.toUpperCase(), [type, base]);
101
+ }
102
+ }
103
+ return nominalValueLeaves.get(token);
104
+ }
105
+ /**
106
+ * The EXPRESS bases a property of `type` may be re-declared as. `null` for the
107
+ * kinds that are not a single scalar `IfcValue` at all — an `Enum` is a bare
108
+ * enumeration token, a `Reference` is a different property CLASS, and a `List`
109
+ * is an aggregate.
110
+ */
111
+ function acceptedBases(type) {
112
+ switch (type) {
113
+ case PropertyValueType.String:
114
+ case PropertyValueType.Label:
115
+ case PropertyValueType.Text:
116
+ case PropertyValueType.Identifier:
117
+ return ['STRING'];
118
+ case PropertyValueType.Real:
119
+ // NUMBER as well as REAL: several IfcValue members (IfcCountMeasure,
120
+ // IfcNumericMeasure) bottom out in NUMBER and are read back as numbers.
121
+ return ['REAL', 'NUMBER'];
122
+ case PropertyValueType.Integer:
123
+ return ['INTEGER', 'NUMBER'];
124
+ case PropertyValueType.Boolean:
125
+ return ['BOOLEAN'];
126
+ case PropertyValueType.Logical:
127
+ return ['LOGICAL'];
128
+ default:
129
+ return null;
130
+ }
131
+ }
132
+ /** Whether `value` can be written into `base` without being coerced into
133
+ * something the shape-derived path would have refused to write at all. */
134
+ function valueFitsBase(value, base) {
135
+ switch (base) {
136
+ case 'STRING':
137
+ return typeof value === 'string';
138
+ case 'REAL':
139
+ case 'NUMBER':
140
+ return typeof value === 'number' && Number.isFinite(value);
141
+ case 'INTEGER':
142
+ return typeof value === 'number' && Number.isInteger(value);
143
+ case 'BOOLEAN':
144
+ case 'LOGICAL':
145
+ // Not a truthiness test: `serializeTypedMarker` reads `'.F.'` and
146
+ // `'maybe'` alike, and the shape-derived path answers `IFCLOGICAL(.U.)`
147
+ // for anything that is not a real boolean. Leave those to it.
148
+ return typeof value === 'boolean';
149
+ default:
150
+ // BINARY and anything the registry resolves to something else: the
151
+ // extractor has no path that produces a faithful JS value for these.
152
+ return false;
153
+ }
154
+ }
155
+ /**
156
+ * The EXPRESS WHERE rule of every CONSTRAINED `IfcValue` member, keyed by the
157
+ * registry's casing. Hand-written because `SCHEMA_REGISTRY` carries alias chains
158
+ * and STRING widths but no WHERE rules — see gate 4 in the module docstring for
159
+ * why a closed table is acceptable here and what keeps it honest.
160
+ *
161
+ * IFC4 ADD2 TC1, IfcMeasureResource.
162
+ */
163
+ const CONSTRAINED_MEMBERS = new Map([
164
+ ['IfcPositiveLengthMeasure', (v) => v > 0],
165
+ ['IfcNonNegativeLengthMeasure', (v) => v >= 0],
166
+ ['IfcPositiveRatioMeasure', (v) => v > 0],
167
+ ['IfcNormalisedRatioMeasure', (v) => v >= 0 && v <= 1],
168
+ ['IfcPositivePlaneAngleMeasure', (v) => v > 0],
169
+ ['IfcPositiveInteger', (v) => v > 0],
170
+ ]);
171
+ /**
172
+ * Exported for the drift test only: the members {@link CONSTRAINED_MEMBERS}
173
+ * claims to cover, so the test holds the table against the LIVE registry rather
174
+ * than against a second copy of the same list.
175
+ */
176
+ export const CONSTRAINED_IFC_VALUE_MEMBERS = [...CONSTRAINED_MEMBERS.keys()];
177
+ /**
178
+ * The nearest ancestor of `name` along the registry's alias chain that is itself
179
+ * an `IfcValue` member over the SAME EXPRESS base and carries no constraint —
180
+ * `IfcPositiveLengthMeasure` → `IfcLengthMeasure`, `IfcNormalisedRatioMeasure` →
181
+ * `IfcRatioMeasure`. `null` when the chain leaves `IfcValue` before reaching one,
182
+ * in which case the caller falls back to the shape-derived primitive.
183
+ */
184
+ function relaxedMember(name, base) {
185
+ const seen = new Set([name]);
186
+ let cursor = SCHEMA_REGISTRY.types[name];
187
+ while (cursor && !seen.has(cursor)) {
188
+ seen.add(cursor);
189
+ const leaf = lookupNominalValueLeaf(cursor.toUpperCase());
190
+ if (leaf && leaf[1] === base && !CONSTRAINED_MEMBERS.has(leaf[0]))
191
+ return leaf[0];
192
+ cursor = SCHEMA_REGISTRY.types[cursor];
193
+ }
194
+ return null;
195
+ }
196
+ /**
197
+ * The schema-cased `IfcValue` member a regenerated property is written back
198
+ * with, else `null` for the shape-derived fallback. See the module docstring for
199
+ * the four gates. Normally this is `dataType`'s own member; for a value outside
200
+ * a CONSTRAINED member's domain it is that member's nearest unconstrained
201
+ * ancestor (gate 4).
202
+ */
203
+ export function declaredNominalValueType(value, type, dataType) {
204
+ if (value === null || value === undefined)
205
+ return null;
206
+ if (!dataType)
207
+ return null;
208
+ const leaf = lookupNominalValueLeaf(dataType.trim().toUpperCase());
209
+ if (!leaf)
210
+ return null;
211
+ const [name, base] = leaf;
212
+ const accepted = acceptedBases(type);
213
+ if (!accepted || !accepted.includes(base))
214
+ return null;
215
+ if (!valueFitsBase(value, base))
216
+ return null;
217
+ const constraint = CONSTRAINED_MEMBERS.get(name);
218
+ // `valueFitsBase` has already established a finite number for every base a
219
+ // constrained member resolves to (REAL / NUMBER / INTEGER) — not a coercion.
220
+ if (constraint && !constraint(value))
221
+ return relaxedMember(name, base);
222
+ return name;
223
+ }
224
+ /**
225
+ * Serialize a property's `NominalValue`, honouring the type the SOURCE line
226
+ * declared when the property carries one and it survives the gates above.
227
+ * Falls back to {@link serializePropertyValue}, which derives the primitive
228
+ * from the property's shape alone.
229
+ */
230
+ export function serializeNominalValue(value, type, dataType) {
231
+ const declared = declaredNominalValueType(value, type, dataType);
232
+ if (declared === null)
233
+ return serializePropertyValue(value, type);
234
+ return serializeTypedMarker(declared, value);
235
+ }
236
+ //# sourceMappingURL=declared-property-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declared-property-type.js","sourceRoot":"","sources":["../src/declared-property-type.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The bookkeeping behind the STEP header's `"Re-exported by ifc-lite, N
3
+ * modification(s)"` claim, behind `stats.modifiedEntityCount`, and behind the
4
+ * warning that says what a delta could not carry.
5
+ *
6
+ * A full export counts at the INTENT sites for the kinds an entity's own line
7
+ * carries, and that is sound there: the source-iteration pass writes every
8
+ * modified host's own (rewritten) line, so intending to modify an emittable host
9
+ * and emitting the modification are the same event. Property and quantity sets
10
+ * are the exception — see {@link EFFECT_SETTLED_KINDS}.
11
+ *
12
+ * `deltaOnly` breaks that identity. It skips the source-iteration pass
13
+ * wholesale, so the only lines a source-backed host can contribute to a delta
14
+ * are the ones the property-set generator, the quantity-set generator and the
15
+ * type-object `HasPropertySets` rewrite produce for it. A modification with no
16
+ * such line is simply not in the file:
17
+ *
18
+ * - an in-place attribute edit (`setAttribute`) is applied by rewriting the
19
+ * entity's own line inside the skipped pass — the report in #2462;
20
+ * - a georeferencing edit to an EXISTING `IfcProjectedCRS` /
21
+ * `IfcMapConversion` is queued as exactly such attribute edits;
22
+ * - a property/quantity-set DELETION produces no replacement content, so
23
+ * there is nothing for the delta to carry either. (A full export applies a
24
+ * rel-defined removal by OMITTING the pset and its relationship, not by
25
+ * rewriting the host's line; either way a delta has no line to carry.)
26
+ *
27
+ * All three used to count. The header then claimed a modification the DATA
28
+ * section did not contain — for an attribute-only session, `"1 modification"`
29
+ * over zero entity lines. So a delta's count is settled at the end, from what
30
+ * was actually emitted.
31
+ *
32
+ * ## Why the ledger is keyed on (entity, kind) and not on the entity
33
+ *
34
+ * One host can carry edits of several kinds, and the passes that survive
35
+ * `deltaOnly` deliver some kinds and not others. Rename a wall AND add a
36
+ * property set to it: the pset generator writes replacement lines, the rename
37
+ * has nowhere to go. Keyed per ENTITY, that pset emission marked the whole host
38
+ * delivered, so the ledger reported one delivered modification and said nothing
39
+ * about the rename — a caller could apply the delta believing the rename was in
40
+ * it. That is the same silent misreport this module exists to remove, one level
41
+ * down, so delivery is tracked per EDIT KIND:
42
+ *
43
+ * - {@link ModificationLedger.nominate} takes the kind that was edited;
44
+ * - {@link ModificationLedger.recordEmitted} takes the kind the emitted
45
+ * content genuinely delivers — generated pset lines deliver `property-set`
46
+ * and nothing else; a rewritten source line delivers the in-place kinds
47
+ * ({@link recordSourceLineDelivery});
48
+ * - {@link ModificationLedger.settle} counts an ENTITY as modified when ANY
49
+ * of its kinds was delivered — `modifiedEntityCount` counts entities and
50
+ * the header claim is unchanged by this keying — but warns for EVERY
51
+ * nominated-and-undelivered (entity, kind) pair.
52
+ *
53
+ * The warning names the kind, deliberately. "entity #8's edits were not
54
+ * carried" is subtly false when only the rename was dropped and the pset landed,
55
+ * and a warning a reader learns to distrust is worse than no warning. One
56
+ * warning per kind also lets each one say WHY that kind cannot survive a delta,
57
+ * which is the actionable part.
58
+ *
59
+ * ## What is still not covered
60
+ *
61
+ * Retypes and positional edits are nominated only by the source-iteration pass,
62
+ * which `deltaOnly` skips — so a delta still drops them without naming them.
63
+ * They are not lost in every case: the type-object `HasPropertySets` rewrite
64
+ * runs the whole mutation pipeline, so a retype or positional edit to such a
65
+ * host does reach the delta and IS recorded as delivered here. It is the
66
+ * nomination that a delta cannot see, not the delivery.
67
+ *
68
+ * That leaves one narrow spot where the count under-claims rather than
69
+ * over-claims: a type object whose repoint FAILED, whose only other edit is a
70
+ * retype or a positional edit, emits its fallback line while nothing nominated
71
+ * the kind that line carries. Every other delta drops those two kinds without
72
+ * emitting anything at all, where claiming nothing is exactly right.
73
+ *
74
+ * NOTE this is all about what a DELTA contains, not about what may reference a
75
+ * source host. `willBeEmitted` / `hasEmittableHostBytes` deliberately answer
76
+ * "yes" for a source record under `deltaOnly` — a generated
77
+ * `IFCRELDEFINESBYPROPERTIES` may name a host whose line lives in the file
78
+ * being patched. That carve-out is right, and orthogonal to this count.
79
+ */
80
+ /**
81
+ * What was edited about a host. Every value here is a distinction the exporter
82
+ * can actually make at its own nomination sites — no finer, because a kind the
83
+ * emit passes cannot tell apart could never be settled.
84
+ */
85
+ export type ModificationKind = 'attribute' | 'georeferencing' | 'property-set' | 'quantity-set' | 'retype' | 'positional';
86
+ export interface ModificationLedger {
87
+ /** This host has an edit of `kind` that would make it count as modified. */
88
+ nominate(entityId: number, kind: ModificationKind): void;
89
+ /** A pass wrote content that genuinely delivers this host's `kind` edit. */
90
+ recordEmitted(entityId: number, kind: ModificationKind): void;
91
+ /**
92
+ * A pass left content OUT of the file because of this host's `kind` edit —
93
+ * the way a full export applies a set deletion, and the other half of "did
94
+ * this edit change the file".
95
+ *
96
+ * Deliberately not `recordEmitted`: under `deltaOnly` there is no source
97
+ * content to leave out, so withholding delivers nothing there and the
98
+ * existing "a removal produces no replacement content for a delta to carry"
99
+ * warning stays exactly right.
100
+ */
101
+ recordWithheld(entityId: number, kind: ModificationKind): void;
102
+ /**
103
+ * This host's `kind` edit did NOT land, and a more specific warning has
104
+ * already said so. Keeps the pair out of the count (it really is undelivered)
105
+ * and out of the generic warning, which would otherwise blame the delta
106
+ * format for a drop that has nothing to do with it.
107
+ */
108
+ acknowledgeUndelivered(entityId: number, kind: ModificationKind): void;
109
+ /** Final count, plus a warning per kind the file left undelivered. */
110
+ settle(): {
111
+ modifiedEntityCount: number;
112
+ warnings: string[];
113
+ };
114
+ }
115
+ export declare function createModificationLedger(deltaOnly: boolean): ModificationLedger;
116
+ /**
117
+ * Which kinds a rewritten source line actually DELIVERED.
118
+ *
119
+ * Every flag is an effect, measured by comparing the line across the operation
120
+ * that claims it — never "an edit of this kind was requested". A retype to the
121
+ * class the entity already is, a positional write of the token already in the
122
+ * slot, and a named attribute the class has no slot for all leave the text
123
+ * exactly as it was, and a line that did not change delivered nothing.
124
+ */
125
+ export interface SourceLineDelivery {
126
+ attributed: boolean;
127
+ retyped: boolean;
128
+ positional: boolean;
129
+ }
130
+ /**
131
+ * Record what a rewritten SOURCE LINE delivered for `entityId`.
132
+ *
133
+ * A source line carries every IN-PLACE edit and nothing else: property and
134
+ * quantity sets live in separate records, and a type object's type-owned pset
135
+ * edit is delivered by the `HasPropertySets` REPOINT, not by the line rewrite
136
+ * that carries it — which is why the repoint site records `property-set`
137
+ * itself and the fallback site (where the repoint failed) does not.
138
+ *
139
+ * Only the kinds the mutation pipeline REPORTS having applied. A named
140
+ * attribute whose name resolves to no slot in the record's class is discarded
141
+ * by `applyAttributeMutations`, and calling that delivered would re-create the
142
+ * exact misreport this ledger exists to remove. `attributed` covers BOTH kinds
143
+ * a named-attribute rewrite can deliver: georeferencing edits to an existing
144
+ * `IfcProjectedCRS` / `IfcMapConversion` are queued into the very same
145
+ * `modifiedAttributes` map and applied by the very same call.
146
+ */
147
+ export declare function recordSourceLineDelivery(ledger: ModificationLedger, entityId: number, mutated: SourceLineDelivery): void;
148
+ //# sourceMappingURL=delta-modification-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta-modification-ledger.d.ts","sourceRoot":"","sources":["../src/delta-modification-ledger.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AAEH;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,QAAQ,GACR,YAAY,CAAC;AAqFjB,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzD,4EAA4E;IAC5E,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC9D;;;;;;;;;OASG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/D;;;;;OAKG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACvE,sEAAsE;IACtE,MAAM,IAAI;QAAE,mBAAmB,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC/D;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,kBAAkB,CA0E/E;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,IAAI,CAON"}