From a12db87131f137fbe124afcf0805ad9c53599799 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 12:15:46 +0200 Subject: [PATCH 01/32] extend controller base --- ...document-type-detail.server.data-source.ts | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index b937292ce561..77f8d4d3c77d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -7,27 +7,19 @@ import type { UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecute } from '@umbraco-cms/backoffice/resources'; import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; /** * A data source for the Document Type that fetches data from the server * @class UmbDocumentTypeServerDataSource * @implements {RepositoryDetailDataSource} */ -export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSource { - #host: UmbControllerHost; - - /** - * Creates an instance of UmbDocumentTypeServerDataSource. - * @param {UmbControllerHost} host - The controller host for this controller to be appended to - * @memberof UmbDocumentTypeServerDataSource - */ - constructor(host: UmbControllerHost) { - this.#host = host; - } - +export class UmbDocumentTypeDetailServerDataSource + extends UmbControllerBase + implements UmbDetailDataSource +{ /** * Creates a new Document Type scaffold * @param {(string | null)} parentUnique @@ -74,10 +66,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc async read(unique: string) { if (!unique) throw new Error('Unique is missing'); - const { data, error } = await tryExecute( - this.#host, - DocumentTypeService.getDocumentTypeById({ path: { id: unique } }), - ); + const { data, error } = await tryExecute(this, DocumentTypeService.getDocumentTypeById({ path: { id: unique } })); if (error || !data) { return { error }; @@ -191,7 +180,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc }; const { data, error } = await tryExecute( - this.#host, + this, DocumentTypeService.postDocumentType({ body: body, }), @@ -267,7 +256,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc }; const { error } = await tryExecute( - this.#host, + this, DocumentTypeService.putDocumentTypeById({ path: { id: model.unique }, body: body, @@ -291,7 +280,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc if (!unique) throw new Error('Unique is missing'); return tryExecute( - this.#host, + this, DocumentTypeService.deleteDocumentTypeById({ path: { id: unique }, }), From 5d6415f132d39e86cb2d451c607487f8a51c8ccd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 13:16:45 +0200 Subject: [PATCH 02/32] extend controller base --- .../data-type-detail.server.data-source.ts | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 9963b80846cf..65ed91cc6030 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -7,26 +7,18 @@ import type { UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecute } from '@umbraco-cms/backoffice/resources'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; /** * A data source for the Data Type that fetches data from the server * @class UmbDataTypeServerDataSource * @implements {RepositoryDetailDataSource} */ -export class UmbDataTypeServerDataSource implements UmbDetailDataSource { - #host: UmbControllerHost; - - /** - * Creates an instance of UmbDataTypeServerDataSource. - * @param {UmbControllerHost} host - The controller host for this controller to be appended to - * @memberof UmbDataTypeServerDataSource - */ - constructor(host: UmbControllerHost) { - this.#host = host; - } - +export class UmbDataTypeServerDataSource + extends UmbControllerBase + implements UmbDetailDataSource +{ /** * Creates a new Data Type scaffold * @param {(string | null)} parentUnique @@ -57,7 +49,7 @@ export class UmbDataTypeServerDataSource implements UmbDetailDataSource Date: Mon, 11 Aug 2025 14:30:28 +0200 Subject: [PATCH 03/32] add package for management api --- src/Umbraco.Web.UI.Client/package-lock.json | 7 ++++ src/Umbraco.Web.UI.Client/package.json | 1 + .../src/apps/backoffice/backoffice.element.ts | 3 +- .../src/packages/management-api/index.ts | 1 + .../src/packages/management-api/package.json | 8 +++++ .../management-api/runtime-cache/index.ts | 33 +++++++++++++++++++ .../management-api/umbraco-package.ts | 1 + .../packages/management-api/vite.config.ts | 12 +++++++ src/Umbraco.Web.UI.Client/tsconfig.json | 3 +- 9 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/package.json create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/vite.config.ts diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 2886a3bcef1d..1d8d8c320132 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -3883,6 +3883,10 @@ "resolved": "src/packages/log-viewer", "link": true }, + "node_modules/@umbraco-backoffice/management-api": { + "resolved": "src/packages/management-api", + "link": true + }, "node_modules/@umbraco-backoffice/markdown": { "resolved": "src/packages/markdown-editor", "link": true @@ -17373,6 +17377,9 @@ "src/packages/log-viewer": { "name": "@umbraco-backoffice/log-viewer" }, + "src/packages/management-api": { + "name": "@umbraco-backoffice/management-api" + }, "src/packages/markdown-editor": { "name": "@umbraco-backoffice/markdown" }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 640d3feac830..6e85c83dde80 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -60,6 +60,7 @@ "./lit-element": "./dist-cms/packages/core/lit-element/index.js", "./localization": "./dist-cms/packages/core/localization/index.js", "./log-viewer": "./dist-cms/packages/log-viewer/index.js", + "./management-api": "./dist-cms/packages/management-api/index.js", "./markdown-editor": "./dist-cms/packages/markdown-editor/index.js", "./media-type": "./dist-cms/packages/media/media-types/index.js", "./media": "./dist-cms/packages/media/media/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index 2c0b5fd8761e..b284635d588e 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -15,6 +15,7 @@ const CORE_PACKAGES = [ import('../../packages/block/umbraco-package.js'), import('../../packages/clipboard/umbraco-package.js'), import('../../packages/code-editor/umbraco-package.js'), + import('../../packages/content/umbraco-package.js'), import('../../packages/data-type/umbraco-package.js'), import('../../packages/dictionary/umbraco-package.js'), import('../../packages/documents/umbraco-package.js'), @@ -24,6 +25,7 @@ const CORE_PACKAGES = [ import('../../packages/help/umbraco-package.js'), import('../../packages/language/umbraco-package.js'), import('../../packages/log-viewer/umbraco-package.js'), + import('../../packages/management-api/umbraco-package.js'), import('../../packages/markdown-editor/umbraco-package.js'), import('../../packages/media/umbraco-package.js'), import('../../packages/members/umbraco-package.js'), @@ -48,7 +50,6 @@ const CORE_PACKAGES = [ import('../../packages/umbraco-news/umbraco-package.js'), import('../../packages/user/umbraco-package.js'), import('../../packages/webhook/umbraco-package.js'), - import('../../packages/content/umbraco-package.js'), ]; @customElement('umb-backoffice') diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts new file mode 100644 index 000000000000..69f2bfc05b70 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts @@ -0,0 +1 @@ +export * from './runtime-cache/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json b/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json new file mode 100644 index 000000000000..d8653e983520 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json @@ -0,0 +1,8 @@ +{ + "name": "@umbraco-backoffice/management-api", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts new file mode 100644 index 000000000000..66421d50e3d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts @@ -0,0 +1,33 @@ +// Keep internal +interface UmbCacheEntryModel { + id: string; + data: DataModelType; + insertDate: number; +} + +export class UmbManagementApiRuntimeCache { + #entries: Map> = new Map(); + + has(id: string): boolean { + return this.#entries.has(id); + } + + set(id: string, data: DataModelType): void { + const cacheEntry: UmbCacheEntryModel = { + id: id, + data, + insertDate: Date.now(), + }; + + this.#entries.set(id, cacheEntry); + } + + get(id: string) { + const entry = this.#entries.get(id); + return entry ? entry.data : undefined; + } + + delete(id: string): void { + this.#entries.delete(id); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts new file mode 100644 index 000000000000..919dcabc50b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts @@ -0,0 +1 @@ +export const name = 'Umbraco.ManagementApi'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/vite.config.ts new file mode 100644 index 000000000000..ff9cbc2c0d12 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import { rmSync } from 'fs'; +import { getDefaultConfig } from '../../vite-config-base'; + +const dist = '../../../dist-cms/packages/management-api'; + +// delete the unbundled dist folder +rmSync(dist, { recursive: true, force: true }); + +export default defineConfig({ + ...getDefaultConfig({ dist }), +}); diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 23592d2c5b0d..09943d9e5f8a 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -14,7 +14,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "moduleDetection": "force", "verbatimModuleSyntax": true, "target": "es2022", - "lib": ["es2022", "dom", "dom.iterable", "WebWorker"], + "lib": ["es2022", "dom", "dom.iterable"], "outDir": "./types", "allowSyntheticDefaultImports": true, "experimentalDecorators": true, @@ -89,6 +89,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/lit-element": ["./src/packages/core/lit-element/index.ts"], "@umbraco-cms/backoffice/localization": ["./src/packages/core/localization/index.ts"], "@umbraco-cms/backoffice/log-viewer": ["./src/packages/log-viewer/index.ts"], + "@umbraco-cms/backoffice/management-api": ["./src/packages/management-api/index.ts"], "@umbraco-cms/backoffice/markdown-editor": ["./src/packages/markdown-editor/index.ts"], "@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"], "@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"], From 62120278ff3d4e46bca1a86c86bd9c3e0833aae2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 15:02:56 +0200 Subject: [PATCH 04/32] add signalr as external package --- src/Umbraco.Web.UI.Client/package-lock.json | 128 +++++++++++++++++- src/Umbraco.Web.UI.Client/package.json | 1 + .../src/external/signalr/index.ts | 1 + .../src/external/signalr/package.json | 11 ++ .../src/external/signalr/vite.config.ts | 18 +++ src/Umbraco.Web.UI.Client/tsconfig.json | 1 + 6 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/external/signalr/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/external/signalr/package.json create mode 100644 src/Umbraco.Web.UI.Client/src/external/signalr/vite.config.ts diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 1d8d8c320132..7338f50d909e 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1237,6 +1237,19 @@ "react": ">=16" } }, + "node_modules/@microsoft/signalr": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-9.0.6.tgz", + "integrity": "sha512-DrhgzFWI9JE4RPTsHYRxh4yr+OhnwKz8bnJe7eIi7mLLjqhJpEb62CiUy/YbFvLqLzcGzlzz1QWgVAW0zyipMQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.2", + "fetch-cookie": "^2.0.3", + "node-fetch": "^2.6.7", + "ws": "^7.5.10" + } + }, "node_modules/@mswjs/cookies": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", @@ -3959,6 +3972,10 @@ "resolved": "src/packages/settings", "link": true }, + "node_modules/@umbraco-backoffice/signalr": { + "resolved": "src/external/signalr", + "link": true + }, "node_modules/@umbraco-backoffice/static-file": { "resolved": "src/packages/static-file", "link": true @@ -5452,6 +5469,18 @@ "license": "(Unlicense OR Apache-2.0)", "optional": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -8406,6 +8435,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8416,6 +8454,15 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -8606,6 +8653,16 @@ } } }, + "node_modules/fetch-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", + "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", + "license": "Unlicense", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^4.0.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -12179,7 +12236,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -12207,21 +12263,18 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -14141,6 +14194,18 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -14156,7 +14221,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -14227,6 +14291,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", @@ -14592,6 +14662,12 @@ "node": ">=10.13.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -14927,7 +15003,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, "license": "MIT" }, "node_modules/set-function-length": { @@ -15746,6 +15821,30 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -16548,6 +16647,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -17095,7 +17204,6 @@ "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.3.0" @@ -17279,6 +17387,12 @@ "rxjs": "^7.8.2" } }, + "src/external/signalr": { + "name": "@umbraco-backoffice/signalr", + "dependencies": { + "@microsoft/signalr": "9.0.6" + } + }, "src/external/tiptap": { "name": "@umbraco-backoffice/external-tiptap", "dependencies": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 6e85c83dde80..e3d2a0dd4bf8 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -127,6 +127,7 @@ "./external/monaco-editor": "./dist-cms/external/monaco-editor/index.js", "./external/openid": "./dist-cms/external/openid/index.js", "./external/rxjs": "./dist-cms/external/rxjs/index.js", + "./external/signalr": "./dist-cms/external/signalr/index.js", "./external/tiptap": "./dist-cms/external/tiptap/index.js", "./external/uui": "./dist-cms/external/uui/index.js" }, diff --git a/src/Umbraco.Web.UI.Client/src/external/signalr/index.ts b/src/Umbraco.Web.UI.Client/src/external/signalr/index.ts new file mode 100644 index 000000000000..324f4d0a561c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/signalr/index.ts @@ -0,0 +1 @@ +export * from '@microsoft/signalr'; diff --git a/src/Umbraco.Web.UI.Client/src/external/signalr/package.json b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json new file mode 100644 index 000000000000..08e7abbc27e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json @@ -0,0 +1,11 @@ +{ + "name": "@umbraco-backoffice/signalr", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "dependencies": { + "@microsoft/signalr": "9.0.6" + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/signalr/vite.config.ts b/src/Umbraco.Web.UI.Client/src/external/signalr/vite.config.ts new file mode 100644 index 000000000000..6729a413c587 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/signalr/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import { rmSync } from 'fs'; +import { getDefaultConfig } from '../../vite-config-base'; + +const dist = '../../../dist-cms/external/signalr'; + +// delete the unbundled dist folder +rmSync(dist, { recursive: true, force: true }); + +export default defineConfig({ + ...getDefaultConfig({ + dist, + base: '/umbraco/backoffice/external/signalr', + entry: { + index: './index.ts', + }, + }), +}); diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 09943d9e5f8a..88e278fb2d9a 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -156,6 +156,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/external/monaco-editor": ["./src/external/monaco-editor/index.ts"], "@umbraco-cms/backoffice/external/openid": ["./src/external/openid/index.ts"], "@umbraco-cms/backoffice/external/rxjs": ["./src/external/rxjs/index.ts"], + "@umbraco-cms/backoffice/external/signalr": ["./src/external/signalr/index.ts"], "@umbraco-cms/backoffice/external/tiptap": ["./src/external/tiptap/index.ts"], "@umbraco-cms/backoffice/external/uui": ["./src/external/uui/index.ts"] } From 2b1be18d793111baa3edd3df235aa4416467ddd9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 15:13:47 +0200 Subject: [PATCH 05/32] connect to server event hub --- .../src/packages/management-api/manifests.ts | 3 + .../server-event/global-context/manifests.ts | 8 ++ .../server-event.context-token.ts | 7 ++ .../global-context/server-event.context.ts | 85 +++++++++++++++++++ .../management-api/server-event/manifests.ts | 3 + .../management-api/umbraco-package.ts | 8 ++ 6 files changed, 114 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/manifests.ts new file mode 100644 index 000000000000..26c13f92b91d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/manifests.ts @@ -0,0 +1,3 @@ +import { manifests as serverEventManifests } from './server-event/manifests.js'; + +export const manifests: Array = [...serverEventManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/manifests.ts new file mode 100644 index 000000000000..6aaa311e9be8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/manifests.ts @@ -0,0 +1,8 @@ +export const manifests: Array = [ + { + type: 'globalContext', + alias: 'Umb.GlobalContext.ManagementApi.ServerEvent', + name: 'Management Api Server Event Global Context', + api: () => import('./server-event.context.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts new file mode 100644 index 000000000000..96c3c38dc535 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts @@ -0,0 +1,7 @@ +import type { UmbClipboardContext } from './clipboard.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbManagementApiServerEventContext } from './server-event.context.js'; + +export const UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT = new UmbContextToken( + 'UmbClipboardContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts new file mode 100644 index 000000000000..e634f4fca0a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts @@ -0,0 +1,85 @@ +import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from './server-event.context-token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; +import { HubConnectionBuilder, type HubConnection } from '@umbraco-cms/backoffice/external/signalr'; +import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; + +interface UmbManagementApiServerEventModel { + eventSource: string; + eventType: string; + key: string; +} + +export class UmbManagementApiServerEventContext extends UmbContextBase { + #connection?: HubConnection; + #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; + #serverContext?: typeof UMB_SERVER_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host, UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT); + + this.consumeContext(UMB_AUTH_CONTEXT, (context) => { + this.#authContext = context; + this.#observeIsAuthorized(); + }); + + this.consumeContext(UMB_SERVER_CONTEXT, (context) => { + this.#serverContext = context; + }); + } + + #observeIsAuthorized() { + this.observe(this.#authContext?.isAuthorized, async (isAuthorized) => { + if (isAuthorized) { + const token = await this.#authContext?.getLatestToken(); + if (token) { + this.#initHubConnection(token); + } else { + throw new Error('No auth token found'); + } + } else { + this.#connection?.stop(); + this.#connection = undefined; + } + }); + } + + #initHubConnection(token: string) { + const serverURL = this.#serverContext?.getServerUrl(); + + if (!serverURL) { + throw new Error('Server URL is not defined in the server context'); + } + + // TODO: get the url from a server config? + const serverEventHubUrl = `${serverURL}/umbraco/serverEventHub`; + + this.#connection = new HubConnectionBuilder() + .withUrl(serverEventHubUrl, { + accessTokenFactory: () => token, + }) + .build(); + + this.#connection.on('notify', (payload: UmbManagementApiServerEventModel) => { + console.log('payloadReceived', payload); + }); + + this.#connection + .start() + .then(function () { + console.log('Connected!'); + }) + .catch(function (err) { + console.log(err); + }); + + this.#connection.onclose((err?: Error) => { + if (err) { + console.error('Connection closed with error: ', err); + } + }); + } +} + +export { UmbManagementApiServerEventContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/manifests.ts new file mode 100644 index 000000000000..c81aafcfb402 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/manifests.ts @@ -0,0 +1,3 @@ +import { manifests as globalContextManifests } from './global-context/manifests.js'; + +export const manifests: Array = [...globalContextManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts index 919dcabc50b8..03e442609e97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/umbraco-package.ts @@ -1 +1,9 @@ export const name = 'Umbraco.ManagementApi'; +export const extensions = [ + { + name: 'Management Api Bundle', + alias: 'Umb.Bundle.ManagementApi', + type: 'bundle', + js: () => import('./manifests.js'), + }, +]; From 2490bf8cff7c0d8502098254e9ab0fe7e949cdc9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 19:55:09 +0200 Subject: [PATCH 06/32] do no act on undefined --- .../server-event/global-context/server-event.context.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts index e634f4fca0a3..2a9d8bd474ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts @@ -31,6 +31,8 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { #observeIsAuthorized() { this.observe(this.#authContext?.isAuthorized, async (isAuthorized) => { + if (isAuthorized === undefined) return; + if (isAuthorized) { const token = await this.#authContext?.getLatestToken(); if (token) { @@ -68,10 +70,10 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { this.#connection .start() .then(function () { - console.log('Connected!'); + //console.log('Connected!'); }) .catch(function (err) { - console.log(err); + console.log('SignalR', err); }); this.#connection.onclose((err?: Error) => { From 8c592e3ea0df563bb15c6aa3fa931070174d4f56 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 20:59:01 +0200 Subject: [PATCH 07/32] add event subject --- .../server-event/global-context/server-event.context.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts index 2a9d8bd474ce..ade29f9653d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts @@ -4,6 +4,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; import { HubConnectionBuilder, type HubConnection } from '@umbraco-cms/backoffice/external/signalr'; import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; +import { Subject } from '@umbraco-cms/backoffice/external/rxjs'; interface UmbManagementApiServerEventModel { eventSource: string; @@ -16,6 +17,9 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; #serverContext?: typeof UMB_SERVER_CONTEXT.TYPE; + #events = new Subject(); + public readonly events = this.#events.asObservable(); + constructor(host: UmbControllerHost) { super(host, UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT); @@ -64,7 +68,7 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { .build(); this.#connection.on('notify', (payload: UmbManagementApiServerEventModel) => { - console.log('payloadReceived', payload); + this.#events.next(payload); }); this.#connection From c139362457a8ec29f36de096c6d62648f09a9101 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 20:59:10 +0200 Subject: [PATCH 08/32] correct alias --- .../global-context/server-event.context-token.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts index 96c3c38dc535..223eb624bba2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context-token.ts @@ -1,7 +1,6 @@ -import type { UmbClipboardContext } from './clipboard.context.js'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbManagementApiServerEventContext } from './server-event.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export const UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT = new UmbContextToken( - 'UmbClipboardContext', + 'UmbManagementApiServerEventContext', ); From 62799bddb6339b915df6510640bbf51492c976ad Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 20:59:26 +0200 Subject: [PATCH 09/32] export token --- src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts | 1 + .../src/packages/management-api/server-event/constants.ts | 1 + .../management-api/server-event/global-context/constants.ts | 1 + 3 files changed, 3 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts index 69f2bfc05b70..be401f731a14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts @@ -1 +1,2 @@ export * from './runtime-cache/index.js'; +export * from './server-event/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/constants.ts new file mode 100644 index 000000000000..b69915b2cfcd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/constants.ts @@ -0,0 +1 @@ +export * from './global-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/constants.ts new file mode 100644 index 000000000000..5822d52362ec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/constants.ts @@ -0,0 +1 @@ +export * from './server-event.context-token.js'; From 5a3f8f95e9c7a57e1aed3af52b30495c19374ac7 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 21:54:24 +0200 Subject: [PATCH 10/32] add helper methods --- .../global-context/server-event.context.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts index ade29f9653d4..3b6f09d1b0dd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts @@ -4,7 +4,8 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; import { HubConnectionBuilder, type HubConnection } from '@umbraco-cms/backoffice/external/signalr'; import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; -import { Subject } from '@umbraco-cms/backoffice/external/rxjs'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { filter, Subject } from '@umbraco-cms/backoffice/external/rxjs'; interface UmbManagementApiServerEventModel { eventSource: string; @@ -20,6 +21,29 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { #events = new Subject(); public readonly events = this.#events.asObservable(); + /** + * Filters events by the given event source + * @param {string} eventSource + * @returns {Observable} - The filtered events + * @memberof UmbManagementApiServerEventContext + */ + byEventSource(eventSource: string): Observable { + return this.#events.asObservable().pipe(filter((event) => event.eventSource === eventSource)); + } + + /** + * Filters events by the given event source and event types + * @param {string} eventSource + * @param {Array} eventTypes + * @returns {Observable} - The filtered events + * @memberof UmbManagementApiServerEventContext + */ + byEventSourceAndTypes(eventSource: string, eventTypes: Array): Observable { + return this.#events + .asObservable() + .pipe(filter((event) => event.eventSource === eventSource && eventTypes.includes(event.eventType))); + } + constructor(host: UmbControllerHost) { super(host, UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT); From 2deb7cc987ce010f87bd4b0d6f8c4bf9d72bfcb6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 21:55:08 +0200 Subject: [PATCH 11/32] cache server responses --- .../data-type-detail.server.data-source.ts | 59 +++++++++++++++-- .../data-type-detail.server.runtime-cache.ts | 7 +++ ...document-type-detail.server.data-source.ts | 63 ++++++++++++++++--- ...cument-type-detail.server.runtime-cache.ts | 6 ++ 4 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 65ed91cc6030..3f2f18f0b6b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -1,14 +1,18 @@ import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../../types.js'; import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../entity.js'; +import { cache } from './data-type-detail.server.runtime-cache.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { CreateDataTypeRequestModel, + DataTypeResponseModel, UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute } from '@umbraco-cms/backoffice/resources'; +import { tryExecute, UmbError } from '@umbraco-cms/backoffice/resources'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '@umbraco-cms/backoffice/management-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; /** * A data source for the Data Type that fetches data from the server @@ -19,6 +23,29 @@ export class UmbDataTypeServerDataSource extends UmbControllerBase implements UmbDetailDataSource { + #runtimeCache = cache; + #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + #eventSource = 'Umbraco:CMS:DataType'; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { + this.#serverEventContext = context; + this.#observeServerEvents(); + }); + } + + #observeServerEvents() { + this.observe( + this.#serverEventContext?.byEventSourceAndTypes(this.#eventSource, ['Updated', 'Deleted']), + (event) => { + if (!event) return; + this.#runtimeCache.delete(event.key); + }, + ); + } + /** * Creates a new Data Type scaffold * @param {(string | null)} parentUnique @@ -49,10 +76,26 @@ export class UmbDataTypeServerDataSource async read(unique: string) { if (!unique) throw new Error('Unique is missing'); - const { data, error } = await tryExecute(this, DataTypeService.getDataTypeById({ path: { id: unique } })); + let data: DataTypeResponseModel | undefined; + + if (this.#runtimeCache.has(unique)) { + data = this.#runtimeCache.get(unique); + } else { + const { data: serverData, error: serverError } = await tryExecute( + this, + DataTypeService.getDataTypeById({ path: { id: unique } }), + ); + + if (serverError || !serverData) { + return { error: serverError }; + } - if (error || !data) { - return { error }; + this.#runtimeCache.set(unique, serverData); + data = serverData; + } + + if (!data) { + return { error: new UmbError(`Data Type with unique "${unique}" not found.`) }; } // TODO: make data mapper to prevent errors @@ -149,11 +192,17 @@ export class UmbDataTypeServerDataSource async delete(unique: string) { if (!unique) throw new Error('Unique is missing'); - return tryExecute( + const { error } = await tryExecute( this, DataTypeService.deleteDataTypeById({ path: { id: unique }, }), ); + + if (!error) { + this.#runtimeCache.delete(unique); + } + + return { error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts new file mode 100644 index 000000000000..564db87115d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts @@ -0,0 +1,7 @@ +import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbManagementApiRuntimeCache } from '@umbraco-cms/backoffice/management-api'; + +// We use a singleton so we can share the cache across the application +const cache = new UmbManagementApiRuntimeCache(); + +export { cache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index 77f8d4d3c77d..665e7e952102 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -1,15 +1,19 @@ import type { UmbDocumentTypeDetailModel } from '../../types.js'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; +import { cache } from './document-type-detail.server.cache.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { CreateDocumentTypeRequestModel, + DocumentTypeResponseModel, UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute } from '@umbraco-cms/backoffice/resources'; +import { tryExecute, UmbError } from '@umbraco-cms/backoffice/resources'; import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '@umbraco-cms/backoffice/management-api'; /** * A data source for the Document Type that fetches data from the server @@ -20,6 +24,29 @@ export class UmbDocumentTypeDetailServerDataSource extends UmbControllerBase implements UmbDetailDataSource { + #runtimeCache = cache; + #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { + this.#serverEventContext = context; + this.#observeServerEvents(); + }); + } + + #observeServerEvents() { + this.observe(this.#serverEventContext?.events, (event) => { + const eventSource = 'Umbraco:CMS:DocumentType'; + if (event?.eventSource === eventSource) { + if (event.eventType === 'Updated' || event.eventType === 'Deleted') { + this.#runtimeCache.delete(event.key); + } + } + }); + } + /** * Creates a new Document Type scaffold * @param {(string | null)} parentUnique @@ -66,14 +93,30 @@ export class UmbDocumentTypeDetailServerDataSource async read(unique: string) { if (!unique) throw new Error('Unique is missing'); - const { data, error } = await tryExecute(this, DocumentTypeService.getDocumentTypeById({ path: { id: unique } })); + let data: DocumentTypeResponseModel | undefined; + + if (this.#runtimeCache.has(unique)) { + data = this.#runtimeCache.get(unique); + } else { + const { data: serverData, error: serverError } = await tryExecute( + this, + DocumentTypeService.getDocumentTypeById({ path: { id: unique } }), + ); + + if (serverError || !serverData) { + return { error: serverError }; + } - if (error || !data) { - return { error }; + this.#runtimeCache.set(unique, serverData); + data = serverData; + } + + if (!data) { + return { error: new UmbError(`Document Type with unique "${unique}" not found.`) }; } // TODO: make data mapper to prevent errors - const DocumentType: UmbDocumentTypeDetailModel = { + const documentType: UmbDocumentTypeDetailModel = { entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, unique: data.id, name: data.name, @@ -119,7 +162,7 @@ export class UmbDocumentTypeDetailServerDataSource collection: data.collection ? { unique: data.collection?.id } : null, }; - return { data: DocumentType }; + return { data: documentType }; } /** @@ -279,11 +322,17 @@ export class UmbDocumentTypeDetailServerDataSource async delete(unique: string) { if (!unique) throw new Error('Unique is missing'); - return tryExecute( + const { error } = await tryExecute( this, DocumentTypeService.deleteDocumentTypeById({ path: { id: unique }, }), ); + + if (!error) { + this.#runtimeCache.delete(unique); + } + + return { error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts new file mode 100644 index 000000000000..9db50555fda1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts @@ -0,0 +1,6 @@ +import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbManagementApiRuntimeCache } from '@umbraco-cms/backoffice/management-api'; + +const cache = new UmbManagementApiRuntimeCache(); + +export { cache }; From ad257cf3f9d2a35cba6e2da6d05e278e4adc77d6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 21:56:01 +0200 Subject: [PATCH 12/32] fix import --- .../detail/document-type-detail.server.data-source.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index 665e7e952102..342a7ceb5f65 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -1,6 +1,6 @@ import type { UmbDocumentTypeDetailModel } from '../../types.js'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; -import { cache } from './document-type-detail.server.cache.js'; +import { cache } from './document-type-detail.server.runtime-cache.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { From 03c1fc8eb3c8cfc97b9d8c7e90fa93058c5c55c5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Aug 2025 21:58:49 +0200 Subject: [PATCH 13/32] use helpers --- .../document-type-detail.server.data-source.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index 342a7ceb5f65..b38f83e33168 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -26,6 +26,7 @@ export class UmbDocumentTypeDetailServerDataSource { #runtimeCache = cache; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + #eventSource = 'Umbraco:CMS:DocumentType'; constructor(host: UmbControllerHost) { super(host); @@ -37,14 +38,13 @@ export class UmbDocumentTypeDetailServerDataSource } #observeServerEvents() { - this.observe(this.#serverEventContext?.events, (event) => { - const eventSource = 'Umbraco:CMS:DocumentType'; - if (event?.eventSource === eventSource) { - if (event.eventType === 'Updated' || event.eventType === 'Deleted') { - this.#runtimeCache.delete(event.key); - } - } - }); + this.observe( + this.#serverEventContext?.byEventSourceAndTypes(this.#eventSource, ['Updated', 'Deleted']), + (event) => { + if (!event) return; + this.#runtimeCache.delete(event.key); + }, + ); } /** From 1f1baa6170c08ebc8cefa8e3c97e05c2cef43a45 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 14:46:55 +0200 Subject: [PATCH 14/32] add detail request manager --- .../detail/detail-request.manager.ts | 86 +++++++++++++++++++ .../packages/management-api/detail/index.ts | 1 + .../src/packages/management-api/index.ts | 1 + 3 files changed, 88 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts new file mode 100644 index 000000000000..356eaa69d20e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -0,0 +1,86 @@ +import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js'; +import type { UmbManagementApiRuntimeCache } from '../runtime-cache/index.js'; +import { + tryExecute, + type UmbApiError, + type UmbCancelError, + type UmbApiResponse, + type UmbApiWithErrorResponse, +} from '@umbraco-cms/backoffice/resources'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbManagementApiDetailDataRequestManagerArgs { + read: (id: string) => Promise>; + delete: (id: string) => Promise>; + cache: UmbManagementApiRuntimeCache; + serverEventSource: string; +} + +export class UmbManagementApiDetailDataRequestManager extends UmbControllerBase { + #cache: UmbManagementApiRuntimeCache; + #serverEventSource: string; + #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + + #read; + #delete; + + constructor(host: UmbControllerHost, args: UmbManagementApiDetailDataRequestManagerArgs) { + super(host); + + this.#read = args.read; + this.#delete = args.delete; + + this.#cache = args.cache; + this.#serverEventSource = args.serverEventSource; + + this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { + this.#serverEventContext = context; + this.#observeServerEvents(); + }); + } + + async read(id: string): Promise> { + let data: DetailResponseModelType | undefined; + let error: UmbApiError | UmbCancelError | undefined; + + if (this.#cache.has(id)) { + data = this.#cache.get(id); + } else { + const { data: serverData, error: serverError } = await tryExecute(this, this.#read(id)); + + if (serverData) { + this.#cache.set(id, serverData); + } + + data = serverData; + error = serverError; + } + + return { data, error }; + } + + async delete(id: string): Promise { + const { error } = await this.#delete(id); + + if (!error) { + this.#cache.delete(id); + } + + return { error }; + } + + #observeServerEvents() { + this.observe( + this.#serverEventContext?.byEventSourceAndTypes(this.#serverEventSource, ['Updated', 'Deleted']), + (event) => { + if (!event) return; + this.#invalidateCache(event.key); + }, + ); + } + + #invalidateCache(id: string) { + this.#cache.delete(id); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts new file mode 100644 index 000000000000..1faee88e7d37 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts @@ -0,0 +1 @@ +export * from './detail-request.manager.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts index be401f731a14..5d62af12852e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts @@ -1,2 +1,3 @@ +export * from './detail/index.js'; export * from './runtime-cache/index.js'; export * from './server-event/constants.js'; From a2c328346e736dd8a5bd08ca5a763cd7ad7bd2a0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 14:47:10 +0200 Subject: [PATCH 15/32] implement for document type --- ...document-type-detail.server.data-source.ts | 69 ++++--------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index b38f83e33168..b3d0fbbd436c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -9,11 +9,10 @@ import type { UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute, UmbError } from '@umbraco-cms/backoffice/resources'; +import { tryExecute } from '@umbraco-cms/backoffice/resources'; import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '@umbraco-cms/backoffice/management-api'; +import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; /** * A data source for the Document Type that fetches data from the server @@ -24,28 +23,14 @@ export class UmbDocumentTypeDetailServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #runtimeCache = cache; - #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; - #eventSource = 'Umbraco:CMS:DocumentType'; - - constructor(host: UmbControllerHost) { - super(host); - - this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { - this.#serverEventContext = context; - this.#observeServerEvents(); - }); - } - - #observeServerEvents() { - this.observe( - this.#serverEventContext?.byEventSourceAndTypes(this.#eventSource, ['Updated', 'Deleted']), - (event) => { - if (!event) return; - this.#runtimeCache.delete(event.key); - }, - ); - } + #detailRequestManager = new UmbManagementApiDetailDataRequestManager(this, { + // eslint-disable-next-line local-rules/no-direct-api-import + read: (id: string) => DocumentTypeService.getDocumentTypeById({ path: { id } }), + // eslint-disable-next-line local-rules/no-direct-api-import + delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), + cache: cache, + serverEventSource: 'Umbraco:CMS:DocumentType', + }); /** * Creates a new Document Type scaffold @@ -93,26 +78,10 @@ export class UmbDocumentTypeDetailServerDataSource async read(unique: string) { if (!unique) throw new Error('Unique is missing'); - let data: DocumentTypeResponseModel | undefined; - - if (this.#runtimeCache.has(unique)) { - data = this.#runtimeCache.get(unique); - } else { - const { data: serverData, error: serverError } = await tryExecute( - this, - DocumentTypeService.getDocumentTypeById({ path: { id: unique } }), - ); - - if (serverError || !serverData) { - return { error: serverError }; - } - - this.#runtimeCache.set(unique, serverData); - data = serverData; - } + const { data, error } = await this.#detailRequestManager.read(unique); if (!data) { - return { error: new UmbError(`Document Type with unique "${unique}" not found.`) }; + return { error }; } // TODO: make data mapper to prevent errors @@ -321,18 +290,6 @@ export class UmbDocumentTypeDetailServerDataSource */ async delete(unique: string) { if (!unique) throw new Error('Unique is missing'); - - const { error } = await tryExecute( - this, - DocumentTypeService.deleteDocumentTypeById({ - path: { id: unique }, - }), - ); - - if (!error) { - this.#runtimeCache.delete(unique); - } - - return { error }; + return this.#detailRequestManager.delete(unique); } } From 9aa73a7e93a528449a72a7bf90b66f99d0a70ec6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 14:53:12 +0200 Subject: [PATCH 16/32] implement for data type --- .../data-type-detail.server.data-source.ts | 69 ++++--------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 3f2f18f0b6b9..1d1b957c677e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -9,10 +9,9 @@ import type { UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute, UmbError } from '@umbraco-cms/backoffice/resources'; +import { tryExecute } from '@umbraco-cms/backoffice/resources'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '@umbraco-cms/backoffice/management-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; /** * A data source for the Data Type that fetches data from the server @@ -23,28 +22,14 @@ export class UmbDataTypeServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #runtimeCache = cache; - #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; - #eventSource = 'Umbraco:CMS:DataType'; - - constructor(host: UmbControllerHost) { - super(host); - - this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { - this.#serverEventContext = context; - this.#observeServerEvents(); - }); - } - - #observeServerEvents() { - this.observe( - this.#serverEventContext?.byEventSourceAndTypes(this.#eventSource, ['Updated', 'Deleted']), - (event) => { - if (!event) return; - this.#runtimeCache.delete(event.key); - }, - ); - } + #detailRequestManager = new UmbManagementApiDetailDataRequestManager(this, { + // eslint-disable-next-line local-rules/no-direct-api-import + read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), + // eslint-disable-next-line local-rules/no-direct-api-import + delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), + cache: cache, + serverEventSource: 'Umbraco:CMS:DataType', + }); /** * Creates a new Data Type scaffold @@ -76,26 +61,10 @@ export class UmbDataTypeServerDataSource async read(unique: string) { if (!unique) throw new Error('Unique is missing'); - let data: DataTypeResponseModel | undefined; - - if (this.#runtimeCache.has(unique)) { - data = this.#runtimeCache.get(unique); - } else { - const { data: serverData, error: serverError } = await tryExecute( - this, - DataTypeService.getDataTypeById({ path: { id: unique } }), - ); - - if (serverError || !serverData) { - return { error: serverError }; - } - - this.#runtimeCache.set(unique, serverData); - data = serverData; - } + const { data, error } = await this.#detailRequestManager.read(unique); if (!data) { - return { error: new UmbError(`Data Type with unique "${unique}" not found.`) }; + return { error }; } // TODO: make data mapper to prevent errors @@ -191,18 +160,6 @@ export class UmbDataTypeServerDataSource */ async delete(unique: string) { if (!unique) throw new Error('Unique is missing'); - - const { error } = await tryExecute( - this, - DataTypeService.deleteDataTypeById({ - path: { id: unique }, - }), - ); - - if (!error) { - this.#runtimeCache.delete(unique); - } - - return { error }; + return this.#detailRequestManager.delete(unique); } } From 9d33c00238bdcd88500b5a2fbfa8a072c623e233 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 15:20:35 +0200 Subject: [PATCH 17/32] add method for update --- .../data-type-detail.server.data-source.ts | 30 ++++++++++++------- .../detail/detail-request.manager.ts | 29 ++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 1d1b957c677e..19dde058048d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -22,9 +22,15 @@ export class UmbDataTypeServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #detailRequestManager = new UmbManagementApiDetailDataRequestManager(this, { + #detailRequestManager = new UmbManagementApiDetailDataRequestManager< + DataTypeResponseModel, + UpdateDataTypeRequestModel + >(this, { // eslint-disable-next-line local-rules/no-direct-api-import read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), + update: (id: string, body: UpdateDataTypeRequestModel) => + // eslint-disable-next-line local-rules/no-direct-api-import + DataTypeService.putDataTypeById({ path: { id }, body }), // eslint-disable-next-line local-rules/no-direct-api-import delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), cache: cache, @@ -137,16 +143,20 @@ export class UmbDataTypeServerDataSource values: model.values, }; - const { error } = await tryExecute( - this, - DataTypeService.putDataTypeById({ - path: { id: model.unique }, - body: body, - }), - ); + const { data, error } = await this.#detailRequestManager.update(model.unique, body); - if (!error) { - return this.read(model.unique); + if (data) { + // TODO: make data mapper to prevent errors + const dataType: UmbDataTypeDetailModel = { + entityType: UMB_DATA_TYPE_ENTITY_TYPE, + unique: data.id, + name: data.name, + editorAlias: data.editorAlias, + editorUiAlias: data.editorUiAlias || null, + values: data.values as Array, + }; + + return { data: dataType }; } return { error }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts index 356eaa69d20e..39cdd9751da6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -10,25 +10,34 @@ import { import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export interface UmbManagementApiDetailDataRequestManagerArgs { - read: (id: string) => Promise>; +export interface UmbManagementApiDetailDataRequestManagerArgs { + read: (id: string) => Promise>; + update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; cache: UmbManagementApiRuntimeCache; serverEventSource: string; } -export class UmbManagementApiDetailDataRequestManager extends UmbControllerBase { +export class UmbManagementApiDetailDataRequestManager< + DetailResponseModelType, + UpdateRequestModelType, +> extends UmbControllerBase { #cache: UmbManagementApiRuntimeCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; #read; + #update; #delete; - constructor(host: UmbControllerHost, args: UmbManagementApiDetailDataRequestManagerArgs) { + constructor( + host: UmbControllerHost, + args: UmbManagementApiDetailDataRequestManagerArgs, + ) { super(host); this.#read = args.read; + this.#update = args.update; this.#delete = args.delete; this.#cache = args.cache; @@ -40,7 +49,7 @@ export class UmbManagementApiDetailDataRequestManager e }); } - async read(id: string): Promise> { + async read(id: string): Promise> { let data: DetailResponseModelType | undefined; let error: UmbApiError | UmbCancelError | undefined; @@ -60,6 +69,16 @@ export class UmbManagementApiDetailDataRequestManager e return { data, error }; } + async update(id: string, data: UpdateRequestModelType): Promise> { + const { error } = await tryExecute(this, this.#update(id, data)); + + if (!error) { + return this.read(id); + } + + return { error }; + } + async delete(id: string): Promise { const { error } = await this.#delete(id); From ba732e35ebaf9de9782821f0361051c509455a85 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 18:33:03 +0200 Subject: [PATCH 18/32] add support for create method --- .../detail/detail-request.manager.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts index 39cdd9751da6..3aa903b891a6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -10,7 +10,12 @@ import { import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export interface UmbManagementApiDetailDataRequestManagerArgs { +export interface UmbManagementApiDetailDataRequestManagerArgs< + DetailResponseModelType, + CreateRequestModelType, + UpdateRequestModelType, +> { + create: (data: CreateRequestModelType) => Promise>; read: (id: string) => Promise>; update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; @@ -20,22 +25,29 @@ export interface UmbManagementApiDetailDataRequestManagerArgs extends UmbControllerBase { #cache: UmbManagementApiRuntimeCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + #create; #read; #update; #delete; constructor( host: UmbControllerHost, - args: UmbManagementApiDetailDataRequestManagerArgs, + args: UmbManagementApiDetailDataRequestManagerArgs< + DetailResponseModelType, + CreateRequestModelType, + UpdateRequestModelType + >, ) { super(host); + this.#create = args.create; this.#read = args.read; this.#update = args.update; this.#delete = args.delete; @@ -49,6 +61,16 @@ export class UmbManagementApiDetailDataRequestManager< }); } + async create(data: CreateRequestModelType): Promise> { + const { data: createdId, error } = await tryExecute(this, this.#create(data)); + + if (!error) { + return this.read(createdId as string); + } + + return { error }; + } + async read(id: string): Promise> { let data: DetailResponseModelType | undefined; let error: UmbApiError | UmbCancelError | undefined; From 9e1deb6abf56fd6813c24c1513ed82ca1f969c35 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 21:00:10 +0200 Subject: [PATCH 19/32] align code --- .../data-type-detail.server.data-source.ts | 63 +++----- ...document-type-detail.server.data-source.ts | 140 ++++++++---------- 2 files changed, 84 insertions(+), 119 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 19dde058048d..e27e3f6ebb44 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -9,7 +9,6 @@ import type { UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute } from '@umbraco-cms/backoffice/resources'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; @@ -24,8 +23,11 @@ export class UmbDataTypeServerDataSource { #detailRequestManager = new UmbManagementApiDetailDataRequestManager< DataTypeResponseModel, - UpdateDataTypeRequestModel + UpdateDataTypeRequestModel, + CreateDataTypeRequestModel >(this, { + // eslint-disable-next-line local-rules/no-direct-api-import + create: (body: CreateDataTypeRequestModel) => DataTypeService.postDataType({ body }), // eslint-disable-next-line local-rules/no-direct-api-import read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), update: (id: string, body: UpdateDataTypeRequestModel) => @@ -69,21 +71,7 @@ export class UmbDataTypeServerDataSource const { data, error } = await this.#detailRequestManager.read(unique); - if (!data) { - return { error }; - } - - // TODO: make data mapper to prevent errors - const dataType: UmbDataTypeDetailModel = { - entityType: UMB_DATA_TYPE_ENTITY_TYPE, - unique: data.id, - name: data.name, - editorAlias: data.editorAlias, - editorUiAlias: data.editorUiAlias || null, - values: data.values as Array, - }; - - return { data: dataType }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -109,18 +97,9 @@ export class UmbDataTypeServerDataSource values: model.values, }; - const { data, error } = await tryExecute( - this, - DataTypeService.postDataType({ - body: body, - }), - ); + const { data, error } = await this.#detailRequestManager.create(body); - if (data) { - return this.read(data as any); - } - - return { error }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -145,21 +124,7 @@ export class UmbDataTypeServerDataSource const { data, error } = await this.#detailRequestManager.update(model.unique, body); - if (data) { - // TODO: make data mapper to prevent errors - const dataType: UmbDataTypeDetailModel = { - entityType: UMB_DATA_TYPE_ENTITY_TYPE, - unique: data.id, - name: data.name, - editorAlias: data.editorAlias, - editorUiAlias: data.editorUiAlias || null, - values: data.values as Array, - }; - - return { data: dataType }; - } - - return { error }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -172,4 +137,16 @@ export class UmbDataTypeServerDataSource if (!unique) throw new Error('Unique is missing'); return this.#detailRequestManager.delete(unique); } + + // TODO: change this to a mapper extension when the endpoints returns a $type for DataTypeResponseModel + #mapServerResponseModelToEntityDetailModel(data: DataTypeResponseModel): UmbDataTypeDetailModel { + return { + entityType: UMB_DATA_TYPE_ENTITY_TYPE, + unique: data.id, + name: data.name, + editorAlias: data.editorAlias, + editorUiAlias: data.editorUiAlias || null, + values: data.values as Array, + }; + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index b3d0fbbd436c..f57864c7d32c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -23,9 +23,18 @@ export class UmbDocumentTypeDetailServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #detailRequestManager = new UmbManagementApiDetailDataRequestManager(this, { + #detailRequestManager = new UmbManagementApiDetailDataRequestManager< + DocumentTypeResponseModel, + CreateDocumentTypeRequestModel, + UpdateDocumentTypeRequestModel + >(this, { + // eslint-disable-next-line local-rules/no-direct-api-import + create: (data: CreateDocumentTypeRequestModel) => DocumentTypeService.postDocumentType({ body: data }), // eslint-disable-next-line local-rules/no-direct-api-import read: (id: string) => DocumentTypeService.getDocumentTypeById({ path: { id } }), + update: (id: string, body: UpdateDocumentTypeRequestModel) => + // eslint-disable-next-line local-rules/no-direct-api-import + DocumentTypeService.putDocumentTypeById({ path: { id }, body }), // eslint-disable-next-line local-rules/no-direct-api-import delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), cache: cache, @@ -80,58 +89,7 @@ export class UmbDocumentTypeDetailServerDataSource const { data, error } = await this.#detailRequestManager.read(unique); - if (!data) { - return { error }; - } - - // TODO: make data mapper to prevent errors - const documentType: UmbDocumentTypeDetailModel = { - entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, - unique: data.id, - name: data.name, - alias: data.alias, - description: data.description ?? '', - icon: data.icon, - allowedAtRoot: data.allowedAsRoot, - variesByCulture: data.variesByCulture, - variesBySegment: data.variesBySegment, - isElement: data.isElement, - properties: data.properties.map((property) => { - return { - id: property.id, - unique: property.id, - container: property.container, - sortOrder: property.sortOrder, - alias: property.alias, - name: property.name, - description: property.description, - dataType: { unique: property.dataType.id }, - variesByCulture: property.variesByCulture, - variesBySegment: property.variesBySegment, - validation: property.validation, - appearance: property.appearance, - }; - }), - containers: data.containers as UmbPropertyTypeContainerModel[], - allowedContentTypes: data.allowedDocumentTypes.map((allowedDocumentType) => { - return { - contentType: { unique: allowedDocumentType.documentType.id }, - sortOrder: allowedDocumentType.sortOrder, - }; - }), - compositions: data.compositions.map((composition) => { - return { - contentType: { unique: composition.documentType.id }, - compositionType: composition.compositionType, - }; - }), - allowedTemplates: data.allowedTemplates, - defaultTemplate: data.defaultTemplate ? { id: data.defaultTemplate.id } : null, - cleanup: data.cleanup, - collection: data.collection ? { unique: data.collection?.id } : null, - }; - - return { data: documentType }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -191,18 +149,9 @@ export class UmbDocumentTypeDetailServerDataSource collection: model.collection?.unique ? { id: model.collection?.unique } : null, }; - const { data, error } = await tryExecute( - this, - DocumentTypeService.postDocumentType({ - body: body, - }), - ); - - if (data) { - return this.read(data as any); - } + const { data, error } = await this.#detailRequestManager.create(body); - return { error }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -267,19 +216,9 @@ export class UmbDocumentTypeDetailServerDataSource collection: model.collection?.unique ? { id: model.collection?.unique } : null, }; - const { error } = await tryExecute( - this, - DocumentTypeService.putDocumentTypeById({ - path: { id: model.unique }, - body: body, - }), - ); - - if (!error) { - return this.read(model.unique); - } + const { data, error } = await this.#detailRequestManager.update(model.unique, body); - return { error }; + return { data: data ? this.#mapServerResponseModelToEntityDetailModel(data) : undefined, error }; } /** @@ -292,4 +231,53 @@ export class UmbDocumentTypeDetailServerDataSource if (!unique) throw new Error('Unique is missing'); return this.#detailRequestManager.delete(unique); } + + // TODO: change this to a mapper extension when the endpoints returns a $type for DocumentTypeResponseModel + #mapServerResponseModelToEntityDetailModel(data: DocumentTypeResponseModel): UmbDocumentTypeDetailModel { + return { + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, + unique: data.id, + name: data.name, + alias: data.alias, + description: data.description ?? '', + icon: data.icon, + allowedAtRoot: data.allowedAsRoot, + variesByCulture: data.variesByCulture, + variesBySegment: data.variesBySegment, + isElement: data.isElement, + properties: data.properties.map((property) => { + return { + id: property.id, + unique: property.id, + container: property.container, + sortOrder: property.sortOrder, + alias: property.alias, + name: property.name, + description: property.description, + dataType: { unique: property.dataType.id }, + variesByCulture: property.variesByCulture, + variesBySegment: property.variesBySegment, + validation: property.validation, + appearance: property.appearance, + }; + }), + containers: data.containers as UmbPropertyTypeContainerModel[], + allowedContentTypes: data.allowedDocumentTypes.map((allowedDocumentType) => { + return { + contentType: { unique: allowedDocumentType.documentType.id }, + sortOrder: allowedDocumentType.sortOrder, + }; + }), + compositions: data.compositions.map((composition) => { + return { + contentType: { unique: composition.documentType.id }, + compositionType: composition.compositionType, + }; + }), + allowedTemplates: data.allowedTemplates, + defaultTemplate: data.defaultTemplate ? { id: data.defaultTemplate.id } : null, + cleanup: data.cleanup, + collection: data.collection ? { unique: data.collection?.id } : null, + }; + } } From cefc5e04d3995e18cb18d11b537e6f56b2f2f093 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 21:07:58 +0200 Subject: [PATCH 20/32] Update detail-request.manager.ts --- .../packages/management-api/detail/detail-request.manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts index 3aa903b891a6..133ebdf8697d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -116,12 +116,12 @@ export class UmbManagementApiDetailDataRequestManager< this.#serverEventContext?.byEventSourceAndTypes(this.#serverEventSource, ['Updated', 'Deleted']), (event) => { if (!event) return; - this.#invalidateCache(event.key); + this.#invalidateCacheEntry(event.key); }, ); } - #invalidateCache(id: string) { + #invalidateCacheEntry(id: string) { this.#cache.delete(id); } } From b38cdf22116e0859155d27ce0a3ed7008f273dfd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 21:47:39 +0200 Subject: [PATCH 21/32] move explicit naming --- .../detail/data-type-detail.server.runtime-cache.ts | 4 ++-- .../detail/document-type-detail.server.runtime-cache.ts | 4 ++-- .../management-api/detail/detail-request.manager.ts | 6 +++--- .../src/packages/management-api/detail/index.ts | 1 + .../{runtime-cache/index.ts => detail/runtime-cache.ts} | 4 +--- 5 files changed, 9 insertions(+), 10 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/management-api/{runtime-cache/index.ts => detail/runtime-cache.ts} (84%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts index 564db87115d1..f6e273e5613b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts @@ -1,7 +1,7 @@ import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiRuntimeCache } from '@umbraco-cms/backoffice/management-api'; +import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; // We use a singleton so we can share the cache across the application -const cache = new UmbManagementApiRuntimeCache(); +const cache = new UmbManagementApiDetailDataRuntimeCache(); export { cache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts index 9db50555fda1..2b5b8f8f02f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts @@ -1,6 +1,6 @@ import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiRuntimeCache } from '@umbraco-cms/backoffice/management-api'; +import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; -const cache = new UmbManagementApiRuntimeCache(); +const cache = new UmbManagementApiDetailDataRuntimeCache(); export { cache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts index 133ebdf8697d..ad9eb957164d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -1,5 +1,5 @@ import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js'; -import type { UmbManagementApiRuntimeCache } from '../runtime-cache/index.js'; +import type { UmbManagementApiDetailDataCache } from '../runtime-cache/index.js'; import { tryExecute, type UmbApiError, @@ -19,7 +19,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< read: (id: string) => Promise>; update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; - cache: UmbManagementApiRuntimeCache; + cache: UmbManagementApiDetailDataCache; serverEventSource: string; } @@ -28,7 +28,7 @@ export class UmbManagementApiDetailDataRequestManager< CreateRequestModelType, UpdateRequestModelType, > extends UmbControllerBase { - #cache: UmbManagementApiRuntimeCache; + #cache: UmbManagementApiDetailDataCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts index 1faee88e7d37..6b00516988f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts @@ -1 +1,2 @@ export * from './detail-request.manager.js'; +export * from './runtime-cache.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts index 66421d50e3d3..d219ac19ea5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/runtime-cache/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts @@ -2,10 +2,9 @@ interface UmbCacheEntryModel { id: string; data: DataModelType; - insertDate: number; } -export class UmbManagementApiRuntimeCache { +export class UmbManagementApiDetailDataRuntimeCache { #entries: Map> = new Map(); has(id: string): boolean { @@ -16,7 +15,6 @@ export class UmbManagementApiRuntimeCache { const cacheEntry: UmbCacheEntryModel = { id: id, data, - insertDate: Date.now(), }; this.#entries.set(id, cacheEntry); From 990530299c78b2d9c3686a4405b9a06137b07fde Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 21:53:07 +0200 Subject: [PATCH 22/32] move into folder --- .../repository/detail/data-type-detail.repository.ts | 2 +- .../data-type-detail.server.data-source.ts | 4 ++-- .../data-type-detail.server.runtime-cache.ts | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/{ => server-data-source}/data-type-detail.server.data-source.ts (98%) rename src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/{ => server-data-source}/data-type-detail.server.runtime-cache.ts (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.repository.ts index 0cac018f431d..54011c48c8da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.repository.ts @@ -1,5 +1,5 @@ import type { UmbDataTypeDetailModel } from '../../types.js'; -import { UmbDataTypeServerDataSource } from './data-type-detail.server.data-source.js'; +import { UmbDataTypeServerDataSource } from './server-data-source/data-type-detail.server.data-source.js'; import type { UmbDataTypeDetailStore } from './data-type-detail.store.js'; import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT } from './data-type-detail.store.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts rename to src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts index e27e3f6ebb44..f3ddcd011096 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts @@ -1,5 +1,5 @@ -import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../../types.js'; -import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../entity.js'; +import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../../../types.js'; +import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../../entity.js'; import { cache } from './data-type-detail.server.runtime-cache.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.runtime-cache.ts rename to src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts From eef5acb13b3ba898123803921711af02e9ef87cc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 22:06:58 +0200 Subject: [PATCH 23/32] collect server code in folder --- .../repository/detail/document-type-detail.repository.ts | 2 +- .../documents/document-types/repository/detail/index.ts | 2 +- .../document-type-detail.server.data-source.ts | 5 ++--- .../document-type-detail.server.runtime-cache.ts | 0 .../repository/detail/document-detail.server.data-source.ts | 1 + .../management-api/detail/detail-request.manager.ts | 6 +++--- .../src/packages/management-api/index.ts | 1 - 7 files changed, 8 insertions(+), 9 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/{ => server-data-source}/document-type-detail.server.data-source.ts (98%) rename src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/{ => server-data-source}/document-type-detail.server.runtime-cache.ts (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.repository.ts index faf5883411ae..f79ee9a5b2cc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.repository.ts @@ -1,6 +1,6 @@ import type { UmbDocumentTypeDetailModel } from '../../types.js'; -import { UmbDocumentTypeDetailServerDataSource } from './document-type-detail.server.data-source.js'; import { UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT } from './document-type-detail.store.context-token.js'; +import { UmbDocumentTypeDetailServerDataSource } from './server-data-source/document-type-detail.server.data-source.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository'; export class UmbDocumentTypeDetailRepository extends UmbDetailRepositoryBase { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/index.ts index 68d24e6f3e40..356cd69c0113 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/index.ts @@ -1,2 +1,2 @@ export { UmbDocumentTypeDetailRepository } from './document-type-detail.repository.js'; -export * from './document-type-detail.server.data-source.js'; +export * from './server-data-source/document-type-detail.server.data-source.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts index f57864c7d32c..f555cc0766cb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts @@ -1,5 +1,5 @@ -import type { UmbDocumentTypeDetailModel } from '../../types.js'; -import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; +import type { UmbDocumentTypeDetailModel } from '../../../types.js'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../../entity.js'; import { cache } from './document-type-detail.server.runtime-cache.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; @@ -9,7 +9,6 @@ import type { UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; -import { tryExecute } from '@umbraco-cms/backoffice/resources'; import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.runtime-cache.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index 9adeb01d11d2..b051f6ed3fce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -38,6 +38,7 @@ export class UmbDocumentServerDataSource throw new Error('Document type unique is missing'); } + // TODO: investigate if we can use the repository here instead const { data } = await new UmbDocumentTypeDetailServerDataSource(this).read(documentTypeUnique); documentTypeIcon = data?.icon ?? null; documentTypeCollection = data?.collection ?? null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts index ad9eb957164d..2a02774cb7ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts @@ -1,5 +1,5 @@ import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js'; -import type { UmbManagementApiDetailDataCache } from '../runtime-cache/index.js'; +import type { UmbManagementApiDetailDataRuntimeCache } from './runtime-cache.js'; import { tryExecute, type UmbApiError, @@ -19,7 +19,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< read: (id: string) => Promise>; update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; - cache: UmbManagementApiDetailDataCache; + cache: UmbManagementApiDetailDataRuntimeCache; serverEventSource: string; } @@ -28,7 +28,7 @@ export class UmbManagementApiDetailDataRequestManager< CreateRequestModelType, UpdateRequestModelType, > extends UmbControllerBase { - #cache: UmbManagementApiDetailDataCache; + #cache: UmbManagementApiDetailDataRuntimeCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts index 5d62af12852e..5b2e199c8474 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts @@ -1,3 +1,2 @@ export * from './detail/index.js'; -export * from './runtime-cache/index.js'; export * from './server-event/constants.js'; From 0045a7444bdf29834b1fcba50a90f82402228cc7 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 22:37:41 +0200 Subject: [PATCH 24/32] add implementation for data type request manager --- .../data-type-detail.server.data-source.ts | 22 ++------------- ...data-type-detail.server.request-manager.ts | 27 +++++++++++++++++++ .../data-type-detail.server.runtime-cache.ts | 4 +-- ...ager.ts => detail-data.request-manager.ts} | 0 .../packages/management-api/detail/index.ts | 2 +- 5 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts rename src/Umbraco.Web.UI.Client/src/packages/management-api/detail/{detail-request.manager.ts => detail-data.request-manager.ts} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts index f3ddcd011096..a79c9437c5bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts @@ -1,6 +1,6 @@ import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../../../types.js'; import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../../entity.js'; -import { cache } from './data-type-detail.server.runtime-cache.js'; +import { UmbManagementApiDataTypeDetailDataRequestManager } from './data-type-detail.server.request-manager.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { @@ -8,9 +8,7 @@ import type { DataTypeResponseModel, UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; -import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; /** * A data source for the Data Type that fetches data from the server @@ -21,23 +19,7 @@ export class UmbDataTypeServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #detailRequestManager = new UmbManagementApiDetailDataRequestManager< - DataTypeResponseModel, - UpdateDataTypeRequestModel, - CreateDataTypeRequestModel - >(this, { - // eslint-disable-next-line local-rules/no-direct-api-import - create: (body: CreateDataTypeRequestModel) => DataTypeService.postDataType({ body }), - // eslint-disable-next-line local-rules/no-direct-api-import - read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), - update: (id: string, body: UpdateDataTypeRequestModel) => - // eslint-disable-next-line local-rules/no-direct-api-import - DataTypeService.putDataTypeById({ path: { id }, body }), - // eslint-disable-next-line local-rules/no-direct-api-import - delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), - cache: cache, - serverEventSource: 'Umbraco:CMS:DataType', - }); + #detailRequestManager = new UmbManagementApiDataTypeDetailDataRequestManager(this); /** * Creates a new Data Type scaffold diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts new file mode 100644 index 000000000000..8270833d9491 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts @@ -0,0 +1,27 @@ +/* eslint-disable local-rules/no-direct-api-import */ +import { dataTypeDetailCache } from './data-type-detail.server.runtime-cache.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + DataTypeService, + type CreateDataTypeRequestModel, + type DataTypeResponseModel, + type UpdateDataTypeRequestModel, +} from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; + +export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagementApiDetailDataRequestManager< + DataTypeResponseModel, + UpdateDataTypeRequestModel, + CreateDataTypeRequestModel +> { + constructor(host: UmbControllerHost) { + super(host, { + create: (body: CreateDataTypeRequestModel) => DataTypeService.postDataType({ body }), + read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), + update: (id: string, body: UpdateDataTypeRequestModel) => DataTypeService.putDataTypeById({ path: { id }, body }), + delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), + cache: dataTypeDetailCache, + serverEventSource: 'Umbraco:CMS:DataType', + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts index f6e273e5613b..d4f8c4c501b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts @@ -2,6 +2,6 @@ import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/external/bac import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; // We use a singleton so we can share the cache across the application -const cache = new UmbManagementApiDetailDataRuntimeCache(); +const dataTypeDetailCache = new UmbManagementApiDetailDataRuntimeCache(); -export { cache }; +export { dataTypeDetailCache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-request.manager.ts rename to src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts index 6b00516988f3..01bb07440647 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts @@ -1,2 +1,2 @@ -export * from './detail-request.manager.js'; +export * from './detail-data.request-manager.js'; export * from './runtime-cache.js'; From 9058d2687bba2335931be1fade5a8d7e452473b8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 12 Aug 2025 22:46:34 +0200 Subject: [PATCH 25/32] implement for document type --- ...document-type-detail.server.data-source.ts | 22 ++------------- ...ment-type-detail.server.request-manager.ts | 28 +++++++++++++++++++ ...cument-type-detail.server.runtime-cache.ts | 4 +-- 3 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts index f555cc0766cb..9ac3abe0a1e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.data-source.ts @@ -1,6 +1,6 @@ import type { UmbDocumentTypeDetailModel } from '../../../types.js'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../../entity.js'; -import { cache } from './document-type-detail.server.runtime-cache.js'; +import { UmbManagementApiDocumentTypeDetailDataRequestManager } from './document-type-detail.server.request-manager.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { @@ -8,10 +8,8 @@ import type { DocumentTypeResponseModel, UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; -import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; /** * A data source for the Document Type that fetches data from the server @@ -22,23 +20,7 @@ export class UmbDocumentTypeDetailServerDataSource extends UmbControllerBase implements UmbDetailDataSource { - #detailRequestManager = new UmbManagementApiDetailDataRequestManager< - DocumentTypeResponseModel, - CreateDocumentTypeRequestModel, - UpdateDocumentTypeRequestModel - >(this, { - // eslint-disable-next-line local-rules/no-direct-api-import - create: (data: CreateDocumentTypeRequestModel) => DocumentTypeService.postDocumentType({ body: data }), - // eslint-disable-next-line local-rules/no-direct-api-import - read: (id: string) => DocumentTypeService.getDocumentTypeById({ path: { id } }), - update: (id: string, body: UpdateDocumentTypeRequestModel) => - // eslint-disable-next-line local-rules/no-direct-api-import - DocumentTypeService.putDocumentTypeById({ path: { id }, body }), - // eslint-disable-next-line local-rules/no-direct-api-import - delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), - cache: cache, - serverEventSource: 'Umbraco:CMS:DocumentType', - }); + #detailRequestManager = new UmbManagementApiDocumentTypeDetailDataRequestManager(this); /** * Creates a new Document Type scaffold diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts new file mode 100644 index 000000000000..556c2cbb9c62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts @@ -0,0 +1,28 @@ +/* eslint-disable local-rules/no-direct-api-import */ +import { documentTypeDetailCache } from './document-type-detail.server.runtime-cache.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + DocumentTypeService, + type CreateDocumentTypeRequestModel, + type DocumentTypeResponseModel, + type UpdateDocumentTypeRequestModel, +} from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; + +export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbManagementApiDetailDataRequestManager< + DocumentTypeResponseModel, + UpdateDocumentTypeRequestModel, + CreateDocumentTypeRequestModel +> { + constructor(host: UmbControllerHost) { + super(host, { + create: (body: CreateDocumentTypeRequestModel) => DocumentTypeService.postDocumentType({ body }), + read: (id: string) => DocumentTypeService.getDocumentTypeById({ path: { id } }), + update: (id: string, body: UpdateDocumentTypeRequestModel) => + DocumentTypeService.putDocumentTypeById({ path: { id }, body }), + delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), + cache: documentTypeDetailCache, + serverEventSource: 'Umbraco:CMS:DocumentType', + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts index 2b5b8f8f02f0..392c53095a59 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts @@ -1,6 +1,6 @@ import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; -const cache = new UmbManagementApiDetailDataRuntimeCache(); +const documentTypeDetailCache = new UmbManagementApiDetailDataRuntimeCache(); -export { cache }; +export { documentTypeDetailCache }; From c9f701f4b3ef970579e7175dbb83f141eb0598a4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 13 Aug 2025 13:25:09 +0200 Subject: [PATCH 26/32] only cache when we have connection to the server events --- ...data-type-detail.server.request-manager.ts | 2 +- ...ment-type-detail.server.request-manager.ts | 2 +- .../detail/detail-data.request-manager.ts | 45 +++++++++++++------ .../management-api/detail/runtime-cache.ts | 38 +++++++++++++++- .../global-context/server-event.context.ts | 26 ++++------- .../server-event/global-context/types.ts | 5 +++ 6 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts index 8270833d9491..4a939438f9a1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts @@ -20,7 +20,7 @@ export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagem read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), update: (id: string, body: UpdateDataTypeRequestModel) => DataTypeService.putDataTypeById({ path: { id }, body }), delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), - cache: dataTypeDetailCache, + runtimeCache: dataTypeDetailCache, serverEventSource: 'Umbraco:CMS:DataType', }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts index 556c2cbb9c62..2544b51d32f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts @@ -21,7 +21,7 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan update: (id: string, body: UpdateDocumentTypeRequestModel) => DocumentTypeService.putDocumentTypeById({ path: { id }, body }), delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), - cache: documentTypeDetailCache, + runtimeCache: documentTypeDetailCache, serverEventSource: 'Umbraco:CMS:DocumentType', }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index 2a02774cb7ff..af14fb7767c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -19,7 +19,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< read: (id: string) => Promise>; update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; - cache: UmbManagementApiDetailDataRuntimeCache; + runtimeCache: UmbManagementApiDetailDataRuntimeCache; serverEventSource: string; } @@ -28,7 +28,7 @@ export class UmbManagementApiDetailDataRequestManager< CreateRequestModelType, UpdateRequestModelType, > extends UmbControllerBase { - #cache: UmbManagementApiDetailDataRuntimeCache; + #runtimeCache: UmbManagementApiDetailDataRuntimeCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; @@ -36,6 +36,7 @@ export class UmbManagementApiDetailDataRequestManager< #read; #update; #delete; + #isConnectedToServerEvents = false; constructor( host: UmbControllerHost, @@ -52,7 +53,7 @@ export class UmbManagementApiDetailDataRequestManager< this.#update = args.update; this.#delete = args.delete; - this.#cache = args.cache; + this.#runtimeCache = args.runtimeCache; this.#serverEventSource = args.serverEventSource; this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { @@ -75,13 +76,14 @@ export class UmbManagementApiDetailDataRequestManager< let data: DetailResponseModelType | undefined; let error: UmbApiError | UmbCancelError | undefined; - if (this.#cache.has(id)) { - data = this.#cache.get(id); + // Only read from the cache when we are connected to the server events + if (this.#isConnectedToServerEvents && this.#runtimeCache.has(id)) { + data = this.#runtimeCache.get(id); } else { const { data: serverData, error: serverError } = await tryExecute(this, this.#read(id)); - if (serverData) { - this.#cache.set(id, serverData); + if (this.#isConnectedToServerEvents && serverData) { + this.#runtimeCache.set(id, serverData); } data = serverData; @@ -104,24 +106,39 @@ export class UmbManagementApiDetailDataRequestManager< async delete(id: string): Promise { const { error } = await this.#delete(id); - if (!error) { - this.#cache.delete(id); + // Only update the cache when we are connected to the server events + if (this.#isConnectedToServerEvents && !error) { + this.#runtimeCache.delete(id); } return { error }; } #observeServerEvents() { + this.observe( + this.#serverEventContext?.isConnected, + (isConnected) => { + /* We purposefully ignore the initial value of isConnected. + We only care about whether the connection is established or not (true/false) */ + if (isConnected === undefined) return; + this.#isConnectedToServerEvents = isConnected; + + // Clear the cache if we lose connection to the server events + if (this.#isConnectedToServerEvents === false) { + this.#runtimeCache.clear(); + } + }, + 'umbObserveServerEventsConnection', + ); + + // Invalidate cache entries when entities are updated or deleted this.observe( this.#serverEventContext?.byEventSourceAndTypes(this.#serverEventSource, ['Updated', 'Deleted']), (event) => { if (!event) return; - this.#invalidateCacheEntry(event.key); + this.#runtimeCache.delete(event.key); }, + 'umbObserveServerEvents', ); } - - #invalidateCacheEntry(id: string) { - this.#cache.delete(id); - } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts index d219ac19ea5f..1642db2115ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts @@ -4,13 +4,30 @@ interface UmbCacheEntryModel { data: DataModelType; } +/** + * A runtime cache for storing entity detail data from the Management Api + * @class UmbManagementApiDetailDataRuntimeCache + * @template DataModelType + */ export class UmbManagementApiDetailDataRuntimeCache { #entries: Map> = new Map(); + /** + * Checks if an entry exists in the cache + * @param {string} id - The ID of the entry to check + * @returns {boolean} - True if the entry exists, false otherwise + * @memberof UmbManagementApiDetailDataRuntimeCache + */ has(id: string): boolean { return this.#entries.has(id); } + /** + * Adds an entry to the cache + * @param {string} id - The ID of the entry to add + * @param {DataModelType} data - The data to cache + * @memberof UmbManagementApiDetailDataRuntimeCache + */ set(id: string, data: DataModelType): void { const cacheEntry: UmbCacheEntryModel = { id: id, @@ -20,12 +37,31 @@ export class UmbManagementApiDetailDataRuntimeCache { this.#entries.set(id, cacheEntry); } - get(id: string) { + /** + * Retrieves an entry from the cache + * @param {string} id - The ID of the entry to retrieve + * @returns {DataModelType | undefined} - The cached entry or undefined if not found + * @memberof UmbManagementApiDetailDataRuntimeCache + */ + get(id: string): DataModelType | undefined { const entry = this.#entries.get(id); return entry ? entry.data : undefined; } + /** + * Deletes an entry from the cache + * @param {string} id - The ID of the entry to delete + * @memberof UmbManagementApiDetailDataRuntimeCache + */ delete(id: string): void { this.#entries.delete(id); } + + /** + * Clears all entries from the cache + * @memberof UmbManagementApiDetailDataRuntimeCache + */ + clear(): void { + this.#entries.clear(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts index 3b6f09d1b0dd..34f025ebff7f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/server-event.context.ts @@ -1,4 +1,5 @@ import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from './server-event.context-token.js'; +import type { UmbManagementApiServerEventModel } from './types.js'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; @@ -6,12 +7,7 @@ import { HubConnectionBuilder, type HubConnection } from '@umbraco-cms/backoffic import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { filter, Subject } from '@umbraco-cms/backoffice/external/rxjs'; - -interface UmbManagementApiServerEventModel { - eventSource: string; - eventType: string; - key: string; -} +import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; export class UmbManagementApiServerEventContext extends UmbContextBase { #connection?: HubConnection; @@ -21,6 +17,9 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { #events = new Subject(); public readonly events = this.#events.asObservable(); + #isConnected = new UmbBooleanState(undefined); + public readonly isConnected = this.#isConnected.asObservable(); + /** * Filters events by the given event source * @param {string} eventSource @@ -69,6 +68,7 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { throw new Error('No auth token found'); } } else { + this.#isConnected.setValue(false); this.#connection?.stop(); this.#connection = undefined; } @@ -97,18 +97,10 @@ export class UmbManagementApiServerEventContext extends UmbContextBase { this.#connection .start() - .then(function () { - //console.log('Connected!'); - }) - .catch(function (err) { - console.log('SignalR', err); - }); + .then(() => this.#isConnected.setValue(true)) + .catch(() => this.#isConnected.setValue(false)); - this.#connection.onclose((err?: Error) => { - if (err) { - console.error('Connection closed with error: ', err); - } - }); + this.#connection.onclose(() => this.#isConnected.setValue(false)); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/types.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/types.ts new file mode 100644 index 000000000000..5a0341345d99 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/server-event/global-context/types.ts @@ -0,0 +1,5 @@ +export interface UmbManagementApiServerEventModel { + eventSource: string; + eventType: string; + key: string; +} From b34df6ae36518fb8a8aaadd7633f095e74d0f62a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 14 Aug 2025 15:06:42 +0200 Subject: [PATCH 27/32] poc inflight request cache --- ...data-type-detail.server.request-manager.ts | 3 ++ ...ment-type-detail.server.request-manager.ts | 3 ++ .../detail/detail-data.request-manager.ts | 33 +++++++++++++++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts index 4a939438f9a1..f04955433cc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts @@ -14,6 +14,8 @@ export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagem UpdateDataTypeRequestModel, CreateDataTypeRequestModel > { + static #inflightRequestCache: Map> = new Map(); + constructor(host: UmbControllerHost) { super(host, { create: (body: CreateDataTypeRequestModel) => DataTypeService.postDataType({ body }), @@ -22,6 +24,7 @@ export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagem delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), runtimeCache: dataTypeDetailCache, serverEventSource: 'Umbraco:CMS:DataType', + inflightRequestCache: UmbManagementApiDataTypeDetailDataRequestManager.#inflightRequestCache, }); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts index 2544b51d32f3..f5c58d5a53c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts @@ -14,6 +14,8 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan UpdateDocumentTypeRequestModel, CreateDocumentTypeRequestModel > { + static #inflightRequestCache: Map> = new Map(); + constructor(host: UmbControllerHost) { super(host, { create: (body: CreateDocumentTypeRequestModel) => DocumentTypeService.postDocumentType({ body }), @@ -23,6 +25,7 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), runtimeCache: documentTypeDetailCache, serverEventSource: 'Umbraco:CMS:DocumentType', + inflightRequestCache: UmbManagementApiDocumentTypeDetailDataRequestManager.#inflightRequestCache, }); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index af14fb7767c1..f4fdc8efb41d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -21,6 +21,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< delete: (id: string) => Promise>; runtimeCache: UmbManagementApiDetailDataRuntimeCache; serverEventSource: string; + inflightRequestCache: Map>>; } export class UmbManagementApiDetailDataRequestManager< @@ -31,6 +32,7 @@ export class UmbManagementApiDetailDataRequestManager< #runtimeCache: UmbManagementApiDetailDataRuntimeCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; + #inflightRequestCache: Map>>; #create; #read; @@ -55,6 +57,7 @@ export class UmbManagementApiDetailDataRequestManager< this.#runtimeCache = args.runtimeCache; this.#serverEventSource = args.serverEventSource; + this.#inflightRequestCache = args.inflightRequestCache; this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { this.#serverEventContext = context; @@ -76,18 +79,36 @@ export class UmbManagementApiDetailDataRequestManager< let data: DetailResponseModelType | undefined; let error: UmbApiError | UmbCancelError | undefined; + const inflightCacheKey = `read:${id}`; + // Only read from the cache when we are connected to the server events if (this.#isConnectedToServerEvents && this.#runtimeCache.has(id)) { data = this.#runtimeCache.get(id); } else { - const { data: serverData, error: serverError } = await tryExecute(this, this.#read(id)); - - if (this.#isConnectedToServerEvents && serverData) { - this.#runtimeCache.set(id, serverData); + const hasInflightRequest = this.#inflightRequestCache.has(inflightCacheKey); + console.log(`hasInflightRequest for:${hasInflightRequest} ${inflightCacheKey}`); + const myTryExecute = hasInflightRequest + ? this.#inflightRequestCache.get(inflightCacheKey) + : tryExecute(this, this.#read(id)); + + if (!myTryExecute) { + throw new Error('No Request. Aborting read.'); } - data = serverData; - error = serverError; + this.#inflightRequestCache.set(inflightCacheKey, myTryExecute); + + try { + const { data: serverData, error: serverError } = await myTryExecute; + + if (this.#isConnectedToServerEvents && serverData) { + this.#runtimeCache.set(id, serverData); + } + + data = serverData; + error = serverError; + } finally { + this.#inflightRequestCache.delete(inflightCacheKey); + } } return { data, error }; From ed1a2597a33d2ed41582b6b6cb8df2abd683d6b2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 14 Aug 2025 18:56:30 +0200 Subject: [PATCH 28/32] clean up --- .../detail/detail-data.request-manager.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index f4fdc8efb41d..7d587ec8f478 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -86,19 +86,19 @@ export class UmbManagementApiDetailDataRequestManager< data = this.#runtimeCache.get(id); } else { const hasInflightRequest = this.#inflightRequestCache.has(inflightCacheKey); - console.log(`hasInflightRequest for:${hasInflightRequest} ${inflightCacheKey}`); - const myTryExecute = hasInflightRequest + + const request = hasInflightRequest ? this.#inflightRequestCache.get(inflightCacheKey) : tryExecute(this, this.#read(id)); - if (!myTryExecute) { + if (!request) { throw new Error('No Request. Aborting read.'); } - this.#inflightRequestCache.set(inflightCacheKey, myTryExecute); + this.#inflightRequestCache.set(inflightCacheKey, request); try { - const { data: serverData, error: serverError } = await myTryExecute; + const { data: serverData, error: serverError } = await request; if (this.#isConnectedToServerEvents && serverData) { this.#runtimeCache.set(id, serverData); From da203e6996d9c0e8a0bdbc82af8baf7bd9b96ed8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 14 Aug 2025 19:58:56 +0200 Subject: [PATCH 29/32] update --- ...he.ts => data-type-detail.server.cache.ts} | 4 ++-- ...data-type-detail.server.request-manager.ts | 2 +- .../document-type-detail.server.cache.ts | 6 ++++++ ...ment-type-detail.server.request-manager.ts | 2 +- ...cument-type-detail.server.runtime-cache.ts | 6 ------ .../detail/{runtime-cache.ts => cache.ts} | 14 ++++++------- .../detail/detail-data.request-manager.ts | 20 +++++++++---------- .../packages/management-api/detail/index.ts | 2 +- 8 files changed, 28 insertions(+), 28 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/{data-type-detail.server.runtime-cache.ts => data-type-detail.server.cache.ts} (50%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.cache.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts rename src/Umbraco.Web.UI.Client/src/packages/management-api/detail/{runtime-cache.ts => cache.ts} (78%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.cache.ts similarity index 50% rename from src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts rename to src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.cache.ts index d4f8c4c501b0..65ec94f65168 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.cache.ts @@ -1,7 +1,7 @@ import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; +import { UmbManagementApiDetailDataCache } from '@umbraco-cms/backoffice/management-api'; // We use a singleton so we can share the cache across the application -const dataTypeDetailCache = new UmbManagementApiDetailDataRuntimeCache(); +const dataTypeDetailCache = new UmbManagementApiDetailDataCache(); export { dataTypeDetailCache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts index 4a939438f9a1..621d98bff49f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts @@ -20,7 +20,7 @@ export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagem read: (id: string) => DataTypeService.getDataTypeById({ path: { id } }), update: (id: string, body: UpdateDataTypeRequestModel) => DataTypeService.putDataTypeById({ path: { id }, body }), delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }), - runtimeCache: dataTypeDetailCache, + dataCache: dataTypeDetailCache, serverEventSource: 'Umbraco:CMS:DataType', }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.cache.ts new file mode 100644 index 000000000000..208efdacbd6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.cache.ts @@ -0,0 +1,6 @@ +import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbManagementApiDetailDataCache } from '@umbraco-cms/backoffice/management-api'; + +const documentTypeDetailCache = new UmbManagementApiDetailDataCache(); + +export { documentTypeDetailCache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts index 2544b51d32f3..914693545bbd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts @@ -21,7 +21,7 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan update: (id: string, body: UpdateDocumentTypeRequestModel) => DocumentTypeService.putDocumentTypeById({ path: { id }, body }), delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }), - runtimeCache: documentTypeDetailCache, + dataCache: documentTypeDetailCache, serverEventSource: 'Umbraco:CMS:DocumentType', }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts deleted file mode 100644 index 392c53095a59..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.runtime-cache.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiDetailDataRuntimeCache } from '@umbraco-cms/backoffice/management-api'; - -const documentTypeDetailCache = new UmbManagementApiDetailDataRuntimeCache(); - -export { documentTypeDetailCache }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/cache.ts similarity index 78% rename from src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts rename to src/Umbraco.Web.UI.Client/src/packages/management-api/detail/cache.ts index 1642db2115ad..a8b51f3c9ec8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/runtime-cache.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/cache.ts @@ -6,17 +6,17 @@ interface UmbCacheEntryModel { /** * A runtime cache for storing entity detail data from the Management Api - * @class UmbManagementApiDetailDataRuntimeCache + * @class UmbManagementApiDetailDataCache * @template DataModelType */ -export class UmbManagementApiDetailDataRuntimeCache { +export class UmbManagementApiDetailDataCache { #entries: Map> = new Map(); /** * Checks if an entry exists in the cache * @param {string} id - The ID of the entry to check * @returns {boolean} - True if the entry exists, false otherwise - * @memberof UmbManagementApiDetailDataRuntimeCache + * @memberof UmbManagementApiDetailDataCache */ has(id: string): boolean { return this.#entries.has(id); @@ -26,7 +26,7 @@ export class UmbManagementApiDetailDataRuntimeCache { * Adds an entry to the cache * @param {string} id - The ID of the entry to add * @param {DataModelType} data - The data to cache - * @memberof UmbManagementApiDetailDataRuntimeCache + * @memberof UmbManagementApiDetailDataCache */ set(id: string, data: DataModelType): void { const cacheEntry: UmbCacheEntryModel = { @@ -41,7 +41,7 @@ export class UmbManagementApiDetailDataRuntimeCache { * Retrieves an entry from the cache * @param {string} id - The ID of the entry to retrieve * @returns {DataModelType | undefined} - The cached entry or undefined if not found - * @memberof UmbManagementApiDetailDataRuntimeCache + * @memberof UmbManagementApiDetailDataCache */ get(id: string): DataModelType | undefined { const entry = this.#entries.get(id); @@ -51,7 +51,7 @@ export class UmbManagementApiDetailDataRuntimeCache { /** * Deletes an entry from the cache * @param {string} id - The ID of the entry to delete - * @memberof UmbManagementApiDetailDataRuntimeCache + * @memberof UmbManagementApiDetailDataCache */ delete(id: string): void { this.#entries.delete(id); @@ -59,7 +59,7 @@ export class UmbManagementApiDetailDataRuntimeCache { /** * Clears all entries from the cache - * @memberof UmbManagementApiDetailDataRuntimeCache + * @memberof UmbManagementApiDetailDataCache */ clear(): void { this.#entries.clear(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index af14fb7767c1..db2228dd8612 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -1,5 +1,5 @@ import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js'; -import type { UmbManagementApiDetailDataRuntimeCache } from './runtime-cache.js'; +import type { UmbManagementApiDetailDataCache } from './cache.js'; import { tryExecute, type UmbApiError, @@ -19,7 +19,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< read: (id: string) => Promise>; update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; - runtimeCache: UmbManagementApiDetailDataRuntimeCache; + dataCache: UmbManagementApiDetailDataCache; serverEventSource: string; } @@ -28,7 +28,7 @@ export class UmbManagementApiDetailDataRequestManager< CreateRequestModelType, UpdateRequestModelType, > extends UmbControllerBase { - #runtimeCache: UmbManagementApiDetailDataRuntimeCache; + #dataCache: UmbManagementApiDetailDataCache; #serverEventSource: string; #serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE; @@ -53,7 +53,7 @@ export class UmbManagementApiDetailDataRequestManager< this.#update = args.update; this.#delete = args.delete; - this.#runtimeCache = args.runtimeCache; + this.#dataCache = args.dataCache; this.#serverEventSource = args.serverEventSource; this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => { @@ -77,13 +77,13 @@ export class UmbManagementApiDetailDataRequestManager< let error: UmbApiError | UmbCancelError | undefined; // Only read from the cache when we are connected to the server events - if (this.#isConnectedToServerEvents && this.#runtimeCache.has(id)) { - data = this.#runtimeCache.get(id); + if (this.#isConnectedToServerEvents && this.#dataCache.has(id)) { + data = this.#dataCache.get(id); } else { const { data: serverData, error: serverError } = await tryExecute(this, this.#read(id)); if (this.#isConnectedToServerEvents && serverData) { - this.#runtimeCache.set(id, serverData); + this.#dataCache.set(id, serverData); } data = serverData; @@ -108,7 +108,7 @@ export class UmbManagementApiDetailDataRequestManager< // Only update the cache when we are connected to the server events if (this.#isConnectedToServerEvents && !error) { - this.#runtimeCache.delete(id); + this.#dataCache.delete(id); } return { error }; @@ -125,7 +125,7 @@ export class UmbManagementApiDetailDataRequestManager< // Clear the cache if we lose connection to the server events if (this.#isConnectedToServerEvents === false) { - this.#runtimeCache.clear(); + this.#dataCache.clear(); } }, 'umbObserveServerEventsConnection', @@ -136,7 +136,7 @@ export class UmbManagementApiDetailDataRequestManager< this.#serverEventContext?.byEventSourceAndTypes(this.#serverEventSource, ['Updated', 'Deleted']), (event) => { if (!event) return; - this.#runtimeCache.delete(event.key); + this.#dataCache.delete(event.key); }, 'umbObserveServerEvents', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts index 01bb07440647..890386ff4786 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/index.ts @@ -1,2 +1,2 @@ export * from './detail-data.request-manager.js'; -export * from './runtime-cache.js'; +export * from './cache.js'; From a5bc05bbda7aa261950a193761e00cba79d07d02 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 20 Aug 2025 22:40:24 +0200 Subject: [PATCH 30/32] add management api inflight request cache --- ...data-type-detail.server.request-manager.ts | 8 ++- .../detail/detail-data.request-manager.ts | 5 +- .../src/packages/management-api/index.ts | 1 + .../management-api/inflight-request/cache.ts | 60 +++++++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/management-api/inflight-request/cache.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts index d708438a3adc..03e70ef346d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.request-manager.ts @@ -7,15 +7,17 @@ import { type DataTypeResponseModel, type UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; -import type { UmbApiResponse } from '@umbraco-cms/backoffice/resources'; +import { + UmbManagementApiDetailDataRequestManager, + UmbManagementApiInflightRequestCache, +} from '@umbraco-cms/backoffice/management-api'; export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagementApiDetailDataRequestManager< DataTypeResponseModel, UpdateDataTypeRequestModel, CreateDataTypeRequestModel > { - static #inflightRequestCache = new Map>>(); + static #inflightRequestCache = new UmbManagementApiInflightRequestCache(); constructor(host: UmbControllerHost) { super(host, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index 6366fb00015c..4178162bc5f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -1,4 +1,5 @@ import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js'; +import type { UmbManagementApiInflightRequestCache } from '../inflight-request/cache.js'; import type { UmbManagementApiDetailDataCache } from './cache.js'; import { tryExecute, @@ -20,7 +21,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs< update: (id: string, data: UpdateRequestModelType) => Promise>; delete: (id: string) => Promise>; dataCache: UmbManagementApiDetailDataCache; - inflightRequestCache: Map>>; + inflightRequestCache: UmbManagementApiInflightRequestCache; } export class UmbManagementApiDetailDataRequestManager< @@ -29,7 +30,7 @@ export class UmbManagementApiDetailDataRequestManager< UpdateRequestModelType, > extends UmbControllerBase { #dataCache: UmbManagementApiDetailDataCache; - #inflightRequestCache: Map>>; + #inflightRequestCache: UmbManagementApiInflightRequestCache; #create; #read; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts index 772b286b1e9d..ae6beba0a6f1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/index.ts @@ -1,4 +1,5 @@ export * from './detail/index.js'; export * from './item/index.js'; export * from './server-event/constants.js'; +export * from './inflight-request/cache.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/inflight-request/cache.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/inflight-request/cache.ts new file mode 100644 index 000000000000..d1ba9a744193 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/inflight-request/cache.ts @@ -0,0 +1,60 @@ +import type { UmbApiResponse } from '@umbraco-cms/backoffice/resources'; + +// Keep internal +type RequestResolvedType = UmbApiResponse<{ data?: ResponseModelType }>; + +/** + * A cache for inflight requests to the Management Api. Use this class to cache requests and avoid duplicate calls. + * @class UmbManagementApiInflightRequestCache + * @template ResponseModelType + */ +export class UmbManagementApiInflightRequestCache { + #entries = new Map>>(); + + /** + * Checks if an entry exists in the cache + * @param {string} key - The ID of the entry to check + * @returns {boolean} - True if the entry exists, false otherwise + * @memberof UmbManagementApiInflightRequestCache + */ + has(key: string): boolean { + return this.#entries.has(key); + } + + /** + * Adds an entry to the cache + * @param {string} key - A unique key representing the promise + * @param {Promise>>} promise - The promise to cache + * @memberof UmbManagementApiInflightRequestCache + */ + set(key: string, promise: Promise>): void { + this.#entries.set(key, promise); + } + + /** + * Retrieves an entry from the cache + * @param {string} key - The ID of the entry to retrieve + * @returns {Promise> | undefined} - The cached promise or undefined if not found + * @memberof UmbManagementApiInflightRequestCache + */ + get(key: string): Promise> | undefined { + return this.#entries.get(key); + } + + /** + * Deletes an entry from the cache + * @param {string} key - The ID of the entry to delete + * @memberof UmbManagementApiInflightRequestCache + */ + delete(key: string): void { + this.#entries.delete(key); + } + + /** + * Clears all entries from the cache + * @memberof UmbManagementApiInflightRequestCache + */ + clear(): void { + this.#entries.clear(); + } +} From 70e2c837f743e8ebdbcbe933df7910605c5ab7e8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 21 Aug 2025 13:31:46 +0200 Subject: [PATCH 31/32] Update document-type-detail.server.request-manager.ts --- .../document-type-detail.server.request-manager.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts index d6e99126d828..ca22ff6cf10f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/server-data-source/document-type-detail.server.request-manager.ts @@ -7,14 +7,17 @@ import { type DocumentTypeResponseModel, type UpdateDocumentTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbManagementApiDetailDataRequestManager } from '@umbraco-cms/backoffice/management-api'; +import { + UmbManagementApiDetailDataRequestManager, + UmbManagementApiInflightRequestCache, +} from '@umbraco-cms/backoffice/management-api'; export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbManagementApiDetailDataRequestManager< DocumentTypeResponseModel, UpdateDocumentTypeRequestModel, CreateDocumentTypeRequestModel > { - static #inflightRequestCache: Map> = new Map(); + static #inflightRequestCache = new UmbManagementApiInflightRequestCache(); constructor(host: UmbControllerHost) { super(host, { From ac305bb4924634415185696b9e7310927a019b7f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 21 Aug 2025 15:32:02 +0200 Subject: [PATCH 32/32] Update src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../management-api/detail/detail-data.request-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts index 4178162bc5f0..37d501d9914e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/detail/detail-data.request-manager.ts @@ -90,7 +90,7 @@ export class UmbManagementApiDetailDataRequestManager< : tryExecute(this, this.#read(id)); if (!request) { - throw new Error('No Request. Aborting read.'); + throw new Error(`Failed to create or retrieve 'read' request for ID: ${id} (cache key: ${inflightCacheKey}). Aborting read.`); } this.#inflightRequestCache.set(inflightCacheKey, request);