@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 +77 -17
- package/dist/cli.js.map +1 -1
- package/dist/core/history.d.ts +15 -0
- package/dist/core/recipes.d.ts +100 -14
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +603 -550
- package/dist/index.es.js.map +1 -1
- package/docs/ai-quickstart.md +30 -1
- package/docs/flow-plugin-guide.md +7 -0
- package/docs/rhino-interop-quickstart.md +7 -1
- package/package.json +1 -1
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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;
|