@thatopen/services 0.26.0 → 0.27.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.
package/dist/cli.js CHANGED
@@ -7142,7 +7142,13 @@ class FlowDoc {
7142
7142
  const response = await this.client.downloadHiddenFile(version2.deltaId);
7143
7143
  return await response.blob();
7144
7144
  }
7145
- /** The visible model .frag as last fully exported. */
7145
+ /**
7146
+ * The visible model .frag AS LAST FULLY EXPORTED — the first commit, or the
7147
+ * last explicit publish. Ordinary syncs only add hidden deltas, so this is
7148
+ * NOT necessarily the head: anything computed on it (a clash run) is as of
7149
+ * the last `full: true` version. For the model at a specific version, read
7150
+ * {@link modelParts} and compose.
7151
+ */
7146
7152
  async model() {
7147
7153
  const file = await this.#modelFile();
7148
7154
  if (!file) {
@@ -7153,6 +7159,69 @@ class FlowDoc {
7153
7159
  const response = await this.client.downloadFile(file.id);
7154
7160
  return await response.blob();
7155
7161
  }
7162
+ /**
7163
+ * The ordered recipe for the model AT a version: the baseline full export
7164
+ * at-or-before it, then one step per later version up to it — each step's
7165
+ * delta .frag (absent on geometry-less versions) and the element uniqueIds
7166
+ * it removed. The consumer composes with its geometry engine: load the
7167
+ * baseline, then per step replace elements by uniqueId from the delta and
7168
+ * drop the removed. Composing is NOT done here: writing a merged .frag
7169
+ * takes the fragments engine, which this package deliberately does not
7170
+ * carry.
7171
+ *
7172
+ * UniqueIds are the native application's own stable element ids (a Revit
7173
+ * UniqueId survives syncs, machines and full exports), which is what makes
7174
+ * replace-by-uniqueId the whole composition rule.
7175
+ *
7176
+ * When the baseline is NEWER than the asked version (somebody published
7177
+ * after it), the visible file no longer shows that moment: `baseline`
7178
+ * then carries the older version's `storageVersion` when the history
7179
+ * recorded one, so the consumer can download that stored file version
7180
+ * instead of the current one.
7181
+ */
7182
+ async modelParts(options) {
7183
+ const timeline = await this.timeline();
7184
+ if (timeline.length === 0) {
7185
+ throw new Error(
7186
+ `Document ${this.source}-${this.docId} has no history yet.`
7187
+ );
7188
+ }
7189
+ const at = options?.at;
7190
+ const targetIndex = at ? timeline.findIndex((v) => v.guid === at) : timeline.length - 1;
7191
+ if (targetIndex < 0) {
7192
+ throw new Error(`No version ${at} in this document's timeline.`);
7193
+ }
7194
+ let baselineIndex = 0;
7195
+ for (let i = targetIndex; i >= 0; i--) {
7196
+ if (timeline[i].full) {
7197
+ baselineIndex = i;
7198
+ break;
7199
+ }
7200
+ }
7201
+ const file = await this.#modelFile();
7202
+ if (!file) {
7203
+ throw new Error(
7204
+ `Document ${this.source}-${this.docId} has no model file yet.`
7205
+ );
7206
+ }
7207
+ const baselineVersion = timeline[baselineIndex];
7208
+ const rewrittenAfter = timeline.slice(targetIndex + 1).some((v) => v.full);
7209
+ const steps = timeline.slice(baselineIndex + 1, targetIndex + 1).map((version2) => ({
7210
+ guid: version2.guid,
7211
+ ...version2.deltaId ? { deltaId: version2.deltaId } : {},
7212
+ removedUniqueIds: version2.changes.filter((change) => change.type === "delete").map((change) => change.uniqueId)
7213
+ }));
7214
+ return {
7215
+ baseline: {
7216
+ fileId: file.id,
7217
+ guid: baselineVersion.guid,
7218
+ current: !rewrittenAfter,
7219
+ ...baselineVersion.version !== void 0 ? { storageVersion: baselineVersion.version } : {}
7220
+ },
7221
+ steps,
7222
+ at: timeline[targetIndex].guid
7223
+ };
7224
+ }
7156
7225
  /** The document folder's item id — the id the CDE knows this document by. */
7157
7226
  async folderId() {
7158
7227
  return await this.#folder(false);
@@ -7202,6 +7271,27 @@ class FlowDoc {
7202
7271
  views: [view]
7203
7272
  });
7204
7273
  }
7274
+ /**
7275
+ * Makes sure the folder carries a descriptor, WRITING one when it predates
7276
+ * them: the visible model .frag becomes the 'frag' view. The explicit form
7277
+ * of what {@link addView} does implicitly — reads like `descriptor()` never
7278
+ * write, so migrating an old folder is this deliberate call. Returns the
7279
+ * descriptor either way, or null when there is nothing to describe yet (no
7280
+ * model file).
7281
+ */
7282
+ async ensureDescriptor() {
7283
+ const folderId = await this.#folder(false);
7284
+ const existing = await this.#documents.descriptor(folderId);
7285
+ if (existing) return existing;
7286
+ const model = await this.#modelFile();
7287
+ if (!model) return null;
7288
+ const descriptor = {
7289
+ name: model.name.replace(/\.frag$/, ""),
7290
+ views: [{ fileId: model.id, format: "frag" }]
7291
+ };
7292
+ await this.#documents.setDescriptor(folderId, descriptor);
7293
+ return await this.#documents.descriptor(folderId);
7294
+ }
7205
7295
  // ─── Ownership (element locks) ─────────────────────────────────────────────
7206
7296
  /**
7207
7297
  * Who currently holds which elements, read from the lock registry the