@seij/entity-storage 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -6
- package/dist/index.js +30 -39
- package/dist/index.js.map +1 -1
- package/dist/internal/querykeys.js +1 -1
- package/dist/internal/querykeys.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,11 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# entity-storage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runtime storage layer for entity instances, designed to pair with
|
|
4
|
+
`entity-graph`. While `entity-graph` describes _what_ exists (entities,
|
|
5
|
+
properties, relationships), `entity-storage` defines _how_ to fetch, search,
|
|
6
|
+
create, update, and delete the actual data.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## What it provides
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
- A contribution-based registry for entity storage implementations.
|
|
11
|
+
- A single service (`EntityStorageService`) to route CRUD operations by entity
|
|
12
|
+
name.
|
|
13
|
+
- DTOs for list, pagination, and single-item responses.
|
|
14
|
+
- Query key helpers to support cache invalidation (e.g., React Query).
|
|
15
|
+
- Optional mapping helpers for aggregate fetch results.
|
|
8
16
|
|
|
9
|
-
##
|
|
17
|
+
## What it is (and how it differs from usual approaches)
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
Most applications hardcode a repository or API client per entity, tied to a
|
|
20
|
+
single backend and a fixed schema. This module is different: it routes data
|
|
21
|
+
access by `entityName` at runtime, using contributions that can come from
|
|
22
|
+
multiple bounded contexts. That makes it a storage _platform_ rather than a
|
|
23
|
+
collection of static services.
|
|
24
|
+
|
|
25
|
+
The real power comes from its link to `entity-graph`: the graph tells the UI
|
|
26
|
+
what entities exist and how they relate; the storage tells the UI where to fetch
|
|
27
|
+
and mutate the data. Together, they turn a dynamic domain model into a working
|
|
28
|
+
dynamic UI without hardcoding endpoints or per-entity services.
|
|
29
|
+
|
|
30
|
+
Concrete outcomes: fewer per-entity repositories, consistent data access across
|
|
31
|
+
domains, and faster delivery of baseline screens (lists, detail, edit).
|
|
32
|
+
|
|
33
|
+
## Why it matters
|
|
34
|
+
|
|
35
|
+
- Separates _model discovery_ (entity-graph) from _data access_ (entity-storage).
|
|
36
|
+
- Lets multiple backends or storage strategies coexist behind one API.
|
|
37
|
+
- Enables dynamic UI to load and mutate entity data without hardcoding
|
|
38
|
+
endpoints.
|
|
39
|
+
- Provides consistent cache keys and invalidation hooks across entities.
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
- Extensions contribute a storage implementation per entity name.
|
|
44
|
+
- The storage exposes `fetchItem`, `search`, `createItem`, `updateItemProperty`,
|
|
45
|
+
and `deleteItem`.
|
|
46
|
+
- The service resolves the right store by entity name and forwards calls.
|
|
47
|
+
- Additional invalidation keys can be contributed for cross-query consistency.
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { createQueryKeySearchSummary as S, createQueryKeySearch as h, createQueryKeyItem as d, createInvalidationQueryKey as p } from "./internal/querykeys.js";
|
|
8
|
-
class K {
|
|
9
|
-
constructor() {
|
|
10
|
-
i(this, "name", "seij.entity-storage");
|
|
11
|
-
}
|
|
1
|
+
import { throwError as a } from "@seij/common-types";
|
|
2
|
+
import { serviceRef as u } from "@seij/extension-platform";
|
|
3
|
+
import { errorOnDuplicates as c } from "@seij/extension-tooling";
|
|
4
|
+
import { createQueryKeySearchSummary as m, createQueryKeySearch as y, createQueryKeyItem as l, createInvalidationQueryKey as S } from "./internal/querykeys.js";
|
|
5
|
+
class v {
|
|
6
|
+
name = "seij.entity-storage";
|
|
12
7
|
activate(t) {
|
|
13
|
-
t.registerService(
|
|
8
|
+
t.registerService(h, d), t.registerContributionPoint(i), t.registerContributionPoint(n);
|
|
14
9
|
}
|
|
15
10
|
}
|
|
16
|
-
const
|
|
11
|
+
const i = {
|
|
17
12
|
code: "seij.entity-storage.EntityStorageContributions"
|
|
18
|
-
},
|
|
13
|
+
}, n = {
|
|
19
14
|
code: "seij.entity-storage.EntityStorageInvalidationContributions"
|
|
20
|
-
},
|
|
21
|
-
class
|
|
15
|
+
}, h = u("EntityStorageToken");
|
|
16
|
+
class d {
|
|
17
|
+
storageByEntityName;
|
|
18
|
+
invalidationQueryKeysContribs;
|
|
22
19
|
constructor(t) {
|
|
23
|
-
i
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
l(e, (r) => r.entity), this.storageByEntityName = Object.fromEntries(e.map((r) => [r.entity, r])), this.invalidationQueryKeysContribs = t.extensionRegistry.findContributionsFlat(
|
|
27
|
-
a
|
|
20
|
+
const e = t.extensionRegistry.findContributionsFlat(i);
|
|
21
|
+
c(e, (r) => r.entity), this.storageByEntityName = Object.fromEntries(e.map((r) => [r.entity, r])), this.invalidationQueryKeysContribs = t.extensionRegistry.findContributionsFlat(
|
|
22
|
+
n
|
|
28
23
|
);
|
|
29
24
|
}
|
|
30
25
|
findStore(t) {
|
|
31
|
-
return this.storageByEntityName[t] ??
|
|
26
|
+
return this.storageByEntityName[t] ?? a("No entity resolver for " + t);
|
|
32
27
|
}
|
|
33
28
|
/**
|
|
34
29
|
* Searchs for a paginated list of entities using this query parameters
|
|
@@ -46,19 +41,19 @@ class R {
|
|
|
46
41
|
* @returns list of strings to compose a query key that acts as a cache key
|
|
47
42
|
*/
|
|
48
43
|
searchSummaryQueryKey(t, e) {
|
|
49
|
-
return
|
|
44
|
+
return m(t, e);
|
|
50
45
|
}
|
|
51
46
|
search(t, e) {
|
|
52
47
|
return this.findStore(t).search(e);
|
|
53
48
|
}
|
|
54
49
|
searchQueryKey(t, e) {
|
|
55
|
-
return
|
|
50
|
+
return y(t, e);
|
|
56
51
|
}
|
|
57
52
|
fetchItem(t, e) {
|
|
58
53
|
return this.findStore(t).fetchItem({ id: e });
|
|
59
54
|
}
|
|
60
55
|
fetchItemQueryKey(t, e) {
|
|
61
|
-
return
|
|
56
|
+
return l(t, e);
|
|
62
57
|
}
|
|
63
58
|
createItem(t, e) {
|
|
64
59
|
return this.findStore(t).createItem(e);
|
|
@@ -70,29 +65,25 @@ class R {
|
|
|
70
65
|
return this.findStore(t).updateItemProperty(e);
|
|
71
66
|
}
|
|
72
67
|
invalidationQueryKeys(t) {
|
|
73
|
-
const e =
|
|
68
|
+
const e = S(t), r = this.invalidationQueryKeysContribs.filter((o) => o.entityName === t).map((o) => o.queryKeys).flat();
|
|
74
69
|
return [e, ...r];
|
|
75
70
|
}
|
|
76
71
|
mapFetchResultDtoToResourceDto(t, e) {
|
|
77
|
-
|
|
78
|
-
const r = this.findStore(t);
|
|
79
|
-
return ((o = r.mapFetchResultDtoToResourceDto) == null ? void 0 : o.call(r, e)) ?? e;
|
|
72
|
+
return this.findStore(t).mapFetchResultDtoToResourceDto?.(e) ?? e;
|
|
80
73
|
}
|
|
81
74
|
mapSearchResultDtoToSummaryDto(t, e) {
|
|
82
|
-
|
|
83
|
-
const r = this.findStore(t);
|
|
84
|
-
return ((o = r.mapSearchResultDtoToSummaryDto) == null ? void 0 : o.call(r, e)) ?? e;
|
|
75
|
+
return this.findStore(t).mapSearchResultDtoToSummaryDto?.(e) ?? e;
|
|
85
76
|
}
|
|
86
77
|
}
|
|
87
|
-
function
|
|
78
|
+
function E(s, t) {
|
|
88
79
|
return s.then((e) => ({ ...e, items: e.items.map(t) }));
|
|
89
80
|
}
|
|
90
81
|
export {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
i as EntityStorageContributions,
|
|
83
|
+
v as EntityStorageExtension,
|
|
84
|
+
n as EntityStorageInvalidationContributions,
|
|
85
|
+
d as EntityStorageService,
|
|
86
|
+
h as EntityStorageServiceRef,
|
|
87
|
+
E as mapToSearchSummary
|
|
97
88
|
};
|
|
98
89
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { throwError } from \"@seij/common-types\";\nimport {\n ContributionPoint,\n Extension,\n ExtensionContext,\n ServiceConstructorProps,\n serviceRef,\n} from \"@seij/extension-platform\";\nimport { errorOnDuplicates } from \"@seij/extension-tooling\";\nimport { Id, IdDto, ListPaginatedDto, SingleItemDto } from \"./dto\";\nimport {\n createInvalidationQueryKey,\n createQueryKeyItem,\n createQueryKeySearch,\n createQueryKeySearchSummary,\n QueryKey,\n} from \"./internal/querykeys\";\n\nexport * from \"./dto\";\n\n/**\n * Extension to manage entity storage\n */\nexport class EntityStorageExtension implements Extension {\n name = \"seij.entity-storage\";\n activate(context: ExtensionContext) {\n context.registerService(EntityStorageServiceRef, EntityStorageService);\n context.registerContributionPoint(EntityStorageContributions);\n context.registerContributionPoint(EntityStorageInvalidationContributions);\n }\n}\n\n/**\n * Extensions can contribute by providing function to fetch data from\n * remote or local storage\n */\nexport const EntityStorageContributions: ContributionPoint<EntityStorageContribution<any, any, any, any, any>> = {\n code: \"seij.entity-storage.EntityStorageContributions\",\n};\n/**\n * Used to hook on invalidations and provide additional query cache invalidations\n * when entities are mutated\n */\nexport const EntityStorageInvalidationContributions: ContributionPoint<EntityStorageInvalidationContribution> = {\n code: \"seij.entity-storage.EntityStorageInvalidationContributions\",\n};\n\n/**\n * Base interface to mark SearchQueryParams\n */\nexport interface EntityStorageSearchQueryParams {}\n\n/**\n * Base interface for all entity instances\n *\n * To be able to work correctly, all entities need to have an id.\n */\n\nexport interface EntityInstance {\n /** Unique identifier of this */\n id: string;\n}\n\n/**\n * Search for summaries (label + description) of entities. The goal is\n * to provide reduced displays of entities and not fetch the whole planet\n * only to use a label and a description.\n */\nexport interface SearchSummaryResultItem {\n id: string;\n label: string;\n description: string | null;\n}\n\n/**\n * Used as base interface for initializers (the stuff you pass to create entitie instances)\n */\nexport interface InitializerDtoBase extends Record<string, any> {}\n\n/**\n * Used to update the property of an entity instance.\n */\nexport interface UpdateItemPropertyDto {\n /** Id of entity */\n id: Id;\n /** Property name */\n propertyName: string;\n /** Property value, should match then entity definition */\n propertyValue: any;\n}\n\n/**\n * Contribution that allow extensions to resolve entities\n */\nexport interface EntityStorageContribution<\n RESOURCE_DTO extends EntityInstance = EntityInstance, // Base object,\n INITIALIZER_DTO extends InitializerDtoBase = {}, // Object type use for creation\n FETCH_RESULT_DTO = RESOURCE_DTO, // When using fetch, the type of object returned, defaults to RESOURCE_DTO. Does not extends from EntityInstance because it can be an aggregate.\n SEARCH_QUERY_PARAMS extends EntityStorageSearchQueryParams = {}, // Search query parameters\n SEARCH_RESULT_DTO extends EntityInstance = RESOURCE_DTO, // Search result\n> {\n /**\n * Name of entity managed by this storage\n */\n entity: string;\n /**\n * Gets an entity instance using item unique identifier.\n *\n * Resource fetched may contain be an aggregate of multiple things and not\n * directly the RESOURCE_DTO, mostly because screen may have to display more\n * than just the fields of the entity instance, for example related concepts or\n * enrichments.\n */\n fetchItem: (req: IdDto) => Promise<SingleItemDto<FETCH_RESULT_DTO>>;\n /**\n * Searchs for a list of summaries of this entity.\n *\n * This is a separated type of query than search, so that you can implement\n * API endpoints that work faster that what a full search could retreive.\n *\n * Usage is meant for lists (when fetching relationships) or forms/view,\n * wherever you need a very short version of the entities, just to display\n * their name.\n */\n searchSummary: (queryParams: SEARCH_QUERY_PARAMS) => Promise<ListPaginatedDto<SearchSummaryResultItem>>;\n /**\n * Searchs for a list of entity instances\n *\n * This is meant for lists or tables that need details on each entity instance.\n *\n * You can return something else than the base RESOURCE_DTO if (and only if)\n * you implement mapFetchResultDtoToResourceDto()\n */\n search: (queryParams: SEARCH_QUERY_PARAMS) => Promise<ListPaginatedDto<SEARCH_RESULT_DTO>>;\n /**\n * Sends initialization data to item creation.\n *\n * This is meant for lists or tables that need details on each entity instance.\n *\n * You can return something else than the base RESOURCE_DTO if (and only if)\n * you implement mapFetchResultDtoToResourceDto()\n */\n createItem: (req: INITIALIZER_DTO) => Promise<SingleItemDto<RESOURCE_DTO>>;\n /**\n * Deletes item using its id\n */\n deleteItem: (req: IdDto) => Promise<unknown>;\n /**\n * Updates one entity instance property\n */\n updateItemProperty: (req: UpdateItemPropertyDto) => Promise<unknown>;\n mapFetchResultDtoToResourceDto?: (item: FETCH_RESULT_DTO) => RESOURCE_DTO;\n mapSearchResultDtoToSummaryDto?: (item: SEARCH_RESULT_DTO) => SearchSummaryResultItem;\n}\n\n/**\n * Provides a way to invoke additional query cache invalidations when entities\n * are mutated.\n *\n * Imaging you have standalone queries that use entity MyEntity. Those queries\n * are unknown to the extension that defines MyEntity. You wish that\n * if you delete or update entity MyEntity, all other queries gets invalidated,\n * so that user dont see those items in other lists.\n */\nexport interface EntityStorageInvalidationContribution {\n /**\n * Name of the entity mutated\n */\n entityName: string;\n /**\n * Keys to invalidate on entity instances mutation (do not add basic CRUD\n * which are already managed, just yours)\n */\n queryKeys: QueryKey[];\n}\nexport const EntityStorageServiceRef = serviceRef<EntityStorageService>(\"EntityStorageToken\");\nexport class EntityStorageService {\n storageByEntityName: Record<string, EntityStorageContribution>;\n invalidationQueryKeysContribs: EntityStorageInvalidationContribution[];\n constructor(props: ServiceConstructorProps) {\n const entityResolvers = props.extensionRegistry.findContributionsFlat(EntityStorageContributions);\n errorOnDuplicates(entityResolvers, (it) => it.entity);\n this.storageByEntityName = Object.fromEntries(entityResolvers.map((it) => [it.entity, it]));\n this.invalidationQueryKeysContribs = props.extensionRegistry.findContributionsFlat(\n EntityStorageInvalidationContributions,\n );\n }\n private findStore(entityName: string): EntityStorageContribution {\n const store: EntityStorageContribution =\n this.storageByEntityName[entityName] ?? throwError(\"No entity resolver for \" + entityName);\n return store;\n }\n /**\n * Searchs for a paginated list of entities using this query parameters\n * @param entityName name of entity to search for\n * @param searchQueryParams query parameters as a record of string/values\n * @returns list of paginated entities\n */\n searchSummary(\n entityName: string,\n searchQueryParams: EntityStorageSearchQueryParams,\n ): Promise<ListPaginatedDto<SearchSummaryResultItem>> {\n const store = this.findStore(entityName);\n return store.searchSummary(searchQueryParams);\n }\n /**\n * Creates a cache key for this request\n * @param entity name of entity to search for\n * @param searchQueryParams query parameters as a record of string/values\n * @returns list of strings to compose a query key that acts as a cache key\n */\n searchSummaryQueryKey(entity: string, searchQueryParams: EntityStorageSearchQueryParams): string[] {\n return createQueryKeySearchSummary(entity, searchQueryParams);\n }\n\n search<T>(entityName: string, searchQueryParams: EntityStorageSearchQueryParams): Promise<ListPaginatedDto<T>> {\n const store = this.findStore(entityName);\n const promiseResult = store.search(searchQueryParams);\n return promiseResult as Promise<ListPaginatedDto<T>>;\n }\n searchQueryKey(entity: string, searchQueryParams: EntityStorageSearchQueryParams): QueryKey {\n return createQueryKeySearch(entity, searchQueryParams);\n }\n\n fetchItem<T extends EntityInstance>(entityName: string, id: Id): Promise<SingleItemDto<T>> {\n const store = this.findStore(entityName);\n const promiseResult = store.fetchItem({ id: id });\n return promiseResult as Promise<SingleItemDto<T>>;\n }\n fetchItemQueryKey(entityName: string, id: Id): QueryKey {\n return createQueryKeyItem(entityName, id);\n }\n\n createItem<E extends EntityInstance, I extends InitializerDtoBase>(\n entityName: string,\n initializer: I,\n ): Promise<SingleItemDto<E>> {\n const store = this.findStore(entityName);\n const promiseResult = store.createItem(initializer);\n return promiseResult as Promise<SingleItemDto<E>>;\n }\n deleteItem<E extends EntityInstance>(entityName: string, id: Id) {\n const store = this.findStore(entityName);\n const promiseResult = store.deleteItem({ id: id });\n return promiseResult as Promise<SingleItemDto<E>>;\n }\n updateItemProperty(entityName: string, value: UpdateItemPropertyDto) {\n const store = this.findStore(entityName);\n const promiseResult = store.updateItemProperty(value);\n return promiseResult;\n }\n invalidationQueryKeys(entityName: string): QueryKey[] {\n const base = createInvalidationQueryKey(entityName);\n const additionals = this.invalidationQueryKeysContribs\n .filter((it) => it.entityName === entityName)\n .map((it) => it.queryKeys)\n .flat();\n return [base, ...additionals];\n }\n mapFetchResultDtoToResourceDto<X extends EntityInstance, Y extends EntityInstance>(entityName: string, x: X): Y {\n const store = this.findStore(entityName);\n return (store.mapFetchResultDtoToResourceDto?.(x) as Y) ?? x;\n }\n mapSearchResultDtoToSummaryDto<X extends EntityInstance>(entityName: string, x: X): SearchSummaryResultItem {\n const store = this.findStore(entityName);\n return store.mapSearchResultDtoToSummaryDto?.(x) ?? (x as unknown as SearchSummaryResultItem);\n }\n}\n\n/**\n * Helper function to transform a traditional result into search summary when\n * you don't have or want a separate API\n */\nexport function mapToSearchSummary<X, Y>(\n resp: Promise<ListPaginatedDto<X>>,\n mapper: (it: X) => Y,\n): Promise<ListPaginatedDto<Y>> {\n return resp.then((list) => {\n return { ...list, items: list.items.map(mapper) };\n });\n}\n"],"names":["EntityStorageExtension","__publicField","context","EntityStorageServiceRef","EntityStorageService","EntityStorageContributions","EntityStorageInvalidationContributions","serviceRef","props","entityResolvers","errorOnDuplicates","it","entityName","throwError","searchQueryParams","entity","createQueryKeySearchSummary","createQueryKeySearch","id","createQueryKeyItem","initializer","value","base","createInvalidationQueryKey","additionals","x","store","_a","mapToSearchSummary","resp","mapper","list"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { throwError } from \"@seij/common-types\";\nimport {\n ContributionPoint,\n Extension,\n ExtensionContext,\n ServiceConstructorProps,\n serviceRef,\n} from \"@seij/extension-platform\";\nimport { errorOnDuplicates } from \"@seij/extension-tooling\";\nimport { Id, IdDto, ListPaginatedDto, SingleItemDto } from \"./dto\";\nimport {\n createInvalidationQueryKey,\n createQueryKeyItem,\n createQueryKeySearch,\n createQueryKeySearchSummary,\n QueryKey,\n} from \"./internal/querykeys\";\n\nexport * from \"./dto\";\n\n/**\n * Extension to manage entity storage\n */\nexport class EntityStorageExtension implements Extension {\n name = \"seij.entity-storage\";\n activate(context: ExtensionContext) {\n context.registerService(EntityStorageServiceRef, EntityStorageService);\n context.registerContributionPoint(EntityStorageContributions);\n context.registerContributionPoint(EntityStorageInvalidationContributions);\n }\n}\n\n/**\n * Extensions can contribute by providing function to fetch data from\n * remote or local storage\n */\nexport const EntityStorageContributions: ContributionPoint<EntityStorageContribution<any, any, any, any, any>> = {\n code: \"seij.entity-storage.EntityStorageContributions\",\n};\n/**\n * Used to hook on invalidations and provide additional query cache invalidations\n * when entities are mutated\n */\nexport const EntityStorageInvalidationContributions: ContributionPoint<EntityStorageInvalidationContribution> = {\n code: \"seij.entity-storage.EntityStorageInvalidationContributions\",\n};\n\n/**\n * Base interface to mark SearchQueryParams\n */\nexport interface EntityStorageSearchQueryParams {}\n\n/**\n * Base interface for all entity instances\n *\n * To be able to work correctly, all entities need to have an id.\n */\n\nexport interface EntityInstance {\n /** Unique identifier of this */\n id: string;\n}\n\n/**\n * Search for summaries (label + description) of entities. The goal is\n * to provide reduced displays of entities and not fetch the whole planet\n * only to use a label and a description.\n */\nexport interface SearchSummaryResultItem {\n id: string;\n label: string;\n description: string | null;\n}\n\n/**\n * Used as base interface for initializers (the stuff you pass to create entitie instances)\n */\nexport interface InitializerDtoBase extends Record<string, any> {}\n\n/**\n * Used to update the property of an entity instance.\n */\nexport interface UpdateItemPropertyDto {\n /** Id of entity */\n id: Id;\n /** Property name */\n propertyName: string;\n /** Property value, should match then entity definition */\n propertyValue: any;\n}\n\n/**\n * Contribution that allow extensions to resolve entities\n */\nexport interface EntityStorageContribution<\n RESOURCE_DTO extends EntityInstance = EntityInstance, // Base object,\n INITIALIZER_DTO extends InitializerDtoBase = {}, // Object type use for creation\n FETCH_RESULT_DTO = RESOURCE_DTO, // When using fetch, the type of object returned, defaults to RESOURCE_DTO. Does not extends from EntityInstance because it can be an aggregate.\n SEARCH_QUERY_PARAMS extends EntityStorageSearchQueryParams = {}, // Search query parameters\n SEARCH_RESULT_DTO extends EntityInstance = RESOURCE_DTO, // Search result\n> {\n /**\n * Name of entity managed by this storage\n */\n entity: string;\n /**\n * Gets an entity instance using item unique identifier.\n *\n * Resource fetched may contain be an aggregate of multiple things and not\n * directly the RESOURCE_DTO, mostly because screen may have to display more\n * than just the fields of the entity instance, for example related concepts or\n * enrichments.\n */\n fetchItem: (req: IdDto) => Promise<SingleItemDto<FETCH_RESULT_DTO>>;\n /**\n * Searchs for a list of summaries of this entity.\n *\n * This is a separated type of query than search, so that you can implement\n * API endpoints that work faster that what a full search could retreive.\n *\n * Usage is meant for lists (when fetching relationships) or forms/view,\n * wherever you need a very short version of the entities, just to display\n * their name.\n */\n searchSummary: (queryParams: SEARCH_QUERY_PARAMS) => Promise<ListPaginatedDto<SearchSummaryResultItem>>;\n /**\n * Searchs for a list of entity instances\n *\n * This is meant for lists or tables that need details on each entity instance.\n *\n * You can return something else than the base RESOURCE_DTO if (and only if)\n * you implement mapFetchResultDtoToResourceDto()\n */\n search: (queryParams: SEARCH_QUERY_PARAMS) => Promise<ListPaginatedDto<SEARCH_RESULT_DTO>>;\n /**\n * Sends initialization data to item creation.\n *\n * This is meant for lists or tables that need details on each entity instance.\n *\n * You can return something else than the base RESOURCE_DTO if (and only if)\n * you implement mapFetchResultDtoToResourceDto()\n */\n createItem: (req: INITIALIZER_DTO) => Promise<SingleItemDto<RESOURCE_DTO>>;\n /**\n * Deletes item using its id\n */\n deleteItem: (req: IdDto) => Promise<unknown>;\n /**\n * Updates one entity instance property\n */\n updateItemProperty: (req: UpdateItemPropertyDto) => Promise<unknown>;\n mapFetchResultDtoToResourceDto?: (item: FETCH_RESULT_DTO) => RESOURCE_DTO;\n mapSearchResultDtoToSummaryDto?: (item: SEARCH_RESULT_DTO) => SearchSummaryResultItem;\n}\n\n/**\n * Provides a way to invoke additional query cache invalidations when entities\n * are mutated.\n *\n * Imaging you have standalone queries that use entity MyEntity. Those queries\n * are unknown to the extension that defines MyEntity. You wish that\n * if you delete or update entity MyEntity, all other queries gets invalidated,\n * so that user dont see those items in other lists.\n */\nexport interface EntityStorageInvalidationContribution {\n /**\n * Name of the entity mutated\n */\n entityName: string;\n /**\n * Keys to invalidate on entity instances mutation (do not add basic CRUD\n * which are already managed, just yours)\n */\n queryKeys: QueryKey[];\n}\nexport const EntityStorageServiceRef = serviceRef<EntityStorageService>(\"EntityStorageToken\");\nexport class EntityStorageService {\n storageByEntityName: Record<string, EntityStorageContribution>;\n invalidationQueryKeysContribs: EntityStorageInvalidationContribution[];\n constructor(props: ServiceConstructorProps) {\n const entityResolvers = props.extensionRegistry.findContributionsFlat(EntityStorageContributions);\n errorOnDuplicates(entityResolvers, (it) => it.entity);\n this.storageByEntityName = Object.fromEntries(entityResolvers.map((it) => [it.entity, it]));\n this.invalidationQueryKeysContribs = props.extensionRegistry.findContributionsFlat(\n EntityStorageInvalidationContributions,\n );\n }\n private findStore(entityName: string): EntityStorageContribution {\n const store: EntityStorageContribution =\n this.storageByEntityName[entityName] ?? throwError(\"No entity resolver for \" + entityName);\n return store;\n }\n /**\n * Searchs for a paginated list of entities using this query parameters\n * @param entityName name of entity to search for\n * @param searchQueryParams query parameters as a record of string/values\n * @returns list of paginated entities\n */\n searchSummary(\n entityName: string,\n searchQueryParams: EntityStorageSearchQueryParams,\n ): Promise<ListPaginatedDto<SearchSummaryResultItem>> {\n const store = this.findStore(entityName);\n return store.searchSummary(searchQueryParams);\n }\n /**\n * Creates a cache key for this request\n * @param entity name of entity to search for\n * @param searchQueryParams query parameters as a record of string/values\n * @returns list of strings to compose a query key that acts as a cache key\n */\n searchSummaryQueryKey(entity: string, searchQueryParams: EntityStorageSearchQueryParams): string[] {\n return createQueryKeySearchSummary(entity, searchQueryParams);\n }\n\n search<T>(entityName: string, searchQueryParams: EntityStorageSearchQueryParams): Promise<ListPaginatedDto<T>> {\n const store = this.findStore(entityName);\n const promiseResult = store.search(searchQueryParams);\n return promiseResult as Promise<ListPaginatedDto<T>>;\n }\n searchQueryKey(entity: string, searchQueryParams: EntityStorageSearchQueryParams): QueryKey {\n return createQueryKeySearch(entity, searchQueryParams);\n }\n\n fetchItem<T extends EntityInstance>(entityName: string, id: Id): Promise<SingleItemDto<T>> {\n const store = this.findStore(entityName);\n const promiseResult = store.fetchItem({ id: id });\n return promiseResult as Promise<SingleItemDto<T>>;\n }\n fetchItemQueryKey(entityName: string, id: Id): QueryKey {\n return createQueryKeyItem(entityName, id);\n }\n\n createItem<E extends EntityInstance, I extends InitializerDtoBase>(\n entityName: string,\n initializer: I,\n ): Promise<SingleItemDto<E>> {\n const store = this.findStore(entityName);\n const promiseResult = store.createItem(initializer);\n return promiseResult as Promise<SingleItemDto<E>>;\n }\n deleteItem<E extends EntityInstance>(entityName: string, id: Id) {\n const store = this.findStore(entityName);\n const promiseResult = store.deleteItem({ id: id });\n return promiseResult as Promise<SingleItemDto<E>>;\n }\n updateItemProperty(entityName: string, value: UpdateItemPropertyDto) {\n const store = this.findStore(entityName);\n const promiseResult = store.updateItemProperty(value);\n return promiseResult;\n }\n invalidationQueryKeys(entityName: string): QueryKey[] {\n const base = createInvalidationQueryKey(entityName);\n const additionals = this.invalidationQueryKeysContribs\n .filter((it) => it.entityName === entityName)\n .map((it) => it.queryKeys)\n .flat();\n return [base, ...additionals];\n }\n mapFetchResultDtoToResourceDto<X extends EntityInstance, Y extends EntityInstance>(entityName: string, x: X): Y {\n const store = this.findStore(entityName);\n return (store.mapFetchResultDtoToResourceDto?.(x) as Y) ?? x;\n }\n mapSearchResultDtoToSummaryDto<X extends EntityInstance>(entityName: string, x: X): SearchSummaryResultItem {\n const store = this.findStore(entityName);\n return store.mapSearchResultDtoToSummaryDto?.(x) ?? (x as unknown as SearchSummaryResultItem);\n }\n}\n\n/**\n * Helper function to transform a traditional result into search summary when\n * you don't have or want a separate API\n */\nexport function mapToSearchSummary<X, Y>(\n resp: Promise<ListPaginatedDto<X>>,\n mapper: (it: X) => Y,\n): Promise<ListPaginatedDto<Y>> {\n return resp.then((list) => {\n return { ...list, items: list.items.map(mapper) };\n });\n}\n"],"names":["EntityStorageExtension","context","EntityStorageServiceRef","EntityStorageService","EntityStorageContributions","EntityStorageInvalidationContributions","serviceRef","props","entityResolvers","errorOnDuplicates","it","entityName","throwError","searchQueryParams","entity","createQueryKeySearchSummary","createQueryKeySearch","id","createQueryKeyItem","initializer","value","base","createInvalidationQueryKey","additionals","x","mapToSearchSummary","resp","mapper","list"],"mappings":";;;;AAuBO,MAAMA,EAA4C;AAAA,EACvD,OAAO;AAAA,EACP,SAASC,GAA2B;AAClC,IAAAA,EAAQ,gBAAgBC,GAAyBC,CAAoB,GACrEF,EAAQ,0BAA0BG,CAA0B,GAC5DH,EAAQ,0BAA0BI,CAAsC;AAAA,EAC1E;AACF;AAMO,MAAMD,IAAoG;AAAA,EAC/G,MAAM;AACR,GAKaC,IAAmG;AAAA,EAC9G,MAAM;AACR,GAkIaH,IAA0BI,EAAiC,oBAAoB;AACrF,MAAMH,EAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,YAAYI,GAAgC;AAC1C,UAAMC,IAAkBD,EAAM,kBAAkB,sBAAsBH,CAA0B;AAChG,IAAAK,EAAkBD,GAAiB,CAACE,MAAOA,EAAG,MAAM,GACpD,KAAK,sBAAsB,OAAO,YAAYF,EAAgB,IAAI,CAACE,MAAO,CAACA,EAAG,QAAQA,CAAE,CAAC,CAAC,GAC1F,KAAK,gCAAgCH,EAAM,kBAAkB;AAAA,MAC3DF;AAAA,IAAA;AAAA,EAEJ;AAAA,EACQ,UAAUM,GAA+C;AAG/D,WADE,KAAK,oBAAoBA,CAAU,KAAKC,EAAW,4BAA4BD,CAAU;AAAA,EAE7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cACEA,GACAE,GACoD;AAEpD,WADc,KAAK,UAAUF,CAAU,EAC1B,cAAcE,CAAiB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsBC,GAAgBD,GAA6D;AACjG,WAAOE,EAA4BD,GAAQD,CAAiB;AAAA,EAC9D;AAAA,EAEA,OAAUF,GAAoBE,GAAiF;AAG7G,WAFc,KAAK,UAAUF,CAAU,EACX,OAAOE,CAAiB;AAAA,EAEtD;AAAA,EACA,eAAeC,GAAgBD,GAA6D;AAC1F,WAAOG,EAAqBF,GAAQD,CAAiB;AAAA,EACvD;AAAA,EAEA,UAAoCF,GAAoBM,GAAmC;AAGzF,WAFc,KAAK,UAAUN,CAAU,EACX,UAAU,EAAE,IAAAM,GAAQ;AAAA,EAElD;AAAA,EACA,kBAAkBN,GAAoBM,GAAkB;AACtD,WAAOC,EAAmBP,GAAYM,CAAE;AAAA,EAC1C;AAAA,EAEA,WACEN,GACAQ,GAC2B;AAG3B,WAFc,KAAK,UAAUR,CAAU,EACX,WAAWQ,CAAW;AAAA,EAEpD;AAAA,EACA,WAAqCR,GAAoBM,GAAQ;AAG/D,WAFc,KAAK,UAAUN,CAAU,EACX,WAAW,EAAE,IAAAM,GAAQ;AAAA,EAEnD;AAAA,EACA,mBAAmBN,GAAoBS,GAA8B;AAGnE,WAFc,KAAK,UAAUT,CAAU,EACX,mBAAmBS,CAAK;AAAA,EAEtD;AAAA,EACA,sBAAsBT,GAAgC;AACpD,UAAMU,IAAOC,EAA2BX,CAAU,GAC5CY,IAAc,KAAK,8BACtB,OAAO,CAACb,MAAOA,EAAG,eAAeC,CAAU,EAC3C,IAAI,CAACD,MAAOA,EAAG,SAAS,EACxB,KAAA;AACH,WAAO,CAACW,GAAM,GAAGE,CAAW;AAAA,EAC9B;AAAA,EACA,+BAAmFZ,GAAoBa,GAAS;AAE9G,WADc,KAAK,UAAUb,CAAU,EACzB,iCAAiCa,CAAC,KAAWA;AAAA,EAC7D;AAAA,EACA,+BAAyDb,GAAoBa,GAA+B;AAE1G,WADc,KAAK,UAAUb,CAAU,EAC1B,iCAAiCa,CAAC,KAAMA;AAAA,EACvD;AACF;AAMO,SAASC,EACdC,GACAC,GAC8B;AAC9B,SAAOD,EAAK,KAAK,CAACE,OACT,EAAE,GAAGA,GAAM,OAAOA,EAAK,MAAM,IAAID,CAAM,EAAA,EAC/C;AACH;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"querykeys.js","sources":["../../src/internal/querykeys.ts"],"sourcesContent":["export type QueryKey = string[];\nexport function createInvalidationQueryKey(apiResourceName: string): QueryKey {\n return [\"entity\", apiResourceName];\n}\nexport function createQueryKeySearchSummary<SEARCH_DTO extends {}>(apiResourceName: string, req: SEARCH_DTO): QueryKey {\n return [\n \"entity\",\n apiResourceName,\n \"
|
|
1
|
+
{"version":3,"file":"querykeys.js","sources":["../../src/internal/querykeys.ts"],"sourcesContent":["export type QueryKey = string[];\nexport function createInvalidationQueryKey(apiResourceName: string): QueryKey {\n return [\"entity\", apiResourceName];\n}\nexport function createQueryKeySearchSummary<SEARCH_DTO extends {}>(apiResourceName: string, req: SEARCH_DTO): QueryKey {\n return [\n \"entity\",\n apiResourceName,\n \"search_summary\",\n ...Object.entries(req)\n .flat()\n .map((it) => \"\" + it),\n ];\n}\nexport function createQueryKeySearch<SEARCH_DTO extends {}>(apiResourceName: string, req: SEARCH_DTO): QueryKey {\n return [\n \"entity\",\n apiResourceName,\n \"search\",\n ...Object.entries(req)\n .flat()\n .map((it) => \"\" + it),\n ];\n}\n\nexport function createQueryKeyItem(apiResourceName: string, id: string): QueryKey {\n return [\"entity\", apiResourceName, \"item\", id];\n}\n"],"names":["createInvalidationQueryKey","apiResourceName","createQueryKeySearchSummary","req","it","createQueryKeySearch","createQueryKeyItem","id"],"mappings":"AACO,SAASA,EAA2BC,GAAmC;AAC5E,SAAO,CAAC,UAAUA,CAAe;AACnC;AACO,SAASC,EAAmDD,GAAyBE,GAA2B;AACrH,SAAO;AAAA,IACL;AAAA,IACAF;AAAA,IACA;AAAA,IACA,GAAG,OAAO,QAAQE,CAAG,EAClB,KAAA,EACA,IAAI,CAACC,MAAO,KAAKA,CAAE;AAAA,EAAA;AAE1B;AACO,SAASC,EAA4CJ,GAAyBE,GAA2B;AAC9G,SAAO;AAAA,IACL;AAAA,IACAF;AAAA,IACA;AAAA,IACA,GAAG,OAAO,QAAQE,CAAG,EAClB,KAAA,EACA,IAAI,CAACC,MAAO,KAAKA,CAAE;AAAA,EAAA;AAE1B;AAEO,SAASE,EAAmBL,GAAyBM,GAAsB;AAChF,SAAO,CAAC,UAAUN,GAAiB,QAAQM,CAAE;AAC/C;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seij/entity-storage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"url": "https://github.com/seij-net/seij-commons-js.git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@seij/common-types": "0.1.
|
|
12
|
-
"@seij/extension-platform": "0.1.
|
|
13
|
-
"@seij/extension-tooling": "0.1.
|
|
11
|
+
"@seij/common-types": "^0.1.5",
|
|
12
|
+
"@seij/extension-platform": "^0.1.2",
|
|
13
|
+
"@seij/extension-tooling": "^0.1.2"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {},
|
|
16
16
|
"exports": {
|