Upgrade
Overview
To upgrade io.Manager, you must update the @interopio/manager, @interopio/manager-admin-ui, and @interopio/manager-api packages in your project via npm.
If you aren't using automatic schema migration for PostgreSQL and Microsoft SQL Server databases, you must create the databases manually by using the provided schema creation scripts.
As of version 4.0.0, the @interopio/manager and @interopio/manager-admin-ui packages are compatible with each other only when their major and minor versions match. For example, io.Manager Server 4.0.5 is compatible with io.Manager Admin UI 4.0.2. Pairing io.Manager Server 4.0.0 with io.Manager Admin UI 4.1.0 (or vice versa) isn't supported. This change applies only to the relationship between the io.Manager Server and the io.Manager Admin UI - compatibility with @interopio/manager-api, io.Connect Desktop, and io.Connect Browser is unchanged.
Apart from that pairing, and unless explicitly stated in the changelog or on this page, all io.Manager packages support backward and forward compatibility between each other and with the io.Connect platforms. This means that older versions of io.Connect Desktop and io.Connect Browser will operate properly with newer versions of io.Manager and vice versa.
⚠️ Note that before upgrading, it's highly recommended to review the changelog and this section for details about the release to which you want to upgrade and also for all in-between releases. It's important to consider any breaking changes or necessary migration steps related to the packages or to the databases in order to ensure a smoother transition to the targeted version.
@interopio/manager
4.0.0
Dropped support for Node.js 20. io.Manager now requires Node.js 22 LTS or 24 LTS.
Dropped support for MongoDB 6. io.Manager now requires MongoDB 7 or later.
The io.Manager Server is now compatible with the io.Manager Admin UI only when their major and minor versions match. For example, io.Manager Server
4.0.xis compatible with io.Manager Admin UI4.0.x(any patch), but io.Manager Server4.0.0isn't compatible with io.Manager Admin UI4.1.0. Make sure to upgrade both packages together so that their major and minor versions stay aligned. Compatibility with@interopio/manager-api, io.Connect Desktop, and io.Connect Browser is unchanged.Removed the custom-store extension point. The
"custom"value for thetypeproperty of thestoretop-level key in the configuration object for initializing the io.Manager Server is no longer supported, along with the correspondingCustomStoreConfigconfiguration object. Existing deployments that previously plugged in a custom store must switch to one of the three built-in database backends -MongoStoreConfig,PostgreSQLStoreConfig, orMSSQLStoreConfig.Removed the
usersStoreproperty from theMongoStoreConfig,PostgreSQLStoreConfig, andMSSQLStoreConfigconfiguration objects. A separate users store can no longer be supplied alongside one of the built-in database backends.Removed the
StoreBase,UsersStoreBase, andBLOBStoreBaseabstract classes from the public API. Custom implementations of these classes can no longer be supplied to the io.Manager Server.Added a
system_statedatabase table for storing server-managed system state (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
system_statetable manually by running the following scripts.For PostgreSQL:
-- Server-wide state shared by every io.Manager instance, one row per named entry. CREATE TABLE IF NOT EXISTS system_state ( system_state_id SERIAL PRIMARY KEY, -- Primary key. Auto-assigned by the database and not used to identify the row outside it. name VARCHAR(255) NOT NULL, -- Name of the entry. Unique. value JSONB NOT NULL, -- The entry value. JSON. version INT NOT NULL, -- Revision of the entry, raised on every write so concurrent instances cannot overwrite each other. CONSTRAINT system_state_name_unique UNIQUE (name) );For Microsoft SQL Server:
-- Server-wide state shared by every io.Manager instance, one row per named entry. IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='system_state' AND xtype='U') CREATE TABLE system_state ( system_state_id INT IDENTITY PRIMARY KEY, -- Primary key. Auto-assigned by the database and not used to identify the row outside it. name NVARCHAR(255) NOT NULL, -- Name of the entry. Unique. value NVARCHAR(MAX) NOT NULL, -- The entry value. JSON, stored as text. version INT NOT NULL, -- Revision of the entry, raised on every write so concurrent instances cannot overwrite each other. CONSTRAINT system_state_name_unique UNIQUE (name) );
- Sessions and command descriptions are now scoped to io.Connect Desktop or io.Connect Browser. Sessions have separate
productand product-specificproductVersionfields.
⚠️ Note that automatic schema migration applies these changes for MongoDB, PostgreSQL, and Microsoft SQL Server. If you are managing a PostgreSQL or a Microsoft SQL Server schema manually, run the corresponding script below before starting the upgraded server.
For PostgreSQL:
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS product VARCHAR(255), -- The io.Connect product that opened the session. ADD COLUMN IF NOT EXISTS "productVersion" VARCHAR(255); -- Version of that product. Determines which commands the session supports. UPDATE sessions SET product = CASE WHEN glue->'core'->'platform'->>'version' IS NOT NULL OR glue->>'version' LIKE 'io.connect.browser-%' THEN 'ioCB' ELSE 'ioCD' END, "productVersion" = CASE WHEN glue->'core'->'platform'->>'version' IS NOT NULL THEN glue->'core'->'platform'->>'version' WHEN glue->>'version' LIKE 'io.connect.browser-%' THEN substring(glue->>'version' from 20) ELSE glue->>'version' END; ALTER TABLE sessions ALTER COLUMN product SET NOT NULL, ALTER COLUMN "productVersion" SET NOT NULL; ALTER TABLE commands_for_version ADD COLUMN IF NOT EXISTS product VARCHAR(255); -- The io.Connect product. Unique together with the version. ALTER TABLE commands_for_version DROP CONSTRAINT IF EXISTS commands_for_version_version_unique; -- Only rows that have not been re-keyed yet: product is derived from version while version -- itself is rewritten, so a second pass would classify an already-re-keyed row as ioCD. UPDATE commands_for_version SET product = CASE WHEN version LIKE 'io.connect.browser-%' THEN 'ioCB' ELSE 'ioCD' END, version = CASE WHEN version LIKE 'io.connect.browser-%' THEN substring(version from 20) ELSE version END WHERE product IS NULL; ALTER TABLE commands_for_version ALTER COLUMN product SET NOT NULL; ALTER TABLE commands_for_version ADD CONSTRAINT commands_for_version_product_version_unique UNIQUE (product, version);For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[sessions]') AND name = 'product' ) ALTER TABLE sessions ADD product NVARCHAR(255) NULL; -- The io.Connect product that opened the session. GO IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[sessions]') AND name = 'productVersion' ) ALTER TABLE sessions ADD "productVersion" NVARCHAR(255) NULL; -- Version of that product. Determines which commands the session supports. GO UPDATE sessions SET product = CASE WHEN JSON_VALUE(glue, '$.core.platform.version') IS NOT NULL OR JSON_VALUE(glue, '$.version') LIKE 'io.connect.browser-%' THEN 'ioCB' ELSE 'ioCD' END, "productVersion" = CASE WHEN JSON_VALUE(glue, '$.core.platform.version') IS NOT NULL THEN JSON_VALUE(glue, '$.core.platform.version') WHEN JSON_VALUE(glue, '$.version') LIKE 'io.connect.browser-%' THEN SUBSTRING( JSON_VALUE(glue, '$.version'), 20, LEN(JSON_VALUE(glue, '$.version')) ) ELSE JSON_VALUE(glue, '$.version') END; ALTER TABLE sessions ALTER COLUMN product NVARCHAR(255) NOT NULL; ALTER TABLE sessions ALTER COLUMN "productVersion" NVARCHAR(255) NOT NULL; IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[commands_for_version]') AND name = 'product' ) ALTER TABLE commands_for_version ADD product NVARCHAR(255) NULL; -- The io.Connect product. Unique together with the version. GO ALTER TABLE commands_for_version DROP CONSTRAINT IF EXISTS commands_for_version_version_unique; -- Only rows that have not been re-keyed yet: product is derived from version while version -- itself is rewritten, so a second pass would classify an already-re-keyed row as ioCD. UPDATE commands_for_version SET product = CASE WHEN version LIKE 'io.connect.browser-%' THEN 'ioCB' ELSE 'ioCD' END, version = CASE WHEN version LIKE 'io.connect.browser-%' THEN SUBSTRING(version, 20, LEN(version)) ELSE version END WHERE product IS NULL; ALTER TABLE commands_for_version ALTER COLUMN product NVARCHAR(255) NOT NULL; ALTER TABLE commands_for_version ADD CONSTRAINT commands_for_version_product_version_unique UNIQUE (product, version);
- Added a
layouts_advanceddatabase table for storing advanced Layouts (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
layouts_advancedtable manually by running the following scripts.For PostgreSQL:
-- Layouts saved by an io.Connect platform, with per-entity access control. CREATE TABLE IF NOT EXISTS layouts_advanced ( layouts_advanced_id SERIAL PRIMARY KEY, -- Primary key. Auto-assigned by the database and not used to identify the row outside it. id VARCHAR(255) NOT NULL, -- Public identifier of the layout. Unique. type VARCHAR(255) NOT NULL, -- Kind of layout, for example 'Global' or 'Workspace'. name VARCHAR(255) NOT NULL, -- Name of the layout. Unique per type per owner while private, and unique per type across all owners once shared or public. disabled BOOLEAN NOT NULL, -- Whether the layout is withheld from all users. "accessLevel" VARCHAR(255) NOT NULL, -- Who can reach the layout: 'private', 'shared' or 'public'. "owner" VARCHAR(255) NOT NULL, -- User the layout belongs to. "accessInfo" JSON NULL, -- Users and groups granted access, with the level granted to each. Present only for shared layouts. JSON. definition JSON NOT NULL, -- The layout payload as saved by the io.Connect platform. JSON. "createdBy" VARCHAR(255) NOT NULL, -- User that created the layout. "createdOn" BIGINT NOT NULL, -- Creation time. Epoch milliseconds (UTC). "lastModifiedBy" VARCHAR(255) NULL, -- User that last modified the layout. "lastModifiedOn" BIGINT NULL, -- Time of the last modification. Epoch milliseconds (UTC). CONSTRAINT layouts_advanced_id_unique UNIQUE (id) ); CREATE UNIQUE INDEX IF NOT EXISTS layouts_advanced__name__type__owner__unique ON layouts_advanced (name, type, owner) WHERE "accessLevel" = 'private'; CREATE UNIQUE INDEX IF NOT EXISTS layouts_advanced__name__type__nonprivate__unique ON layouts_advanced (name, type) WHERE "accessLevel" IN ('shared', 'public');For Microsoft SQL Server:
-- Layouts saved by an io.Connect platform, with per-entity access control. IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='layouts_advanced' AND xtype='U') CREATE TABLE layouts_advanced ( layouts_advanced_id INT IDENTITY PRIMARY KEY, -- Primary key. Auto-assigned by the database and not used to identify the row outside it. id NVARCHAR(255) NOT NULL, -- Public identifier of the layout. Unique. type NVARCHAR(255) NOT NULL, -- Kind of layout, for example 'Global' or 'Workspace'. name NVARCHAR(255) NOT NULL, -- Name of the layout. Unique per type per owner while private, and unique per type across all owners once shared or public. disabled BIT NOT NULL, -- Whether the layout is withheld from all users. Boolean, stored as 0 or 1. accessLevel NVARCHAR(255) NOT NULL, -- Who can reach the layout: 'private', 'shared' or 'public'. owner NVARCHAR(255) NOT NULL, -- User the layout belongs to. "accessInfo" NVARCHAR(MAX) NULL, -- Users and groups granted access, with the level granted to each. Present only for shared layouts. JSON, stored as text. definition NVARCHAR(MAX) NOT NULL, -- The layout payload as saved by the io.Connect platform. JSON, stored as text. "createdBy" NVARCHAR(255) NOT NULL, -- User that created the layout. "createdOn" BIGINT NOT NULL, -- Creation time. Epoch milliseconds (UTC). "lastModifiedBy" NVARCHAR(255) NULL, -- User that last modified the layout. "lastModifiedOn" BIGINT NULL, -- Time of the last modification. Epoch milliseconds (UTC). CONSTRAINT layouts_advanced_id_unique UNIQUE (id) ); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'layouts_advanced__name__type__owner__unique' AND object_id = OBJECT_ID('layouts_advanced')) CREATE UNIQUE INDEX layouts_advanced__name__type__owner__unique ON layouts_advanced (name, type, owner) WHERE accessLevel = 'private'; IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'layouts_advanced__name__type__nonprivate__unique' AND object_id = OBJECT_ID('layouts_advanced')) CREATE UNIQUE INDEX layouts_advanced__name__type__nonprivate__unique ON layouts_advanced (name, type) WHERE accessLevel IN ('shared', 'public');
- Added an
othersdatabase column to theuserstable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
otherscolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE users ADD COLUMN IF NOT EXISTS others JSONB NULL; -- Per-user timestamps that do not have a column of their own. JSON.For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[users]') AND name = 'others' ) ALTER TABLE users ADD others NVARCHAR(MAX) NULL; -- Per-user timestamps that do not have a column of their own. JSON, stored as text.
- Added
descriptionandexpandsTodatabase columns to thegroupstable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
descriptionandexpandsTocolumns manually by running the following scripts.For PostgreSQL:
ALTER TABLE groups ADD COLUMN IF NOT EXISTS description TEXT NULL, -- Description of the group. ADD COLUMN IF NOT EXISTS "expandsTo" VARCHAR(255)[] NOT NULL DEFAULT '{}'::varchar[]; -- Groups this group also grants membership of. Empty when it expands into no others. Array of strings.For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[groups]') AND name = 'description' ) ALTER TABLE groups ADD description NVARCHAR(MAX) NULL; -- Description of the group. IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[groups]') AND name = 'expandsTo' ) ALTER TABLE groups ADD expandsTo NVARCHAR(MAX) NOT NULL CONSTRAINT DF_groups_expandsTo DEFAULT '[]'; -- Groups this group also grants membership of. Empty when it expands into no others. JSON array of strings, stored as text.
- Added a
migrateddatabase column to thelayoutstable (for PostgreSQL and Microsoft SQL Server only). The Layout migration uses it to mark legacy Layouts that have already been migrated to advanced Layouts.
⚠️ Note that users who don't use automatic schema migration must create the
migratedcolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE layouts ADD COLUMN IF NOT EXISTS migrated BOOLEAN NULL; -- Whether this layout has already been migrated to the layouts_advanced table.For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[layouts]') AND name = 'migrated' ) ALTER TABLE layouts ADD migrated BIT; -- Whether this layout has already been migrated to the layouts_advanced table. Boolean, stored as 0 or 1.
- On Microsoft SQL Server, all
VARCHAR(255)columns are nowNVARCHAR(255), so usernames, Layout names, and every other text field store the full Unicode range. Previously, a character outside the code page of the database collation, such as Cyrillic or Greek, was stored as?.
⚠️ Note that users who don't use automatic schema migration must convert the columns manually by running the following script, after applying every schema script above it on this page and before creating the username indexes below. Each unique constraint is dropped and recreated around the conversion, because a column can't be altered while a constraint references it.
ALTER TABLE app DROP CONSTRAINT app_name_unique; ALTER TABLE audit DROP CONSTRAINT audit_id_unique; ALTER TABLE blobs DROP CONSTRAINT blobs_id_unique; ALTER TABLE commands DROP CONSTRAINT commands_id_unique; ALTER TABLE commands_for_version DROP CONSTRAINT commands_for_version_product_version_unique; ALTER TABLE crashes DROP CONSTRAINT crashes_id_unique; ALTER TABLE feedback DROP CONSTRAINT feedback_id_unique; ALTER TABLE groups DROP CONSTRAINT groups_name_unique; ALTER TABLE layouts DROP CONSTRAINT layouts_id_unique; ALTER TABLE layouts DROP CONSTRAINT layouts__name__type__owner__unique; ALTER TABLE machines DROP CONSTRAINT machines_id_unique; ALTER TABLE prefs DROP CONSTRAINT prefs_id_unique; ALTER TABLE prefs DROP CONSTRAINT prefs__app__user__unique; ALTER TABLE sessions DROP CONSTRAINT sessions_id_unique; ALTER TABLE users DROP CONSTRAINT users_id_unique; ALTER TABLE app ALTER COLUMN name NVARCHAR(255) NOT NULL; ALTER TABLE app ALTER COLUMN [createdBy] NVARCHAR(255) NOT NULL; ALTER TABLE app ALTER COLUMN [lastModifiedBy] NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE audit ALTER COLUMN [user] NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN parent NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN session NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN server NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN [entityType] NVARCHAR(255) NOT NULL; ALTER TABLE audit ALTER COLUMN [entityId] NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN [entityDisplayName] NVARCHAR(255) NULL; ALTER TABLE audit ALTER COLUMN operation NVARCHAR(255) NOT NULL; ALTER TABLE audit ALTER COLUMN [operationComment] NVARCHAR(255) NULL; ALTER TABLE blobs ALTER COLUMN [type] NVARCHAR(255) NOT NULL; ALTER TABLE blobs ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE blobs ALTER COLUMN [fileName] NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN [user] NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN status NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN session NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN machine NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN [createdBy] NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN command NVARCHAR(255) NOT NULL; ALTER TABLE commands ALTER COLUMN [commandParams] NVARCHAR(255) NULL; ALTER TABLE commands ALTER COLUMN [resultType] NVARCHAR(255) NULL; ALTER TABLE commands_for_version ALTER COLUMN version NVARCHAR(255) NOT NULL; ALTER TABLE crashes ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE crashes ALTER COLUMN [user] NVARCHAR(255) NULL; ALTER TABLE feedback ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE feedback ALTER COLUMN [user] NVARCHAR(255) NOT NULL; ALTER TABLE feedback ALTER COLUMN session NVARCHAR(255) NOT NULL; ALTER TABLE groups ALTER COLUMN name NVARCHAR(255) NOT NULL; ALTER TABLE layouts ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE layouts ALTER COLUMN [type] NVARCHAR(255) NOT NULL; ALTER TABLE layouts ALTER COLUMN name NVARCHAR(255) NOT NULL; ALTER TABLE layouts ALTER COLUMN [owner] NVARCHAR(255) NULL; ALTER TABLE layouts ALTER COLUMN [createdBy] NVARCHAR(255) NOT NULL; ALTER TABLE layouts ALTER COLUMN [lastModifiedBy] NVARCHAR(255) NULL; ALTER TABLE machines ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE machines ALTER COLUMN [user] NVARCHAR(255) NOT NULL; ALTER TABLE machines ALTER COLUMN name NVARCHAR(255) NULL; ALTER TABLE prefs ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE prefs ALTER COLUMN app NVARCHAR(255) NOT NULL; ALTER TABLE prefs ALTER COLUMN [user] NVARCHAR(255) NOT NULL; ALTER TABLE sessions ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE sessions ALTER COLUMN machine NVARCHAR(255) NOT NULL; ALTER TABLE sessions ALTER COLUMN [user] NVARCHAR(255) NOT NULL; ALTER TABLE sessions ALTER COLUMN [closeReason] NVARCHAR(255) NULL; ALTER TABLE users ALTER COLUMN id NVARCHAR(255) NOT NULL; ALTER TABLE users ALTER COLUMN email NVARCHAR(255) NULL; ALTER TABLE users ALTER COLUMN password NVARCHAR(255) NULL; ALTER TABLE users ALTER COLUMN [firstName] NVARCHAR(255) NULL; ALTER TABLE users ALTER COLUMN [lastName] NVARCHAR(255) NULL; ALTER TABLE app ADD CONSTRAINT app_name_unique UNIQUE (name); ALTER TABLE audit ADD CONSTRAINT audit_id_unique UNIQUE (id); ALTER TABLE blobs ADD CONSTRAINT blobs_id_unique UNIQUE (id); ALTER TABLE commands ADD CONSTRAINT commands_id_unique UNIQUE (id); ALTER TABLE commands_for_version ADD CONSTRAINT commands_for_version_product_version_unique UNIQUE (product, version); ALTER TABLE crashes ADD CONSTRAINT crashes_id_unique UNIQUE (id); ALTER TABLE feedback ADD CONSTRAINT feedback_id_unique UNIQUE (id); ALTER TABLE groups ADD CONSTRAINT groups_name_unique UNIQUE (name); ALTER TABLE layouts ADD CONSTRAINT layouts_id_unique UNIQUE (id); ALTER TABLE layouts ADD CONSTRAINT layouts__name__type__owner__unique UNIQUE ([name], [type], [owner]); ALTER TABLE machines ADD CONSTRAINT machines_id_unique UNIQUE (id); ALTER TABLE prefs ADD CONSTRAINT prefs_id_unique UNIQUE (id); ALTER TABLE prefs ADD CONSTRAINT prefs__app__user__unique UNIQUE ([app], [user]); ALTER TABLE sessions ADD CONSTRAINT sessions_id_unique UNIQUE (id); ALTER TABLE users ADD CONSTRAINT users_id_unique UNIQUE (id);
- Added database indexes for every column a username is resolved by -
users.id,layouts.owner,prefs.user, andmachines.user- in both a case-folded and an exact form, so that both settings ofusername_case_sensitiveuse an index instead of scanning the table. On Microsoft SQL Server, the case-folded form is a persisted computed column with an index on it, because Microsoft SQL Server has no expression indexes.
⚠️ Note that users who don't use automatic schema migration must create the indexes manually by running the following scripts, after the
NVARCHARconversion above. On MongoDB, the indexes are created by the server on startup and don't need any manual step.For PostgreSQL:
CREATE INDEX IF NOT EXISTS users__id__lower ON users (lower(id)); CREATE INDEX IF NOT EXISTS layouts__owner__lower ON layouts (lower("owner")); CREATE INDEX IF NOT EXISTS layouts__owner ON layouts ("owner"); CREATE INDEX IF NOT EXISTS prefs__user__lower__app ON prefs (lower("user"), app); CREATE INDEX IF NOT EXISTS prefs__user ON prefs ("user"); CREATE INDEX IF NOT EXISTS machines__user__lower ON machines (lower("user")); CREATE INDEX IF NOT EXISTS machines__user ON machines ("user");For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[users]') AND name = 'id_lower' ) ALTER TABLE users ADD id_lower AS LOWER(id) PERSISTED; -- The identifier, lowercased, for resolving a username when username_case_sensitive is false. Derived by the database. GO IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[layouts]') AND name = 'owner_lower' ) ALTER TABLE layouts ADD owner_lower AS LOWER([owner]) PERSISTED; -- The owner, lowercased, for resolving a username when username_case_sensitive is false. Derived by the database. GO IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[prefs]') AND name = 'user_lower' ) ALTER TABLE prefs ADD user_lower AS LOWER([user]) PERSISTED; -- The user, lowercased, for resolving a username when username_case_sensitive is false. Derived by the database. GO IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[machines]') AND name = 'user_lower' ) ALTER TABLE machines ADD user_lower AS LOWER([user]) PERSISTED; -- The user, lowercased, for resolving a username when username_case_sensitive is false. Derived by the database. GO IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'users__id__lower' AND object_id = OBJECT_ID('users')) CREATE INDEX users__id__lower ON users (id_lower); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'layouts__owner__lower' AND object_id = OBJECT_ID('layouts')) CREATE INDEX layouts__owner__lower ON layouts (owner_lower); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'layouts__owner' AND object_id = OBJECT_ID('layouts')) CREATE INDEX layouts__owner ON layouts ([owner]); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'prefs__user__lower__app' AND object_id = OBJECT_ID('prefs')) CREATE INDEX prefs__user__lower__app ON prefs (user_lower, app); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'prefs__user' AND object_id = OBJECT_ID('prefs')) CREATE INDEX prefs__user ON prefs ([user]); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'machines__user__lower' AND object_id = OBJECT_ID('machines')) CREATE INDEX machines__user__lower ON machines (user_lower); IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'machines__user' AND object_id = OBJECT_ID('machines')) CREATE INDEX machines__user ON machines ([user]);
The
GET /api/crashes/{id}/dumpendpoint of the io.Manager Server REST API now requires authentication and theIO_MANAGER:CRASHES:READpermission group. External callers must send valid credentials according to the configured auth provider, as a user whose permission groups includeIO_MANAGER:CRASHES:READ. Alternatively, use the newCrashes.getDump(id): Promise<Blob>method on@interopio/manager-api, which reuses the client's existing authentication and returns the dump as aBlob.Added an
encodingdatabase column to theblobstable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
encodingcolumn and mark the existing rows manually by running the following scripts.For PostgreSQL:
ALTER TABLE blobs ADD COLUMN IF NOT EXISTS encoding VARCHAR(20); -- Encoding of the content: "raw" when it is the file's own bytes, "base64" when it is those bytes base64-encoded as text. Absent on content stored before the encoding was recorded. -- Only rows that predate the column are NULL, so re-running this script never -- reclassifies files stored after the upgrade. UPDATE blobs SET encoding = 'base64' WHERE type = 'command_result' AND encoding IS NULL;For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[blobs]') AND name = 'encoding' ) ALTER TABLE blobs ADD encoding NVARCHAR(20) NULL; -- Encoding of the content: "raw" when it is the file's own bytes, "base64" when it is those bytes base64-encoded as text. Absent on content stored before the encoding was recorded. GO -- Only rows that predate the column are NULL, so re-running this script never -- reclassifies files stored after the upgrade. UPDATE blobs SET encoding = 'base64' WHERE type = 'command_result' AND encoding IS NULL;
The
POST /groupsendpoint no longer accepts the name of an existing group - it now returns400when a group with thatnamealready exists, instead of succeeding without changing it. If you relied onPOST /groupsto idempotently ensure a group exists, switch toPOST /v2/groups, which creates the group or updates it if it already exists. The newPOST /v2/groups/addis create-only and also returns400on a duplicate name; usePOST /v2/groups/updateto update an existing group.The
GroupsServiceinterface changed. If you provide a custom Groups service via thegroups_serviceconfiguration property, update your implementation:- Add the new capability flags to the object returned by
getSupportedFeatures:canGetGroup,canUpdateGroup,canAddOrUpdateGroup, andcanRemoveAll. The io.Manager Server calls a group operation only when its capability flag istrue, so set a flag totrueonly if your implementation supports that operation. - The new
getGroup,updateGroup, andaddOrUpdateGroupmethods are optional - they are never called unless you enable their capability flags, so an implementation that doesn't support them can leave them as stubs. - The
addGroupmethod now receives aGroupobject instead of a group name and returns the createdGroup(previously it received the name and returnedvoid). - The
getAllGroupsmethod'srequestparameter is now optional. - Each
Groupyou return may include anexpandsToarray listing the granular permission groups (or other groups) it expands into; omit it or use an empty array for a group that doesn't expand into others.
- Add the new capability flags to the object returned by
getSupportedFeatures(): GroupsFeatures {
return {
canGetUserGroups: true,
canGetAllGroups: true,
canGetGroup: true,
canAddGroup: true,
canUpdateGroup: true,
canAddOrUpdateGroup: true,
canRemoveGroup: true,
canRemoveAll: true,
canAddUserToGroup: true,
canRemoveUserFromGroup: true
};
}
async addGroup(group: Group, audit: AuditBuilder): Promise<Group> {
// Persist the group (including its `expandsTo`) and return it.
return group;
}For a complete example, see Configuration > Server Extension Points > Custom Groups Service.
2.0.0
As of version
2.0.0, the@interopio/managerpackage will be published in the public NPM registry. Previous versions of the package will still be available in the private JFROG registry.io.Manager now requires a license key.️ To acquire a license key, contact us at
sales@interop.io. Existing customers should contact their Client Success representative to obtain a license key. For more details, see the Requirements > Licensing section.
The license key can be passed to io.Manager via the licenseKey property of the configuration object for initializing io.Manager, or via the API_LICENSE_KEY environment variable, depending on your deployment approach.
The following example demonstrates providing the license key when initializing io.Manager:
import { start } from "@interopio/manager";
const config = {
// It's now required to provide a valid license key for io.Manager to operate.
licenseKey: "my-license-key"
};
const server = await start(config);The following example demonstrates providing the license key by using an environment variable:
API_LICENSE_KEY=my-license-keyDropped Node.js 18 support. io.Manager now requires Node.js 20 LTS or 22 LTS.
The default value for
skipProcessExitOnStopis changed totrue. If you wantprocess.exit()to be called whenserver.stop()is called or when the server fails to start, setskipProcessExitOnStoptofalse.TypeScript type changes. The following changes will affect only clients who are using TypeScript instead of pure JavaScript.
- When implementing
CustomAuthenticator, you now have to import the following types from@interopio/managerinstead of@interopio/manager-api:TokenUser
- When implementing
AuditService, you now have to import the following types from@interopio/managerinstead of@interopio/manager-api:AuditLogAuditLogDataResultCleanAuditLogRequestDataRequestUser
- When implementing
GroupsService, you now have to import the following types from@interopio/managerinstead of@interopio/manager-api:DataRequestGroupGroupDataResultGroupsFeaturesUser
- The
AuditService.getAll()method now returnsAuditLogDataResultinstead ofDataResult<AuditLog>. The types are structurally equivalent. - The types
AuditLogEntityTypeandAuditLogOperationhave been converted from TypeScript Union types to TypeScript Enums. - The
@interopio/manager-apipackage is no longer required for io.Manager Server deployments and was subsequently dropped as a dependency.
- When implementing
In
CustomAuthenticatorimplementations, the value of thereq.urlproperty will now include the value of thebaseproperty provided to io.Manager via configuration. For example, if a request is received for/userandbaseis set to"api", in version 1.8.0 the value ofreq.urlwill be"/user", and in version 2.0.0 the value ofreq.urlwill be"/api/user".Implementations of
CustomAuthenticatorshould always callnexteven when rejecting requests and should not respond to the request directly. Previously, a common pattern forCustomAuthenticatorimplementations was rejecting the requests directly:
import type { Request, Response } from "express";
import {
type CustomAuthenticator,
type User,
type Token
} from "@interopio/manager";
export class MyAuthenticator implements CustomAuthenticator {
initialize(): void {};
authenticate(
req: Request,
res: Response,
next: (err?: Error, info?: User) = void,
token?: Token
): void {
const user = this.findUser(req);
if (user) {
// Successful authentication request.
next(undefined, user);
} else {
// Failed authentication request.
res.status(401);
res.send();
return;
};
};
private findUser(req: Request): User {
// Custom authentication logic.
throw new Error("Not implemented.");
};
};Rejecting the requests directly could cause memory leaks and is therefore not recommended. Instead, implementations of CustomAuthenticator should import CustomAuthUnauthorizedError and use it in the following way:
import type { Request, Response } from "express";
import {
type CustomAuthenticator,
type User,
type Token,
CustomAuthUnauthorizedError
} from "@interopio/manager";
export class MyAuthenticator implements CustomAuthenticator {
initialize(): void {};
authenticate(
req: Request,
res: Response,
next: (err?: Error, info?: User) = void,
token?: Token
): void {
const user = this.findUser(req);
if (user) {
// Successful authentication request.
next(undefined, user);
} else {
// Failed authentication request.
next(new CustomAuthUnauthorizedError());
};
};
private findUser(req: Request): User {
// Custom authentication logic.
throw new Error("Not implemented.");
};
};If you are using an older versions of io.Manager, you can work around this problem by manually defining an error class and passing an instance of it to the next() function.
ℹ️ For an example of defining an error class and passing it to the
next()function, see the Custom Authentication example on GitHub.
As of version 2.0.0, io.Manager will produce warnings when it detects that the authentication pipeline is short-circuited by omitting the call to the next() function.
- Added a
traceContextdatabase column to thecommandstable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
traceContextcolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE commands ADD COLUMN IF NOT EXISTS "traceContext" JSON;For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[commands]') AND name = 'traceContext' ) ALTER TABLE commands ADD traceContext NVARCHAR(MAX);
- Changed the type of the
commandParamsdatabase column of thecommandstable toJSON(for PostgreSQL only).
⚠️ Note that users who don't use automatic schema migration must change the type of the
commandParamscolumn manually by running the following script:ALTER TABLE commands ALTER COLUMN "commandParams" TYPE JSON USING "commandParams"::json;
- Changed the precision of the
weightdatabase column in theglue42SystemConfigtable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must change the precision of the
weightcolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE "glue42SystemConfig" ALTER COLUMN weight TYPE numeric(8, 6) USING weight::numeric(8, 6);For Microsoft SQL Server:
ALTER TABLE glue42SystemConfig ALTER COLUMN weight decimal(8, 6);
- Changed the type of the
definitiondatabase column of thelayoutstable toJSON(for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must change the type of the
definitioncolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE layouts ALTER COLUMN definition TYPE JSON USING definition::json;For Microsoft SQL Server:
ALTER TABLE layouts ALTER COLUMN definition NVARCHAR(max) NOT NULL;
- Changed the type of the
commentdatabase column of thecrashestable toNVARCHAR(max)(for Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must change the type of the
commentcolumn manually by running the following script:ALTER TABLE crashes ALTER COLUMN comment NVARCHAR(max);
- Changed the types of the
comment,descriptionandattachmentdatabase columns of thefeedbacktable toNVARCHAR(max)(for Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must change the type of the columns manually by running the following script:
ALTER TABLE feedback ALTER COLUMN comment NVARCHAR(max); ALTER TABLE feedback ALTER COLUMN description NVARCHAR(max) NOT NULL; ALTER TABLE feedback ALTER COLUMN attachment NVARCHAR(max) NOT NULL;
Monitoring configuration changes:
- Removed
false,"none", and"sentry"as values for themonitoringproperty. Use{ type: "none" }or{ type: "sentry" }instead.
- Removed
Sentry monitoring configuration changes:
- Removed the
tracesSampleRateproperty from the Sentry monitoring configuration. UsesentryOptions.tracesSampleRateinstead. - Removed the
dsnproperty from the Sentry monitoring configuration. UsesentryOptions.dsninstead. - Tracing via the Sentry SDK is no longer supported. The recommended way to send traces to Sentry is by using OpenTelemetry traces. See also the OpenTelemetry with Sentry example on GitHub.
- Removed the
Removed
["admin"]as a default value for theAPI_AUTH_EXCLUSIVE_USERSenvironment variable. This change doesn't affect users who don't use environment variables for configuring io.Manager as the default value was valid only for theAPI_AUTH_EXCLUSIVE_USERSenvironment variable.Due to a change in the FDC3 specification, the
nameanddefinition.nameapp properties won't be checked for consistency for FDC3 app definitions. Thenameanddefinition.appIdproperties will still be checked. For more details, see the FDC3 Standard 2.1.When using environment variable configuration with
.envfiles, values from the.envfiles will override the process environment variables. This means that if an environment variable is both defined in the.envfile and passed to the process, the value from the.envfile will be used.
1.7.0
- Added database column
othersto thelast_updatedtable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
otherscolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE last_updated ADD COLUMN IF NOT EXISTS others JSON NULL;For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[last_updated]') AND name = 'others' ) ALTER TABLE last_updated ADD others NVARCHAR(MAX);
1.6.0
- Added database column
browserto themachinestable (for PostgreSQL and Microsoft SQL Server only).
⚠️ Note that users who don't use automatic schema migration must create the
browsercolumn manually by running the following scripts.For PostgreSQL:
ALTER TABLE machines ADD COLUMN IF NOT EXISTS browser JSON NULL;For Microsoft SQL Server:
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[machines]') AND name = 'browser' ) ALTER TABLE machines ADD browser NVARCHAR(MAX);
1.0.0
Users can now be compared in a case-insensitive way. You can use the
username_case_sensitiveproperty in the configuration object for initializing the io.Manager Server or theAPI_USERNAME_CASE_SENSITIVEenvironment variable to switch between the two modes. The new default is case-insensitive. To switch back to case sensitive mode, set theusername_case_sensitiveproperty or theAPI_USERNAME_CASE_SENSITIVEenvironment variable totrue.Removed status endpoint. Use
GET /instead.
@interopio/manager-admin-ui
4.0.0
The io.Manager Admin UI is now compatible with the io.Manager Server only when their major and minor versions match. For example, io.Manager Admin UI
4.0.xis compatible with io.Manager Server4.0.x(any patch), but io.Manager Admin UI4.0.0isn't compatible with io.Manager Server4.1.0. Make sure to upgrade both packages together so that their major and minor versions stay aligned. Compatibility with@interopio/manager-api, io.Connect Desktop, and io.Connect Browser is unchanged.The
onUnauthorizedResponse()callback on theAuthProviderinterface is now also invoked on403 Forbiddenresponses, in addition to the existing401 Unauthorizedtrigger. Consumers implementing a customAuthProvidershould review the callback and update any logic that previously assumed it fires only on401- for example, token-refresh flows that should not run when the user is authenticated but lacks the required permissions.
3.0.0
As of version
3.0.0, the@interopio/manager-admin-uipackage will be published in the public NPM registry. Previous versions of the package will still be available in the private JFROG registry.Dropped React 17 support. The io.Manager Admin UI now requires React 18. See the React 18 Upgrade Guide and the Admin UI example on GitHub.
Removed the
userstop-level configuration property. Its options will now be implied from theauthtop-level configuration property.If using TypeScript, the
"jsx"property of the"compilerOptions"object in thetsconfig.jsonfile must be set to"react-jsx".All io.Manager Admin UI style imports have been combined in one:
// This is the only required style import.
import "@interopio/manager-admin-ui/styles.css";⚠️ Note that for the Admin UI to function properly, you must remove the
@interopio/theme-demo-appsstyle import.
The @interopio/theme-demo-apps dependency can be safely removed from your package.json file. For a complete example, see the Admin UI example on GitHub.
For clients using Auth0 authentication: The
@auth0/auth0-reactpackage has been updated from v1 to v2. The@auth0/auth0-reactv2 package introduces breaking changes to theauth_auth0property which accepts a value of typeAuth0ProviderOptionsimported from@auth0/auth0-react. For more details, see the Auth0-React v2 Migration Guide.For clients using the
manager-admin-uidocker image: ThePUBLIC_URLenvironment variable has been deprecated and won't have any effect. Setting theREACT_APP_BASEenvironment variable will be enough for the routing to work correctly.
@interopio/manager-api
5.0.0
- The type of the
attachmentfield of theAddFeedbackRequestDTO has been changed fromstringtoFile | NodeJSReadStream. Callers must pass aFilein the browser or a Node.jsReadStreamin Node. - The type of the
upload_file_minidumpfield of theCreateCrashRequestDTO has been changed fromstringtoFile | NodeJSReadStream. Callers must pass aFilein the browser or a Node.jsReadStreamin Node. - The declared return type of the
whoAmI()method has been changed fromPromise<User>toPromise<WhoAmIResponse>. The method has always returned only theid,email,apps, andgroupsfields at runtime, which means that the runtime behavior hasn't changed. TypeScript callers that read any other field ofUserfrom the result won't compile - read those fields via theusersclient instead. - The following exports have been removed from the
@interopio/manager-apipackage:BaseAPIAuthOptionsCustomRequestCustomRequestResponseClientOptionsIOManagerCacheIOManagerCacheItemIOManagerLoggerIOManagerAsyncSequelizerSanitizedIOManagerApiErrorIOManagerRequestValidationErrorCachedClientAPICachedClientOptionsIOManagerConnectionStateisIOManagerTimeoutError
4.0.0
TypeScript type changes. The following changes will affect only clients who are using TypeScript instead of pure JavaScript.
- The usage of type
DataResult<T>has been replaced with several concrete types:AppPreferenceDataResultAuditLogDataResultCommandDataResultClientCrashDataResultFeedbackDataResultGroupDataResultLayoutDataResultMachineDataResultUserSessionDataResultSystemConfigEntryDataResult
- Removed type alias
GetAppsRequest- useDataRequestinstead. - Removed type alias
GetCommandsResponse- useCommandDataResultinstead. - Removed type alias
GetUsersResponse- useUserDataResultinstead. - The types of the following interface properties have been converted from inline TypeScript Union types to TypeScript Enums:
CleanSessionsAction.actionis now of typeCleanSessionsAction.UserAppStatus.explainis now of typeUserAppStatusExplain.AuditLog.entityTypeis now of typeAuditLogEntityType.AuditLog.operationis now of typeAuditLogOperation.Command.statusis now of typeCommandStatus.Command.resultTypeis now of typeResultType.TextFilterCondition.typeis now of typeTextFilterType.BooleanFilterCondition.typeis now of typeBooleanFilterType.NumberFilterCondition.typeis now of typeNumberFilterType.DateFilterCondition.typeis now of typeDateFilterType.SortOrder.sortis now of typeSortOrderEnum.LogicalCondition.operatoris now of typeLogicalConditionOperator.
- Removed types
SimpleConditionandFilter.
- The usage of type
The
MatchAllexport has been renamed toConfigMatchAll.