# Upgrade

Source: https://docs.interop.io/manager/upgrade/index.html

## Overview

To upgrade **io.Manager**, you must update the [`@interopio/manager`](https://www.npmjs.com/package/@interopio/manager), [`@interopio/manager-admin-ui`](https://www.npmjs.com/package/@interopio/manager-admin-ui), and [`@interopio/manager-api`](https://www.npmjs.com/package/@interopio/manager-api) packages in your project via `npm`.

If you aren't using automatic schema migration for [PostgreSQL](https://docs.interop.io/manager/databases/postgresql/index.md) and [Microsoft SQL Server](https://docs.interop.io/manager/databases/microsoft-sql/index.md) databases, you must create the databases manually by using the provided schema creation scripts.

As of version `4.0.0`, the [`@interopio/manager`](https://www.npmjs.com/package/@interopio/manager) and [`@interopio/manager-admin-ui`](https://www.npmjs.com/package/@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`](https://www.npmjs.com/package/@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.x` is compatible with **io.Manager** Admin UI `4.0.x` (any patch), but **io.Manager** Server `4.0.0` isn't compatible with **io.Manager** Admin UI `4.1.0`. Make sure to upgrade both packages together so that their major and minor versions stay aligned. Compatibility with [`@interopio/manager-api`](https://www.npmjs.com/package/@interopio/manager-api), **io.Connect Desktop**, and **io.Connect Browser** is unchanged.

- Removed the custom-store extension point. The `"custom"` value for the `type` property of the `store` top-level key in the configuration object for initializing the **io.Manager** Server is no longer supported, along with the corresponding `CustomStoreConfig` configuration object. Existing deployments that previously plugged in a custom store must switch to one of the three built-in database backends - [`MongoStoreConfig`](https://docs.interop.io/manager/configuration/server/index.md#configuration_object-mongostoreconfig), [`PostgreSQLStoreConfig`](https://docs.interop.io/manager/configuration/server/index.md#configuration_object-postgresqlstoreconfig), or [`MSSQLStoreConfig`](https://docs.interop.io/manager/configuration/server/index.md#configuration_object-mssqlstoreconfig).

- Removed the `usersStore` property from the `MongoStoreConfig`, `PostgreSQLStoreConfig`, and `MSSQLStoreConfig` configuration objects. A separate users store can no longer be supplied alongside one of the built-in database backends.

- Removed the `StoreBase`, `UsersStoreBase`, and `BLOBStoreBase` abstract classes from the public API. Custom implementations of these classes can no longer be supplied to the **io.Manager** Server.

- Added a `system_state` database 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_state` table manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> -- 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:
>
> ```sql
> -- 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 `product` and product-specific `productVersion` fields.

> ⚠️ *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:
>
> ```sql
> 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:
>
> ```sql
> 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_advanced` database 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_advanced` table manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> -- 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:
>
> ```sql
> -- 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 `others` database column to the `users` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `others` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> 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:
>
> ```sql
> 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 `description` and `expandsTo` database columns to the `groups` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `description` and `expandsTo` columns manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> 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:
>
> ```sql
> 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 `migrated` database column to the `layouts` table (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 `migrated` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> 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:
>
> ```sql
> 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 now `NVARCHAR(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.*
>
> ```sql
> 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`, and `machines.user` - in both a case-folded and an exact form, so that both settings of `username_case_sensitive` use 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 `NVARCHAR` conversion above. On MongoDB, the indexes are created by the server on startup and don't need any manual step.*
>
> For PostgreSQL:
>
> ```sql
> 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:
>
> ```sql
> 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}/dump` endpoint of the **io.Manager** Server REST API now requires authentication and the `IO_MANAGER:CRASHES:READ` permission group. External callers must send valid credentials according to the configured auth provider, as a user whose permission groups include `IO_MANAGER:CRASHES:READ`. Alternatively, use the new `Crashes.getDump(id): Promise<Blob>` method on [`@interopio/manager-api`](https://www.npmjs.com/package/@interopio/manager-api), which reuses the client's existing authentication and returns the dump as a `Blob`.

- Added an `encoding` database column to the `blobs` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `encoding` column and mark the existing rows manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> 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:
>
> ```sql
> 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 /groups` endpoint no longer accepts the name of an existing group - it now returns `400` when a group with that `name` already exists, instead of succeeding without changing it. If you relied on `POST /groups` to idempotently ensure a group exists, switch to `POST /v2/groups`, which creates the group or updates it if it already exists. The new `POST /v2/groups/add` is create-only and also returns `400` on a duplicate name; use `POST /v2/groups/update` to update an existing group.

- The `GroupsService` interface changed. If you provide a custom Groups service via the `groups_service` configuration property, update your implementation:
   - Add the new capability flags to the object returned by `getSupportedFeatures`: `canGetGroup`, `canUpdateGroup`, `canAddOrUpdateGroup`, and `canRemoveAll`. The **io.Manager** Server calls a group operation only when its capability flag is `true`, so set a flag to `true` only if your implementation supports that operation.
   - The new `getGroup`, `updateGroup`, and `addOrUpdateGroup` methods 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 `addGroup` method now receives a `Group` object instead of a group name and returns the created `Group` (previously it received the name and returned `void`).
   - The `getAllGroups` method's `request` parameter is now optional.
   - Each `Group` you return may include an `expandsTo` array 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.

```typescript
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](https://docs.interop.io/manager/configuration/server/index.md#server_extension_points-custom_groups_service).

### 2.0.0

- As of version `2.0.0`, the [`@interopio/manager`](https://www.npmjs.com/package/@interopio/manager) package will be published in the public NPM registry. Previous versions of the package will still be available in the private [JFROG](https://glue42.jfrog.io/) 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](https://docs.interop.io/manager/requirements/index.md#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](https://docs.interop.io/manager/deployment/index.md) approach.

The following example demonstrates providing the license key when initializing **io.Manager**:

```javascript
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:

```cmd
API_LICENSE_KEY=my-license-key
```

- Dropped Node.js 18 support. **io.Manager** now requires Node.js 20 LTS or 22 LTS.

- The default value for `skipProcessExitOnStop` is changed to `true`. If you want `process.exit()` to be called when `server.stop()` is called or when the server fails to start, set `skipProcessExitOnStop` to `false`.

- 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/manager` instead of `@interopio/manager-api`:
       - `Token`
       - `User`
   - When implementing `AuditService`, you now have to import the following types from `@interopio/manager` instead of `@interopio/manager-api`:
       - `AuditLog`
       - `AuditLogDataResult`
       - `CleanAuditLogRequest`
       - `DataRequest`
       - `User`
   - When implementing `GroupsService`, you now have to import the following types from `@interopio/manager` instead of `@interopio/manager-api`:
       - `DataRequest`
       - `Group`
       - `GroupDataResult`
       - `GroupsFeatures`
       - `User`
   - The `AuditService.getAll()` method now returns `AuditLogDataResult` instead of `DataResult<AuditLog>`. The types are structurally equivalent.
   - The types `AuditLogEntityType` and `AuditLogOperation` have been converted from TypeScript Union types to TypeScript Enums.
   - The [`@interopio/manager-api`](https://www.npmjs.com/package/@interopio/manager-api) package is no longer required for **io.Manager** Server deployments and was subsequently dropped as a dependency.

- In `CustomAuthenticator` implementations, the value of the `req.url` property will now include the value of the `base` property provided to **io.Manager** via configuration. For example, if a request is received for `/user` and `base` is set to `"api"`, in version 1.8.0 the value of `req.url` will be `"/user"`, and in version 2.0.0 the value of `req.url` will be `"/api/user"`.

- Implementations of `CustomAuthenticator` should always call `next` even when rejecting requests and should not respond to the request directly. Previously, a common pattern for `CustomAuthenticator` implementations was rejecting the requests directly:

```javascript
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:

```javascript
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](https://github.com/InteropIO/manager-examples/blob/88f0f73a480782d8b01681eecb91cb220dd98553/auth-custom/io-manager-server/src/MyAuthenticator.ts#L61) 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 `traceContext` database column to the `commands` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `traceContext` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> ALTER TABLE commands
>     ADD COLUMN IF NOT EXISTS "traceContext" JSON;
> ```
>
> For Microsoft SQL Server:
>
> ```sql
> 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 `commandParams` database column of the `commands` table to `JSON` (for PostgreSQL only).

> ⚠️ *Note that users who don't use automatic schema migration must change the type of the `commandParams` column manually by running the following script:*
>
> ```sql
> ALTER TABLE commands
>     ALTER COLUMN "commandParams" TYPE JSON USING "commandParams"::json;
> ```

- Changed the precision of the `weight` database column in the `glue42SystemConfig` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must change the precision of the `weight` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> ALTER TABLE "glue42SystemConfig"
>    ALTER COLUMN weight TYPE numeric(8, 6) USING weight::numeric(8, 6);
> ```
>
> For Microsoft SQL Server:
>
> ```sql
> ALTER TABLE glue42SystemConfig
>     ALTER COLUMN weight decimal(8, 6);
> ```

- Changed the type of the `definition` database column of the `layouts` table to `JSON` (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must change the type of the `definition` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> ALTER TABLE layouts
>     ALTER COLUMN definition TYPE JSON USING definition::json;
> ```
>
> For Microsoft SQL Server:
>
> ```sql
> ALTER TABLE layouts
>     ALTER COLUMN definition NVARCHAR(max) NOT NULL;
> ```

- Changed the type of the `comment` database column of the `crashes` table to `NVARCHAR(max)` (for Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must change the type of the `comment` column manually by running the following script:*
>
> ```sql
> ALTER TABLE crashes
>     ALTER COLUMN comment NVARCHAR(max);
> ```

- Changed the types of the `comment`, `description` and `attachment` database columns of the `feedback` table to `NVARCHAR(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:*
>
> ```sql
> 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 the `monitoring` property. Use `{ type: "none" }` or `{ type: "sentry" }` instead.

- Sentry monitoring configuration changes:
   - Removed the `tracesSampleRate` property from the Sentry monitoring configuration. Use `sentryOptions.tracesSampleRate` instead.
   - Removed the `dsn` property from the Sentry monitoring configuration. Use `sentryOptions.dsn` instead.
   - Tracing via the Sentry SDK is no longer supported. The recommended way to send traces to Sentry is by using [OpenTelemetry traces](https://docs.interop.io/manager/opentelemetry-support/traces/index.md). See also the [OpenTelemetry with Sentry](https://github.com/InteropIO/manager-examples/tree/main/otel-sentry) example on GitHub.

- Removed `["admin"]` as a default value for the `API_AUTH_EXCLUSIVE_USERS` environment 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 the `API_AUTH_EXCLUSIVE_USERS` environment variable.

- Due to a change in the FDC3 specification, the `name` and `definition.name` app properties won't be checked for consistency for FDC3 app definitions. The `name` and `definition.appId` properties will still be checked. For more details, see the [FDC3 Standard 2.1](https://github.com/finos/FDC3/releases/tag/v2.1).

- When using environment variable configuration with `.env` files, values from the `.env` files will override the process environment variables. This means that if an environment variable is both defined in the `.env` file and passed to the process, the value from the `.env` file will be used.

### 1.7.0

- Added database column `others` to the `last_updated` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `others` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> ALTER TABLE last_updated
>     ADD COLUMN IF NOT EXISTS others JSON NULL;
> ```
>
> For Microsoft SQL Server:
>
> ```sql
> 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 `browser` to the `machines` table (for PostgreSQL and Microsoft SQL Server only).

> ⚠️ *Note that users who don't use automatic schema migration must create the `browser` column manually by running the following scripts.*
>
> For PostgreSQL:
>
> ```sql
> ALTER TABLE machines
>     ADD COLUMN IF NOT EXISTS browser JSON NULL;
> ```
>
> For Microsoft SQL Server:
>
> ```sql
> 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_sensitive` property in the configuration object for initializing the **io.Manager** Server or the `API_USERNAME_CASE_SENSITIVE` environment variable to switch between the two modes. The new default is case-insensitive. To switch back to case sensitive mode, set the `username_case_sensitive` property or the `API_USERNAME_CASE_SENSITIVE` environment variable to `true`.

- 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.x` is compatible with **io.Manager** Server `4.0.x` (any patch), but **io.Manager** Admin UI `4.0.0` isn't compatible with **io.Manager** Server `4.1.0`. Make sure to upgrade both packages together so that their major and minor versions stay aligned. Compatibility with [`@interopio/manager-api`](https://www.npmjs.com/package/@interopio/manager-api), **io.Connect Desktop**, and **io.Connect Browser** is unchanged.

- The `onUnauthorizedResponse()` callback on the `AuthProvider` interface is now also invoked on `403 Forbidden` responses, in addition to the existing `401 Unauthorized` trigger. Consumers implementing a custom `AuthProvider` should review the callback and update any logic that previously assumed it fires only on `401` - 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-ui`](https://www.npmjs.com/package/@interopio/manager-admin-ui) package will be published in the public NPM registry. Previous versions of the package will still be available in the private [JFROG](https://glue42.jfrog.io/) registry.

- Dropped React 17 support. The **io.Manager** Admin UI now requires React 18. See the [React 18 Upgrade Guide](https://react.dev/blog/2022/03/08/react-18-upgrade-guide) and the [Admin UI](https://github.com/InteropIO/manager-examples/tree/main/auth-none/io-manager-admin-ui) example on GitHub.

- Removed the `users` top-level configuration property. Its options will now be implied from the `auth` top-level configuration property.

- If using TypeScript, the `"jsx"` property of the `"compilerOptions"` object in the `tsconfig.json` file must be set to `"react-jsx"`.

- All **io.Manager** Admin UI style imports have been combined in one:

```javascript
// 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-apps` style import.*

The `@interopio/theme-demo-apps` dependency can be safely removed from your `package.json` file. For a complete example, see the [Admin UI](https://github.com/InteropIO/manager-examples/tree/main/auth-none/io-manager-admin-ui) example on GitHub.

- For clients using Auth0 authentication: The [`@auth0/auth0-react`](https://www.npmjs.com/package/@auth0/auth0-react) package has been updated from v1 to v2. The `@auth0/auth0-react` v2 package introduces breaking changes to the `auth_auth0` property which accepts a value of type `Auth0ProviderOptions` imported from `@auth0/auth0-react`. For more details, see the [Auth0-React v2 Migration Guide](https://github.com/auth0/auth0-react/blob/main/MIGRATION_GUIDE.md).

- For clients using the `manager-admin-ui` docker image: The `PUBLIC_URL` environment variable has been deprecated and won't have any effect. Setting the `REACT_APP_BASE` environment variable will be enough for the routing to work correctly.

## @interopio/manager-api

### 5.0.0

- The type of the `attachment` field of the `AddFeedbackRequest` DTO has been changed from `string` to `File | NodeJSReadStream`. Callers must pass a `File` in the browser or a Node.js `ReadStream` in Node.
- The type of the `upload_file_minidump` field of the `CreateCrashRequest` DTO has been changed from `string` to `File | NodeJSReadStream`. Callers must pass a `File` in the browser or a Node.js `ReadStream` in Node.
- The declared return type of the `whoAmI()` method has been changed from `Promise<User>` to `Promise<WhoAmIResponse>`. The method has always returned only the `id`, `email`, `apps`, and `groups` fields at runtime, which means that the runtime behavior hasn't changed. TypeScript callers that read any other field of `User` from the result won't compile - read those fields via the `users` client instead.
- The following exports have been removed from the `@interopio/manager-api` package:
    - `BaseAPI`
    - `AuthOptions`
    - `CustomRequest`
    - `CustomRequestResponse`
    - `ClientOptions`
    - `IOManagerCache`
    - `IOManagerCacheItem`
    - `IOManagerLogger`
    - `IOManagerAsyncSequelizer`
    - `SanitizedIOManagerApiError`
    - `IOManagerRequestValidationError`
    - `CachedClientAPI`
    - `CachedClientOptions`
    - `IOManagerConnectionState`
    - `isIOManagerTimeoutError`

### 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:
        - `AppPreferenceDataResult`
        - `AuditLogDataResult`
        - `CommandDataResult`
        - `ClientCrashDataResult`
        - `FeedbackDataResult`
        - `GroupDataResult`
        - `LayoutDataResult`
        - `MachineDataResult`
        - `UserSessionDataResult`
        - `SystemConfigEntryDataResult`
    - Removed type alias `GetAppsRequest` - use `DataRequest` instead.
    - Removed type alias `GetCommandsResponse` - use `CommandDataResult` instead.
    - Removed type alias `GetUsersResponse` - use `UserDataResult` instead.
    - The types of the following interface properties have been converted from inline TypeScript Union types to TypeScript Enums:
        - `CleanSessionsAction.action` is now of type `CleanSessionsAction`.
        - `UserAppStatus.explain` is now of type `UserAppStatusExplain`.
        - `AuditLog.entityType` is now of type `AuditLogEntityType`.
        - `AuditLog.operation` is now of type `AuditLogOperation`.
        - `Command.status` is now of type `CommandStatus`.
        - `Command.resultType` is now of type `ResultType`.
        - `TextFilterCondition.type` is now of type `TextFilterType`.
        - `BooleanFilterCondition.type` is now of type `BooleanFilterType`.
        - `NumberFilterCondition.type` is now of type `NumberFilterType`.
        - `DateFilterCondition.type` is now of type `DateFilterType`.
        - `SortOrder.sort` is now of type `SortOrderEnum`.
        - `LogicalCondition.operator` is now of type `LogicalConditionOperator`.
    - Removed types `SimpleCondition` and `Filter`.

- The `MatchAll` export has been renamed to `ConfigMatchAll`.
