Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a12db87
extend controller base
madsrasmussen Aug 11, 2025
5d6415f
extend controller base
madsrasmussen Aug 11, 2025
05a4235
add package for management api
madsrasmussen Aug 11, 2025
6212027
add signalr as external package
madsrasmussen Aug 11, 2025
2b1be18
connect to server event hub
madsrasmussen Aug 11, 2025
2490bf8
do no act on undefined
madsrasmussen Aug 11, 2025
8c592e3
add event subject
madsrasmussen Aug 11, 2025
c139362
correct alias
madsrasmussen Aug 11, 2025
62799bd
export token
madsrasmussen Aug 11, 2025
5a3f8f9
add helper methods
madsrasmussen Aug 11, 2025
2deb7cc
cache server responses
madsrasmussen Aug 11, 2025
ad257cf
fix import
madsrasmussen Aug 11, 2025
03c1fc8
use helpers
madsrasmussen Aug 11, 2025
1f1baa6
add detail request manager
madsrasmussen Aug 12, 2025
a2c3283
implement for document type
madsrasmussen Aug 12, 2025
9aa73a7
implement for data type
madsrasmussen Aug 12, 2025
9d33c00
add method for update
madsrasmussen Aug 12, 2025
ba732e3
add support for create method
madsrasmussen Aug 12, 2025
9e1deb6
align code
madsrasmussen Aug 12, 2025
cefc5e0
Update detail-request.manager.ts
madsrasmussen Aug 12, 2025
b38cdf2
move explicit naming
madsrasmussen Aug 12, 2025
9905302
move into folder
madsrasmussen Aug 12, 2025
eef5acb
collect server code in folder
madsrasmussen Aug 12, 2025
0045a74
add implementation for data type request manager
madsrasmussen Aug 12, 2025
9058d26
implement for document type
madsrasmussen Aug 12, 2025
c9f701f
only cache when we have connection to the server events
madsrasmussen Aug 13, 2025
da203e6
update
madsrasmussen Aug 14, 2025
de86ac7
fix imports
madsrasmussen Aug 14, 2025
107e6d6
introduce item cache
madsrasmussen Aug 14, 2025
f4af0e3
call trough get items controller
madsrasmussen Aug 15, 2025
feba22b
remove log
madsrasmussen Aug 15, 2025
523e63b
add unit tests for item cache
madsrasmussen Aug 15, 2025
ebdb339
Create cache.test.ts
madsrasmussen Aug 15, 2025
1f181c3
use sync method to lookup data type item
madsrasmussen Aug 15, 2025
82be7df
Merge branch 'main' into v16/feature/entity-detail-runtime-cache
madsrasmussen Aug 15, 2025
2cdfef1
use correct alias
madsrasmussen Aug 15, 2025
c663462
Merge branch 'v16/feature/entity-detail-runtime-cache' into v16/featu…
madsrasmussen Aug 15, 2025
bfa9918
remove unused code
madsrasmussen Aug 15, 2025
2df8103
Merge branch 'main' into v16/feature/item-data-runtime-cache
iOvergaard Aug 18, 2025
aa5c1d6
split detail cache invalidation from request manager
madsrasmussen Aug 18, 2025
729d65b
introduce item cache invalidation manager
madsrasmussen Aug 18, 2025
7847a7f
Merge branch 'main' into v16/feature/item-data-runtime-cache
madsrasmussen Aug 18, 2025
5961dd6
remove unused
madsrasmussen Aug 18, 2025
c29c498
invalidate documents when document types changes
madsrasmussen Aug 18, 2025
e8d3f65
align naming
madsrasmussen Aug 18, 2025
cffd86e
add method to get unique
madsrasmussen Aug 18, 2025
1a6f81d
use server model instead of mapping
madsrasmussen Aug 18, 2025
e74a23a
call method
madsrasmussen Aug 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Umbraco.Web.UI.Client/src/packages/data-type/entry-point.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UmbManagementApiDataTypeDetailDataCacheInvalidationManager } from './repository/detail/server-data-source/data-type-detail.server.cache-invalidation.manager.js';
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api';

