@thatopen/services 0.6.0 → 0.7.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/README.md +62 -62
- package/dist/built-in/index.d.ts +485 -100
- package/dist/cli.js +12 -23
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +41 -50
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +18 -3
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/docs/flow-architecture.md +1 -1
- package/docs/flow-plugin-guide.md +1 -1
- package/docs/platform-token.md +81 -0
- package/docs/revit-collab-quickstart.md +89 -18
- package/docs/rhino-interop-quickstart.md +391 -0
- package/package.json +1 -1
- package/resources/AGENTS.md +40 -40
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
- package/src/built-in/index.ts +485 -100
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
// Production build config — builds dist/bundle.js as IIFE.
|
|
2
|
-
// For local development, use: npm run dev (runs thatopen serve with esbuild).
|
|
3
|
-
// Do NOT run "vite" or "vite build --watch" directly for dev.
|
|
4
|
-
import { defineConfig } from 'vite';
|
|
5
|
-
import { resolve } from 'path';
|
|
6
|
-
import { existsSync, readFileSync } from 'fs';
|
|
7
|
-
|
|
8
|
-
// When `.thatopen` marks the project as beta, alias the public engine imports
|
|
9
|
-
// to their `@thatopen-platform/*-beta` packages at build time, so the source
|
|
10
|
-
// can use the stable import names in both stable and beta modes.
|
|
11
|
-
function getBetaAliases() {
|
|
12
|
-
if (!existsSync('.thatopen')) return {};
|
|
13
|
-
try {
|
|
14
|
-
const config = JSON.parse(readFileSync('.thatopen', 'utf-8'));
|
|
15
|
-
if (!config.beta) return {};
|
|
16
|
-
return {
|
|
17
|
-
'@thatopen/components': '@thatopen-platform/components-beta',
|
|
18
|
-
'@thatopen/components-front': '@thatopen-platform/components-front-beta',
|
|
19
|
-
'@thatopen/fragments': '@thatopen-platform/fragments-beta',
|
|
20
|
-
};
|
|
21
|
-
} catch {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Some three.js example loaders (e.g. TTFLoader) import their deps from a
|
|
27
|
-
// jsdelivr `/+esm` CDN URL. A bundler can't put a URL import in an IIFE — it
|
|
28
|
-
// degrades to a runtime `require("https://…")` that throws — so rewrite any such
|
|
29
|
-
// URL back to the bare package name and let it resolve from node_modules.
|
|
30
|
-
const CDN_ESM = /^https:\/\/cdn\.jsdelivr\.net\/npm\/((?:@[^/]+\/)?[^@/]+)(?:@[^/]+)?\/\+esm$/;
|
|
31
|
-
|
|
32
|
-
export default defineConfig({
|
|
33
|
-
resolve: {
|
|
34
|
-
alias: [
|
|
35
|
-
{ find: CDN_ESM, replacement: '$1' },
|
|
36
|
-
...Object.entries(getBetaAliases()).map(([find, replacement]) => ({
|
|
37
|
-
find,
|
|
38
|
-
replacement,
|
|
39
|
-
})),
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
build: {
|
|
43
|
-
lib: {
|
|
44
|
-
entry: resolve(__dirname, 'src/main.ts'),
|
|
45
|
-
name: 'ThatOpenApp',
|
|
46
|
-
formats: ['iife'],
|
|
47
|
-
fileName: () => 'bundle.js',
|
|
48
|
-
},
|
|
49
|
-
outDir: 'dist',
|
|
50
|
-
sourcemap: true,
|
|
51
|
-
rollupOptions: {
|
|
52
|
-
output: {
|
|
53
|
-
entryFileNames: 'bundle.js',
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
});
|
|
1
|
+
// Production build config — builds dist/bundle.js as IIFE.
|
|
2
|
+
// For local development, use: npm run dev (runs thatopen serve with esbuild).
|
|
3
|
+
// Do NOT run "vite" or "vite build --watch" directly for dev.
|
|
4
|
+
import { defineConfig } from 'vite';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
|
|
8
|
+
// When `.thatopen` marks the project as beta, alias the public engine imports
|
|
9
|
+
// to their `@thatopen-platform/*-beta` packages at build time, so the source
|
|
10
|
+
// can use the stable import names in both stable and beta modes.
|
|
11
|
+
function getBetaAliases() {
|
|
12
|
+
if (!existsSync('.thatopen')) return {};
|
|
13
|
+
try {
|
|
14
|
+
const config = JSON.parse(readFileSync('.thatopen', 'utf-8'));
|
|
15
|
+
if (!config.beta) return {};
|
|
16
|
+
return {
|
|
17
|
+
'@thatopen/components': '@thatopen-platform/components-beta',
|
|
18
|
+
'@thatopen/components-front': '@thatopen-platform/components-front-beta',
|
|
19
|
+
'@thatopen/fragments': '@thatopen-platform/fragments-beta',
|
|
20
|
+
};
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Some three.js example loaders (e.g. TTFLoader) import their deps from a
|
|
27
|
+
// jsdelivr `/+esm` CDN URL. A bundler can't put a URL import in an IIFE — it
|
|
28
|
+
// degrades to a runtime `require("https://…")` that throws — so rewrite any such
|
|
29
|
+
// URL back to the bare package name and let it resolve from node_modules.
|
|
30
|
+
const CDN_ESM = /^https:\/\/cdn\.jsdelivr\.net\/npm\/((?:@[^/]+\/)?[^@/]+)(?:@[^/]+)?\/\+esm$/;
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
resolve: {
|
|
34
|
+
alias: [
|
|
35
|
+
{ find: CDN_ESM, replacement: '$1' },
|
|
36
|
+
...Object.entries(getBetaAliases()).map(([find, replacement]) => ({
|
|
37
|
+
find,
|
|
38
|
+
replacement,
|
|
39
|
+
})),
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
build: {
|
|
43
|
+
lib: {
|
|
44
|
+
entry: resolve(__dirname, 'src/main.ts'),
|
|
45
|
+
name: 'ThatOpenApp',
|
|
46
|
+
formats: ['iife'],
|
|
47
|
+
fileName: () => 'bundle.js',
|
|
48
|
+
},
|
|
49
|
+
outDir: 'dist',
|
|
50
|
+
sourcemap: true,
|
|
51
|
+
rollupOptions: {
|
|
52
|
+
output: {
|
|
53
|
+
entryFileNames: 'bundle.js',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
# ThatOpen Cloud Component
|
|
2
|
-
|
|
3
|
-
This is a cloud component for the That Open Platform.
|
|
4
|
-
It runs on the server as a Node.js process, triggered via the platform API.
|
|
5
|
-
|
|
6
|
-
## How this component works
|
|
7
|
-
|
|
8
|
-
- **Entry point**: `src/main.ts` — must export an `async function main()`.
|
|
9
|
-
- **Parameter schema**: `declarations.json` — the list of parameters this component accepts. Bundled next to the code so the platform, the UI, and `thatopen run` know what to pass in.
|
|
10
|
-
- **Build output**: `dist/bundle.js` — an IIFE built by Vite with `@thatopen/services` externalized.
|
|
11
|
-
- **Execution**: The platform (or `thatopen run` locally) wraps the bundle in an execution engine that provides globals and calls `main()`.
|
|
12
|
-
|
|
13
|
-
## Parameters (`declarations.json`)
|
|
14
|
-
|
|
15
|
-
Every cloud component declares its runtime parameters in a root-level `declarations.json` file. The CLI includes this file in the zip at publish time, and the platform refuses to publish a component without it. It's a plain JSON array of `{ id, label, type }` entries:
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
[
|
|
19
|
-
{ "id": "projectName", "label": "Project Name", "type": "string" },
|
|
20
|
-
{ "id": "iterations", "label": "Number of Iterations", "type": "number" }
|
|
21
|
-
]
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
- `id` — the key users read off `executionParams` inside `main()`.
|
|
25
|
-
- `label` — human-readable name shown to the user on the platform's execution form.
|
|
26
|
-
- `type` — `"string"` or `"number"` (the only supported types for now).
|
|
27
|
-
|
|
28
|
-
**Rule:** `declarations.json` and `src/main.ts` must stay in sync. When you add, remove, or rename a parameter in one, update the other. `thatopen publish` fails if `declarations.json` is missing, and `thatopen run --params '{...}'` warns if the keys or types don't match the schema.
|
|
29
|
-
|
|
30
|
-
## Commands
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm run build # Build dist/bundle.js
|
|
34
|
-
npm run run # Build and run locally (one-shot execution via thatopen run)
|
|
35
|
-
npm run local-server # Start local execution server (API-compatible with EngineServicesClient)
|
|
36
|
-
npm run login # Authenticate with the platform (saves token locally)
|
|
37
|
-
npm run publish # Publish to the platform
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
To pass parameters when running locally:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
npx thatopen run --params '{"inputFile": "model.ifc", "threshold": 0.5}'
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Local execution server
|
|
47
|
-
|
|
48
|
-
The local server (`npm run local-server`) starts an HTTP + WebSocket server that implements
|
|
49
|
-
the same execution API as the cloud. This lets apps using `EngineServicesClient` test against
|
|
50
|
-
local component code without publishing:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npm run local-server # Start on default port 4001
|
|
54
|
-
npx thatopen local-server --port 5000 # Custom port
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Then in your app or test script:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import { EngineServicesClient } from "@thatopen/services";
|
|
61
|
-
|
|
62
|
-
const client = new EngineServicesClient(token, apiUrl, {
|
|
63
|
-
localServerUrl: "http://localhost:4001",
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const { executionId } = await client.executeComponent("any-id", { param: "value" });
|
|
67
|
-
client.onExecutionProgress(executionId, (data) => {
|
|
68
|
-
console.log(data.progressUpdate?.progress);
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
The server watches source files and auto-rebuilds — changes are picked up on the next execution.
|
|
73
|
-
|
|
74
|
-
## Globals available at runtime
|
|
75
|
-
|
|
76
|
-
The execution engine injects **exactly these four** into scope — do NOT import them, do NOT assume anything else is available:
|
|
77
|
-
|
|
78
|
-
| Global | Type | Purpose |
|
|
79
|
-
|--------|------|---------|
|
|
80
|
-
| `thatOpenServices` | `EngineServicesClient` | Authenticated API client (files, folders, components, executions) |
|
|
81
|
-
| `executionParams` | `Record<string, any>` | User-supplied parameters (shape defined in `declarations.json`) |
|
|
82
|
-
| `executionContext` | `{ projectId?, executionId, toolId, toolVersion }` | Platform-supplied run context. Use `executionContext.projectId` to scope operations to the project the component was launched from. Undefined when run outside a project context. |
|
|
83
|
-
| `executionReporter` | `{ message(msg), error(msg), progress(pct) }` | Send live status updates, error lines, and a numeric progress percentage to the execution UI |
|
|
84
|
-
|
|
85
|
-
Libraries like `@thatopen/components`, `three`, `web-ifc`, or Node's `fs` are **not** available as globals. If you need them, `import` them the normal way and let the bundler include them in `dist/bundle.js`. The execution environment is a plain Node.js process with no special additions beyond the four globals above.
|
|
86
|
-
|
|
87
|
-
### TypeScript declarations
|
|
88
|
-
|
|
89
|
-
Already declared in `src/main.ts`. Keep them there for type checking:
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
declare const thatOpenServices: import("@thatopen/services").EngineServicesClient;
|
|
93
|
-
declare const executionParams: Record<string, unknown>;
|
|
94
|
-
declare const executionContext: {
|
|
95
|
-
projectId?: string;
|
|
96
|
-
executionId: string;
|
|
97
|
-
toolId: string;
|
|
98
|
-
toolVersion: string;
|
|
99
|
-
};
|
|
100
|
-
declare const executionReporter: {
|
|
101
|
-
message(msg: string): void;
|
|
102
|
-
error(msg: string): void;
|
|
103
|
-
progress(pct: number): void;
|
|
104
|
-
};
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Return value
|
|
108
|
-
|
|
109
|
-
`main()` must return an object with `type` and `message`:
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
return { type: "SUCCESS", message: "Processed 42 elements" };
|
|
113
|
-
// type: "SUCCESS" | "FAIL" | "WARNING"
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Common patterns
|
|
117
|
-
|
|
118
|
-
### Scoping uploads to the launching project
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
export async function main() {
|
|
122
|
-
const projectId = executionContext?.projectId;
|
|
123
|
-
if (!projectId) {
|
|
124
|
-
return { type: "FAIL", message: "This component must be launched from a project" };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const blob = new Blob(["hello"], { type: "text/plain" });
|
|
128
|
-
await thatOpenServices.createFile({
|
|
129
|
-
file: blob,
|
|
130
|
-
name: "hello.txt",
|
|
131
|
-
versionTag: "v1",
|
|
132
|
-
projectId,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return { type: "SUCCESS", message: "Uploaded into the project" };
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Processing a file from platform storage
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
export async function main() {
|
|
143
|
-
const { fileId } = executionParams;
|
|
144
|
-
|
|
145
|
-
executionReporter.message("Downloading file...");
|
|
146
|
-
const response = await thatOpenServices.downloadFile(fileId as string);
|
|
147
|
-
const buffer = await response.arrayBuffer();
|
|
148
|
-
|
|
149
|
-
executionReporter.progress(50);
|
|
150
|
-
// ... process the buffer ...
|
|
151
|
-
executionReporter.progress(100);
|
|
152
|
-
|
|
153
|
-
return { type: "SUCCESS", message: "Done" };
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Triggering another cloud component
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
const { executionId } = await thatOpenServices.executeComponent(otherComponentId, { param: "value" });
|
|
161
|
-
const result = await thatOpenServices.getExecution(executionId);
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Listing resources inside a project
|
|
165
|
-
|
|
166
|
-
Pass `projectId` to the list methods to enumerate a specific project's
|
|
167
|
-
resources. The backend enforces project-scoped permissions — the token
|
|
168
|
-
running your component must have `STORAGE:READ` (or the relevant role)
|
|
169
|
-
on that project; otherwise the call is rejected with `403`. A missing
|
|
170
|
-
`projectId` still works and lists items in the caller's personal scope.
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
const projectId = executionContext?.projectId;
|
|
174
|
-
if (projectId) {
|
|
175
|
-
const files = await thatOpenServices.listFiles({ projectId });
|
|
176
|
-
const folders = await thatOpenServices.listFolders({ projectId });
|
|
177
|
-
// listApps / listComponents / listExecutions all accept projectId too
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Gating an action with a permission check
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
const { hasPermission, scope } = await thatOpenServices.checkPermission({
|
|
185
|
-
resourceType: "STORAGE",
|
|
186
|
-
action: "WRITE",
|
|
187
|
-
projectId: executionContext?.projectId,
|
|
188
|
-
});
|
|
189
|
-
if (!hasPermission) {
|
|
190
|
-
return { type: "FAIL", message: "Caller cannot write to this project" };
|
|
191
|
-
}
|
|
192
|
-
// scope is 'global' | 'project' | 'entity' | 'none' — useful when you
|
|
193
|
-
// want the UI to know *why* a permission was granted. checkPermissionBatch
|
|
194
|
-
// accepts a list of checks in a single round-trip.
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Build configuration
|
|
198
|
-
|
|
199
|
-
- `vite.config.js` builds to IIFE format.
|
|
200
|
-
- Only `@thatopen/services` is externalized — the wrapper provides it at runtime via `require()`. Everything else (including any third-party npm dependency you install) is bundled into `dist/bundle.js`.
|
|
201
|
-
- The build output has a footer `var main = ThatOpenComponent.main;` so the engine can find the entry point as a top-level `main` variable.
|
|
202
|
-
|
|
203
|
-
## Configuration
|
|
204
|
-
|
|
205
|
-
- `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, `itemType: "COMPONENT"`, and `componentId` after first publish.
|
|
1
|
+
# ThatOpen Cloud Component
|
|
2
|
+
|
|
3
|
+
This is a cloud component for the That Open Platform.
|
|
4
|
+
It runs on the server as a Node.js process, triggered via the platform API.
|
|
5
|
+
|
|
6
|
+
## How this component works
|
|
7
|
+
|
|
8
|
+
- **Entry point**: `src/main.ts` — must export an `async function main()`.
|
|
9
|
+
- **Parameter schema**: `declarations.json` — the list of parameters this component accepts. Bundled next to the code so the platform, the UI, and `thatopen run` know what to pass in.
|
|
10
|
+
- **Build output**: `dist/bundle.js` — an IIFE built by Vite with `@thatopen/services` externalized.
|
|
11
|
+
- **Execution**: The platform (or `thatopen run` locally) wraps the bundle in an execution engine that provides globals and calls `main()`.
|
|
12
|
+
|
|
13
|
+
## Parameters (`declarations.json`)
|
|
14
|
+
|
|
15
|
+
Every cloud component declares its runtime parameters in a root-level `declarations.json` file. The CLI includes this file in the zip at publish time, and the platform refuses to publish a component without it. It's a plain JSON array of `{ id, label, type }` entries:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
[
|
|
19
|
+
{ "id": "projectName", "label": "Project Name", "type": "string" },
|
|
20
|
+
{ "id": "iterations", "label": "Number of Iterations", "type": "number" }
|
|
21
|
+
]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `id` — the key users read off `executionParams` inside `main()`.
|
|
25
|
+
- `label` — human-readable name shown to the user on the platform's execution form.
|
|
26
|
+
- `type` — `"string"` or `"number"` (the only supported types for now).
|
|
27
|
+
|
|
28
|
+
**Rule:** `declarations.json` and `src/main.ts` must stay in sync. When you add, remove, or rename a parameter in one, update the other. `thatopen publish` fails if `declarations.json` is missing, and `thatopen run --params '{...}'` warns if the keys or types don't match the schema.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run build # Build dist/bundle.js
|
|
34
|
+
npm run run # Build and run locally (one-shot execution via thatopen run)
|
|
35
|
+
npm run local-server # Start local execution server (API-compatible with EngineServicesClient)
|
|
36
|
+
npm run login # Authenticate with the platform (saves token locally)
|
|
37
|
+
npm run publish # Publish to the platform
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To pass parameters when running locally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx thatopen run --params '{"inputFile": "model.ifc", "threshold": 0.5}'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Local execution server
|
|
47
|
+
|
|
48
|
+
The local server (`npm run local-server`) starts an HTTP + WebSocket server that implements
|
|
49
|
+
the same execution API as the cloud. This lets apps using `EngineServicesClient` test against
|
|
50
|
+
local component code without publishing:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run local-server # Start on default port 4001
|
|
54
|
+
npx thatopen local-server --port 5000 # Custom port
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then in your app or test script:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { EngineServicesClient } from "@thatopen/services";
|
|
61
|
+
|
|
62
|
+
const client = new EngineServicesClient(token, apiUrl, {
|
|
63
|
+
localServerUrl: "http://localhost:4001",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const { executionId } = await client.executeComponent("any-id", { param: "value" });
|
|
67
|
+
client.onExecutionProgress(executionId, (data) => {
|
|
68
|
+
console.log(data.progressUpdate?.progress);
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The server watches source files and auto-rebuilds — changes are picked up on the next execution.
|
|
73
|
+
|
|
74
|
+
## Globals available at runtime
|
|
75
|
+
|
|
76
|
+
The execution engine injects **exactly these four** into scope — do NOT import them, do NOT assume anything else is available:
|
|
77
|
+
|
|
78
|
+
| Global | Type | Purpose |
|
|
79
|
+
|--------|------|---------|
|
|
80
|
+
| `thatOpenServices` | `EngineServicesClient` | Authenticated API client (files, folders, components, executions) |
|
|
81
|
+
| `executionParams` | `Record<string, any>` | User-supplied parameters (shape defined in `declarations.json`) |
|
|
82
|
+
| `executionContext` | `{ projectId?, executionId, toolId, toolVersion }` | Platform-supplied run context. Use `executionContext.projectId` to scope operations to the project the component was launched from. Undefined when run outside a project context. |
|
|
83
|
+
| `executionReporter` | `{ message(msg), error(msg), progress(pct) }` | Send live status updates, error lines, and a numeric progress percentage to the execution UI |
|
|
84
|
+
|
|
85
|
+
Libraries like `@thatopen/components`, `three`, `web-ifc`, or Node's `fs` are **not** available as globals. If you need them, `import` them the normal way and let the bundler include them in `dist/bundle.js`. The execution environment is a plain Node.js process with no special additions beyond the four globals above.
|
|
86
|
+
|
|
87
|
+
### TypeScript declarations
|
|
88
|
+
|
|
89
|
+
Already declared in `src/main.ts`. Keep them there for type checking:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
declare const thatOpenServices: import("@thatopen/services").EngineServicesClient;
|
|
93
|
+
declare const executionParams: Record<string, unknown>;
|
|
94
|
+
declare const executionContext: {
|
|
95
|
+
projectId?: string;
|
|
96
|
+
executionId: string;
|
|
97
|
+
toolId: string;
|
|
98
|
+
toolVersion: string;
|
|
99
|
+
};
|
|
100
|
+
declare const executionReporter: {
|
|
101
|
+
message(msg: string): void;
|
|
102
|
+
error(msg: string): void;
|
|
103
|
+
progress(pct: number): void;
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Return value
|
|
108
|
+
|
|
109
|
+
`main()` must return an object with `type` and `message`:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
return { type: "SUCCESS", message: "Processed 42 elements" };
|
|
113
|
+
// type: "SUCCESS" | "FAIL" | "WARNING"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Common patterns
|
|
117
|
+
|
|
118
|
+
### Scoping uploads to the launching project
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
export async function main() {
|
|
122
|
+
const projectId = executionContext?.projectId;
|
|
123
|
+
if (!projectId) {
|
|
124
|
+
return { type: "FAIL", message: "This component must be launched from a project" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const blob = new Blob(["hello"], { type: "text/plain" });
|
|
128
|
+
await thatOpenServices.createFile({
|
|
129
|
+
file: blob,
|
|
130
|
+
name: "hello.txt",
|
|
131
|
+
versionTag: "v1",
|
|
132
|
+
projectId,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { type: "SUCCESS", message: "Uploaded into the project" };
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Processing a file from platform storage
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
export async function main() {
|
|
143
|
+
const { fileId } = executionParams;
|
|
144
|
+
|
|
145
|
+
executionReporter.message("Downloading file...");
|
|
146
|
+
const response = await thatOpenServices.downloadFile(fileId as string);
|
|
147
|
+
const buffer = await response.arrayBuffer();
|
|
148
|
+
|
|
149
|
+
executionReporter.progress(50);
|
|
150
|
+
// ... process the buffer ...
|
|
151
|
+
executionReporter.progress(100);
|
|
152
|
+
|
|
153
|
+
return { type: "SUCCESS", message: "Done" };
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Triggering another cloud component
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const { executionId } = await thatOpenServices.executeComponent(otherComponentId, { param: "value" });
|
|
161
|
+
const result = await thatOpenServices.getExecution(executionId);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Listing resources inside a project
|
|
165
|
+
|
|
166
|
+
Pass `projectId` to the list methods to enumerate a specific project's
|
|
167
|
+
resources. The backend enforces project-scoped permissions — the token
|
|
168
|
+
running your component must have `STORAGE:READ` (or the relevant role)
|
|
169
|
+
on that project; otherwise the call is rejected with `403`. A missing
|
|
170
|
+
`projectId` still works and lists items in the caller's personal scope.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const projectId = executionContext?.projectId;
|
|
174
|
+
if (projectId) {
|
|
175
|
+
const files = await thatOpenServices.listFiles({ projectId });
|
|
176
|
+
const folders = await thatOpenServices.listFolders({ projectId });
|
|
177
|
+
// listApps / listComponents / listExecutions all accept projectId too
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Gating an action with a permission check
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const { hasPermission, scope } = await thatOpenServices.checkPermission({
|
|
185
|
+
resourceType: "STORAGE",
|
|
186
|
+
action: "WRITE",
|
|
187
|
+
projectId: executionContext?.projectId,
|
|
188
|
+
});
|
|
189
|
+
if (!hasPermission) {
|
|
190
|
+
return { type: "FAIL", message: "Caller cannot write to this project" };
|
|
191
|
+
}
|
|
192
|
+
// scope is 'global' | 'project' | 'entity' | 'none' — useful when you
|
|
193
|
+
// want the UI to know *why* a permission was granted. checkPermissionBatch
|
|
194
|
+
// accepts a list of checks in a single round-trip.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Build configuration
|
|
198
|
+
|
|
199
|
+
- `vite.config.js` builds to IIFE format.
|
|
200
|
+
- Only `@thatopen/services` is externalized — the wrapper provides it at runtime via `require()`. Everything else (including any third-party npm dependency you install) is bundled into `dist/bundle.js`.
|
|
201
|
+
- The build output has a footer `var main = ThatOpenComponent.main;` so the engine can find the entry point as a top-level `main` variable.
|
|
202
|
+
|
|
203
|
+
## Configuration
|
|
204
|
+
|
|
205
|
+
- `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, `itemType: "COMPONENT"`, and `componentId` after first publish.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
{
|
|
2
|
-
"accessToken": "",
|
|
3
|
-
"apiUrl": "",
|
|
4
|
-
"itemType": "COMPONENT"
|
|
5
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"accessToken": "",
|
|
3
|
+
"apiUrl": "",
|
|
4
|
+
"itemType": "COMPONENT"
|
|
5
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[
|
|
2
|
-
{ "id": "projectName", "label": "Project Name", "type": "string" },
|
|
3
|
-
{ "id": "iterations", "label": "Number of Iterations", "type": "number" }
|
|
4
|
-
]
|
|
1
|
+
[
|
|
2
|
+
{ "id": "projectName", "label": "Project Name", "type": "string" },
|
|
3
|
+
{ "id": "iterations", "label": "Number of Iterations", "type": "number" }
|
|
4
|
+
]
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PROJECT_NAME}}",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"build": "vite build",
|
|
7
|
-
"run": "thatopen run",
|
|
8
|
-
"local-server": "thatopen local-server",
|
|
9
|
-
"login": "thatopen login --local",
|
|
10
|
-
"publish": "thatopen publish"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@thatopen/components": "~3.4.0",
|
|
14
|
-
"@thatopen/services": "^{{VERSION}}",
|
|
15
|
-
"three": "^0.182.0"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/three": "^0.182.0",
|
|
19
|
-
"typescript": "^5.2.0",
|
|
20
|
-
"vite": "^5.2.0"
|
|
21
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "vite build",
|
|
7
|
+
"run": "thatopen run",
|
|
8
|
+
"local-server": "thatopen local-server",
|
|
9
|
+
"login": "thatopen login --local",
|
|
10
|
+
"publish": "thatopen publish"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@thatopen/components": "~3.4.0",
|
|
14
|
+
"@thatopen/services": "^{{VERSION}}",
|
|
15
|
+
"three": "^0.182.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/three": "^0.182.0",
|
|
19
|
+
"typescript": "^5.2.0",
|
|
20
|
+
"vite": "^5.2.0"
|
|
21
|
+
}
|
|
22
22
|
}
|