@ifc-lite/collab 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,250 @@
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
+ * Structured-branch ↔ namespaced-attribute conversion (#1031).
6
+ *
7
+ * The Y.Doc keeps psets / quantities / classifications / materials /
8
+ * geometryRef as dedicated CRDT branches so concurrent edits to
9
+ * different branches never conflict, but the IFCX wire type only has
10
+ * `attributes` / `children` / `inherits`. At the snapshot boundary the
11
+ * structured branches fold into ordinary attributes; `seedFromIfcx`
12
+ * re-inflates them, so structured edits survive snapshot → seed
13
+ * round-trips.
14
+ *
15
+ * Representation (the convention the MCP draft tools, the merge
16
+ * engine's `pset:`/`qset:` component keys, scope verification, and the
17
+ * IFC4→5 migration already share):
18
+ * - pset property → `bsi::ifc::v5a::<Set>::<Prop>` with the full
19
+ * typed PropertyValue object as the attribute value;
20
+ * - quantity → `bsi::ifc::v5a::<Qto_Set>::<Name>` with the raw
21
+ * number as the attribute value;
22
+ * - classifications / materials / geometryRef → single attributes
23
+ * under the `ifclite::` extension namespace (whole-array values:
24
+ * the Y.Array is the unit users edit, and merge granularity at the
25
+ * array level matches that).
26
+ *
27
+ * Inflation is shape-gated so legacy flat attributes written by the
28
+ * IFC4→IFC5 migration (raw values under the same v5a keys) stay flat:
29
+ * only PropertyValue-shaped objects inflate into psets, and numbers
30
+ * inflate into quantities unless the set is `Pset_`-named — the
31
+ * migration only ever emits `Pset_*` set names, so that exclusion is
32
+ * exactly the legacy population, and custom quantity-set names still
33
+ * round-trip. Both directions are inverse to each other, so
34
+ * flatten(inflate(x)) == x and a doc holding the same key in both the
35
+ * flat and structured branch serializes deterministically (structured
36
+ * wins).
37
+ */
38
+ import { IFCLITE_ATTR, V5A_ATTR_PREFIX, isTypedPropertyValue, parseV5aKey } from '@ifc-lite/ifcx';
39
+ import * as Y from 'yjs';
40
+ import { GEOMETRY_KEY, geometryMap, } from '../doc/schema.js';
41
+ // Re-exported for existing consumers; the canonical definition lives in
42
+ // @ifc-lite/ifcx next to the routing rule that shares the dialect.
43
+ export { V5A_ATTR_PREFIX };
44
+ /**
45
+ * Numbers under `Pset_*` sets never inflate into quantities: the
46
+ * IFC4→IFC5 migration writes raw scalars under `Pset_*` keys only, so
47
+ * this exclusion keeps exactly that legacy population flat while any
48
+ * other set name (Qto_* or custom) round-trips as a quantity.
49
+ */
50
+ const PSET_SET_RE = /^Pset_/;
51
+ /** `Qto_*` members route to quantities even when wrapped in a typed record. */
52
+ const QTO_SET_RE = /^Qto_/;
53
+ /**
54
+ * Shape test for the typed PropertyValue record — the canonical wire
55
+ * shape every writer and reader shares (`isTypedPropertyValue` in
56
+ * `@ifc-lite/ifcx`). Strict on purpose: legacy migrated attributes
57
+ * carry raw scalars, never `{type, value}` objects, so this is what
58
+ * disambiguates pset inflation from "leave it as a flat attribute".
59
+ */
60
+ export function isPropertyValueShaped(value) {
61
+ return isTypedPropertyValue(value);
62
+ }
63
+ function isClassificationRefShaped(value) {
64
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
65
+ return false;
66
+ const record = value;
67
+ return typeof record.system === 'string' && typeof record.code === 'string';
68
+ }
69
+ function isMaterialAssignmentShaped(value) {
70
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
71
+ return false;
72
+ return typeof value.materialId === 'string';
73
+ }
74
+ /** Attribute key for one pset property / quantity. */
75
+ export function structuredAttributeKey(setName, name) {
76
+ return `${V5A_ATTR_PREFIX}${setName}::${name}`;
77
+ }
78
+ /**
79
+ * Fold an entity's structured branches into its flat attribute record.
80
+ * Structured values overwrite same-key flat attributes — the typed
81
+ * branch is the newer write path, so it wins deterministically.
82
+ */
83
+ export function flattenStructuredBranches(json, options = {}) {
84
+ const out = { ...json.attributes };
85
+ for (const [setName, props] of Object.entries(json.psets)) {
86
+ for (const [propName, value] of Object.entries(props)) {
87
+ out[structuredAttributeKey(setName, propName)] = { ...value };
88
+ }
89
+ }
90
+ for (const [setName, qtys] of Object.entries(json.quantities)) {
91
+ for (const [qtyName, value] of Object.entries(qtys)) {
92
+ out[structuredAttributeKey(setName, qtyName)] = value;
93
+ }
94
+ }
95
+ if (json.classifications.length > 0) {
96
+ out[IFCLITE_ATTR.CLASSIFICATIONS] = json.classifications.map((ref) => ({ ...ref }));
97
+ }
98
+ if (json.materials.length > 0) {
99
+ out[IFCLITE_ATTR.MATERIALS] = json.materials.map((mat) => ({ ...mat }));
100
+ }
101
+ if (json.geometryRefs.length > 0) {
102
+ // One ref keeps the legacy scalar wire shape (string or carrier) so
103
+ // single-geometry snapshots stay byte-identical; several refs ride as
104
+ // an ordered list of the same per-ref shapes.
105
+ const entries = json.geometryRefs.map((geomId) => {
106
+ const record = options.geometryRecordFor?.(geomId);
107
+ return record ? { geomId, ...record } : geomId;
108
+ });
109
+ out[IFCLITE_ATTR.GEOMETRY_REF] = entries.length === 1 ? entries[0] : entries;
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Inverse of `flattenStructuredBranches`: split an IFCX attribute
115
+ * record into the flat remainder and the structured branches. Keys
116
+ * whose values don't pass the shape gates stay flat (legacy migrated
117
+ * raw values, foreign data under colliding namespaces).
118
+ */
119
+ export function inflateStructuredAttributes(attributes) {
120
+ const flat = {};
121
+ const psets = {};
122
+ const quantities = {};
123
+ let classifications = [];
124
+ let materials = [];
125
+ let geometryRefs = [];
126
+ const geometryCarriers = [];
127
+ for (const [key, value] of Object.entries(attributes)) {
128
+ if (key === IFCLITE_ATTR.CLASSIFICATIONS) {
129
+ if (Array.isArray(value) && value.every(isClassificationRefShaped)) {
130
+ classifications = value.map((ref) => ({ ...ref }));
131
+ continue;
132
+ }
133
+ flat[key] = value;
134
+ continue;
135
+ }
136
+ if (key === IFCLITE_ATTR.MATERIALS) {
137
+ if (Array.isArray(value) && value.every(isMaterialAssignmentShaped)) {
138
+ materials = value.map((mat) => ({ ...mat }));
139
+ continue;
140
+ }
141
+ flat[key] = value;
142
+ continue;
143
+ }
144
+ if (key === IFCLITE_ATTR.GEOMETRY_REF) {
145
+ // Scalar (string or carrier) is the single-ref wire shape; a list of
146
+ // the same per-ref shapes carries multi-geometry entities. Anything
147
+ // else stays flat.
148
+ const entries = Array.isArray(value) ? value : [value];
149
+ const ids = [];
150
+ const carriers = [];
151
+ let recognized = entries.length > 0;
152
+ for (const entry of entries) {
153
+ if (typeof entry === 'string') {
154
+ ids.push(entry);
155
+ }
156
+ else if (isGeometryRefCarrierShaped(entry)) {
157
+ ids.push(entry.geomId);
158
+ carriers.push({ ...entry });
159
+ }
160
+ else {
161
+ recognized = false;
162
+ break;
163
+ }
164
+ }
165
+ if (recognized) {
166
+ geometryRefs = ids;
167
+ geometryCarriers.push(...carriers);
168
+ continue;
169
+ }
170
+ flat[key] = value;
171
+ continue;
172
+ }
173
+ {
174
+ const v5a = parseV5aKey(key);
175
+ if (v5a) {
176
+ const { setName, name } = v5a;
177
+ // Qto_* members are quantities before anything else: a typed
178
+ // record under a quantity set (e.g. written by a draft
179
+ // set_property op) unwraps to its number — otherwise the value
180
+ // would land in psets and compete with later quantity edits on
181
+ // the same wire key. The quantities branch stores plain numbers,
182
+ // so the raw number is the canonical re-flattened shape.
183
+ if (QTO_SET_RE.test(setName)) {
184
+ const candidate = isPropertyValueShaped(value) ? value.value : value;
185
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
186
+ (quantities[setName] ??= {})[name] = candidate;
187
+ continue;
188
+ }
189
+ }
190
+ if (isPropertyValueShaped(value)) {
191
+ (psets[setName] ??= {})[name] = { ...value };
192
+ continue;
193
+ }
194
+ if (typeof value === 'number' && Number.isFinite(value) && !PSET_SET_RE.test(setName)) {
195
+ (quantities[setName] ??= {})[name] = value;
196
+ continue;
197
+ }
198
+ }
199
+ }
200
+ flat[key] = value;
201
+ }
202
+ return {
203
+ attributes: flat,
204
+ psets,
205
+ quantities,
206
+ classifications,
207
+ materials,
208
+ geometryRefs,
209
+ geometryRefRecord: geometryRefs.length > 0 ? { geomIds: geometryRefs } : undefined,
210
+ geometryCarriers,
211
+ };
212
+ }
213
+ function isGeometryRefCarrierShaped(value) {
214
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
215
+ return false;
216
+ return typeof value.geomId === 'string';
217
+ }
218
+ /**
219
+ * `FlattenOptions.geometryRecordFor` backed by `doc`'s geometry map.
220
+ * The CRDT version vector is replica bookkeeping and never travels.
221
+ */
222
+ export function geometryRecordLookup(doc) {
223
+ return (geomId) => {
224
+ const entry = geometryMap(doc).get(geomId);
225
+ if (!entry)
226
+ return undefined;
227
+ const record = {};
228
+ const type = entry.get(GEOMETRY_KEY.TYPE);
229
+ if (typeof type === 'string')
230
+ record.type = type;
231
+ const source = entry.get(GEOMETRY_KEY.SOURCE);
232
+ if (typeof source === 'string')
233
+ record.source = source;
234
+ const blobHash = entry.get(GEOMETRY_KEY.BLOB_HASH);
235
+ if (typeof blobHash === 'string')
236
+ record.blobHash = blobHash;
237
+ const params = entry.get(GEOMETRY_KEY.PARAMS);
238
+ if (params instanceof Y.Map && params.size > 0) {
239
+ const plain = {};
240
+ for (const [k, v] of params.entries())
241
+ plain[k] = v;
242
+ record.params = plain;
243
+ }
244
+ const bbox = entry.get(GEOMETRY_KEY.BBOX);
245
+ if (Array.isArray(bbox))
246
+ record.bbox = bbox;
247
+ return record;
248
+ };
249
+ }
250
+ //# sourceMappingURL=structured-attrs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-attrs.js","sourceRoot":"","sources":["../../src/snapshot/structured-attrs.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,10 @@
4
4
  * Round-trips with `seedFromIfcx`: seeding a doc, snapshotting, then
5
5
  * seeding a fresh doc from the snapshot must produce structurally equal
6
6
  * Y states (verified by tests).
7
+ *
8
+ * Structured branches (psets / quantities / classifications / materials
9
+ * / geometryRef) fold into namespaced attributes on the wire — see
10
+ * `structured-attrs.ts` for the representation contract (#1031).
7
11
  */
8
12
  import type { IfcxFile } from '@ifc-lite/ifcx';
9
13
  import * as Y from 'yjs';
@@ -1 +1 @@
1
- {"version":3,"file":"to-ifcx.d.ts","sourceRoot":"","sources":["../../src/snapshot/to-ifcx.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAoC,MAAM,gBAAgB,CAAC;AACjF,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAIzB,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAiDlF;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,UAAO,GAAG,MAAM,CAEnE"}
1
+ {"version":3,"file":"to-ifcx.d.ts","sourceRoot":"","sources":["../../src/snapshot/to-ifcx.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAoC,MAAM,gBAAgB,CAAC;AACjF,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAKzB,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAmDlF;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,UAAO,GAAG,MAAM,CAEnE"}
@@ -3,6 +3,7 @@
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
4
  import { entityToJSON, iterEntities } from '../doc/entity.js';
5
5
  import { metaMap } from '../doc/schema.js';
6
+ import { flattenStructuredBranches, geometryRecordLookup } from './structured-attrs.js';
6
7
  export function snapshotToIfcx(doc, options = {}) {
7
8
  const meta = metaMap(doc);
8
9
  const seededHeader = meta.get('header') ?? undefined;
@@ -16,6 +17,7 @@ export function snapshotToIfcx(doc, options = {}) {
16
17
  timestamp: options.timestamp ?? new Date().toISOString(),
17
18
  };
18
19
  const data = [];
20
+ const geometryRecordFor = geometryRecordLookup(doc);
19
21
  for (const [path, entity] of iterEntities(doc)) {
20
22
  const json = entityToJSON(entity);
21
23
  const node = { path };
@@ -35,8 +37,9 @@ export function snapshotToIfcx(doc, options = {}) {
35
37
  for (const k of inheritsKeys)
36
38
  node.inherits[k] = json.inherits[k];
37
39
  }
38
- if (Object.keys(json.attributes).length > 0) {
39
- node.attributes = { ...json.attributes };
40
+ const attributes = flattenStructuredBranches(json, { geometryRecordFor });
41
+ if (Object.keys(attributes).length > 0) {
42
+ node.attributes = attributes;
40
43
  }
41
44
  data.push(node);
42
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"to-ifcx.js","sourceRoot":"","sources":["../../src/snapshot/to-ifcx.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"to-ifcx.js","sourceRoot":"","sources":["../../src/snapshot/to-ifcx.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/collab",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "Real-time collaborative BIM via CRDT on IFCX",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,9 +39,9 @@
39
39
  "y-protocols": "^1.0.6",
40
40
  "y-websocket": "^3.0.0",
41
41
  "yjs": "^13.6.31",
42
- "@ifc-lite/data": "^2.5.2",
43
- "@ifc-lite/ifcx": "^2.2.3",
44
- "@ifc-lite/mutations": "^1.18.1"
42
+ "@ifc-lite/data": "^2.5.3",
43
+ "@ifc-lite/ifcx": "^2.3.0",
44
+ "@ifc-lite/mutations": "^1.19.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "typescript": "^6.0.3",