@thatopen/services 0.0.1

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.
Files changed (80) hide show
  1. package/CONTEXT.md +258 -0
  2. package/README.md +285 -0
  3. package/dist/built-in/index.d.ts +723 -0
  4. package/dist/cli/commands/create-tests.d.ts +3 -0
  5. package/dist/cli/commands/create.d.ts +3 -0
  6. package/dist/cli/commands/local-server.d.ts +3 -0
  7. package/dist/cli/commands/login.d.ts +3 -0
  8. package/dist/cli/commands/publish.d.ts +3 -0
  9. package/dist/cli/commands/run.d.ts +3 -0
  10. package/dist/cli/commands/serve-tests.d.ts +3 -0
  11. package/dist/cli/commands/serve.d.ts +3 -0
  12. package/dist/cli/index.d.ts +1 -0
  13. package/dist/cli/lib/config.d.ts +25 -0
  14. package/dist/cli/lib/declarations.d.ts +19 -0
  15. package/dist/cli/lib/engine-script.d.ts +10 -0
  16. package/dist/cli/lib/execution-manager.d.ts +52 -0
  17. package/dist/cli/lib/zip.d.ts +6 -0
  18. package/dist/cli.js +11566 -0
  19. package/dist/core/client.d.ts +682 -0
  20. package/dist/core/client.test.d.ts +1 -0
  21. package/dist/core/platform-client.d.ts +106 -0
  22. package/dist/core/platform-client.test.d.ts +1 -0
  23. package/dist/core/request-error.d.ts +25 -0
  24. package/dist/core/request-error.test.d.ts +1 -0
  25. package/dist/index.cjs.js +2 -0
  26. package/dist/index.d.ts +12 -0
  27. package/dist/index.es.js +3310 -0
  28. package/dist/types/base.d.ts +9 -0
  29. package/dist/types/context.d.ts +20 -0
  30. package/dist/types/execution.d.ts +19 -0
  31. package/dist/types/files.d.ts +19 -0
  32. package/dist/types/item.dto.d.ts +24 -0
  33. package/dist/types/items.d.ts +57 -0
  34. package/dist/types/projects.d.ts +59 -0
  35. package/dist/types/response.d.ts +10 -0
  36. package/dist/types/storage.d.ts +11 -0
  37. package/dist/vite-env.d.ts +1 -0
  38. package/package.json +100 -0
  39. package/src/built-in/index.ts +755 -0
  40. package/src/cli/templates/bim/CONTEXT.md +244 -0
  41. package/src/cli/templates/bim/package.json +26 -0
  42. package/src/cli/templates/bim/src/app.ts +16 -0
  43. package/src/cli/templates/bim/src/bim-components/CloudRunner/index.ts +91 -0
  44. package/src/cli/templates/bim/src/bim-components/CloudRunner/src/index.ts +1 -0
  45. package/src/cli/templates/bim/src/bim-components/CloudRunner/src/types.ts +5 -0
  46. package/src/cli/templates/bim/src/bim-components/index.ts +1 -0
  47. package/src/cli/templates/bim/src/globals.ts +1 -0
  48. package/src/cli/templates/bim/src/main.ts +90 -0
  49. package/src/cli/templates/bim/src/setups/cloud-runner.ts +13 -0
  50. package/src/cli/templates/bim/src/setups/index.ts +3 -0
  51. package/src/cli/templates/bim/src/setups/ui-manager.ts +27 -0
  52. package/src/cli/templates/bim/src/setups/viewports-manager.ts +22 -0
  53. package/src/cli/templates/bim/src/ui-components/app-info-section/index.ts +26 -0
  54. package/src/cli/templates/bim/src/ui-components/app-info-section/src/index.ts +1 -0
  55. package/src/cli/templates/bim/src/ui-components/app-info-section/src/types.ts +15 -0
  56. package/src/cli/templates/bim/src/ui-components/cloud-runner-section/index.ts +37 -0
  57. package/src/cli/templates/bim/src/ui-components/cloud-runner-section/src/index.ts +1 -0
  58. package/src/cli/templates/bim/src/ui-components/cloud-runner-section/src/types.ts +14 -0
  59. package/src/cli/templates/bim/src/ui-components/index.ts +2 -0
  60. package/src/cli/templates/cloud/CONTEXT.md +205 -0
  61. package/src/cli/templates/cloud/_thatopen +5 -0
  62. package/src/cli/templates/cloud/declarations.json +4 -0
  63. package/src/cli/templates/cloud/package.json +22 -0
  64. package/src/cli/templates/cloud/src/main.ts +70 -0
  65. package/src/cli/templates/cloud-test/CONTEXT.md +56 -0
  66. package/src/cli/templates/cloud-test/_thatopen +5 -0
  67. package/src/cli/templates/cloud-test/package.json +22 -0
  68. package/src/cli/templates/cloud-test/src/main.ts +565 -0
  69. package/src/cli/templates/default/CONTEXT.md +92 -0
  70. package/src/cli/templates/default/package.json +15 -0
  71. package/src/cli/templates/default/src/main.ts +62 -0
  72. package/src/cli/templates/shared/_gitignore +4 -0
  73. package/src/cli/templates/shared/app/index.html +27 -0
  74. package/src/cli/templates/shared/app/tsconfig.json +16 -0
  75. package/src/cli/templates/shared/app/vite.config.js +23 -0
  76. package/src/cli/templates/shared/cloud/tsconfig.json +16 -0
  77. package/src/cli/templates/shared/cloud/vite.config.js +27 -0
  78. package/src/cli/templates/test/CONTEXT.md +53 -0
  79. package/src/cli/templates/test/package.json +25 -0
  80. package/src/cli/templates/test/src/main.ts +955 -0
