- dfa30c8: Inject the
executionContextglobal (projectId,executionId,toolId,toolVersion) intothatopen run/local-server executions, matching what the platform injects at real execution time. Components readingexecutionContextlocally no longer crash with "executionContext is not defined".
- 47b18f3: Export
ProjectManager, the canonical project-level data store for origin/georeferencing, BIM site coordinates, asset coordinates, and graphics settings.
-
21e4fb5: Rate limit guidance and a backoff-aware retry policy.
docs/rate-limits.mddocuments the per-endpoint limits, the429body, and the local-draft save pattern (keep work in progress inlocalStorage/ IndexedDB, write on an explicit save).resources/AGENTS.mdgains a hard rule against autosaving to the platform on every change, so assistants stop building write-per-keystroke loops.RequestError.retryAfterexposes the wait in seconds, read fromRetry-Afterordetails.retryAfter.- Retries now back off exponentially with jitter and honour
Retry-After. Only network failures,429and5xxare retried — other4xxfail immediately instead of being repeated. Retries remain off by default.
-
3d53d8e: Add batch read methods so a page of files, or a model's tiles, can be hydrated in one request instead of one per id.
getHiddenFileSignedUrlsBatch(hiddenIds, expiresIn?)— signs many hidden files at once. This is the call tile-based viewers (splats, point clouds) should use; minting one URL per tile as the camera moves is what pushes a single session past the rate limit.listVersionsBatch(itemIds, { archived? })— versions for many items. The records carry their metadata, so a list that only needs metadata does not need a second call.getFileVersionMetadataBatch(entries, { withDraft? })— metadata for many{ itemId, versionTag }pairs.getFoldersBatch(folderIds)— resolves a known set of folder ids.
All four split inputs longer than
STORAGE_BATCH_MAX(100) into several requests automatically, return entries in request order, and mark an id the caller cannot read with anerrorinstead of failing the whole batch.Requires the matching backend endpoints (
POST /item/hidden/signed-url/batch,POST /item/batch/versions,POST /item/batch/version-metadata,POST /item/batch/folders).
-
94e91d4: Add
outcome,groupKeyandgroupLabeltoNotificationDto, matching what the API already returns.Read
outcometo tell how an automation run ended rather than matching on the copy:titleis built from the user's own automation name, so an automation called "Failover sync" makes every successful run look failed to anything parsing the text.groupKeyandgroupLabelare what a client needs to collapse a busy automation's runs into one row.
-
9660e7d: Add notification methods to
PlatformClient:getNotifications,getUnreadNotificationCount,markNotificationsRead,markAllNotificationsRead,getNotificationSubscriptionsandunsubscribeFromAutomation. All scoped to the signed-in user via the bearer token an app already has.The notification types mirror the backend's wire DTOs in
src/types, the same as every other type here. -
9660e7d: Add
subscribeToAutomationandupdateAutomationSubscription, so an app can create a subscription rather than only listing and cancelling one.Add
onNotification, a live socket subscription for the signed-in user. UnlikeonExecutionProgressit stays connected for the session rather than closing on a terminal event, and it returns a function that disconnects.
-
Two commands that collapse the work of about a dozen.
thatopen revit share --file <path> --project <id> [--doc <name>]takes a.rvton disk to your own local open in Revit. It installs the add-in when nothing is listening, starts Revit and waits for it, checks whether the file is already a central, publishes it, and joins. Five named steps, so a long silence is always something you can point at. It accepts the dashboard URL as well as the project id, derives--docfrom the file name, and always works on a copy. The pieces it drives are unchanged and still available on their own.thatopen create <name> --beta --historyscaffolds an app with the revit-flow commit history panel already wired, opening on the History layout. It swaps in a wholemain.tsrather than patching one: the wiring is four additions in three places and the order between them is load-bearing.
-
History commits are named by guid, and a commit's parents are a list.
The built-in history types described a schema the shipped component no longer speaks. A commit's
id: numberis nowguid: string: the integer was the platform's storage version, a number the CDE hands out, and an application that does not push through a Revit central has none. It survives asversion, data rather than identity, withordinalderived from the graph for ordering questions.parent: number | nullis nowparents?: string[]. A fork was already expressible with one parent; a merge needs both lines named. Revit never writes more than one.Also declared here rather than reimplemented per app:
deltaId(the hidden-file id of a commit's delta geometry, absent on the baseline), the proposal types, and the panel's own state.The Rhino guide gains the converter step: what Rhino publishes, the units contract, one
localIdperCREATE_ITEM, and the two upload fields that fail at runtime rather than at compile time.
-
01bcb5b: Export the
GitHistoryManagerbuilt-in, so an app can use the revit-flow history.The component and its panel (
top-git-history) are published and the platform serves them by UUID, but nothing in this package named them:import { GitHistoryManager } from "@thatopen/services"did not compile, which is the first line of any app that wants to show a model's history. Verified against the published tarball —UIManagerwas in it andGitHistoryManagerappeared in no file at all.It reads the history the Revit add-in publishes per sync and colours a commit's changed elements in the shared viewer: green created, blue modified, red deleted.
-
31576b1: CLI:
thatopen revit installandthatopen rhino install(both aliased asupdate) install the That Open Revit add-in and Rhino plug-in from their private npm packages.Access comes from the platform token, the same way
--betagets the private engine libraries: the CLI trades it for read-only registry credentials, so nobody needs an npm account or an npm token.npm does the downloading, the version resolution and the integrity check, rather than this hand-rolling HTTP and getting one of the three subtly wrong. The Revit package ships its own
install.ps1, so the CLI never has to know where Revit keeps its add-ins; the Rhino package is installed through Yak, which records the Rhino versions the plug-in targets and refuses to install into one it was not built for.Both refuse while the host application is running. That is the difference between a failed install and a broken one: Revit and Rhino hold their plug-in assemblies open for the whole session, so a copy over a running host fails on the first locked file after copying the ones it reached, leaving a folder with some new DLLs and some old that loads and misbehaves.
This is also why installing is the CLI's job at all. A Revit add-in cannot replace itself while Revit runs, so it can only notice it is out of date and say so; the CLI is what runs with Revit closed.
- e4452e7: Add
PlatformClient.getAvatar(accountId)to fetch a user's profile picture as an imageBlob, so apps can render member avatars.
- Rename the Revit add-in auth header to
X-RevitFlow-Token(revit-flow rebrand of the former "BT3" collaboration add-in). Requires the matching revit-flow add-in build. Also updates therevitcommand/lib comments and the collaboration quickstart guide.
-
Add
getHiddenFileSignedUrl(hiddenId, expiresIn?)to the client — returns a short-lived signed URL so large hidden files (e.g. a point cloud'soctree.bin) can be fetched directly with native HTTPRangerequests instead of downloading the whole object. -
Fix scaffolded apps failing to build with
Dynamic require of "https://cdn.jsdelivr.net/npm/…/+esm" is not supported. Some three.js example loaders (e.g.TTFLoader, pulled in bycomponents-front-beta) import their deps from a jsdelivr/+esmCDN URL, which a bundler can't place in an IIFE.thatopen serveand the app template'svite buildnow rewrite such URLs to the local package; the template also depends onopentype.jsand pinsthreeto0.185.0so a future three release can't reintroduce a different CDN import.
-
30a9034: CLI: auto-configure
.npmrcfor private beta packages.thatopen create --beta(andthatopen logininside a beta project) now fetch read-only npm credentials from the platform and write a project.npmrc, sonpm installof the private@thatopen-platform/*-betapackages just works for Founding members — no manual token setup. AddsEngineServicesClient.getNpmCredentials()and exports theNpmCredentialstype.
- 6f845c1: Republish attempt — ship
createHiddenFilesBatchto npm.
- 067b1af: Republish attempt — ship
createHiddenFilesBatchto npm now that publish credentials are configured.
- 15d6c25: Republish — the 0.1.0 release (which added
createHiddenFilesBatch) failed to publish to npm on an expired token. This ships that change.
- 4568b81: Add
EngineServicesClient.createHiddenFilesBatch()to upload many hidden files in a single request, for large 3D-tile sets (point clouds / gaussian splats) without hitting the per-file upload throttle. Exports theCreateHiddenItemsBatchResulttype.
- b598e3c: Surface structured API errors via a new RequestError class
-
e4fbb63: Per-version free-JSON metadata for files. Replaces the old single-endpoint
getFileMetadatawith three explicit version-scoped methods aligned with the new backend CRUD on/item/:id/version/:tag/metadata.New methods.
getFileVersionMetadata(fileId, versionTag, params?)—GET /item/:id/version/:tag/metadata. Returns{}when the version exists but has no metadata.updateFileVersionMetadata(fileId, versionTag, metadata)—PUT …/metadata. Replaces the version's metadata with the provided object.deleteFileVersionMetadata(fileId, versionTag)—DELETE …/metadata. Clears the version's metadata.
New types and constants.
Metadata = Record<string, MetadataValue>,MetadataValue = string | number | boolean | null, andMETADATA_LIMITS(200 fields, 50-char keys, 50-char values) are exported from the package root.metadatais now typed asMetadataeverywhere it appears:CreateItemProps,UpdateItemProps,createVersion's optional last argument.Breaking.
getFileMetadata(itemId, params?)is removed. It hitGET /item/:id/metadata, which has been deleted on the backend in favour of the version-scoped routes. Replace withgetFileVersionMetadata(fileId, versionTag, params?)— the version tag is now required because metadata is per-version. To target the live version, pass the tag of the latest non-draft version (the equivalent of the old default behaviour).Migration.
// before const metadata = await client.getFileMetadata(fileId); // after const metadata = await client.getFileVersionMetadata(fileId, 'v1');
createFile,updateFile, andcreateVersioncontinue to accept an optionalmetadataargument; the only change is the type — values can now bestring | number | boolean | nullinstead of juststring.
-
9f124f1: Add per-version lifecycle methods so callers can list, archive, recover, and permanently delete a single version of an item.
listVersions(itemId, { archived })—GET /item/:itemId/versions. Passarchived: trueto receive only archived versions,falsefor active only, or omit the option to receive both. Sorted by creation date descending.archiveVersion(itemId, versionTag)—PUT /item/:itemId/version/:versionTag/archive. Archived versions are hidden from the active list and queued for cleanup after the platform's retention window.recoverVersion(itemId, versionTag)—PUT /item/:itemId/version/:versionTag/recover. Returns an archived version to the active list.deleteVersion(itemId, versionTag)—DELETE /item/:itemId/version/:versionTag. The version must be archived first; the backend rejects the call otherwise. Removes the underlying object from S3 in addition to the database row.
All four go through the existing request layer, so they work with both auth modes (
accessTokenquery string for API tokens,Authorization: Bearer …forPlatformClientJWTs).
- 3a0b129: Send named File (with filename and mimetype) for bundle and icon uploads in the publish command.
-
b108648: Align the client with the platform's new project-scoped permissions model and split the client surface for apps vs components.
New:
PlatformClient. ExtendsEngineServicesClientwith a bearer-only constructor. Use it from apps, frontends, and any caller authenticating with a user JWT. On top of the inherited API-token-compatible surface,PlatformClientowns the JWT-only routesgetProject,getProjectData,checkPermission, andcheckPermissionBatch— those hitProjectControlleron the backend which is guarded by JWT, so they're not reachable from an access token.EngineServicesClientremains the right choice for components (API-token auth, local server, WebSocket progress).The
PlatformClientconstructor accepts either a static JWT string or a provider function (() => string | Promise<string>) that's called on every request — so Auth0'sgetAccessTokenSilently()and similar refreshing sources can be passed directly and expired tokens never stick.PlatformClient.fromPlatformContext()is available as a static factory for apps running inside the platform iframe.Project-scoped listings on the main list methods.
listFiles,listFolders,listApps, andlistComponentsnow accept an optionalprojectIdand forward it to the new publicGET /item?projectId=X/GET /item/folder?projectId=Xroutes. Per-entity role overrides are applied server-side; callers without project role permission get 403 (not an empty list). PassitemType: 'APP' | 'TOOL' | 'FILE'to switch what comes back.Updated permission checks.
checkPermissionnow returns{ hasPermission, scope }wherescopeis'global' | 'project' | 'entity' | 'none'. NewcheckPermissionBatch(checks)evaluates multiple checks in one round-trip.Execution scoping.
executeComponentacceptsprojectIdas a reserved key onexecutionParams; foreign project ids are rejected by the backend.listExecutions(componentId, projectId?)forwards the query param.Breaking. The v1 convenience helpers
listProjectFiles,listProjectFolders,listProjectApps,listProjectComponentsare removed. They pointed at JWT-only/project/:id/*routes, which was the wrong target for an API-token client. Replace withlistFiles({ projectId })/listFolders({ projectId })/listApps({ projectId })/listComponents({ projectId }).
- d92f4e9: update @thatopen dependencies to version 3.4.0 across templates
- 09341b5: fix: update default login API URL
- 7ce2d0f: templates refactor to align them with SKILL patterns
- 28cd180: Updates templates to use new app setup logic
- 626d202: better type handling for built-in components
- rebuild built-in types
- c6516d0: Deploy new version
- Rename
client.initApp()toclient.setup()for a cleaner API surface
-
b7949c0: Add icon support for items (apps, components, files).
Library: New
uploadItemIcon,getItemIcon, andremoveItemIconmethods onEngineServicesClientfor managing item icons via thePUT/GET/DELETE /api/item/:id/iconendpoints. Accepts PNG, WebP, and ICO images up to 512 KB.CLI:
thatopen publish --icon <path>uploads an icon after publishing. The icon path is saved to.thatopenconfig so subsequent publishes reuse it automatically.
- 5e75861: Adds dev improvements
- Improve naming
- Adding parameters related to metadata in items
- Allow for parentId when creating folders
- Allow for fetching with versions for components
- Improve hidden items
- Add hidden files
- fix error result
- Remove axios | add retries
- Fix execution callback
- File download improvements
- Fix socket connect
- fix execute not sending params
- Add abortExecution
- Add standard downloadComponent function
- Return bundle from downloadComponentBundle
- Fix get file
- improve types
- Add show versions parameter to item fetch
- Remove socket return from progress
- Improve returning types
- fix typings
- Adding execution function and listeners
- Change types in component creation
- Allow creation of drafts
- Add execution params
- Move extraProps to version data
- Fix folders not being sent
- Fix accept method in result
- Fix fetch content type
- Cleanup query object in main function
- Fix optional fields in list functions
- return proper error message
- Add more verbosity to error
- Fix issue with empty json responses
- Replace axios with fetch for a better dev experience
- Add generics and responseType to file downloads
- Change return type of downloads to ReadableStream
- remove type module
- remove gaxios due to a bug in the browser
- Added Stream return type to downloads
-
- Improved build
- Fixed minor bugs
- Improved lint for dev experience
-
- File and folder download functions
- add build
- fix build not working
- Allow for creation and update of components
- Fix issues with folders and files | add recovering