@thatopen/services 0.19.0 → 0.21.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.
@@ -1,3 +1,5 @@
1
+ import { ProjectHistory } from './history';
2
+ import { Recipes } from './recipes';
1
3
  import { ExecutionEntity, ExecutionSuscriptionReturnType } from '../types/execution';
2
4
  import { AppVersionProps, ComponentVersionProps, ComponentItem, AppItem, Item, ItemFolder, ItemVersion, ItemWithVersions } from '../types/items';
3
5
  import { CreateItemResponse, UpdateItemResponse } from '../types/response';
@@ -629,6 +631,18 @@ export declare class EngineServicesClient {
629
631
  * @param onUpdateCallback - Callback invoked on each progress/result event.
630
632
  */
631
633
  onExecutionProgress(executionId: string, onUpdateCallback: (data: ExecutionSuscriptionReturnType) => void): Promise<void>;
634
+ /**
635
+ * A project's history of connected documents (Revit models, AutoCAD
636
+ * drawings…): their version timelines and their change requests. See
637
+ * `src/core/history.ts` — this surface is the only supported way to read
638
+ * or write it; the storage underneath is private and will move.
639
+ */
640
+ history(projectId: string): ProjectHistory;
641
+ /**
642
+ * The recipe registry and runner: scripts that write change requests in a
643
+ * native application's language. See `src/core/recipes.ts`.
644
+ */
645
+ get recipes(): Recipes;
632
646
  /**
633
647
  * Creates a hidden file attached to a parent item (e.g., dependencies, assets).
634
648
  * @param file - The file to upload.
@@ -0,0 +1,4 @@
1
+ import { EngineServicesClient } from '../client';
2
+
3
+ export declare function readTheProjectHistory(client: EngineServicesClient, projectId: string): Promise<void>;
4
+ export declare function runARecipe(client: EngineServicesClient, projectId: string): Promise<void>;
@@ -0,0 +1,212 @@
1
+ import { EngineServicesClient } from './client';
2
+
3
+ /** One element mutation inside a version. Wire-compatible with the plugins. */
4
+ export interface FlowChange {
5
+ /** create | update | delete. */
6
+ type: 'create' | 'update' | 'delete' | string;
7
+ /** The native application's own id for the element (Revit UniqueId, AutoCAD handle). */
8
+ uniqueId: string;
9
+ }
10
+ /** Per-version change tallies. */
11
+ export interface FlowCounts {
12
+ added: number;
13
+ modified: number;
14
+ removed: number;
15
+ }
16
+ /**
17
+ * One sync of a document. Field names are the wire format the plugins write
18
+ * (`revitflow_history.json`); do not rename them.
19
+ */
20
+ export interface FlowVersion {
21
+ /** Identity, minted by the writer. */
22
+ guid: string;
23
+ /** The versions this one was built on; absent on a root. First entry is the continued line. */
24
+ parents?: string[] | null;
25
+ /** The CDE's storage version number, when there is one. Data, never identity. */
26
+ version?: number;
27
+ /** What the author said it was for, when they said anything. */
28
+ message?: string;
29
+ author: string;
30
+ /** Which application wrote it: 'revit', 'autocad', 'rhino'… */
31
+ source: string;
32
+ /** ISO UTC. */
33
+ timestamp: string;
34
+ changes: FlowChange[];
35
+ counts: FlowCounts;
36
+ /** Hidden-file id of the delta .frag; absent on the baseline and geometry-less versions. */
37
+ deltaId?: string;
38
+ /** Hidden-file id of the per-element geometry fingerprints, when the writer keeps them. */
39
+ geomId?: string;
40
+ /** Platform user id of the committer, when the writer knew it. */
41
+ userId?: string;
42
+ /** This version re-exported the whole model (the visible .frag shows it as of here). */
43
+ full?: boolean;
44
+ compact?: boolean;
45
+ /** Suggestions this version applied (their opIds). */
46
+ appliedOps?: string[];
47
+ /** Position in the timeline, derived from the parent graph when read. Never stored. */
48
+ ordinal?: number;
49
+ }
50
+ /**
51
+ * A change request queued for a document. Wire-compatible with
52
+ * `revitflow_ops.json` (schema `revitflow/ops@1`).
53
+ */
54
+ export interface FlowSuggestion {
55
+ opId: string;
56
+ /** Groups the suggestions one authored action produced across several docs. */
57
+ batchId?: string;
58
+ /** Hidden-file id of the payload .frag (envelope `revitflow/op@2`). */
59
+ fragId?: string;
60
+ title?: string;
61
+ /** add | set | remove | mixed. */
62
+ verb?: string;
63
+ author?: string;
64
+ userId?: string;
65
+ createdAt?: string;
66
+ /** The version this was authored against, by guid. */
67
+ baseCommitGuid?: string;
68
+ /** The storage version that version landed in, when known. Data only. */
69
+ baseCommit?: number;
70
+ /** How many elements it targets. */
71
+ targets?: number;
72
+ state?: 'pending' | 'applied' | 'discarded' | string;
73
+ /** The native version that materialised it, once applied (storage version number). */
74
+ appliedInCommit?: number | null;
75
+ /** Native ids it touched or created, written by the plugin when it applied. */
76
+ affectedIds?: string[];
77
+ }
78
+ /** A connected document, as listed from the project. */
79
+ export interface FlowDocInfo {
80
+ /** The native application that owns it: 'revit', 'autocad'… */
81
+ source: string;
82
+ /** The document's own id within that application. */
83
+ docId: string;
84
+ /** The visible model file's name, when one exists yet. */
85
+ modelName?: string;
86
+ }
87
+ /** What `sync` needs: the change list plus whatever the writer knows. */
88
+ export interface SyncInput {
89
+ author: string;
90
+ /** Which application/tool is writing: 'revit', 'rhino', 'recipe'… */
91
+ source: string;
92
+ changes: FlowChange[];
93
+ message?: string;
94
+ /** Delta geometry for this version, uploaded as a hidden file. */
95
+ delta?: Blob;
96
+ /** Re-upload of the whole model; marks the version `full`. */
97
+ model?: Blob;
98
+ /** Platform user id, when known. */
99
+ userId?: string;
100
+ /** Suggestions this version applied. */
101
+ appliedOps?: string[];
102
+ /**
103
+ * The head you built on. When given and the stored head differs, `sync`
104
+ * refuses with {@link StaleHistoryError} instead of forking silently.
105
+ */
106
+ expectedLatestGuid?: string;
107
+ }
108
+ /** What `suggest` needs. */
109
+ export interface SuggestInput {
110
+ /**
111
+ * The suggestion's id, when the caller minted it up front — a payload
112
+ * (envelope `revitflow/op@2`) carries the opId INSIDE, so whoever builds
113
+ * one needs the id before this call. Minted here when absent.
114
+ */
115
+ opId?: string;
116
+ title: string;
117
+ /** add | set | remove | mixed. */
118
+ verb: string;
119
+ author: string;
120
+ /** The suggestion's .frag payload (envelope `revitflow/op@2`), when it has one. */
121
+ payload?: Blob;
122
+ targets?: number;
123
+ batchId?: string;
124
+ userId?: string;
125
+ /** The version it was authored against. */
126
+ baseCommitGuid?: string;
127
+ }
128
+ /** The head moved while a writer prepared its version. Re-read and retry. */
129
+ export declare class StaleHistoryError extends Error {
130
+ readonly expectedGuid: string;
131
+ readonly actualGuid: string | undefined;
132
+ constructor(expectedGuid: string, actualGuid: string | undefined);
133
+ }
134
+ /**
135
+ * The history of one project. Get it from {@link EngineServicesClient.history}.
136
+ */
137
+ export declare class ProjectHistory {
138
+ #private;
139
+ private readonly client;
140
+ readonly projectId: string;
141
+ constructor(client: EngineServicesClient, projectId: string);
142
+ /** The project's connected documents. */
143
+ docs(): Promise<FlowDocInfo[]>;
144
+ /** One document's history. `source` and `docId` as {@link docs} lists them. */
145
+ doc(source: string, docId: string): FlowDoc;
146
+ /** @internal The `bimterop` folder id, resolved once. */
147
+ rootFolderId(create?: boolean): Promise<string | undefined>;
148
+ }
149
+ /**
150
+ * One connected document: its timeline, its model, and its suggestions.
151
+ */
152
+ export declare class FlowDoc {
153
+ #private;
154
+ private readonly client;
155
+ private readonly project;
156
+ readonly source: string;
157
+ readonly docId: string;
158
+ constructor(client: EngineServicesClient, project: ProjectHistory, source: string, docId: string);
159
+ /**
160
+ * The document's versions, oldest first, ordered by the parent graph (not
161
+ * by array position, which concurrent writers do not keep meaningful).
162
+ * Each version gets its `ordinal` filled in.
163
+ */
164
+ timeline(): Promise<FlowVersion[]>;
165
+ /** The head of the timeline, or null before the first sync. */
166
+ latest(): Promise<FlowVersion | null>;
167
+ /**
168
+ * A version's delta geometry as a .frag. The baseline (`full`) versions have
169
+ * no delta: their geometry IS the model file — use {@link model}.
170
+ */
171
+ delta(version: FlowVersion): Promise<Blob>;
172
+ /** The visible model .frag as last fully exported. */
173
+ model(): Promise<Blob>;
174
+ /**
175
+ * Appends one version: uploads the geometry it carries, then the updated
176
+ * history. Give `expectedLatestGuid` whenever you built on a head you read,
177
+ * so a concurrent writer surfaces as {@link StaleHistoryError} instead of a
178
+ * silent fork.
179
+ */
180
+ sync(input: SyncInput): Promise<FlowVersion>;
181
+ /** Every suggestion in the document's queue, pending or resolved. */
182
+ suggestions(): Promise<FlowSuggestion[]>;
183
+ /** The suggestions still waiting for the owner, oldest first. */
184
+ pendingSuggestions(): Promise<FlowSuggestion[]>;
185
+ /**
186
+ * Queues a change request for this document. The payload, when given, is
187
+ * uploaded as a hidden file under the suggestions index.
188
+ */
189
+ suggest(input: SuggestInput): Promise<FlowSuggestion>;
190
+ /** A suggestion's .frag payload. */
191
+ suggestionPayload(suggestion: FlowSuggestion): Promise<Blob>;
192
+ /**
193
+ * Marks a suggestion applied (naming the version that materialised it) or
194
+ * discarded. The native plugins do this themselves when they apply; apps
195
+ * use it for `discarded`.
196
+ */
197
+ resolveSuggestion(opId: string, outcome: {
198
+ state: 'applied';
199
+ appliedInCommit?: number;
200
+ affectedIds?: string[];
201
+ } | {
202
+ state: 'discarded';
203
+ }): Promise<FlowSuggestion>;
204
+ }
205
+ /**
206
+ * Orders versions by walking the parent graph from the root along first
207
+ * parents, breaking forks by timestamp — array position is not meaningful
208
+ * once more than one application writes. Fills `ordinal`.
209
+ */
210
+ export declare function orderByGraph(versions: FlowVersion[]): FlowVersion[];
211
+ /** A random v4 guid, for version guids and suggestion opIds. */
212
+ export declare function mintGuid(): string;
@@ -0,0 +1,150 @@
1
+ import { EngineServicesClient } from './client';
2
+ import { FlowDoc, FlowSuggestion } from './history';
3
+
4
+ /**
5
+ * How a settings property says "this names something that EXISTS". Put it on
6
+ * the property as `x-existing` inside {@link RecipeManifest.settingsSchema}:
7
+ * the app resolves the candidates and renders a list — people pick, they
8
+ * never type the name of a thing that exists.
9
+ *
10
+ * Resolving the candidates is the APP's job today (reading `doc.model()` of
11
+ * the target, or the declared source file, takes the fragments toolkit, which
12
+ * the SDK does not carry). The kinds are stable so nothing changes for
13
+ * recipes when a shared reader lands in that toolkit.
14
+ */
15
+ export interface ExistingLookup {
16
+ /** What kind of existing thing: 'document' | 'family-type' | 'level' | 'layer' | 'workset'… */
17
+ of: string;
18
+ /** Where it exists: the target document, or the declared source. Default 'target'. */
19
+ from?: 'target' | 'source';
20
+ }
21
+ /** What a recipe declares about itself. Shown in catalogs, checked by apps. */
22
+ export interface RecipeManifest {
23
+ /** Machine name, unique in a catalog: 'rhino-points-to-revit-columns'. */
24
+ name: string;
25
+ /** The human name a card shows: 'Rhino points to Revit columns'. */
26
+ title?: string;
27
+ /** The one sentence a non-programmer reads: what it converts into what. */
28
+ description: string;
29
+ /** The long-form explanation an app renders, as markdown. */
30
+ doc?: string;
31
+ /** The native application whose language it writes: 'revit', 'autocad'… */
32
+ to: string;
33
+ /** The input kinds it understands: 'rhino-model', 'platform-elements', 'points', 'csv'… */
34
+ inputs: string[];
35
+ /**
36
+ * What the app should offer as the input picker, when the input is a thing
37
+ * of the project rather than inline data: another connected document, or a
38
+ * project file (then `extensions` narrows the listing). The picked value is
39
+ * what arrives as `input`, and what `x-existing: { from: 'source' }`
40
+ * lookups read.
41
+ */
42
+ source?: {
43
+ kind: 'document';
44
+ source?: string;
45
+ } | {
46
+ kind: 'project-file';
47
+ extensions?: string[];
48
+ };
49
+ /** Where it runs by default. Browser unless it must run unattended. */
50
+ runtime?: 'browser' | 'cloud';
51
+ /**
52
+ * JSON Schema for `settings`. Apps generate the settings form from it, so
53
+ * anything that names an EXISTING thing (a target document, a family, a
54
+ * level) carries `x-existing` ({@link ExistingLookup}) and renders as a
55
+ * list the app fills — people pick, they never type names of things that
56
+ * exist. Free text is only for what a person invents.
57
+ */
58
+ settingsSchema?: Record<string, unknown>;
59
+ /**
60
+ * The self-documentation an app renders: a "reads → proposes" table whose
61
+ * rows are RULES (not objects), so a dry run can fill it with real tallies.
62
+ */
63
+ rules?: {
64
+ reads: string;
65
+ proposes: string;
66
+ }[];
67
+ }
68
+ /** Everything a recipe's `run` gets to work with. */
69
+ export interface RecipeContext<Settings = Record<string, unknown>> {
70
+ client: EngineServicesClient;
71
+ projectId: string;
72
+ /** The document the suggestion is for. Read its timeline/model if needed. */
73
+ targetDoc: FlowDoc;
74
+ /**
75
+ * The suggestion's id, minted BEFORE the run: the payload envelope
76
+ * (`revitflow/op@2`) carries the opId inside, so the builder needs it.
77
+ * When the run is only a plan, nothing is queued and the id is discarded.
78
+ */
79
+ opId: string;
80
+ /** The declared input, already fetched by the caller (bytes, ids, rows…). */
81
+ input: unknown;
82
+ settings: Settings;
83
+ }
84
+ /** What a recipe's `run` returns: the suggestion, ready to queue. */
85
+ export interface RecipeResult {
86
+ title: string;
87
+ /** add | set | remove | mixed. */
88
+ verb: string;
89
+ /** The .frag payload (envelope `revitflow/op@2`), when the recipe builds one. */
90
+ payload?: Blob;
91
+ targets?: number;
92
+ /** Per-rule tallies matching `manifest.rules`, filled by the run. */
93
+ ruleTallies?: {
94
+ read: number;
95
+ proposed: number;
96
+ }[];
97
+ }
98
+ /** A recipe: its manifest plus the function that writes the suggestion. */
99
+ export interface Recipe<Settings = Record<string, unknown>> extends RecipeManifest {
100
+ run(context: RecipeContext<Settings>): Promise<RecipeResult>;
101
+ }
102
+ /** What {@link Recipes.run} returns: the queued suggestion plus the tallies. */
103
+ export interface RecipeRunOutcome {
104
+ suggestion: FlowSuggestion;
105
+ result: RecipeResult;
106
+ }
107
+ /**
108
+ * The local recipe registry and runner. Reach it as `client.recipes`.
109
+ */
110
+ export declare class Recipes {
111
+ #private;
112
+ private readonly client;
113
+ constructor(client: EngineServicesClient);
114
+ /** Registers a recipe. One per name; a second with the same name throws. */
115
+ register<S>(recipe: Recipe<S>): void;
116
+ /** The registered recipes, optionally filtered by target application. */
117
+ list(filter?: {
118
+ to?: string;
119
+ input?: string;
120
+ }): RecipeManifest[];
121
+ /** A registered recipe by name, or undefined. */
122
+ get(name: string): RecipeManifest | undefined;
123
+ /**
124
+ * Runs a recipe WITHOUT queueing anything: the dry pass. Returns the
125
+ * recipe's result — title, tallies per rule, targets — so an app can show
126
+ * what would be proposed before anybody confirms. The payload it may have
127
+ * built is discarded along with its opId.
128
+ */
129
+ plan<S>(name: string, options: RecipeRunOptions<S>): Promise<RecipeResult>;
130
+ /**
131
+ * Runs a recipe and queues its suggestion on the target document. Safe to
132
+ * call freely: nothing touches the native application until its owner
133
+ * accepts the suggestion there. Use {@link plan} first when the app wants
134
+ * to show the tallies before queueing.
135
+ */
136
+ run<S>(name: string, options: RecipeRunOptions<S>): Promise<RecipeRunOutcome>;
137
+ }
138
+ /** The options {@link Recipes.plan} and {@link Recipes.run} share. */
139
+ export interface RecipeRunOptions<S = Record<string, unknown>> {
140
+ projectId: string;
141
+ /** The target document, as `client.history(projectId).docs()` lists it. */
142
+ target: {
143
+ source: string;
144
+ docId: string;
145
+ };
146
+ input?: unknown;
147
+ settings?: S;
148
+ author: string;
149
+ userId?: string;
150
+ }