Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
b34df6a
poc inflight request cache
madsrasmussen Aug 14, 2025
ed1a259
clean up
madsrasmussen Aug 14, 2025
da203e6
update
madsrasmussen Aug 14, 2025
e5911c0
Merge branch 'v16/feature/entity-detail-runtime-cache' into v16/bugfi…
madsrasmussen Aug 14, 2025
2a4190a
Merge branch 'main' into v16/bugfix/inflight-request-cache
madsrasmussen Aug 20, 2025
a5bc05b
add management api inflight request cache
madsrasmussen Aug 20, 2025
70e2c83
Update document-type-detail.server.request-manager.ts
madsrasmussen Aug 21, 2025
ac305bb
Update src/Umbraco.Web.UI.Client/src/packages/management-api/detail/d…
madsrasmussen Aug 21, 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ import {
type DataTypeResponseModel,
type UpdateDataTypeRequestModel,
} 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 UmbManagementApiDataTypeDetailDataRequestManager extends UmbManagementApiDetailDataRequestManager<
DataTypeResponseModel,
UpdateDataTypeRequestModel,
CreateDataTypeRequestModel
> {
static #inflightRequestCache = new UmbManagementApiInflightRequestCache<DataTypeResponseModel>();

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 } }),
dataCache: dataTypeDetailCache,
inflightRequestCache: UmbManagementApiDataTypeDetailDataRequestManager.#inflightRequestCache,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ 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 = new UmbManagementApiInflightRequestCache<DocumentTypeResponseModel>();

constructor(host: UmbControllerHost) {
super(host, {
create: (body: CreateDocumentTypeRequestModel) => DocumentTypeService.postDocumentType({ body }),
Expand All @@ -22,6 +27,7 @@ export class UmbManagementApiDocumentTypeDetailDataRequestManager extends UmbMan
DocumentTypeService.putDocumentTypeById({ path: { id }, body }),
delete: (id: string) => DocumentTypeService.deleteDocumentTypeById({ path: { id } }),
dataCache: documentTypeDetailCache,
inflightRequestCache: UmbManagementApiDocumentTypeDetailDataRequestManager.#inflightRequestCache,
});
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -20,6 +21,7 @@ export interface UmbManagementApiDetailDataRequestManagerArgs<
update: (id: string, data: UpdateRequestModelType) => Promise<UmbApiResponse<{ data: unknown }>>;
delete: (id: string) => Promise<UmbApiResponse<{ data: unknown }>>;
dataCache: UmbManagementApiDetailDataCache<DetailResponseModelType>;
inflightRequestCache: UmbManagementApiInflightRequestCache<DetailResponseModelType>;
}

export class UmbManagementApiDetailDataRequestManager<
Expand All @@ -28,6 +30,7 @@ export class UmbManagementApiDetailDataRequestManager<
UpdateRequestModelType,
> extends UmbControllerBase {
#dataCache: UmbManagementApiDetailDataCache<DetailResponseModelType>;
#inflightRequestCache: UmbManagementApiInflightRequestCache<DetailResponseModelType>;

#create;
#read;
Expand All @@ -52,6 +55,7 @@ export class UmbManagementApiDetailDataRequestManager<
this.#delete = args.delete;

this.#dataCache = args.dataCache;
this.#inflightRequestCache = args.inflightRequestCache;

this.consumeContext(UMB_MANAGEMENT_API_SERVER_EVENT_CONTEXT, (context) => {
this.#serverEventContext = context;
Expand All @@ -73,18 +77,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.#dataCache.has(id)) {
data = this.#dataCache.get(id);
} else {
const { data: serverData, error: serverError } = await tryExecute(this, this.#read(id));
const hasInflightRequest = this.#inflightRequestCache.has(inflightCacheKey);

const request = hasInflightRequest
? this.#inflightRequestCache.get(inflightCacheKey)
: tryExecute(this, this.#read(id));

if (this.#isConnectedToServerEvents && serverData) {
this.#dataCache.set(id, serverData);
if (!request) {
throw new Error(`Failed to create or retrieve 'read' request for ID: ${id} (cache key: ${inflightCacheKey}). Aborting read.`);
}

data = serverData;
error = serverError;
this.#inflightRequestCache.set(inflightCacheKey, request);

try {
const { data: serverData, error: serverError } = await request;

if (this.#isConnectedToServerEvents && serverData) {
this.#dataCache.set(id, serverData);
}

data = serverData;
error = serverError;
} finally {
this.#inflightRequestCache.delete(inflightCacheKey);
}
}

return { data, error };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './detail-data.request-manager.js';
export * from './cache.js';
export * from './cache-invalidation.manager.js';
export * from './cache.js';
export * from './detail-data.request-manager.js';
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { UmbApiResponse } from '@umbraco-cms/backoffice/resources';

// Keep internal
type RequestResolvedType<ResponseModelType> = 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<ResponseModelType> {
#entries = new Map<string, Promise<RequestResolvedType<ResponseModelType>>>();

/**
* 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<UmbApiResponse<RequestResolvedType<ResponseModelType>>>} promise - The promise to cache
* @memberof UmbManagementApiInflightRequestCache
*/
set(key: string, promise: Promise<RequestResolvedType<ResponseModelType>>): void {
this.#entries.set(key, promise);
}

/**
* Retrieves an entry from the cache
* @param {string} key - The ID of the entry to retrieve
* @returns {Promise<RequestResolvedType<ResponseModelType>> | undefined} - The cached promise or undefined if not found
* @memberof UmbManagementApiInflightRequestCache
*/
get(key: string): Promise<RequestResolvedType<ResponseModelType>> | 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();
}
}
Loading