@thatopen/services 0.20.0 → 0.22.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
@@ -6812,6 +6812,9 @@ const OPS_SCHEMA = "revitflow/ops@1";
6812
6812
  const LEGACY_MODEL_FILE = "revitflow_model.frag";
6813
6813
  const INTERNAL_PREFIX = "revitflow_";
6814
6814
  const versionTag = () => `v-${Date.now()}`;
6815
+ function childrenOf(folders, parentId) {
6816
+ return folders.filter((f) => String(f.parentId ?? "") === parentId);
6817
+ }
6815
6818
  function idOf(item) {
6816
6819
  return String(item._id ?? item.id ?? "");
6817
6820
  }
@@ -6830,14 +6833,25 @@ class ProjectHistory {
6830
6833
  projectId: this.projectId
6831
6834
  });
6832
6835
  const docs = [];
6833
- for (const folder of folders) {
6836
+ for (const folder of childrenOf(folders, root)) {
6834
6837
  const name = String(folder.name ?? "");
6835
6838
  const dash = name.indexOf("-");
6836
6839
  if (dash <= 0) continue;
6840
+ const files = await this.client.listFiles({
6841
+ folderId: idOf(folder)
6842
+ });
6843
+ const names2 = files.map(
6844
+ (f) => String(f.name ?? "")
6845
+ );
6846
+ const model = names2.find((n) => n === LEGACY_MODEL_FILE) ?? names2.find(
6847
+ (n) => n.endsWith(".frag") && !n.startsWith(INTERNAL_PREFIX)
6848
+ );
6849
+ const hasAny = names2.includes(HISTORY_FILE) || names2.includes(OPS_FILE) || !!model;
6837
6850
  docs.push({
6838
6851
  source: name.slice(0, dash),
6839
6852
  docId: name.slice(dash + 1),
6840
- modelName: void 0
6853
+ modelName: model,
6854
+ ...hasAny ? {} : { empty: true }
6841
6855
  });
6842
6856
  }
6843
6857
  return docs;
@@ -6937,7 +6951,7 @@ class FlowDoc {
6937
6951
  throw new StaleHistoryError(input.expectedLatestGuid, head?.guid);
6938
6952
  }
6939
6953
  const version2 = {
6940
- guid: cryptoRandomGuid(),
6954
+ guid: mintGuid(),
6941
6955
  ...head ? { parents: [head.guid] } : {},
6942
6956
  author: input.author,
6943
6957
  source: input.source,
@@ -6999,7 +7013,7 @@ class FlowDoc {
6999
7013
  const index = await this.#readOps() ?? { schema: OPS_SCHEMA, ops: [] };
7000
7014
  const indexFile = await this.#file(OPS_FILE);
7001
7015
  const suggestion = {
7002
- opId: cryptoRandomGuid(),
7016
+ opId: input.opId ?? mintGuid(),
7003
7017
  title: input.title,
7004
7018
  verb: input.verb,
7005
7019
  author: input.author,
@@ -7067,7 +7081,7 @@ class FlowDoc {
7067
7081
  parentFolderId: rootId,
7068
7082
  projectId: this.project.projectId
7069
7083
  });
7070
- const found = folders.find(
7084
+ const found = childrenOf(folders, rootId).find(
7071
7085
  (f) => String(f.name ?? "") === this.#folderName
7072
7086
  );
7073
7087
  if (found) {
@@ -7225,7 +7239,7 @@ function lastByGraph(versions) {
7225
7239
  const ordered = orderByGraph(versions);
7226
7240
  return ordered[ordered.length - 1];
7227
7241
  }
7228
- function cryptoRandomGuid() {
7242
+ function mintGuid() {
7229
7243
  const anyCrypto = globalThis.crypto;
7230
7244
  if (anyCrypto?.randomUUID) return anyCrypto.randomUUID();
7231
7245
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
@@ -7256,12 +7270,65 @@ class Recipes {
7256
7270
  get(name) {
7257
7271
  return this.#registry.get(name);
7258
7272
  }
7273
+ /**
7274
+ * Runs a recipe WITHOUT queueing anything: the dry pass. Returns a
7275
+ * {@link RecipePlan} — the result to show (title, tallies, targets) plus
7276
+ * everything {@link queue} needs to queue EXACTLY this, payload and opId
7277
+ * included, with no second run. Discard it and the plan never happened.
7278
+ */
7279
+ async plan(name, options) {
7280
+ const { base, opId, result } = await this.#execute(name, options);
7281
+ return {
7282
+ opId,
7283
+ projectId: options.projectId,
7284
+ target: options.target,
7285
+ ...base?.guid ? { baseCommitGuid: base.guid } : {},
7286
+ result
7287
+ };
7288
+ }
7289
+ /**
7290
+ * Queues exactly what {@link plan} planned: same opId, same payload, no
7291
+ * second execution. The pair plan → confirm → queue is the UI flow; `run`
7292
+ * is the one-shot shortcut.
7293
+ */
7294
+ async queue(plan, options) {
7295
+ const targetDoc = this.client.history(plan.projectId).doc(plan.target.source, plan.target.docId);
7296
+ const suggestion = await targetDoc.suggest({
7297
+ opId: plan.opId,
7298
+ title: plan.result.title,
7299
+ verb: plan.result.verb,
7300
+ author: options.author,
7301
+ payload: plan.result.payload,
7302
+ targets: plan.result.targets,
7303
+ userId: options.userId,
7304
+ baseCommitGuid: plan.baseCommitGuid
7305
+ });
7306
+ return { suggestion, result: plan.result };
7307
+ }
7259
7308
  /**
7260
7309
  * Runs a recipe and queues its suggestion on the target document. Safe to
7261
7310
  * call freely: nothing touches the native application until its owner
7262
- * accepts the suggestion there.
7311
+ * accepts the suggestion there. Use {@link plan} first when the app wants
7312
+ * to show the tallies before queueing.
7263
7313
  */
7264
7314
  async run(name, options) {
7315
+ const { targetDoc, base, opId, result } = await this.#execute(
7316
+ name,
7317
+ options
7318
+ );
7319
+ const suggestion = await targetDoc.suggest({
7320
+ opId,
7321
+ title: result.title,
7322
+ verb: result.verb,
7323
+ author: options.author,
7324
+ payload: result.payload,
7325
+ targets: result.targets,
7326
+ userId: options.userId,
7327
+ baseCommitGuid: base?.guid
7328
+ });
7329
+ return { suggestion, result };
7330
+ }
7331
+ async #execute(name, options) {
7265
7332
  const recipe = this.#registry.get(name);
7266
7333
  if (!recipe) throw new Error(`No recipe named "${name}" is registered.`);
7267
7334
  if (recipe.to !== options.target.source) {
@@ -7271,23 +7338,16 @@ class Recipes {
7271
7338
  }
7272
7339
  const targetDoc = this.client.history(options.projectId).doc(options.target.source, options.target.docId);
7273
7340
  const base = await targetDoc.latest();
7341
+ const opId = mintGuid();
7274
7342
  const result = await recipe.run({
7275
7343
  client: this.client,
7276
7344
  projectId: options.projectId,
7277
7345
  targetDoc,
7346
+ opId,
7278
7347
  input: options.input,
7279
7348
  settings: options.settings ?? {}
7280
7349
  });
7281
- const suggestion = await targetDoc.suggest({
7282
- title: result.title,
7283
- verb: result.verb,
7284
- author: options.author,
7285
- payload: result.payload,
7286
- targets: result.targets,
7287
- userId: options.userId,
7288
- baseCommitGuid: base?.guid
7289
- });
7290
- return { suggestion, result };
7350
+ return { recipe, targetDoc, base, opId, result };
7291
7351
  }
7292
7352
  }
7293
7353
  const STORAGE_BATCH_MAX = 100;