let detailDataCacheInvalidationManager: UmbManagementApiDataTypeDetailDataCacheInvalidationManager | undefined;

export const onInit: UmbEntryPointOnInit = (host) => {
detailDataCacheInvalidationManager = new UmbManagementApiDataTypeDetailDataCacheInvalidationManager(host);
};

export const onUnload: UmbEntryPointOnUnload = () => {
detailDataCacheInvalidationManager?.destroy();
};
6 changes: 6 additions & 0 deletions src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
...searchProviderManifests,
...treeManifests,
...workspaceManifests,
{
name: 'Data Type Backoffice Entry Point',
alias: 'Umb.EntryPoint.DataType',
type: 'backofficeEntryPoint',
js: () => import('./entry-point.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { dataTypeDetailCache } from './data-type-detail.server.cache.js';
import { UmbManagementApiDetailDataCacheInvalidationManager } from '@umbraco-cms/backoffice/management-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api';

export class UmbManagementApiDataTypeDetailDataCacheInvalidationManager extends UmbManagementApiDetailDataCacheInvalidationManager<DataTypeResponseModel> {
constructor(host: UmbControllerHost) {
super(host, {
dataCache: dataTypeDetailCache,
eventSources: ['Umbraco:CMS:DataType'],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export class UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagem
update: (id: string, body: UpdateDataTypeRequestModel) => DataTypeService.putDataTypeById({ path: { id }, body }),
delete: (id: string) => DataTypeService.deleteDataTypeById({ path: { id } }),
dataCache: dataTypeDetailCache,
serverEventSource: 'Umbraco:CMS:DataType',
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UmbManagementApiDocumentTypeDetailDataCacheInvalidationManager } from './repository/detail/server-data-source/document-type-detail.server.cache-invalidation.manager.js';
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api';

let detailDataCacheInvalidationManager: UmbManagementApiDocumentTypeDetailDataCacheInvalidationManager | undefined;

export const onInit: UmbEntryPointOnInit = (host) => {
detailDataCacheInvalidationManager = new UmbManagementApiDocumentTypeDetailDataCacheInvalidationManager(host);
};

export const onUnload: UmbEntryPointOnUnload = () => {
detailDataCacheInvalidationManager?.destroy();
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
...searchManifests,
...treeManifests,
...workspaceManifests,
{
name: 'Document Type Backoffice Entry Point',
alias: 'Umb.EntryPoint.DocumentType',
type: 'backofficeEntryPoint',
js: () => import('./entry-point.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { documentTypeDetailCache } from './document-type-detail.server.cache.js';
import { UmbManagementApiDetailDataCacheInvalidationManager } from '@umbraco-cms/backoffice/management-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api';

export class UmbManagementApiDocumentTypeDetailDataCacheInvalidationManager extends UmbManagementApiDetailDataCacheInvalidationManager<DocumentTypeResponseModel> {
constructor(host: UmbControllerHost) {
super(host, {
dataCache: documentTypeDetailCache,
eventSources: ['Umbraco:CMS:DocumentType'],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan
DocumentTypeService.putDocumentTypeById({ path: { id }, body }),
delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }),
dataCache: documentTypeDetailCache,
serverEventSource: 'Umbraco:CMS:DocumentType',
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UmbManagementApiDocumentItemDataCacheInvalidationManager } from './item/repository/document-item.server.cache-invalidation.manager.js';
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api';

let itemDataCacheInvalidationManager: UmbManagementApiDocumentItemDataCacheInvalidationManager | undefined;

export const onInit: UmbEntryPointOnInit = (host) => {
itemDataCacheInvalidationManager = new UmbManagementApiDocumentItemDataCacheInvalidationManager(host);
};

export const onUnload: UmbEntryPointOnUnload = () => {
itemDataCacheInvalidationManager?.destroy();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { documentItemCache } from './document-item.server.cache.js';
import {
UmbManagementApiItemDataCacheInvalidationManager,
type UmbManagementApiServerEventModel,
} from '@umbraco-cms/backoffice/management-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';

export class UmbManagementApiDocumentItemDataCacheInvalidationManager extends UmbManagementApiItemDataCacheInvalidationManager<DocumentItemResponseModel> {
constructor(host: UmbControllerHost) {
super(host, {
dataCache: documentItemCache,
/* The Document item model includes info about the Document Type.
We need to invalidate the cache for both Document and DocumentType events. */
eventSources: ['Umbraco:CMS:Document', 'Umbraco:CMS:DocumentType'],
});
}

protected override _onServerEvent(event: UmbManagementApiServerEventModel) {
if (event.eventSource === 'Umbraco:CMS:DocumentType') {
this.#onDocumentTypeChange(event);
} else {
this.#onDocumentChange(event);
}
}

#onDocumentChange(event: UmbManagementApiServerEventModel) {
// Invalidate the specific document
const documentId = event.key;
this._dataCache.delete(documentId);
}

#onDocumentTypeChange(event: UmbManagementApiServerEventModel) {
// Invalidate all documents of the specified Document Type
const documentTypeId = event.key;
const documentIds = this._dataCache
.getAll()
.filter((cachedItem) => cachedItem.documentType.id === documentTypeId)
.map((item) => item.id);

documentIds.forEach((id) => this._dataCache.delete(id));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbManagementApiItemDataCache } from '@umbraco-cms/backoffice/management-api';

const documentItemCache = new UmbManagementApiItemDataCache<DocumentItemResponseModel>();

export { documentItemCache };
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
import type { UmbDocumentItemModel } from './types.js';
import { UmbManagementApiDocumentItemDataRequestManager } from './document-item.server.request-manager.js';
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository';
import { UmbItemDataApiGetRequestController } from '@umbraco-cms/backoffice/entity-item';

/**
* A data source for Document items that fetches data from the server
Expand All @@ -15,6 +14,8 @@ export class UmbDocumentItemServerDataSource extends UmbItemServerDataSourceBase
DocumentItemResponseModel,
UmbDocumentItemModel
> {
#itemRequestManager = new UmbManagementApiDocumentItemDataRequestManager(this);

/**
* Creates an instance of UmbDocumentItemServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
Expand All @@ -29,13 +30,7 @@ export class UmbDocumentItemServerDataSource extends UmbItemServerDataSourceBase
override async getItems(uniques: Array<string>) {
if (!uniques) throw new Error('Uniques are missing');

const itemRequestManager = new UmbItemDataApiGetRequestController(this, {
// eslint-disable-next-line local-rules/no-direct-api-import
api: (args) => DocumentService.getItemDocument({ query: { id: args.uniques } }),
uniques,
});

const { data, error } = await itemRequestManager.request();
const { data, error } = await this.#itemRequestManager.getItems(uniques);

return { data: this._getMappedItems(data), error };
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable local-rules/no-direct-api-import */
import { documentItemCache } from './document-item.server.cache.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { DocumentService, type DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbManagementApiItemDataRequestManager } from '@umbraco-cms/backoffice/management-api';

export class UmbManagementApiDocumentItemDataRequestManager extends UmbManagementApiItemDataRequestManager<DocumentItemResponseModel> {
constructor(host: UmbControllerHost) {
super(host, {
getItems: (ids: Array<string>) => DocumentService.getItemDocument({ query: { id: ids } }),
dataCache: documentItemCache,
getUniqueMethod: (item: DocumentItemResponseModel) => item.id,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
...urlManifests,
...userPermissionManifests,
...workspaceManifests,
{
name: 'Document Backoffice Entry Point',
alias: 'Umb.BackofficeEntryPoint.Document',
type: 'backofficeEntryPoint',
js: () => import('./entry-point.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT } from '../server-event/constants.js';
import type { UmbManagementApiServerEventModel } from '../server-event/types.js';
import type { UmbManagementApiDetailDataCache } from './cache.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';

export interface UmbManagementApiDetailDataInvalidationManagerArgs<DetailResponseModelType> {
dataCache: UmbManagementApiDetailDataCache<DetailResponseModelType>;
eventSources: Array<string>;
}

export class UmbManagementApiDetailDataCacheInvalidationManager<DetailResponseModelType> extends UmbControllerBase {
protected _dataCache: UmbManagementApiDetailDataCache<DetailResponseModelType>;
#eventSources: Array<string>;
#serverEventContext?: typeof UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT.TYPE;

constructor(
host: UmbControllerHost,
args: UmbManagementApiDetailDataInvalidationManagerArgs<DetailResponseModelType>,
) {
super(host);
{
this._dataCache = args.dataCache;
this.#eventSources = args.eventSources;

this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => {
this.#serverEventContext = context;
this.#observeServerEvents();
});
}
}

/**
* Handles server events
* @protected
* @param {UmbManagementApiServerEventModel} event - The server event to handle
* @memberof UmbManagementApiDetailDataCacheInvalidationManager
*/
protected _onServerEvent(event: UmbManagementApiServerEventModel) {
this._dataCache.delete(event.key);
}

#observeServerEvents() {
this.observe(
this.#serverEventContext?.byEventSourcesAndEventTypes(this.#eventSources, ['Updated', 'Deleted']),
(event) => {
if (!event) return;
this._onServerEvent(event);
},
'umbObserveServerEvents',
);
}

override destroy(): void {
this._dataCache.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ describe('UmbManagementApiDetailDataCache', () => {
expect(cache).to.have.property('get').that.is.a('function');
});

it('has a getAll method', () => {
expect(cache).to.have.property('getAll').that.is.a('function');
});

it('has a delete method', () => {
expect(cache).to.have.property('delete').that.is.a('function');
});
Expand Down Expand Up @@ -69,6 +73,14 @@ describe('UmbManagementApiDetailDataCache', () => {
});
});

describe('GetAll', () => {
it('returns all items from the cache', () => {
cache.set('item1', { id: 'item1' });
cache.set('item2', { id: 'item2' });
expect(cache.getAll()).to.deep.equal([{ id: 'item1' }, { id: 'item2' }]);
});
});

describe('Delete', () => {
it('removes an item from the cache', () => {
cache.set('item1', { id: 'item1' });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Keep internal
interface UmbCacheEntryModel<DataModelType> {
interface UmbCacheEntryModel<DetailDataModelType> {
id: string;
data: DataModelType;
data: DetailDataModelType;
}

/**
* A runtime cache for storing entity detail data from the Management Api
* @class UmbManagementApiDetailDataCache
* @template DataModelType
* @template DetailDataModelType
*/
export class UmbManagementApiDetailDataCache<DataModelType> {
#entries: Map<string, UmbCacheEntryModel<DataModelType>> = new Map();
export class UmbManagementApiDetailDataCache<DetailDataModelType> {
#entries: Map<string, UmbCacheEntryModel<DetailDataModelType>> = new Map();

/**
* Checks if an entry exists in the cache
Expand All @@ -25,11 +25,11 @@ export class UmbManagementApiDetailDataCache<DataModelType> {
/**
* Adds an entry to the cache
* @param {string} id - The ID of the entry to add
* @param {DataModelType} data - The data to cache
* @param {DetailDataModelType} data - The data to cache
* @memberof UmbManagementApiDetailDataCache
*/
set(id: string, data: DataModelType): void {
const cacheEntry: UmbCacheEntryModel<DataModelType> = {
set(id: string, data: DetailDataModelType): void {
const cacheEntry: UmbCacheEntryModel<DetailDataModelType> = {
id: id,
data,
};
Expand All @@ -40,14 +40,23 @@ export class UmbManagementApiDetailDataCache<DataModelType> {
/**
* 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
* @returns {DetailDataModelType | undefined} - The cached entry or undefined if not found
* @memberof UmbManagementApiDetailDataCache
*/
get(id: string): DataModelType | undefined {
get(id: string): DetailDataModelType | undefined {
const entry = this.#entries.get(id);
return entry ? entry.data : undefined;
}

/**
* Retrieves all entries from the cache
* @returns {Array<DetailDataModelType>} - An array of all cached entries
* @memberof UmbManagementApiItemDataCache
*/
getAll(): Array<DetailDataModelType> {
return Array.from(this.#entries.values()).map((entry) => entry.data);
}

/**
* Deletes an entry from the cache
* @param {string} id - The ID of the entry to delete
Expand Down
Loading
Loading