@thatopen/services 0.3.16 → 0.5.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.
Files changed (76) hide show
  1. package/README.md +62 -62
  2. package/dist/cli/commands/rhino.d.ts +11 -0
  3. package/dist/cli/lib/plugin-install.d.ts +24 -0
  4. package/dist/cli.js +124 -1
  5. package/dist/core/client.d.ts +1 -0
  6. package/dist/core/platform-client.d.ts +7 -0
  7. package/dist/index.cjs.js +2 -2
  8. package/dist/index.es.js +382 -370
  9. package/dist/vite-env.d.ts +1 -1
  10. package/docs/ai-quickstart.md +130 -130
  11. package/docs/cli/create.md +29 -29
  12. package/docs/cli/local-server.md +38 -38
  13. package/docs/cli/login.md +32 -32
  14. package/docs/cli/publish.md +31 -31
  15. package/docs/cli/run.md +26 -26
  16. package/docs/cli/serve.md +43 -43
  17. package/docs/cli/swap.md +30 -30
  18. package/docs/cloud/GaussianSplatConverter.md +43 -43
  19. package/docs/cloud/PointCloudConverter.md +53 -53
  20. package/docs/revit-collab-quickstart.md +256 -256
  21. package/package.json +95 -95
  22. package/resources/AGENTS.md +40 -40
  23. package/src/built-in/CDEManager/example.ts +214 -214
  24. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  25. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  26. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  27. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  28. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  29. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  30. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  31. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  32. package/src/built-in/ClashesManager/example.ts +283 -283
  33. package/src/built-in/UIManager/src/app/example.ts +97 -97
  34. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
  35. package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
  36. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
  37. package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
  38. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
  39. package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
  40. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
  41. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
  42. package/src/cli/templates/app/README.md +217 -217
  43. package/src/cli/templates/app/index.html +35 -35
  44. package/src/cli/templates/app/package.json +32 -32
  45. package/src/cli/templates/app/src/app.ts +37 -37
  46. package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
  47. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
  48. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  49. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  50. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  51. package/src/cli/templates/app/src/globals.ts +1 -1
  52. package/src/cli/templates/app/src/main.ts +199 -199
  53. package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
  54. package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
  55. package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
  56. package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
  57. package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
  58. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
  59. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
  60. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
  61. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
  62. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
  63. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
  64. package/src/cli/templates/app/src/ui-components/index.ts +2 -2
  65. package/src/cli/templates/app/tsconfig.json +16 -16
  66. package/src/cli/templates/app/vite.config.js +57 -57
  67. package/src/cli/templates/cloud-component/README.md +205 -205
  68. package/src/cli/templates/cloud-component/_thatopen +5 -5
  69. package/src/cli/templates/cloud-component/declarations.json +4 -4
  70. package/src/cli/templates/cloud-component/package.json +21 -21
  71. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  72. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  73. package/src/cli/templates/cloud-component/vite.config.js +46 -46
  74. package/src/cli/templates/shared/AGENTS.md +8 -8
  75. package/src/cli/templates/shared/CLAUDE.md +1 -1
  76. package/src/cli/templates/shared/_gitignore +5 -5
