@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 CHANGED
@@ -1,11 +1,47 @@
1
- # common-validation
1
+ # entity-storage
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
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
- ## Building
8
+ ## What it provides
6
9
 
7
- Run `nx build common-validation` to build the library.
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
- ## Running unit tests
17
+ ## What it is (and how it differs from usual approaches)
10
18
 
11
- Run `nx test common-validation` to execute the unit tests via [Vitest](https://vitest.dev/).
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
- var u = Object.defineProperty;
2
- var c = (s, t, e) => t in s ? u(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
3
- var i = (s, t, e) => c(s, typeof t != "symbol" ? t + "" : t, e);
4
- import { throwError as m } from "@seij/common-types";
5
- import { serviceRef as y } from "@seij/extension-platform";
6
- import { errorOnDuplicates as l } from "@seij/extension-tooling";
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(f, R), t.registerContributionPoint(n), t.registerContributionPoint(a);
8
+ t.registerService(h, d), t.registerContributionPoint(i), t.registerContributionPoint(n);
14
9
  }
15
10
  }
16
- const n = {
11
+ const i = {
17
12
  code: "seij.entity-storage.EntityStorageContributions"
18
- }, a = {
13
+ }, n = {
19
14
  code: "seij.entity-storage.EntityStorageInvalidationContributions"
20
- }, f = y("EntityStorageToken");
21
- class R {
15
+ }, h = u("EntityStorageToken");
16
+ class d {
17
+ storageByEntityName;
18
+ invalidationQueryKeysContribs;
22
19
  constructor(t) {
23
- i(this, "storageByEntityName");
24
- i(this, "invalidationQueryKeysContribs");
25
- const e = t.extensionRegistry.findContributionsFlat(n);
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] ?? m("No entity resolver for " + 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 S(t, e);
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 h(t, e);
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 d(t, e);
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 = p(t), r = this.invalidationQueryKeysContribs.filter((o) => o.entityName === t).map((o) => o.queryKeys).flat();
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
- var o;
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
- var o;
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 C(s, t) {
78
+ function E(s, t) {
88
79
  return s.then((e) => ({ ...e, items: e.items.map(t) }));
89
80
  }
90
81
  export {
91
- n as EntityStorageContributions,
92
- K as EntityStorageExtension,
93
- a as EntityStorageInvalidationContributions,
94
- R as EntityStorageService,
95
- f as EntityStorageServiceRef,
96
- C as mapToSearchSummary
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;"}
@@ -5,7 +5,7 @@ function a(e, t) {
5
5
  return [
6
6
  "entity",
7
7
  e,
8
- "search_sumary",
8
+ "search_summary",
9
9
  ...Object.entries(t).flat().map((r) => "" + r)
10
10
  ];
11
11
  }
@@ -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 \"search_sumary\",\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;"}
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.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.3",
12
- "@seij/extension-platform": "0.1.1",
13
- "@seij/extension-tooling": "0.1.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": {