V16: Item and Detail Base Repository should use correct typings for return types (#19447)

* fix: add a catcher to most `asPromise` for stores to prevent cascading errors

* fix: remove conditional instances - they should be able to be undefined

* fix: check for missing store and extract UmbProblemDetails

* fix: only append data if no error

* fix: adds error handling to missing stores and to extract the ProblemDetails object

* revert commit

* fix: ignore errors completely instead of unsetting stores

* revert commit

* chore: cleanup imports

* fix: do not unset store

* stop observation in a proper way

* stop observation of for document-user-permissions

* check for manager twice

* save action

* save action optional

* fix: ensure the right types are used for base stores

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
This commit is contained in:
Jacob Overgaard
2025-06-12 12:59:11 +02:00
committed by GitHub
parent cfcb708d26
commit e89e18f5ba
9 changed files with 45 additions and 62 deletions

View File

@@ -175,7 +175,7 @@ export class UmbContentTypeStructureManager<
* @param {string} unique - The unique of the ContentType to load.
* @returns {Promise} - Promise resolved
*/
public async loadType(unique: string): Promise<UmbRepositoryResponseWithAsObservable<T>> {
public async loadType(unique: string): Promise<UmbRepositoryResponseWithAsObservable<T | undefined>> {
if (this.#ownerContentTypeUnique === unique) {
// Its the same, but we do not know if its done loading jet, so we will wait for the load promise to finish. [NL]
await this.#init;

View File

@@ -60,7 +60,7 @@ export abstract class UmbDetailRepositoryBase<
* @returns {*}
* @memberof UmbDetailRepositoryBase
*/
async requestByUnique(unique: string): Promise<UmbRepositoryResponseWithAsObservable<DetailModelType>> {
async requestByUnique(unique: string): Promise<UmbRepositoryResponseWithAsObservable<DetailModelType | undefined>> {
if (!unique) throw new Error('Unique is missing');
await this.#init;
@@ -73,7 +73,7 @@ export abstract class UmbDetailRepositoryBase<
return {
data,
error,
asObservable: () => this.#detailStore!.byUnique(unique),
asObservable: () => this.#detailStore?.byUnique(unique),
};
}

View File

@@ -3,6 +3,6 @@ import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
export interface UmbReadDetailRepository<DetailModelType> extends UmbApi {
requestByUnique(unique: string): Promise<UmbRepositoryResponseWithAsObservable<DetailModelType>>;
requestByUnique(unique: string): Promise<UmbRepositoryResponseWithAsObservable<DetailModelType | undefined>>;
byUnique(unique: string): Promise<Observable<DetailModelType | undefined>>;
}

View File

@@ -41,22 +41,18 @@ export class UmbItemRepositoryBase<ItemType extends { unique: string }>
try {
await this._init;
} catch {
return {};
return {
asObservable: () => undefined,
};
}
const { data, error } = await this.#itemSource.getItems(uniques);
if (!this._itemStore) {
// If store is gone, then we are most likely in a disassembled state.
return {};
}
if (data) {
this._itemStore.appendItems(data);
this._itemStore?.appendItems(data);
}
// TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
return { data, error: error as any, asObservable: () => this._itemStore!.items(uniques) };
return { data, error, asObservable: () => this._itemStore?.items(uniques) };
}
/**

View File

@@ -1,12 +1,8 @@
import type { UmbRepositoryResponseWithAsObservable } from '../types.js';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources';
export interface UmbItemRepository<ItemType> extends UmbApi {
requestItems: (uniques: string[]) => Promise<{
data?: Array<ItemType> | undefined;
error?: UmbProblemDetails | undefined;
asObservable?: () => Observable<Array<ItemType>>;
}>;
requestItems: (uniques: string[]) => Promise<UmbRepositoryResponseWithAsObservable<ItemType[] | undefined>>;
items: (uniques: string[]) => Promise<Observable<Array<ItemType>> | undefined>;
}

View File

@@ -250,7 +250,8 @@ export class UmbRepositoryItemsManager<ItemType extends { unique: string }> exte
}
}
#sortByUniques(data: Array<ItemType>): Array<ItemType> {
#sortByUniques(data?: Array<ItemType>): Array<ItemType> {
if (!data) return [];
const uniques = this.getUniques();
return [...data].sort((a, b) => {
const aIndex = uniques.indexOf(this.#getUnique(a) ?? '');

View File

@@ -11,8 +11,13 @@ export interface UmbRepositoryResponse<T> extends UmbDataSourceResponse<T> {}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface UmbRepositoryErrorResponse extends UmbDataSourceErrorResponse {}
export interface UmbRepositoryResponseWithAsObservable<T> extends UmbRepositoryResponse<T> {
asObservable: () => Observable<T | undefined>;
/**
* Interface for a repository that can return a paged model.
* @template T - The type of items in the paged model.
* @template T$ - The type of items returned by the asObservable method, defaults to T. You should only use this if you want to return a different type from the asObservable method.
*/
export interface UmbRepositoryResponseWithAsObservable<T, T$ = T> extends UmbRepositoryResponse<T> {
asObservable: () => Observable<T$> | undefined;
}
export type * from './data-mapper/mapping/types.js';

View File

@@ -7,11 +7,10 @@ import type {
UmbTreeChildrenOfRequestArgs,
UmbTreeRootItemsRequestArgs,
} from './types.js';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import { UmbRepositoryBase, type UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources';
import { of } from '@umbraco-cms/backoffice/external/rxjs';
/**
@@ -74,7 +73,7 @@ export abstract class UmbTreeRepositoryBase<
* @returns {*}
* @memberof UmbTreeRepositoryBase
*/
abstract requestTreeRoot(): Promise<{ data?: TreeRootType; error?: UmbProblemDetails }>;
abstract requestTreeRoot(): Promise<UmbRepositoryResponse<TreeRootType>>;
/**
* Requests root items of a tree
@@ -89,15 +88,16 @@ export abstract class UmbTreeRepositoryBase<
if (!this._treeStore) {
// If the tree store is not available, then we most likely are in a destructed setting.
return {};
return {
asObservable: () => undefined,
};
}
if (data) {
this._treeStore.appendItems(data.items);
}
// TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
return { data, error: error as any, asObservable: () => this._treeStore!.rootItems };
return { data, error, asObservable: () => this._treeStore?.rootItems };
}
/**
@@ -117,15 +117,16 @@ export abstract class UmbTreeRepositoryBase<
if (!this._treeStore) {
// If the tree store is not available, then we most likely are in a destructed setting.
return {};
return {
asObservable: () => undefined,
};
}
if (data) {
this._treeStore.appendItems(data.items);
}
// TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
return { data, error: error as any, asObservable: () => this._treeStore!.childrenOf(args.parent.unique) };
return { data, error, asObservable: () => this._treeStore?.childrenOf(args.parent.unique) };
}
/**
@@ -153,12 +154,7 @@ export abstract class UmbTreeRepositoryBase<
async rootTreeItems() {
await this._init;
if (!this._treeStore) {
// If the tree store is not available, then we most likely are in a destructed setting.
return of([]);
}
return this._treeStore.rootItems;
return this._treeStore?.rootItems ?? of([]);
}
/**
@@ -171,11 +167,6 @@ export abstract class UmbTreeRepositoryBase<
if (parentUnique === undefined) throw new Error('Parent unique is missing');
await this._init;
if (!this._treeStore) {
// If the tree store is not available, then we most likely are in a destructed setting.
return of([]);
}
return this._treeStore.childrenOf(parentUnique);
return this._treeStore?.childrenOf(parentUnique) ?? of([]);
}
}

View File

@@ -4,10 +4,13 @@ import type {
UmbTreeAncestorsOfRequestArgs,
UmbTreeRootItemsRequestArgs,
} from './types.js';
import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
import type {
UmbPagedModel,
UmbRepositoryResponse,
UmbRepositoryResponseWithAsObservable,
} from '@umbraco-cms/backoffice/repository';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources';
/**
* Interface for a tree repository.
@@ -27,41 +30,32 @@ export interface UmbTreeRepository<
* Requests the root of the tree.
* @memberof UmbTreeRepository
*/
requestTreeRoot: () => Promise<{
data?: TreeRootType;
error?: UmbProblemDetails;
}>;
requestTreeRoot: () => Promise<UmbRepositoryResponse<TreeRootType>>;
/**
* Requests the root items of the tree.
* @param {UmbTreeRootItemsRequestArgs} args
* @memberof UmbTreeRepository
*/
requestTreeRootItems: (args: TreeRootItemsRequestArgsType) => Promise<{
data?: UmbPagedModel<TreeItemType>;
error?: UmbProblemDetails;
asObservable?: () => Observable<TreeItemType[]>;
}>;
requestTreeRootItems: (
args: TreeRootItemsRequestArgsType,
) => Promise<UmbRepositoryResponseWithAsObservable<UmbPagedModel<TreeItemType>, TreeItemType[]>>;
/**
* Requests the children of the given parent item.
* @param {UmbTreeChildrenOfRequestArgs} args
* @memberof UmbTreeRepository
*/
requestTreeItemsOf: (args: TreeChildrenOfRequestArgsType) => Promise<{
data?: UmbPagedModel<TreeItemType>;
error?: UmbProblemDetails;
asObservable?: () => Observable<TreeItemType[]>;
}>;
requestTreeItemsOf: (
args: TreeChildrenOfRequestArgsType,
) => Promise<UmbRepositoryResponseWithAsObservable<UmbPagedModel<TreeItemType>, TreeItemType[]>>;
/**
* Requests the ancestors of the given item.
* @param {UmbTreeAncestorsOfRequestArgs} args
* @memberof UmbTreeRepository
*/
requestTreeItemAncestors: (
args: TreeAncestorsOfRequestArgsType,
) => Promise<{ data?: TreeItemType[]; error?: UmbProblemDetails; asObservable?: () => Observable<TreeItemType[]> }>;
requestTreeItemAncestors: (args: TreeAncestorsOfRequestArgsType) => Promise<UmbRepositoryResponse<TreeItemType[]>>;
/**
* Returns an observable of the root items of the tree.