@@ -1,210 +1,210 @@
1
- /* MD
2
- ## File uploads, versioning, and the download cache
3
- ---
4
- A site coordinator drops a batch of files onto the CDE every Monday after the
5
- weekend review: some are entirely new models, some are revised versions of files
6
- already in the project, and sometimes the metadata group they upload into should
7
- be stamped automatically onto the new files. If the platform makes the coordinator
8
- sort this out manually, it slows the handoff and introduces naming errors. And if
9
- an upload fails halfway through a batch, the file table should land in a safe state
10
- rather than freeze at a loading spinner.
11
-
12
- CDEManager's upload method compares each incoming file against the current list by
13
- base name and extension. Files with no match are created from scratch; files that
14
- match an existing entry produce a new version instead. When a metadata group is
15
- active, the group's values are stamped onto every file in the batch without any
16
- extra action from the user. The state machine moves through a dedicated uploading
17
- state so any component in the platform can show a progress indicator by reading a
18
- single source of truth.
19
-
20
- This tutorial covers uploading new files and reading the result from the
21
- upload-completed event; uploading a file whose name matches an existing entry and
22
- confirming it is versioned rather than duplicated; attaching metadata automatically
23
- when the upload runs inside an active metadata group; recovering cleanly from an
24
- upload error through the state machine; and reading file content through the
25
- in-memory cache that avoids re-fetching the same version twice.
26
-
27
- By the end, you'll have a working demonstration of the full upload lifecycle —
28
- create, version, auto-metadata, error recovery — plus the cache hit and miss
29
- behavior that the viewer relies on when loading models.
30
- */
31
-
32
- import * as OBC from "@thatopen/components";
33
- // In production platform apps, CDEManager is loaded through the platform client:
34
- // const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
35
- // Here we import directly from source for local testing.
36
- import { CDEManager } from "../../index";
37
- import type { CDEFile } from "../../src/types";
38
-
39
- /* MD
40
- ### 🛠️ Setting up
41
- All flows in this example use a minimal mock client so they run without a backend
42
- connection. Unlike mock flows that clear the client context after loading permissions,
43
- upload and download operations require an active client throughout — so the context
44
- stays set for the full example.
45
- */
46
-
47
- const components = new OBC.Components();
48
- const cde = components.get(CDEManager);
49
-
50
- const output = document.getElementById("output") as HTMLPreElement;
51
- const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
52
-
53
- let fileCounter = 0;
54
- // Track created version records so the versioning demo can find them.
55
- const mockVersions: Record<string, { tag: string }[]> = {};
56
-
57
- const mockClient = {
58
- checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
59
- getProjectData: async () => ({ project: { title: "Demo Project" }, users: [] }),
60
- listFolders: async () => [],
61
- createFile: async ({ name, versionTag, parentFolderId }: any) => {
62
- const id = `file-${fileCounter++}`;
63
- mockVersions[id] = [{ tag: versionTag }];
64
- return {
65
- item: { _id: id, name, folderId: parentFolderId ?? null, createdAt: new Date().toISOString(), createdBy: "mock-user", itemType: "FILE" },
66
- version: { tag: versionTag },
67
- };
68
- },
69
- listVersions: async (fileId: string) => mockVersions[fileId] ?? [],
70
- createVersion: async (fileId: string, _file: File, tag: string) => {
71
- mockVersions[fileId] = [...(mockVersions[fileId] ?? []), { tag }];
72
- return { tag };
73
- },
74
- updateFileVersionMetadata: async () => ({}),
75
- downloadFile: async (_id: string) => ({ arrayBuffer: async () => new ArrayBuffer(4096) }),
76
- archiveFile: async () => ({}),
77
- };
78
-
79
- cde.ctx = { client: mockClient as any, projectId: "mock" };
80
- await cde.loadFolders();
81
-
82
- cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
83
-
84
- /* MD
85
- ### 📤 Uploading new files
86
- Calling the upload method transitions the machine to an uploading state, carries
87
- out the operation, then returns to idle and fires a completion event. The event
88
- payload separates newly created files from versioned ones, so the UI can show a
89
- targeted confirmation — "2 files added" versus "1 file updated" — without parsing
90
- the whole file list.
91
- */
92
-
93
- cde.onUploadCompleted.add(({ created, versioned }) => {
94
- log(` [onUploadCompleted] created: ${created.length}, versioned: ${versioned.length}`);
95
- for (const f of created) log(` + ${f.name}`);
96
- for (const f of versioned) log(` ↑ ${f.name}`);
97
- });
98
-
99
- log("\n--- upload: create new files ---");
100
- const f1 = new File([new ArrayBuffer(100)], "column.ifc", { type: "application/octet-stream" });
101
- const f2 = new File([new ArrayBuffer(50)], "report.pdf");
102
- await cde.uploadFiles([f1, f2]);
103
- log(` cde.files after upload: ${cde.files.length}`);
104
-
105
- /* MD
106
- ### 🔄 Versioning an existing file
107
- When the incoming file's base name and extension match an entry already in the list,
108
- the upload method creates a new version rather than a duplicate. The file counter in
109
- the version tag is computed from the existing version history, so the tag always
110
- follows on from whatever the project already has.
111
- */
112
-
113
- log("\n--- upload: version existing file ---");
114
- // "column.ifc" is already in cde.files from the previous upload.
115
- const f1v2 = new File([new ArrayBuffer(120)], "column.ifc", { type: "application/octet-stream" });
116
- await cde.uploadFiles([f1v2]);
117
- log(` cde.files after version upload: ${cde.files.length}`);
118
-
119
- const colFile = cde.files.find(f => f.name === "column.ifc")!;
120
- log(` column.ifc version tag: ${(colFile as any).versionTag}`);
121
-
122
- /* MD
123
- ### 🏷️ Auto-metadata from a metadata group
124
- When metadata grouping is active and a group is selected, every file uploaded in
125
- that session inherits the group's metadata values automatically. The coordinator
126
- selects the "Structural SD phase" group, drops files, and every new file arrives
127
- already tagged — no post-upload editing required.
128
- */
129
-
130
- log("\n--- upload: auto-metadata ---");
131
- cde.sendMachineEvent({ type: "SET_GROUPS_VIEW_MODE", mode: "metadata" });
132
- cde.selectedMetadataGroup = { discipline: "STR", phase: "SD" };
133
-
134
- const f3 = new File([new ArrayBuffer(80)], "foundations-v2.ifc", { type: "application/octet-stream" });
135
- await cde.uploadFiles([f3]);
136
-
137
- const uploaded = cde.files.find(f => f.name === "foundations-v2.ifc")!;
138
- log(` discipline: ${uploaded.metadata.discipline}, phase: ${uploaded.metadata.phase}`);
139
-
140
- // Reset back to groups view for subsequent sections.
141
- cde.sendMachineEvent({ type: "SET_GROUPS_VIEW_MODE", mode: "groups" });
142
- cde.selectedMetadataGroup = null;
143
-
144
- /* MD
145
- ### ⚠️ Recovering from an upload error
146
- If the backend rejects the upload, the machine moves to an error state rather than
147
- silently returning to idle. The error state carries the reason message so the UI can
148
- surface it, and sending a dismiss event returns to idle so the user can try again.
149
- No manual state cleanup is needed.
150
- */
151
-
152
- log("\n--- upload: error recovery ---");
153
- cde.ctx = {
154
- client: {
155
- ...mockClient,
156
- createFile: async () => { throw new Error("Upload failed — server unavailable"); },
157
- } as any,
158
- projectId: "mock",
159
- };
160
-
161
- const fBroken = new File([new ArrayBuffer(10)], "broken.ifc", { type: "application/octet-stream" });
162
- await cde.uploadFiles([fBroken]);
163
-
164
- if (cde.machineState.kind === "error") {
165
- log(` error reason: ${cde.machineState.reason}`);
166
- cde.sendMachineEvent({ type: "DISMISS" });
167
- }
168
- log(` state after dismiss: ${cde.machineState.kind}`);
169
-
170
- // Restore the working mock client.
171
- cde.ctx = { client: mockClient as any, projectId: "mock" };
172
-
173
- /* MD
174
- ### 💾 File download cache
175
- The download method on CDEManager keeps a buffer cache in memory so the viewer
176
- does not re-fetch the same file version twice during a session. The cache logs each
177
- access — a miss triggers a download, a hit returns the stored buffer immediately.
178
- Only extensions in `cacheExtensions` are stored; all others are downloaded every
179
- time so the cache stays focused on the large binary files that benefit most from it.
180
-
181
- Note: files uploaded in the same session are already placed in the cache by the
182
- upload method. The demo below uses a file injected directly — simulating a file
183
- that arrived from a previous session load — to show a clean miss on the first
184
- access.
185
- */
186
-
187
- log("\n--- download cache ---");
188
-
189
- // A file loaded from the backend (not uploaded this session) starts with no cache entry.
190
- const serverFile = {
191
- _id: "server-ifc", name: "arch.ifc", folderId: null, versionTag: "v3",
192
- createdBy: "carol", createdAt: new Date(), metadata: {},
193
- } as CDEFile;
194
- cde.files = [...cde.files, serverFile];
195
-
196
- // First access — cache miss, buffer fetched and stored.
197
- const buf1 = await cde.downloadFile(serverFile);
198
- log(` arch.ifc first access (miss): ${buf1.byteLength} bytes`);
199
-
200
- // Second access — cache hit, no network call.
201
- const buf2 = await cde.downloadFile(serverFile);
202
- log(` arch.ifc second access (hit): ${buf2.byteLength} bytes`);
203
-
204
- // PDF extension is not in cacheExtensions — fetched but not stored.
205
- const pdfFile = cde.files.find(f => f.name === "report.pdf")!;
206
- const bufPdf = await cde.downloadFile(pdfFile);
207
- log(` report.pdf access (skip): ${bufPdf.byteLength} bytes`);
208
-
209
- log(`\n cacheExtensions: [${[...cde.cacheExtensions].join(", ")}]`);
210
- log(` cacheMaxBytes: ${(cde.cacheMaxBytes / 1024 / 1024).toFixed(0)} MB`);
1
+ /* MD
2
+ ## File uploads, versioning, and the download cache
3
+ ---
4
+ A site coordinator drops a batch of files onto the CDE every Monday after the
5
+ weekend review: some are entirely new models, some are revised versions of files
6
+ already in the project, and sometimes the metadata group they upload into should
7
+ be stamped automatically onto the new files. If the platform makes the coordinator
8
+ sort this out manually, it slows the handoff and introduces naming errors. And if
9
+ an upload fails halfway through a batch, the file table should land in a safe state
10
+ rather than freeze at a loading spinner.
11
+
12
+ CDEManager's upload method compares each incoming file against the current list by
13
+ base name and extension. Files with no match are created from scratch; files that
14
+ match an existing entry produce a new version instead. When a metadata group is
15
+ active, the group's values are stamped onto every file in the batch without any
16
+ extra action from the user. The state machine moves through a dedicated uploading
17
+ state so any component in the platform can show a progress indicator by reading a
18
+ single source of truth.
19
+
20
+ This tutorial covers uploading new files and reading the result from the
21
+ upload-completed event; uploading a file whose name matches an existing entry and
22
+ confirming it is versioned rather than duplicated; attaching metadata automatically
23
+ when the upload runs inside an active metadata group; recovering cleanly from an
24
+ upload error through the state machine; and reading file content through the
25
+ in-memory cache that avoids re-fetching the same version twice.
26
+
27
+ By the end, you'll have a working demonstration of the full upload lifecycle —
28
+ create, version, auto-metadata, error recovery — plus the cache hit and miss
29
+ behavior that the viewer relies on when loading models.
30
+ */
31
+
32
+ import * as OBC from "@thatopen/components";
33
+ // In production platform apps, CDEManager is loaded through the platform client:
34
+ // const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
35
+ // Here we import directly from source for local testing.
36
+ import { CDEManager } from "../../index";
37
+ import type { CDEFile } from "../../src/types";
38
+
39
+ /* MD
40
+ ### 🛠️ Setting up
41
+ All flows in this example use a minimal mock client so they run without a backend
42
+ connection. Unlike mock flows that clear the client context after loading permissions,
43
+ upload and download operations require an active client throughout — so the context
44
+ stays set for the full example.
45
+ */
46
+
47
+ const components = new OBC.Components();
48
+ const cde = components.get(CDEManager);
49
+
50
+ const output = document.getElementById("output") as HTMLPreElement;
51
+ const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
52
+
53
+ let fileCounter = 0;
54
+ // Track created version records so the versioning demo can find them.
55
+ const mockVersions: Record<string, { tag: string }[]> = {};
56
+
57
+ const mockClient = {
58
+ checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
59
+ getProjectData: async () => ({ project: { title: "Demo Project" }, users: [] }),
60
+ listFolders: async () => [],
61
+ createFile: async ({ name, versionTag, parentFolderId }: any) => {
62
+ const id = `file-${fileCounter++}`;
63
+ mockVersions[id] = [{ tag: versionTag }];
64
+ return {
65
+ item: { _id: id, name, folderId: parentFolderId ?? null, createdAt: new Date().toISOString(), createdBy: "mock-user", itemType: "FILE" },
66
+ version: { tag: versionTag },
67
+ };
68
+ },
69
+ listVersions: async (fileId: string) => mockVersions[fileId] ?? [],
70
+ createVersion: async (fileId: string, _file: File, tag: string) => {
71
+ mockVersions[fileId] = [...(mockVersions[fileId] ?? []), { tag }];
72
+ return { tag };
73
+ },
74
+ updateFileVersionMetadata: async () => ({}),
75
+ downloadFile: async (_id: string) => ({ arrayBuffer: async () => new ArrayBuffer(4096) }),
76
+ archiveFile: async () => ({}),
77
+ };
78
+
79
+ cde.ctx = { client: mockClient as any, projectId: "mock" };
80
+ await cde.loadFolders();
81
+
82
+ cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
83
+
84
+ /* MD
85
+ ### 📤 Uploading new files
86
+ Calling the upload method transitions the machine to an uploading state, carries
87
+ out the operation, then returns to idle and fires a completion event. The event
88
+ payload separates newly created files from versioned ones, so the UI can show a
89
+ targeted confirmation — "2 files added" versus "1 file updated" — without parsing
90
+ the whole file list.
91
+ */
92
+
93
+ cde.onUploadCompleted.add(({ created, versioned }) => {
94
+ log(` [onUploadCompleted] created: ${created.length}, versioned: ${versioned.length}`);
95
+ for (const f of created) log(` + ${f.name}`);
96
+ for (const f of versioned) log(` ↑ ${f.name}`);
97
+ });
98
+
99
+ log("\n--- upload: create new files ---");
100
+ const f1 = new File([new ArrayBuffer(100)], "column.ifc", { type: "application/octet-stream" });
101
+ const f2 = new File([new ArrayBuffer(50)], "report.pdf");
102
+ await cde.uploadFiles([f1, f2]);
103
+ log(` cde.files after upload: ${cde.files.length}`);
104
+
105
+ /* MD
106
+ ### 🔄 Versioning an existing file
107
+ When the incoming file's base name and extension match an entry already in the list,
108
+ the upload method creates a new version rather than a duplicate. The file counter in
109
+ the version tag is computed from the existing version history, so the tag always
110
+ follows on from whatever the project already has.
111
+ */
112
+
113
+ log("\n--- upload: version existing file ---");
114
+ // "column.ifc" is already in cde.files from the previous upload.
115
+ const f1v2 = new File([new ArrayBuffer(120)], "column.ifc", { type: "application/octet-stream" });
116
+ await cde.uploadFiles([f1v2]);
117
+ log(` cde.files after version upload: ${cde.files.length}`);
118
+
119
+ const colFile = cde.files.find(f => f.name === "column.ifc")!;
120
+ log(` column.ifc version tag: ${(colFile as any).versionTag}`);
121
+
122
+ /* MD
123
+ ### 🏷️ Auto-metadata from a metadata group
124
+ When metadata grouping is active and a group is selected, every file uploaded in
125
+ that session inherits the group's metadata values automatically. The coordinator
126
+ selects the "Structural SD phase" group, drops files, and every new file arrives
127
+ already tagged — no post-upload editing required.
128
+ */
129
+
130
+ log("\n--- upload: auto-metadata ---");
131
+ cde.sendMachineEvent({ type: "SET_GROUPS_VIEW_MODE", mode: "metadata" });
132
+ cde.selectedMetadataGroup = { discipline: "STR", phase: "SD" };
133
+
134
+ const f3 = new File([new ArrayBuffer(80)], "foundations-v2.ifc", { type: "application/octet-stream" });
135
+ await cde.uploadFiles([f3]);
136
+
137
+ const uploaded = cde.files.find(f => f.name === "foundations-v2.ifc")!;
138
+ log(` discipline: ${uploaded.metadata.discipline}, phase: ${uploaded.metadata.phase}`);
139
+
140
+ // Reset back to groups view for subsequent sections.
141
+ cde.sendMachineEvent({ type: "SET_GROUPS_VIEW_MODE", mode: "groups" });
142
+ cde.selectedMetadataGroup = null;
143
+
144
+ /* MD
145
+ ### ⚠️ Recovering from an upload error
146
+ If the backend rejects the upload, the machine moves to an error state rather than
147
+ silently returning to idle. The error state carries the reason message so the UI can
148
+ surface it, and sending a dismiss event returns to idle so the user can try again.
149
+ No manual state cleanup is needed.
150
+ */
151
+
152
+ log("\n--- upload: error recovery ---");
153
+ cde.ctx = {
154
+ client: {
155
+ ...mockClient,
156
+ createFile: async () => { throw new Error("Upload failed — server unavailable"); },
157
+ } as any,
158
+ projectId: "mock",
159
+ };
160
+
161
+ const fBroken = new File([new ArrayBuffer(10)], "broken.ifc", { type: "application/octet-stream" });
162
+ await cde.uploadFiles([fBroken]);
163
+
164
+ if (cde.machineState.kind === "error") {
165
+ log(` error reason: ${cde.machineState.reason}`);
166
+ cde.sendMachineEvent({ type: "DISMISS" });
167
+ }
168
+ log(` state after dismiss: ${cde.machineState.kind}`);
169
+
170
+ // Restore the working mock client.
171
+ cde.ctx = { client: mockClient as any, projectId: "mock" };
172
+
173
+ /* MD
174
+ ### 💾 File download cache
175
+ The download method on CDEManager keeps a buffer cache in memory so the viewer
176
+ does not re-fetch the same file version twice during a session. The cache logs each
177
+ access — a miss triggers a download, a hit returns the stored buffer immediately.
178
+ Only extensions in `cacheExtensions` are stored; all others are downloaded every
179
+ time so the cache stays focused on the large binary files that benefit most from it.
180
+
181
+ Note: files uploaded in the same session are already placed in the cache by the
182
+ upload method. The demo below uses a file injected directly — simulating a file
183
+ that arrived from a previous session load — to show a clean miss on the first
184
+ access.
185
+ */
186
+
187
+ log("\n--- download cache ---");
188
+
189
+ // A file loaded from the backend (not uploaded this session) starts with no cache entry.
190
+ const serverFile = {
191
+ _id: "server-ifc", name: "arch.ifc", folderId: null, versionTag: "v3",
192
+ createdBy: "carol", createdAt: new Date(), metadata: {},
193
+ } as CDEFile;
194
+ cde.files = [...cde.files, serverFile];
195
+
196
+ // First access — cache miss, buffer fetched and stored.
197
+ const buf1 = await cde.downloadFile(serverFile);
198
+ log(` arch.ifc first access (miss): ${buf1.byteLength} bytes`);
199
+
200
+ // Second access — cache hit, no network call.
201
+ const buf2 = await cde.downloadFile(serverFile);
202
+ log(` arch.ifc second access (hit): ${buf2.byteLength} bytes`);
203
+
204
+ // PDF extension is not in cacheExtensions — fetched but not stored.
205
+ const pdfFile = cde.files.find(f => f.name === "report.pdf")!;
206
+ const bufPdf = await cde.downloadFile(pdfFile);
207
+ log(` report.pdf access (skip): ${bufPdf.byteLength} bytes`);
208
+
209
+ log(`\n cacheExtensions: [${[...cde.cacheExtensions].join(", ")}]`);
210
+ log(` cacheMaxBytes: ${(cde.cacheMaxBytes / 1024 / 1024).toFixed(0)} MB`);