@@ -0,0 +1,565 @@
1
+ // Cloud Component — Platform API Test Suite
2
+ // Tests all EngineServicesClient endpoints from a cloud component context.
3
+ // Generated by: thatopen create --template cloud-test
4
+
5
+ // Globals injected by the execution engine at runtime
6
+ declare const thatOpenServices: import("thatopen-services").EngineServicesClient;
7
+ declare const executionParams: Record<string, unknown>;
8
+ declare const executionReporter: {
9
+ message(msg: string): void;
10
+ progress(pct: number): void;
11
+ };
12
+ declare const OBC: typeof import("@thatopen/components");
13
+ declare const THREE: typeof import("three");
14
+ declare const fs: typeof import("fs");
15
+
16
+ type TestResult = {
17
+ name: string;
18
+ status: "pass" | "fail" | "skip";
19
+ message: string;
20
+ };
21
+
22
+ type TestGroup = {
23
+ name: string;
24
+ results: TestResult[];
25
+ };
26
+
27
+ async function runTest(
28
+ name: string,
29
+ fn: () => Promise<void>,
30
+ ): Promise<TestResult> {
31
+ try {
32
+ await fn();
33
+ return { name, status: "pass", message: "OK" };
34
+ } catch (err) {
35
+ return { name, status: "fail", message: err instanceof Error ? err.message : String(err) };
36
+ }
37
+ }
38
+
39
+ function assert(condition: boolean, msg: string) {
40
+ if (!condition) throw new Error(msg);
41
+ }
42
+
43
+ export async function main() {
44
+ const groups: TestGroup[] = [];
45
+ const ts = Date.now();
46
+ let totalPass = 0;
47
+ let totalFail = 0;
48
+ let totalSkip = 0;
49
+ const groupNames = [
50
+ "Runtime Globals", "Folders", "Files", "Hidden Files", "Icons",
51
+ "General Item Operations", "Components", "Apps", "Execution", "Built-in Components",
52
+ ];
53
+
54
+ function report(group: TestGroup) {
55
+ groups.push(group);
56
+ for (const r of group.results) {
57
+ if (r.status === "pass") totalPass++;
58
+ else if (r.status === "fail") totalFail++;
59
+ else totalSkip++;
60
+ }
61
+ const summary = group.results
62
+ .map((r) => (r.status === "pass" ? " \u2713 " : r.status === "fail" ? " \u2717 " : " \u25CB ") + r.name + ": " + r.message)
63
+ .join("\n");
64
+ executionReporter.message(group.name + "\n" + summary);
65
+ executionReporter.progress(Math.round((groups.length / groupNames.length) * 100));
66
+ }
67
+
68
+ executionReporter.message("Starting Platform API Test Suite...");
69
+
70
+ // ─── Runtime Globals ──────────────────────────────────────
71
+ const globalsResults: TestResult[] = [];
72
+ globalsResults.push(
73
+ await runTest("thatOpenServices exists", async () => {
74
+ assert(typeof thatOpenServices === "object", "Not an object");
75
+ assert(typeof thatOpenServices.listFiles === "function", "listFiles not a function");
76
+ }),
77
+ );
78
+ globalsResults.push(
79
+ await runTest("executionParams exists", async () => {
80
+ assert(typeof executionParams === "object", "Not an object");
81
+ }),
82
+ );
83
+ globalsResults.push(
84
+ await runTest("executionReporter exists", async () => {
85
+ assert(typeof executionReporter === "object", "Not an object");
86
+ assert(typeof executionReporter.message === "function", "message not a function");
87
+ assert(typeof executionReporter.progress === "function", "progress not a function");
88
+ }),
89
+ );
90
+ globalsResults.push(
91
+ await runTest("OBC exists", async () => {
92
+ assert(typeof OBC === "object", "Not an object");
93
+ assert(typeof OBC.Components === "function", "Components not a constructor");
94
+ }),
95
+ );
96
+ globalsResults.push(
97
+ await runTest("THREE exists", async () => {
98
+ assert(typeof THREE === "object", "Not an object");
99
+ assert(typeof THREE.Vector3 === "function", "Vector3 not a constructor");
100
+ }),
101
+ );
102
+ globalsResults.push(
103
+ await runTest("fs exists", async () => {
104
+ assert(typeof fs === "object", "Not an object");
105
+ assert(typeof fs.readFileSync === "function", "readFileSync not a function");
106
+ }),
107
+ );
108
+ report({ name: "Runtime Globals", results: globalsResults });
109
+
110
+ // ─── Folders ───────────────────────────────────────────────
111
+ const folderResults: TestResult[] = [];
112
+ let testFolderId = "";
113
+
114
+ folderResults.push(
115
+ await runTest("createFolder", async () => {
116
+ const folder = await thatOpenServices.createFolder("_cloud_test_folder_" + ts);
117
+ assert(!!folder._id, "_id missing");
118
+ assert(!!folder.name, "name missing");
119
+ testFolderId = folder._id;
120
+ }),
121
+ );
122
+ folderResults.push(
123
+ await runTest("getFolder", async () => {
124
+ assert(!!testFolderId, "No folder");
125
+ const folder = await thatOpenServices.getFolder(testFolderId);
126
+ assert(folder._id === testFolderId, "_id mismatch");
127
+ }),
128
+ );
129
+ folderResults.push(
130
+ await runTest("listFolders", async () => {
131
+ const folders = await thatOpenServices.listFolders();
132
+ assert(Array.isArray(folders), "Not an array");
133
+ }),
134
+ );
135
+ folderResults.push(
136
+ await runTest("updateFolder", async () => {
137
+ assert(!!testFolderId, "No folder");
138
+ const updated = await thatOpenServices.updateFolder(testFolderId, {
139
+ name: "_cloud_test_folder_renamed_" + ts,
140
+ });
141
+ assert(updated.name.includes("renamed"), "Name not updated");
142
+ }),
143
+ );
144
+ folderResults.push(
145
+ await runTest("archiveFolder", async () => {
146
+ assert(!!testFolderId, "No folder");
147
+ await thatOpenServices.archiveFolder(testFolderId);
148
+ }),
149
+ );
150
+ folderResults.push(
151
+ await runTest("recoverFolder", async () => {
152
+ assert(!!testFolderId, "No folder");
153
+ await thatOpenServices.recoverFolder(testFolderId);
154
+ }),
155
+ );
156
+ folderResults.push(
157
+ await runTest("downloadFolder", async () => {
158
+ assert(!!testFolderId, "No folder");
159
+ const response = await thatOpenServices.downloadFolder(testFolderId);
160
+ assert(!!response, "No response");
161
+ }),
162
+ );
163
+ report({ name: "Folders", results: folderResults });
164
+
165
+ // ─── Files ─────────────────────────────────────────────────
166
+ const fileResults: TestResult[] = [];
167
+ let testFileId = "";
168
+ const testContent = "cloud test file content " + ts;
169
+ const testBlob = new Blob([testContent], { type: "text/plain" });
170
+
171
+ fileResults.push(
172
+ await runTest("createFile", async () => {
173
+ const result = await thatOpenServices.createFile({
174
+ file: testBlob,
175
+ name: "_cloud_test_file_" + ts + ".txt",
176
+ versionTag: "v1",
177
+ parentFolderId: testFolderId || undefined,
178
+ metadata: { testKey: "testValue" },
179
+ });
180
+ assert(!!result.item._id, "item._id missing");
181
+ assert(!!result.version, "version missing");
182
+ testFileId = result.item._id;
183
+ }),
184
+ );
185
+ fileResults.push(
186
+ await runTest("getFile (with versions)", async () => {
187
+ assert(!!testFileId, "No file");
188
+ const file = await thatOpenServices.getFile(testFileId, { showVersions: true });
189
+ assert(file._id === testFileId, "_id mismatch");
190
+ assert(Array.isArray(file.versions), "versions not array");
191
+ assert(file.versions.length > 0, "versions empty");
192
+ }),
193
+ );
194
+ fileResults.push(
195
+ await runTest("listFiles", async () => {
196
+ const files = await thatOpenServices.listFiles();
197
+ assert(Array.isArray(files), "Not an array");
198
+ }),
199
+ );
200
+ fileResults.push(
201
+ await runTest("downloadFile", async () => {
202
+ assert(!!testFileId, "No file");
203
+ const response = await thatOpenServices.downloadFile(testFileId);
204
+ assert(response.ok !== false, "Response not ok");
205
+ }),
206
+ );
207
+ fileResults.push(
208
+ await runTest("getFileVersionMetadata", async () => {
209
+ assert(!!testFileId, "No file");
210
+ const metadata = await thatOpenServices.getFileVersionMetadata(
211
+ testFileId,
212
+ "v1",
213
+ );
214
+ assert(typeof metadata === "object", "metadata not object");
215
+ }),
216
+ );
217
+ fileResults.push(
218
+ await runTest("updateFileVersionMetadata", async () => {
219
+ assert(!!testFileId, "No file");
220
+ const result = await thatOpenServices.updateFileVersionMetadata(
221
+ testFileId,
222
+ "v1",
223
+ { discipline: "structural" },
224
+ );
225
+ assert(typeof result === "object", "result not object");
226
+ }),
227
+ );
228
+ fileResults.push(
229
+ await runTest("deleteFileVersionMetadata", async () => {
230
+ assert(!!testFileId, "No file");
231
+ await thatOpenServices.deleteFileVersionMetadata(testFileId, "v1");
232
+ }),
233
+ );
234
+ fileResults.push(
235
+ await runTest("updateFile (rename + new version)", async () => {
236
+ assert(!!testFileId, "No file");
237
+ const blob = new Blob(["updated content"], { type: "text/plain" });
238
+ const result = await thatOpenServices.updateFile(testFileId, {
239
+ name: "_cloud_test_file_renamed_" + ts + ".txt",
240
+ file: blob,
241
+ versionTag: "v2",
242
+ });
243
+ assert(!!result.version || !!result.item, "No update result");
244
+ }),
245
+ );
246
+ fileResults.push(
247
+ await runTest("archiveFile", async () => {
248
+ assert(!!testFileId, "No file");
249
+ await thatOpenServices.archiveFile(testFileId);
250
+ }),
251
+ );
252
+ fileResults.push(
253
+ await runTest("recoverFile", async () => {
254
+ assert(!!testFileId, "No file");
255
+ await thatOpenServices.recoverFile(testFileId);
256
+ }),
257
+ );
258
+ report({ name: "Files", results: fileResults });
259
+
260
+ // ─── Hidden Files ──────────────────────────────────────────
261
+ const hiddenResults: TestResult[] = [];
262
+ let testHiddenId = "";
263
+
264
+ hiddenResults.push(
265
+ await runTest("createHiddenFile", async () => {
266
+ assert(!!testFileId, "No parent file");
267
+ const blob = new Blob(["hidden content"], { type: "text/plain" });
268
+ const result = await thatOpenServices.createHiddenFile(blob, testFileId);
269
+ assert(!!result.hiddenFileId, "hiddenFileId missing");
270
+ testHiddenId = result.hiddenFileId;
271
+ }),
272
+ );
273
+ hiddenResults.push(
274
+ await runTest("getHiddenFile", async () => {
275
+ assert(!!testHiddenId, "No hidden file");
276
+ const hidden = await thatOpenServices.getHiddenFile(testHiddenId);
277
+ assert(!!hidden._id, "_id missing");
278
+ }),
279
+ );
280
+ hiddenResults.push(
281
+ await runTest("getHiddenFilesByParent", async () => {
282
+ assert(!!testFileId, "No parent file");
283
+ const files = await thatOpenServices.getHiddenFilesByParent(testFileId);
284
+ assert(Array.isArray(files), "Not an array");
285
+ assert(files.length > 0, "No hidden files found");
286
+ }),
287
+ );
288
+ hiddenResults.push(
289
+ await runTest("downloadHiddenFile", async () => {
290
+ assert(!!testHiddenId, "No hidden file");
291
+ const response = await thatOpenServices.downloadHiddenFile(testHiddenId);
292
+ assert(!!response, "No response");
293
+ }),
294
+ );
295
+ hiddenResults.push(
296
+ await runTest("deleteHiddenFile", async () => {
297
+ assert(!!testHiddenId, "No hidden file");
298
+ await thatOpenServices.deleteHiddenFile(testHiddenId);
299
+ }),
300
+ );
301
+ hiddenResults.push(
302
+ await runTest("deleteHiddenFilesByParent", async () => {
303
+ assert(!!testFileId, "No parent file");
304
+ const blob = new Blob(["temp hidden"], { type: "text/plain" });
305
+ await thatOpenServices.createHiddenFile(blob, testFileId);
306
+ const result = await thatOpenServices.deleteHiddenFilesByParent(testFileId);
307
+ assert(Array.isArray(result), "Not an array");
308
+ }),
309
+ );
310
+ report({ name: "Hidden Files", results: hiddenResults });
311
+
312
+ // ─── Icons ─────────────────────────────────────────────────
313
+ const iconResults: TestResult[] = [];
314
+ // 1x1 transparent PNG
315
+ const pngBytes = new Uint8Array([
316
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00,
317
+ 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
318
+ 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde,
319
+ 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63,
320
+ 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xe2, 0x21,
321
+ 0xbc, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
322
+ 0x42, 0x60, 0x82,
323
+ ]);
324
+ const iconBlob = new Blob([pngBytes], { type: "image/png" });
325
+
326
+ iconResults.push(
327
+ await runTest("uploadItemIcon", async () => {
328
+ assert(!!testFileId, "No item");
329
+ await thatOpenServices.uploadItemIcon(testFileId, iconBlob);
330
+ }),
331
+ );
332
+ iconResults.push(
333
+ await runTest("getItemIcon", async () => {
334
+ assert(!!testFileId, "No item");
335
+ const response = await thatOpenServices.getItemIcon(testFileId);
336
+ assert(!!response, "No response");
337
+ }),
338
+ );
339
+ iconResults.push(
340
+ await runTest("removeItemIcon", async () => {
341
+ assert(!!testFileId, "No item");
342
+ await thatOpenServices.removeItemIcon(testFileId);
343
+ }),
344
+ );
345
+ report({ name: "Icons", results: iconResults });
346
+
347
+ // ─── General Item Operations ───────────────────────────────
348
+ const generalResults: TestResult[] = [];
349
+
350
+ generalResults.push(
351
+ await runTest("updateItem (rename without version)", async () => {
352
+ assert(!!testFileId, "No item");
353
+ const item = await thatOpenServices.updateItem(testFileId, {
354
+ name: "_cloud_test_file_general_" + ts + ".txt",
355
+ });
356
+ assert(!!item._id, "item._id missing");
357
+ }),
358
+ );
359
+ generalResults.push(
360
+ await runTest("createVersion", async () => {
361
+ assert(!!testFileId, "No item");
362
+ const blob = new Blob(["version 3 content"], { type: "text/plain" });
363
+ const version = await thatOpenServices.createVersion(testFileId, blob, "v3");
364
+ assert(!!version, "version missing");
365
+ }),
366
+ );
367
+ report({ name: "General Item Operations", results: generalResults });
368
+
369
+ // Cleanup test file
370
+ if (testFileId) {
371
+ try { await thatOpenServices.archiveFile(testFileId); } catch { /* cleanup */ }
372
+ }
373
+
374
+ // ─── Components ────────────────────────────────────────────
375
+ const compResults: TestResult[] = [];
376
+ let testComponentId = "";
377
+
378
+ compResults.push(
379
+ await runTest("createComponent", async () => {
380
+ const blob = new Blob(["// test component"], { type: "application/javascript" });
381
+ const result = await thatOpenServices.createComponent({
382
+ file: blob,
383
+ name: "_cloud_test_component_" + ts,
384
+ versionTag: "v1",
385
+ componentProps: { type: "CLOUD", tier: "FREE" },
386
+ });
387
+ assert(!!result.item._id, "item._id missing");
388
+ assert(!!result.version, "version missing");
389
+ testComponentId = result.item._id;
390
+ }),
391
+ );
392
+ compResults.push(
393
+ await runTest("getComponent (with versions)", async () => {
394
+ assert(!!testComponentId, "No component");
395
+ const comp = await thatOpenServices.getComponent(testComponentId, { showVersions: true });
396
+ assert(comp._id === testComponentId, "_id mismatch");
397
+ assert(Array.isArray(comp.versions), "versions not array");
398
+ }),
399
+ );
400
+ compResults.push(
401
+ await runTest("listComponents", async () => {
402
+ const components = await thatOpenServices.listComponents();
403
+ assert(Array.isArray(components), "Not an array");
404
+ }),
405
+ );
406
+ compResults.push(
407
+ await runTest("updateComponent", async () => {
408
+ assert(!!testComponentId, "No component");
409
+ const blob = new Blob(["// updated"], { type: "application/javascript" });
410
+ const result = await thatOpenServices.updateComponent(testComponentId, {
411
+ name: "_cloud_test_component_renamed_" + ts,
412
+ file: blob,
413
+ versionTag: "v2",
414
+ componentProps: { type: "CLOUD", tier: "FREE" },
415
+ });
416
+ assert(!!result.version || !!result.item, "No update result");
417
+ }),
418
+ );
419
+ compResults.push(
420
+ await runTest("downloadComponent", async () => {
421
+ assert(!!testComponentId, "No component");
422
+ const response = await thatOpenServices.downloadComponent(testComponentId);
423
+ assert(!!response, "No response");
424
+ }),
425
+ );
426
+ compResults.push(
427
+ await runTest("downloadComponentBundle", async () => {
428
+ assert(!!testComponentId, "No component");
429
+ const response = await thatOpenServices.downloadComponentBundle(testComponentId);
430
+ assert(!!response, "No response");
431
+ }),
432
+ );
433
+ compResults.push(
434
+ await runTest("archiveComponent", async () => {
435
+ assert(!!testComponentId, "No component");
436
+ await thatOpenServices.archiveComponent(testComponentId);
437
+ }),
438
+ );
439
+ compResults.push(
440
+ await runTest("recoverComponent", async () => {
441
+ assert(!!testComponentId, "No component");
442
+ await thatOpenServices.recoverComponent(testComponentId);
443
+ }),
444
+ );
445
+ report({ name: "Components", results: compResults });
446
+
447
+ // ─── Apps ───────────────────────────────────────────────────
448
+ const appResults: TestResult[] = [];
449
+ let testAppId = "";
450
+
451
+ appResults.push(
452
+ await runTest("listApps", async () => {
453
+ const apps = await thatOpenServices.listApps();
454
+ assert(Array.isArray(apps), "Not an array");
455
+ }),
456
+ );
457
+ appResults.push(
458
+ await runTest("createApp", async () => {
459
+ const blob = new Blob(["// test app"], { type: "application/javascript" });
460
+ const result = await thatOpenServices.createApp({
461
+ file: blob,
462
+ name: "_cloud_test_app_" + ts,
463
+ versionTag: "v1",
464
+ });
465
+ assert(!!result.item._id, "item._id missing");
466
+ assert(!!result.version, "version missing");
467
+ testAppId = result.item._id;
468
+ }),
469
+ );
470
+ appResults.push(
471
+ await runTest("downloadApp", async () => {
472
+ assert(!!testAppId, "No app");
473
+ const response = await thatOpenServices.downloadApp(testAppId);
474
+ assert(!!response, "No response");
475
+ }),
476
+ );
477
+ appResults.push(
478
+ await runTest("downloadAppBundle", async () => {
479
+ assert(!!testAppId, "No app");
480
+ const response = await thatOpenServices.downloadAppBundle(testAppId);
481
+ assert(!!response, "No response");
482
+ }),
483
+ );
484
+ appResults.push(
485
+ await runTest("archiveApp", async () => {
486
+ assert(!!testAppId, "No app");
487
+ await thatOpenServices.archiveApp(testAppId);
488
+ }),
489
+ );
490
+ report({ name: "Apps", results: appResults });
491
+
492
+ // ─── Execution ─────────────────────────────────────────────
493
+ // Uses the test component created above to exercise execution endpoints.
494
+ // Note: executeComponent triggers a NEW execution of the given component.
495
+ // The component bundle is not valid, so the execution itself will likely
496
+ // fail, but the API calls are still fully tested.
497
+ const execResults: TestResult[] = [];
498
+ let testExecId = "";
499
+
500
+ if (testComponentId) {
501
+ execResults.push(
502
+ await runTest("executeComponent", async () => {
503
+ const result = await thatOpenServices.executeComponent(testComponentId, {
504
+ testParam: "hello",
505
+ });
506
+ assert(!!result.executionId, "executionId missing");
507
+ testExecId = result.executionId;
508
+ }),
509
+ );
510
+ execResults.push(
511
+ await runTest("getExecution", async () => {
512
+ assert(!!testExecId, "No execution");
513
+ const execution = await thatOpenServices.getExecution(testExecId);
514
+ assert(!!execution, "execution missing");
515
+ }),
516
+ );
517
+ execResults.push(
518
+ await runTest("listExecutions", async () => {
519
+ assert(!!testComponentId, "No component");
520
+ const executions = await thatOpenServices.listExecutions(testComponentId);
521
+ assert(Array.isArray(executions), "Not an array");
522
+ }),
523
+ );
524
+ } else {
525
+ const skip = (n: string) => ({ name: n, status: "skip" as const, message: "Component creation failed" });
526
+ execResults.push(skip("executeComponent"), skip("getExecution"), skip("listExecutions"));
527
+ }
528
+ report({ name: "Execution", results: execResults });
529
+
530
+ // ─── Built-in Components ───────────────────────────────────
531
+ const builtInResults: TestResult[] = [];
532
+ builtInResults.push(
533
+ await runTest("getBuiltInComponent (HelloWorld)", async () => {
534
+ const source = await thatOpenServices.getBuiltInComponent(
535
+ "2c4ae432-fc24-43e9-9783-0c960c674e96",
536
+ );
537
+ assert(typeof source === "string", "Not a string");
538
+ assert(source.length > 0, "Empty source");
539
+ }),
540
+ );
541
+ report({ name: "Built-in Components", results: builtInResults });
542
+
543
+ // ─── Cleanup ───────────────────────────────────────────────
544
+ if (testComponentId) {
545
+ try { await thatOpenServices.archiveComponent(testComponentId); } catch { /* cleanup */ }
546
+ }
547
+ if (testFolderId) {
548
+ try { await thatOpenServices.archiveFolder(testFolderId); } catch { /* cleanup */ }
549
+ }
550
+
551
+ // ─── Summary ───────────────────────────────────────────────
552
+ const summary = totalPass + " passed, " + totalFail + " failed, " + totalSkip + " skipped";
553
+ executionReporter.message("\nTest suite complete: " + summary);
554
+ executionReporter.progress(100);
555
+
556
+ if (totalFail > 0) {
557
+ const failedNames = groups
558
+ .flatMap((g) => g.results)
559
+ .filter((r) => r.status === "fail")
560
+ .map((r) => r.name + ": " + r.message)
561
+ .join("; ");
562
+ return { type: "WARNING", message: summary + " — Failed: " + failedNames };
563
+ }
564
+ return { type: "SUCCESS", message: summary };
565
+ }
@@ -0,0 +1,92 @@
1
+ # ThatOpen App
2
+
3
+ This is an app built for the That Open Platform.
4
+ It runs in the browser inside the platform's iframe.
5
+
6
+ ## How this app works
7
+
8
+ - **Entry point**: `src/main.ts` — runs as an IIFE when the platform loads the app.
9
+ - **Build output**: `dist/bundle.js` — a single IIFE file built by Vite.
10
+ - **Platform context**: The platform injects `window.__THATOPEN_CONTEXT__` with:
11
+ - `appId` — this app's unique ID
12
+ - `projectId` — the project this app belongs to
13
+ - `accessToken` — Auth0 JWT for API calls
14
+ - `apiUrl` — base URL for the That Open API
15
+ - **Mount point**: Your app renders into the `#that-open-app` element in `index.html`.
16
+
17
+ ## Commands
18
+
19
+ ```bash
20
+ npm run dev # Start dev server (esbuild watch + serve on :4000)
21
+ npm run build # Build dist/bundle.js (Vite/Rollup production build)
22
+ npm run login # Authenticate with the platform (saves token locally)
23
+ npm run publish # Publish to the platform
24
+ ```
25
+
26
+ ### Local development
27
+
28
+ Apps run inside the That Open Platform (platform.thatopen.com) within a project —
29
+ not as standalone websites. To develop locally:
30
+
31
+ 1. Run `npm run dev` — this watches source files with esbuild and serves the bundle on port 4000.
32
+ 2. Open your project on the platform and click the debug button.
33
+ 3. Live reload is enabled — save a file to rebuild automatically.
34
+
35
+ The dev server (`thatopen serve`) uses esbuild for near-instant incremental rebuilds.
36
+ **Important**: Do NOT run `vite`, `vite build --watch`, or `npx vite` directly for development.
37
+ Always use `npm run dev` which runs `thatopen serve` under the hood.
38
+
39
+ ## Adding BIM capabilities
40
+
41
+ To add 3D BIM viewing, install the BIM dependencies:
42
+
43
+ ```bash
44
+ npm install @thatopen/components @thatopen/ui three thatopen-services
45
+ ```
46
+
47
+ Then follow the BIM app pattern:
48
+
49
+ ```ts
50
+ import * as THREE from "three";
51
+ import * as OBC from "@thatopen/components";
52
+ import * as OBF from "@thatopen/components-front";
53
+ import * as FRAGS from "@thatopen/fragments";
54
+ import * as BUI from "@thatopen/ui";
55
+ import * as CUI from "@thatopen/ui-obc";
56
+ import { PlatformClient, AppManager, ViewportManager } from "thatopen-services";
57
+
58
+ const client = PlatformClient.fromPlatformContext();
59
+ const { components } = await client.setup(
60
+ { OBC, OBF, BUI, CUI, THREE, FRAGS },
61
+ AppManager, ViewportManager,
62
+ );
63
+
64
+ const viewports = components.get(ViewportManager);
65
+ const { element, world } = await viewports.create();
66
+ ```
67
+
68
+ ## EngineServicesClient API
69
+
70
+ If you install `thatopen-services`, you can make API calls:
71
+
72
+ ```ts
73
+ import { PlatformClient } from "thatopen-services";
74
+
75
+ const client = PlatformClient.fromPlatformContext();
76
+
77
+ // Files
78
+ const files = await client.listFiles();
79
+ const response = await client.downloadFile(fileId);
80
+
81
+ // Folders
82
+ const folders = await client.listFolders();
83
+ await client.createFolder("My Folder");
84
+
85
+ // Execute cloud components
86
+ const { executionId } = await client.executeComponent(componentId, { param: "value" });
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ - `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, and `appId` after first publish.
92
+ - `vite.config.js` — builds to IIFE format as `dist/bundle.js`. All dependencies are bundled.
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "thatopen serve",
7
+ "build": "vite build",
8
+ "login": "thatopen login --local",
9
+ "publish": "thatopen publish"
10
+ },
11
+ "devDependencies": {
12
+ "typescript": "^5.2.0",
13
+ "vite": "^5.2.0"
14
+ }
15
+ }