@arpproject/recrate 0.1.7-test3 → 0.1.7-test6

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.
Files changed (276) hide show
  1. package/dist/app/App.d.ts +2 -0
  2. package/dist/app/EmbeddedComponent.d.ts +3 -0
  3. package/dist/app/index.d.ts +0 -0
  4. package/dist/app/lookup.d.ts +82 -0
  5. package/dist/crate-builder/CrateManager/contexts.d.ts +6 -0
  6. package/dist/crate-builder/CrateManager/crate-manager-benchmarking.spec.d.ts +1 -0
  7. package/dist/crate-builder/CrateManager/crate-manager-loading-exporting.spec.d.ts +1 -0
  8. package/dist/crate-builder/CrateManager/crate-manager-operations.spec.d.ts +0 -0
  9. package/dist/crate-builder/CrateManager/crate-manager.d.ts +641 -0
  10. package/dist/crate-builder/CrateManager/crate-manager.spec.d.ts +1 -0
  11. package/dist/crate-builder/CrateManager/lib.d.ts +26 -0
  12. package/dist/crate-builder/CrateManager/lib.spec.d.ts +1 -0
  13. package/dist/crate-builder/CrateManager/profile-manager.d.ts +143 -0
  14. package/dist/crate-builder/CrateManager/profile-manager.spec.d.ts +1 -0
  15. package/{src/crate-builder/CrateManager/schema-type-definitions.json → dist/crate-builder/CrateManager/schema-type-definitions.json.d.ts} +4 -1
  16. package/dist/crate-builder/CrateManager/validate-identifier.d.ts +10 -0
  17. package/dist/crate-builder/CrateManager/validate-identifier.spec.d.ts +1 -0
  18. package/dist/crate-builder/RenderEntity/Add.d.ts +26 -0
  19. package/dist/crate-builder/RenderEntity/AddControl.d.ts +14 -0
  20. package/dist/crate-builder/RenderEntity/AddControl.stories.d.ts +29 -0
  21. package/dist/crate-builder/RenderEntity/AutoComplete.d.ts +12 -0
  22. package/dist/crate-builder/RenderEntity/BulkAdd.d.ts +10 -0
  23. package/dist/crate-builder/RenderEntity/DeleteProperty.d.ts +8 -0
  24. package/dist/crate-builder/RenderEntity/DialogAddProperty.d.ts +9 -0
  25. package/dist/crate-builder/RenderEntity/DialogBrowseEntities.d.ts +8 -0
  26. package/dist/crate-builder/RenderEntity/DialogEditContext.d.ts +8 -0
  27. package/dist/crate-builder/RenderEntity/DialogPreviewCrate.d.ts +7 -0
  28. package/dist/crate-builder/RenderEntity/DialogSaveCrateAsTemplate.d.ts +13 -0
  29. package/dist/crate-builder/RenderEntity/DialogSaveEntityTemplate.d.ts +14 -0
  30. package/dist/crate-builder/RenderEntity/DisplayPropertyName.d.ts +8 -0
  31. package/dist/crate-builder/RenderEntity/DisplayPropertyName.stories.d.ts +6 -0
  32. package/dist/crate-builder/RenderEntity/EntityId.d.ts +13 -0
  33. package/dist/crate-builder/RenderEntity/EntityName.d.ts +13 -0
  34. package/dist/crate-builder/RenderEntity/EntityProperty.d.ts +19 -0
  35. package/dist/crate-builder/RenderEntity/EntityPropertyInstance.d.ts +25 -0
  36. package/dist/crate-builder/RenderEntity/EntityType.d.ts +12 -0
  37. package/dist/crate-builder/RenderEntity/ItemLink.d.ts +14 -0
  38. package/dist/crate-builder/RenderEntity/PaginateLinkedEntities.d.ts +23 -0
  39. package/dist/crate-builder/RenderEntity/PaginateLinkedEntities.stories.d.ts +7 -0
  40. package/dist/crate-builder/RenderEntity/PropertyHelp.d.ts +6 -0
  41. package/dist/crate-builder/RenderEntity/RenderControls.d.ts +22 -0
  42. package/dist/crate-builder/RenderEntity/RenderLinkedItem.d.ts +20 -0
  43. package/dist/crate-builder/RenderEntity/RenderPropertyHelp.d.ts +6 -0
  44. package/dist/crate-builder/RenderEntity/RenderReverseConnections.d.ts +16 -0
  45. package/dist/crate-builder/RenderEntity/RenderTypes.d.ts +6 -0
  46. package/dist/crate-builder/RenderEntity/Shell2.d.ts +14 -0
  47. package/dist/crate-builder/RenderEntity/UnlinkEntity.d.ts +14 -0
  48. package/dist/crate-builder/RenderEntity/auto-complete.lib.d.ts +25 -0
  49. package/dist/crate-builder/RenderEntity/keys.d.ts +4 -0
  50. package/dist/crate-builder/RenderEntity/layout.d.ts +14 -0
  51. package/dist/crate-builder/RenderEntity/layout.spec.d.ts +1 -0
  52. package/dist/crate-builder/Shell.d.ts +40 -0
  53. package/dist/crate-builder/editor-state.d.ts +72 -0
  54. package/dist/crate-builder/emotionCache.d.ts +1 -0
  55. package/dist/crate-builder/helpers.d.ts +9 -0
  56. package/dist/crate-builder/i18n.d.ts +2 -0
  57. package/dist/crate-builder/lib/validate-iri.d.ts +6 -0
  58. package/dist/crate-builder/locales/en.d.ts +79 -0
  59. package/dist/crate-builder/locales/hu.d.ts +79 -0
  60. package/dist/crate-builder/primitives/Boolean.d.ts +11 -0
  61. package/dist/crate-builder/primitives/Boolean.stories.d.ts +7 -0
  62. package/dist/crate-builder/primitives/Date.d.ts +11 -0
  63. package/dist/crate-builder/primitives/Date.stories.d.ts +5 -0
  64. package/dist/crate-builder/primitives/DateTime.d.ts +11 -0
  65. package/dist/crate-builder/primitives/DateTime.stories.d.ts +5 -0
  66. package/dist/crate-builder/primitives/Geo.d.ts +12 -0
  67. package/dist/crate-builder/primitives/Geo.stories.d.ts +26 -0
  68. package/dist/crate-builder/primitives/Map.SelectArea.d.ts +9 -0
  69. package/dist/crate-builder/primitives/Map.d.ts +15 -0
  70. package/dist/crate-builder/primitives/Map.stories.d.ts +6 -0
  71. package/dist/crate-builder/primitives/Number.d.ts +16 -0
  72. package/dist/crate-builder/primitives/Number.stories.d.ts +10 -0
  73. package/dist/crate-builder/primitives/Select.d.ts +15 -0
  74. package/dist/crate-builder/primitives/Select.stories.d.ts +9 -0
  75. package/dist/crate-builder/primitives/SelectObject.d.ts +13 -0
  76. package/dist/crate-builder/primitives/SelectObject.stories.d.ts +5 -0
  77. package/dist/crate-builder/primitives/SelectUrl.d.ts +14 -0
  78. package/dist/crate-builder/primitives/SelectUrl.stories.d.ts +7 -0
  79. package/dist/crate-builder/primitives/Text.d.ts +19 -0
  80. package/dist/crate-builder/primitives/Text.stories.d.ts +13 -0
  81. package/dist/crate-builder/primitives/Time.d.ts +11 -0
  82. package/dist/crate-builder/primitives/Time.stories.d.ts +7 -0
  83. package/dist/crate-builder/primitives/Url.d.ts +11 -0
  84. package/dist/crate-builder/primitives/Url.stories.d.ts +7 -0
  85. package/dist/crate-builder/primitives/Value.d.ts +7 -0
  86. package/dist/crate-builder/primitives/Value.stories.d.ts +7 -0
  87. package/dist/crate-builder/primitives/date-libs.d.ts +1 -0
  88. package/dist/crate-builder/property-definitions.d.ts +78 -0
  89. package/dist/crate-builder/store.d.ts +8 -0
  90. package/dist/index.d.ts +5 -0
  91. package/dist/recrate.css +1896 -0
  92. package/dist/recrate.es.js +38508 -0
  93. package/dist/types.d.ts +42 -0
  94. package/package.json +31 -28
  95. package/.eslintrc.json +0 -37
  96. package/.storybook/main.ts +0 -40
  97. package/.storybook/preview.tsx +0 -46
  98. package/babel.config.json +0 -5
  99. package/docker-compose.yml +0 -30
  100. package/docs/.nojekyll +0 -1
  101. package/docs/assets/highlight.css +0 -99
  102. package/docs/assets/icons.js +0 -18
  103. package/docs/assets/icons.svg +0 -1
  104. package/docs/assets/main.js +0 -60
  105. package/docs/assets/navigation.js +0 -1
  106. package/docs/assets/search.js +0 -1
  107. package/docs/assets/style.css +0 -1448
  108. package/docs/classes/src_crate_builder_CrateManager_crate_manager.CrateManager.html +0 -240
  109. package/docs/classes/src_crate_builder_CrateManager_profile_manager.ProfileManager.html +0 -42
  110. package/docs/classes/src_crate_builder_editor_state.EditorState.html +0 -28
  111. package/docs/classes/src_crate_builder_types.CrateManagerType.html +0 -57
  112. package/docs/classes/src_crate_builder_types.ProfileManagerType.html +0 -13
  113. package/docs/functions/src_crate_builder_CrateManager_lib.isURL.html +0 -2
  114. package/docs/functions/src_crate_builder_CrateManager_lib.mintNewCrate.html +0 -3
  115. package/docs/functions/src_crate_builder_CrateManager_lib.normalise.html +0 -5
  116. package/docs/functions/src_crate_builder_CrateManager_lib.normaliseEntityType.html +0 -1
  117. package/docs/index.html +0 -58
  118. package/docs/interfaces/src_crate_builder_types.NormalisedCrate.html +0 -3
  119. package/docs/interfaces/src_crate_builder_types.NormalisedEntityDefinition.html +0 -4
  120. package/docs/interfaces/src_crate_builder_types.NormalisedProfile.html +0 -9
  121. package/docs/interfaces/src_crate_builder_types.ProfileLayout.html +0 -2
  122. package/docs/interfaces/src_crate_builder_types.ProfileLayoutGroup.html +0 -9
  123. package/docs/interfaces/src_crate_builder_types.UnverifiedCrate.html +0 -3
  124. package/docs/interfaces/src_crate_builder_types.UnverifiedEntityDefinition.html +0 -4
  125. package/docs/modules/src_crate_builder_CrateManager_crate_manager.html +0 -2
  126. package/docs/modules/src_crate_builder_CrateManager_lib.html +0 -6
  127. package/docs/modules/src_crate_builder_CrateManager_profile_manager.html +0 -2
  128. package/docs/modules/src_crate_builder_editor_state.html +0 -2
  129. package/docs/modules/src_crate_builder_types.html +0 -16
  130. package/docs/types/src_crate_builder_types.EntityReference.html +0 -1
  131. package/docs/types/src_crate_builder_types.NormalisedContext.html +0 -1
  132. package/docs/types/src_crate_builder_types.PrimitiveType.html +0 -1
  133. package/docs/types/src_crate_builder_types.ProfileAssociation.html +0 -1
  134. package/docs/types/src_crate_builder_types.ProfileInput.html +0 -1
  135. package/docs/types/src_crate_builder_types.UnverifiedContext.html +0 -1
  136. package/docs/variables/src_crate_builder_CrateManager_lib.urlProtocols.html +0 -1
  137. package/index.html +0 -13
  138. package/load-data-packs.cjs +0 -38
  139. package/postcss.config.cjs +0 -6
  140. package/react-app-env.d.ts +0 -1
  141. package/rollup.config.js +0 -26
  142. package/src/app/App.tsx +0 -13
  143. package/src/app/EmbeddedComponent.tsx +0 -432
  144. package/src/app/index.html +0 -20
  145. package/src/app/index.tsx +0 -19
  146. package/src/app/lookup.ts +0 -141
  147. package/src/app/override-styles.css +0 -96
  148. package/src/crate-builder/CrateManager/contexts/1.1-context.jsonld +0 -2660
  149. package/src/crate-builder/CrateManager/contexts/1.2-DRAFT-context.jsonld +0 -2918
  150. package/src/crate-builder/CrateManager/contexts.ts +0 -42
  151. package/src/crate-builder/CrateManager/crate-manager-benchmarking.spec.ts +0 -31
  152. package/src/crate-builder/CrateManager/crate-manager-loading-exporting.spec.ts +0 -431
  153. package/src/crate-builder/CrateManager/crate-manager-operations.spec.ts +0 -298
  154. package/src/crate-builder/CrateManager/crate-manager.spec.ts +0 -2336
  155. package/src/crate-builder/CrateManager/crate-manager.ts +0 -2111
  156. package/src/crate-builder/CrateManager/lib.spec.ts +0 -133
  157. package/src/crate-builder/CrateManager/lib.ts +0 -170
  158. package/src/crate-builder/CrateManager/profile-manager.spec.ts +0 -593
  159. package/src/crate-builder/CrateManager/profile-manager.ts +0 -367
  160. package/src/crate-builder/CrateManager/validate-identifier.spec.ts +0 -82
  161. package/src/crate-builder/CrateManager/validate-identifier.ts +0 -65
  162. package/src/crate-builder/RenderEntity/Add.tsx +0 -249
  163. package/src/crate-builder/RenderEntity/AddControl.stories.tsx +0 -126
  164. package/src/crate-builder/RenderEntity/AddControl.tsx +0 -84
  165. package/src/crate-builder/RenderEntity/AutoComplete.tsx +0 -215
  166. package/src/crate-builder/RenderEntity/BulkAdd.tsx +0 -136
  167. package/src/crate-builder/RenderEntity/DeleteProperty.tsx +0 -33
  168. package/src/crate-builder/RenderEntity/DialogAddProperty.tsx +0 -83
  169. package/src/crate-builder/RenderEntity/DialogBrowseEntities.tsx +0 -136
  170. package/src/crate-builder/RenderEntity/DialogEditContext.tsx +0 -107
  171. package/src/crate-builder/RenderEntity/DialogPreviewCrate.tsx +0 -54
  172. package/src/crate-builder/RenderEntity/DialogSaveCrateAsTemplate.tsx +0 -65
  173. package/src/crate-builder/RenderEntity/DialogSaveEntityTemplate.tsx +0 -87
  174. package/src/crate-builder/RenderEntity/DisplayPropertyName.stories.tsx +0 -30
  175. package/src/crate-builder/RenderEntity/DisplayPropertyName.tsx +0 -21
  176. package/src/crate-builder/RenderEntity/EntityId.tsx +0 -75
  177. package/src/crate-builder/RenderEntity/EntityName.tsx +0 -49
  178. package/src/crate-builder/RenderEntity/EntityProperty.tsx +0 -188
  179. package/src/crate-builder/RenderEntity/EntityPropertyInstance.tsx +0 -255
  180. package/src/crate-builder/RenderEntity/EntityType.tsx +0 -95
  181. package/src/crate-builder/RenderEntity/ItemLink.tsx +0 -37
  182. package/src/crate-builder/RenderEntity/PaginateLinkedEntities.stories.tsx +0 -43
  183. package/src/crate-builder/RenderEntity/PaginateLinkedEntities.tsx +0 -141
  184. package/src/crate-builder/RenderEntity/PropertyHelp.tsx +0 -39
  185. package/src/crate-builder/RenderEntity/RenderControls.tsx +0 -278
  186. package/src/crate-builder/RenderEntity/RenderLinkedItem.tsx +0 -139
  187. package/src/crate-builder/RenderEntity/RenderPropertyHelp.tsx +0 -41
  188. package/src/crate-builder/RenderEntity/RenderReverseConnections.tsx +0 -150
  189. package/src/crate-builder/RenderEntity/RenderTypes.tsx +0 -102
  190. package/src/crate-builder/RenderEntity/Shell2.tsx +0 -576
  191. package/src/crate-builder/RenderEntity/UnlinkEntity.tsx +0 -30
  192. package/src/crate-builder/RenderEntity/auto-complete.lib.ts +0 -184
  193. package/src/crate-builder/RenderEntity/keys.ts +0 -4
  194. package/src/crate-builder/RenderEntity/layout.spec.js +0 -593
  195. package/src/crate-builder/RenderEntity/layout.ts +0 -220
  196. package/src/crate-builder/Shell.tsx +0 -337
  197. package/src/crate-builder/component.css +0 -65
  198. package/src/crate-builder/editor-state.ts +0 -114
  199. package/src/crate-builder/emotionCache.ts +0 -8
  200. package/src/crate-builder/helpers.ts +0 -16
  201. package/src/crate-builder/i18n.ts +0 -22
  202. package/src/crate-builder/lib/validate-iri.ts +0 -57
  203. package/src/crate-builder/locales/en.js +0 -149
  204. package/src/crate-builder/locales/hu.js +0 -147
  205. package/src/crate-builder/primitives/Boolean.stories.tsx +0 -33
  206. package/src/crate-builder/primitives/Boolean.tsx +0 -67
  207. package/src/crate-builder/primitives/Date.stories.tsx +0 -32
  208. package/src/crate-builder/primitives/Date.tsx +0 -58
  209. package/src/crate-builder/primitives/DateTime.stories.tsx +0 -32
  210. package/src/crate-builder/primitives/DateTime.tsx +0 -64
  211. package/src/crate-builder/primitives/Geo.stories.tsx +0 -57
  212. package/src/crate-builder/primitives/Geo.tsx +0 -225
  213. package/src/crate-builder/primitives/Map.SelectArea.js +0 -359
  214. package/src/crate-builder/primitives/Map.stories.tsx +0 -61
  215. package/src/crate-builder/primitives/Map.tsx +0 -124
  216. package/src/crate-builder/primitives/Number.stories.tsx +0 -74
  217. package/src/crate-builder/primitives/Number.tsx +0 -166
  218. package/src/crate-builder/primitives/Select.stories.tsx +0 -66
  219. package/src/crate-builder/primitives/Select.tsx +0 -121
  220. package/src/crate-builder/primitives/SelectObject.stories.tsx +0 -29
  221. package/src/crate-builder/primitives/SelectObject.tsx +0 -105
  222. package/src/crate-builder/primitives/SelectUrl.stories.tsx +0 -42
  223. package/src/crate-builder/primitives/SelectUrl.tsx +0 -110
  224. package/src/crate-builder/primitives/Text.stories.tsx +0 -106
  225. package/src/crate-builder/primitives/Text.tsx +0 -197
  226. package/src/crate-builder/primitives/Time.stories.tsx +0 -38
  227. package/src/crate-builder/primitives/Time.tsx +0 -71
  228. package/src/crate-builder/primitives/Url.stories.tsx +0 -43
  229. package/src/crate-builder/primitives/Url.tsx +0 -75
  230. package/src/crate-builder/primitives/Value.stories.tsx +0 -37
  231. package/src/crate-builder/primitives/Value.tsx +0 -30
  232. package/src/crate-builder/primitives/date-libs.ts +0 -12
  233. package/src/crate-builder/profile-schema.json +0 -145
  234. package/src/crate-builder/property-definitions.ts +0 -78
  235. package/src/crate-builder/recrate.css +0 -3
  236. package/src/crate-builder/store.ts +0 -14
  237. package/src/crate-builder/tailwind.css +0 -5
  238. package/src/crate-builder/types.d.ts +0 -318
  239. package/src/examples/collection/collections-entity-example.json +0 -131
  240. package/src/examples/collection/crate-builder-entity-example.json +0 -33
  241. package/src/examples/item/complex-collection/ro-crate-metadata.json +0 -174
  242. package/src/examples/item/complex-item/ro-crate-metadata.json +0 -769
  243. package/src/examples/item/crate-with-language.json +0 -38
  244. package/src/examples/item/empty/ro-crate-metadata.json +0 -20
  245. package/src/examples/item/item-with-relationship-and-action/ro-crate-metadata.json +0 -66
  246. package/src/examples/item/large-crate/ro-crate-metadata.json +0 -5762
  247. package/src/examples/item/multiple-types/ro-crate-metadata.json +0 -20
  248. package/src/examples/item/ridiculously-big-collection/ro-crate-metadata.json +0 -162977
  249. package/src/examples/profile/aroma.complex.profile.json +0 -11098
  250. package/src/examples/profile/aroma.profile.json +0 -9158
  251. package/src/examples/profile/nyingarn-item-profile.json +0 -426
  252. package/src/examples/profile/profile-to-test-inverse-associations.json +0 -73
  253. package/src/examples/profile/profile-to-test-multiple-types.json +0 -31
  254. package/src/examples/profile/profile-with-all-primitives-and-groups.json +0 -207
  255. package/src/examples/profile/profile-with-all-primitives.json +0 -244
  256. package/src/examples/profile/profile-with-constraints.json +0 -446
  257. package/src/examples/profile/profile-with-resolve.json +0 -57
  258. package/src/examples/profile/vocabulary-creation-profile.json +0 -231
  259. package/src/images.d.ts +0 -5
  260. package/src/index.ts +0 -12
  261. package/src/types.ts +0 -104
  262. package/tailwind.config.js +0 -21
  263. package/tsconfig.app.json +0 -31
  264. package/tsconfig.json +0 -26
  265. package/typedoc.json +0 -11
  266. package/update-deps.sh +0 -4
  267. package/vite-env.d.ts +0 -1
  268. package/vite.config.ts +0 -104
  269. /package/{public → dist}/favicon.ico +0 -0
  270. /package/{public → dist}/index.html +0 -0
  271. /package/{public → dist}/logo192.png +0 -0
  272. /package/{public → dist}/logo512.png +0 -0
  273. /package/{public → dist}/manifest.json +0 -0
  274. /package/{public → dist}/marker-icon.png +0 -0
  275. /package/{public → dist}/marker-shadow.png +0 -0
  276. /package/{public → dist}/robots.txt +0 -0
@@ -1,2111 +0,0 @@
1
- import isArray from "lodash/isArray.js";
2
- import isNumber from "lodash/isNumber.js";
3
- import isBoolean from "lodash/isBoolean.js";
4
- import isString from "lodash/isString.js";
5
- import isEmpty from "lodash/isEmpty.js";
6
- import isUndefined from "lodash/isUndefined.js";
7
- import difference from "lodash/difference.js";
8
- import round from "lodash/round.js";
9
- import uniq from "lodash/uniq.js";
10
- import uniqBy from "lodash/uniqBy.js";
11
- import isPlainObject from "lodash/isPlainObject.js";
12
- import flattenDeep from "lodash/flattenDeep.js";
13
- import intersection from "lodash/intersection.js";
14
- import compact from "lodash/compact.js";
15
- import isEqual from "lodash/isEqual.js";
16
- import { normalise, isURL } from "./lib.js";
17
- import { getContextDefinition } from "./contexts";
18
- import type {
19
- UnverifiedContext,
20
- NormalisedContext,
21
- UnverifiedCrate,
22
- NormalisedCrate,
23
- ProfileManagerType,
24
- UnverifiedEntityDefinition,
25
- NormalisedEntityDefinition,
26
- EntityReference,
27
- PrimitiveType,
28
- NormalisedProfile,
29
- } from "../types.js";
30
-
31
- interface errorsInterface {
32
- hasError: Boolean;
33
- init: { description: string; messages: string[] };
34
- missingIdentifier: { description: string; entity: UnverifiedEntityDefinition[] };
35
- missingTypeDefinition: { description: string; entity: UnverifiedEntityDefinition[] };
36
- invalidIdentifier: { description: string; entity: UnverifiedEntityDefinition[] };
37
- clash: { description: string; messages: string[] };
38
- }
39
-
40
- interface warningsInterface {
41
- hasWarning: Boolean;
42
- init: { description: string; messages: string[] };
43
- invalidIdentifier: { description: string; entity: UnverifiedEntityDefinition[] };
44
- clash: { description: string; messages: string[] };
45
- }
46
-
47
- const entityDateCreatedProperty = "hasCreationDate";
48
- const entityDateUpdatedProperty = "hasModificationDate";
49
-
50
- const structuredClone = function (data: any) {
51
- return window.structuredClone(data);
52
- };
53
-
54
- /**
55
- * @class
56
- *
57
- * CrateManager
58
- *
59
- * A class to work with RO-Crates
60
- *
61
- * @param {crate} - an RO Crate to handle
62
- */
63
- export class CrateManager {
64
- crate: {
65
- "@context": NormalisedContext;
66
- "@graph": Array<NormalisedEntityDefinition | undefined>;
67
- };
68
- pm!: ProfileManagerType;
69
- reverse: {
70
- [key: string]: any;
71
- };
72
- graphLength: number;
73
- rootDescriptor?: number;
74
- rootDataset?: number;
75
- entityIdIndex: {
76
- [key: string]: number;
77
- };
78
- providedContext: UnverifiedContext;
79
- contextDefinitions: { [key: string]: boolean };
80
- localContext: { [key: string]: string };
81
- entityTypes: { [key: string]: number };
82
- entityTimestamps: Boolean;
83
- blankNodes: string[];
84
- coreProperties: string[];
85
- errors: errorsInterface;
86
- warnings: warningsInterface;
87
-
88
- constructor({
89
- crate,
90
- pm,
91
- context = undefined,
92
- entityTimestamps = false,
93
- }: {
94
- crate: UnverifiedCrate;
95
- pm?: ProfileManagerType;
96
- context?: UnverifiedContext;
97
- entityTimestamps?: Boolean;
98
- }) {
99
- // the crate
100
- this.crate = {
101
- "@context": [{}],
102
- "@graph": [],
103
- };
104
-
105
- // the profile manager - if you've set a profile
106
- if (pm) this.pm = pm;
107
-
108
- // entity reverse associations
109
- this.reverse = {};
110
-
111
- // shortcuts to the root descriptor and dataset
112
- this.rootDescriptor = undefined;
113
- this.rootDataset = undefined;
114
-
115
- // the mapping of entity id to index in this.crate['@graph]
116
- this.entityIdIndex = {};
117
-
118
- // reference to a context that has been provided
119
- // this takes precedence over anything else
120
- this.providedContext = undefined;
121
-
122
- // otherwise, Crate Manager will manage the context
123
- this.contextDefinitions = {};
124
- this.localContext = {};
125
-
126
- // entity types in the crate - for browse entities; filter by type
127
- this.entityTypes = {};
128
-
129
- // should entity created and updated timestamps be automatically managed / added
130
- this.entityTimestamps = entityTimestamps;
131
-
132
- // keep track of blank nodes for when we mint new ones
133
- // this.blankNodes = [ '_:Relationship1', '_:Relationship2', '_:CreateAction1', '_:CreateAction2', ... ]
134
- this.blankNodes = [];
135
-
136
- this.graphLength = 0;
137
-
138
- // entity core properties
139
- this.coreProperties = ["@id", "@type", "@reverse", "name"];
140
- this.errors = {
141
- hasError: false,
142
- init: {
143
- description: `Errors encountered on crate load. These need to be fixed manually.`,
144
- messages: [],
145
- },
146
- missingIdentifier: {
147
- description: `The entity does not have an identifier (@id).`,
148
- entity: [],
149
- },
150
- missingTypeDefinition: {
151
- description: `The entity does not have a defined type (@type).`,
152
- entity: [],
153
- },
154
- invalidIdentifier: {
155
- description: `The entity identifier (@id) is not valid. See https://github.com/describo/crate-builder-component/blob/master/README.identifiers.md for more information`,
156
- entity: [],
157
- },
158
- clash: {
159
- description: `The entity already exists and has clash errors.`,
160
- messages: [],
161
- }
162
- };
163
- this.warnings = {
164
- hasWarning: false,
165
- init: {
166
- description: `Issues encountered on crate load that should be fixed but aren't breaking`,
167
- messages: [],
168
- },
169
- invalidIdentifier: {
170
- description: `The entity identifier (@id) has spaces in it that should be encoded. Describo will do this to pass the validate test but the data must be corrected manually.`,
171
- entity: [],
172
- },
173
- clash: {
174
- description: `The entity already exists in the graph. It will be ignored.`,
175
- messages: [],
176
- }
177
- };
178
- const t0 = performance.now();
179
-
180
- // verify some basic structural elements exist
181
- if (!crate["@context"]) {
182
- this.__setError("init", `The crate file does not have a '@context'.`);
183
- return;
184
- }
185
- if (!crate["@graph"] || !isArray(crate["@graph"])) {
186
- this.__setError("init", `The crate file does not have '@graph' or it's not an array.`);
187
- return;
188
- }
189
-
190
- // number of entities in the graph
191
- this.graphLength = crate["@graph"].length;
192
-
193
- // iterate over the graph find the root descriptor first
194
- for (let i = 0; i < this.graphLength; i++) {
195
- if (crate["@graph"][i]["@id"] === "ro-crate-metadata.json") {
196
- this.rootDescriptor = i;
197
-
198
- // while we're here
199
- // check that about exists and is an object not an array
200
- if ("about" in crate["@graph"][i]) {
201
- if (Array.isArray(crate["@graph"][i].about)) {
202
- crate["@graph"][i].about = (crate["@graph"][i].about as any[])[0];
203
- }
204
- } else {
205
- this.__setError(
206
- "init",
207
- `This crate is invalid. The root descriptor does not have an about property.`
208
- );
209
- }
210
-
211
- // check that conformsTo exists and is an object not an array
212
- if (!("conformsTo" in crate["@graph"][i])) {
213
- this.__setWarning(
214
- "init",
215
- `This root descriptor does not specify 'conformsTo'. It will be set to RO Crate v1.1`
216
- );
217
- crate["@graph"][i].conformsTo = { "@id": "https://w3id.org/ro/crate/1.1" };
218
- }
219
-
220
- break;
221
- }
222
- }
223
- // if we haven't located a root descriptor; bail - this crate is borked
224
- if (this.rootDescriptor === undefined) {
225
- this.__setError(
226
- "init",
227
- `This crate is invalid. A root descriptor can not been identified.`
228
- );
229
- return;
230
- }
231
-
232
- // now locate the root dataset and ensure the id is sensible
233
- const rootDescriptor = crate["@graph"][this.rootDescriptor];
234
- for (let i = 0; i < this.graphLength; i++) {
235
- if (
236
- isEntityReference(rootDescriptor.about) &&
237
- crate["@graph"][i]["@id"] === rootDescriptor.about["@id"]
238
- ) {
239
- this.rootDataset = i;
240
-
241
- // set the root dataset @id to './'
242
- crate["@graph"][i]["@id"] = "./";
243
- rootDescriptor.about["@id"] = "./";
244
- break;
245
- }
246
- }
247
-
248
- if (this.rootDataset === undefined) {
249
- this.__setError("init", `This crate is invalid. A root dataset can not be identified.`);
250
- return;
251
- }
252
-
253
- // as we process the graph, we do our best to normalise the entities
254
- // this includes ensuring the id is valid and sensible
255
- // if it's not, it might get rewritten
256
- // if that happens, we need to ensure any links in the graph are also
257
- // rewritten to the new identifier
258
- const idMap: { [key: string]: string } = {};
259
-
260
- // process the the graph
261
- for (let i = 0; i < this.graphLength; i++) {
262
- // if the entity is empty - ignore it
263
- if (isPlainObject(crate["@graph"][i]) && isEmpty(crate["@graph"][i])) continue;
264
-
265
- try {
266
- const normalisedEntity: NormalisedEntityDefinition = normalise(
267
- crate["@graph"][i],
268
- i
269
- );
270
- // store the old to new mappings
271
- idMap[(crate["@graph"][i] as EntityReference)["@id"]] = normalisedEntity["@id"];
272
-
273
- // is it a blank node? Store it if it is
274
- if (normalisedEntity["@id"].match(/^_:/)) {
275
- this.blankNodes.push(normalisedEntity["@id"]);
276
- }
277
- // copy the entity into the internal structure
278
- this.crate["@graph"].push(structuredClone(normalisedEntity));
279
-
280
- // create the id to index reference
281
- this.entityIdIndex[normalisedEntity["@id"]] = this.crate["@graph"].length - 1;
282
- this.reverse[normalisedEntity["@id"]] = {};
283
-
284
- // store the entity type for lookups by type
285
- this.__storeEntityType(normalisedEntity);
286
- } catch (error) {
287
- this.__setError(
288
- "init",
289
- `Ignoring bad entity ${JSON.stringify({ "@id": crate["@graph"][i]["@id"], "@type": crate["@graph"][i]["@type"] })}`
290
- );
291
- }
292
- }
293
- // console.log(this.crate["@graph"]);
294
- // console.log(this.entityIdIndex);
295
- // console.log(this.reverse);
296
- // console.log(this.entityTypes);
297
-
298
- // one final iteration over the crate to record the reverse links
299
- for (let i = 0; i < this.graphLength; i++) {
300
- const normalisedEntity = this.crate["@graph"][i] as NormalisedEntityDefinition;
301
- if (!normalisedEntity) continue;
302
-
303
- for (let property of Object.keys(normalisedEntity)) {
304
- if (this.coreProperties.includes(property)) continue;
305
-
306
- (normalisedEntity[property] as any[]).forEach((entry) => {
307
- // remap any id's that were changed
308
- if (entry?.["@id"])
309
- entry["@id"] = idMap[entry["@id"]] ? idMap[entry["@id"]] : entry["@id"];
310
-
311
- // set up reverse links
312
- if (entry?.["@id"] && this.reverse[entry["@id"]]) {
313
- if (!this.reverse[entry["@id"]][property]) {
314
- this.reverse[entry["@id"]][property] = [];
315
- }
316
- let links = this.reverse[entry["@id"]][property].map(
317
- (l: { "@id": string }) => l["@id"]
318
- );
319
- if (!links.includes(normalisedEntity["@id"])) {
320
- this.reverse[entry["@id"]][property].push({
321
- "@id": normalisedEntity["@id"],
322
- });
323
- }
324
- }
325
- });
326
- }
327
- }
328
-
329
- if (this.errors.hasError) {
330
- return;
331
- }
332
-
333
- // assemble all the definitions for use managing the local context
334
- this.crate["@context"] = this.__normaliseContext(crate["@context"]);
335
- this.contextDefinitions = this.__collectAllDefinitions(this.crate["@context"]);
336
- if (context) {
337
- // if we're given a context, store it for use later
338
- this.providedContext = structuredClone(this.__normaliseContext(context));
339
- }
340
-
341
- const t1 = performance.now();
342
- console.debug(`Crate ingest and prep: ${round(t1 - t0, 1)}ms`);
343
- }
344
-
345
- /** Get the context
346
- * @returns the crate context
347
- */
348
- getContext(): NormalisedContext {
349
- if (this.providedContext) {
350
- return structuredClone(this.providedContext);
351
- } else {
352
- let context = this.crate["@context"] as UnverifiedContext;
353
- if (!isEmpty(this.localContext)) {
354
- context = [...context, this.localContext];
355
- context = this.__normaliseContext(context);
356
- }
357
- return structuredClone(context);
358
- }
359
- }
360
-
361
- /**
362
- * Set the context
363
- *
364
- * This is equivalent to a profile author setting a context. It gets
365
- * used as is and data updates going forward do not get dealth with.
366
- * @param {*} context
367
- */
368
- setContext(context: NormalisedContext) {
369
- this.providedContext = this.__normaliseContext(context);
370
- }
371
-
372
- /**
373
- * Set a profile
374
- *
375
- * CrateManager can set reverse associations if defined in a profile.
376
- *
377
- * */
378
- setProfileManager(pm: ProfileManagerType) {
379
- this.pm = pm;
380
-
381
- if (this.pm.profile.context) {
382
- // if we're given a context, store it for use later
383
- this.providedContext = structuredClone(
384
- this.__normaliseContext(this.pm.profile.context)
385
- );
386
- }
387
- }
388
-
389
- /**
390
- * Get the root dataset
391
- *
392
- * @returns the root dataset entity
393
- * @example
394
-
395
- const cm = new CrateManager({ crate })
396
- let rd = cm.getRootDataset()
397
-
398
- */
399
- getRootDataset(): NormalisedEntityDefinition {
400
- let rootDataset = structuredClone(this.crate["@graph"][this.rootDataset as number]);
401
- rootDataset["@reverse"] = structuredClone(this.reverse[rootDataset["@id"]]);
402
- return rootDataset;
403
- }
404
-
405
- getRootDescriptor(): NormalisedEntityDefinition {
406
- let rootDescriptor = structuredClone(this.crate["@graph"][this.rootDescriptor as number]);
407
- rootDescriptor["@reverse"] = structuredClone(this.reverse[rootDescriptor["@id"]]);
408
- return rootDescriptor;
409
- }
410
-
411
- /**
412
- * Get an entity
413
- *
414
- * @param {Object} options
415
- * @param {string} options.id - the id of the entity to get
416
- * @param {boolean} options.stub - if true, only the `@id, @type and name` prop's will be returned. That is,
417
- * you get a stub entry not the complete entity data.
418
- * @param {boolean} options.link - if true, all the associated entities are filled out as stubs
419
- * @param {boolean} options.materialise - if true, the entity will be created if it doesn't exist (consider
420
- * when URL's point outside the crate, in this case, they will be created as entities inside it)
421
- * @returns the entity
422
- *
423
- * @example
424
-
425
- const cm = new CrateManager({ crate })
426
-
427
- // get the full entity
428
- let rd = cm.getEntity({ id: './' })
429
-
430
- // return a stub entry
431
- rd = cm.getEntity({ id: './', stub: true })
432
-
433
- */
434
- getEntity({
435
- id,
436
- stub = false,
437
- link = true,
438
- materialise = true,
439
- }: {
440
- id: string;
441
- stub?: boolean;
442
- link?: boolean;
443
- materialise?: boolean;
444
- }): NormalisedEntityDefinition | undefined {
445
- if (!id) throw new Error(`An id must be provided`);
446
- let indexRef = this.entityIdIndex[id];
447
-
448
- if (indexRef === undefined && !materialise) {
449
- return undefined;
450
- }
451
- if (indexRef === undefined && materialise) {
452
- // id's pointing outside the crate won't resolve so we
453
- // 'materialise' them here
454
- return this.__materialiseEntity({ id });
455
- }
456
-
457
- // otherwise we found it so return it
458
- let entity = structuredClone(this.crate["@graph"][indexRef]);
459
-
460
- // encode the id
461
- id = encodeURI(id);
462
-
463
- entity["@reverse"] = structuredClone(this.reverse[entity["@id"]]) ?? {};
464
- if (stub) {
465
- return { "@id": entity["@id"], "@type": entity["@type"], name: entity.name };
466
- }
467
- if (link) {
468
- for (let property of Object.keys(entity)) {
469
- if (this.coreProperties.includes(property)) continue;
470
- entity[property] = entity[property].map((value: EntityReference) => {
471
- if (value?.["@id"]) {
472
- return this.getEntity({ id: value["@id"], stub: true });
473
- }
474
- return value;
475
- });
476
- }
477
- }
478
- return entity;
479
- }
480
-
481
- /**
482
- * Get Entity Types
483
- *
484
- * @returns an array of entity types, sorted, found in the crate
485
- */
486
- getEntityTypes(): string[] {
487
- return Object.keys(this.entityTypes).sort();
488
- }
489
-
490
- /**
491
- * @generator
492
- *
493
- * @param {Object} params
494
- * @param {Number} params.limit - how many entities to return
495
- * @param {string} params.query - a string to match against @id, @type and name
496
- * @param {string} params.type - a string to match against @type
497
- * @yields {entity}
498
- * @example
499
-
500
- const cm = new CrateManager({crate})
501
- let entities = cm.getEntities()
502
-
503
- for (let entity of entities) {
504
- ...
505
- }
506
-
507
- To get an array just spread the return
508
- let entities = [ ...cm.getEntities() ]
509
-
510
- // query @id, @type and name
511
- entities = cm.getEntities({ query: 'person' })
512
-
513
- // query @id, @type and name for entites of type Person
514
- entities = cm.getEntities({ query: 'person', type: 'Person' })
515
-
516
- // query @id, @type and name for entites of type Person - limit 10 matches
517
- entities = cm.getEntities({ query: 'person', type: 'Person', limit: 10 })
518
-
519
- */
520
- *getEntities(
521
- params: { limit?: number; query?: string; type?: string } = {
522
- limit: undefined,
523
- query: undefined,
524
- type: undefined,
525
- }
526
- ): Generator<NormalisedEntityDefinition> {
527
- let { limit, query, type } = params;
528
-
529
- if (!isString(query) && !isUndefined(query)) {
530
- throw new Error(`query must be a string`);
531
- }
532
- if (!isString(type) && !isUndefined(type)) {
533
- throw new Error(`type must be a string`);
534
- }
535
- if (query) {
536
- query = query.toLowerCase();
537
- }
538
- if (type === "ANY") type = undefined;
539
-
540
- let count = 0;
541
- for (let i = 0; i < this.graphLength; i++) {
542
- let entity = this.crate["@graph"][i];
543
- if (!entity) continue;
544
- if (query || type) {
545
- let eid = entity["@id"].toLowerCase();
546
- let etype = isArray(entity["@type"])
547
- ? (entity["@type"].join(", ") as string).toLowerCase()
548
- : (entity["@type"] as string).toLowerCase();
549
- let name = String(entity.name).toLowerCase();
550
- if (type && !query) {
551
- type = type.toLowerCase();
552
- if (etype.match(type)) {
553
- yield structuredClone(entity);
554
- count += 1;
555
- }
556
- } else if (query && !type) {
557
- if (eid.match(query) || name.match(query)) {
558
- yield structuredClone(entity);
559
- count += 1;
560
- }
561
- } else if (query && type) {
562
- type = type.toLowerCase();
563
- if (etype.match(type) && (eid.match(query) || name.match(query))) {
564
- yield structuredClone(entity);
565
- count += 1;
566
- }
567
- }
568
- } else {
569
- if (entity) {
570
- yield structuredClone(entity);
571
- count += 1;
572
- }
573
- }
574
-
575
- if (limit && count === limit) return;
576
- }
577
- }
578
-
579
- /**
580
- *
581
- * locateEntity
582
- *
583
- * Given a set of id's, find the entity or entities that link to all of them.
584
- * This is really for finding grouping type entities like Relationships and Actions so that
585
- * you can augment their description.
586
- * @param { object } params
587
- * @param { array } params.entityIds - an array of entity id's that are linked to from another entity
588
- * @param { boolean } params.strict - if true return entities that have exactly entityIds linked. If false,
589
- * return entities that have at least entityIds linked
590
- * @returns [] entities matching or undefined
591
- */
592
-
593
- locateEntity({
594
- entityIds,
595
- strict = true,
596
- }: {
597
- entityIds: string[];
598
- strict?: boolean;
599
- }): NormalisedEntityDefinition[] | undefined {
600
- // encode entityIds
601
- entityIds = entityIds.map((eid) => encodeURI(eid));
602
-
603
- // console.log(entityIds);
604
- // get one id and use that to resolve what it links to
605
- let entity = this.getEntity({ id: entityIds[0], materialise: false });
606
-
607
- // if it doesn't link to anything then there's no match
608
- if (!entity) return undefined;
609
-
610
- // if it does, for each match walk the entity forward and find out what it links to
611
- // console.log(entity, this.reverse);
612
- let thisEntityLinksTo = this.reverse[entity["@id"]];
613
-
614
- let matches: { [key: string]: string[] } = {};
615
- for (let property of Object.keys(thisEntityLinksTo)) {
616
- for (let e of thisEntityLinksTo[property]) {
617
- matches[e["@id"]] = [];
618
- }
619
- }
620
-
621
- let entityMatches: NormalisedEntityDefinition[] = [];
622
- for (let entityId of Object.keys(matches)) {
623
- let entity = this.getEntity({ id: entityId });
624
- if (entity) {
625
- for (let property of Object.keys(entity)) {
626
- if (this.coreProperties.includes(property)) continue;
627
- for (let instance of entity[property]) {
628
- if (!isEntityReference(instance)) continue;
629
- if ((instance as EntityReference)?.["@id"])
630
- matches[entityId].push(instance["@id"]);
631
- }
632
- }
633
- if (strict) {
634
- // if strict is true then check if the linked entities match exactly
635
- if (isEqual(matches[entityId].sort(), entityIds.sort()))
636
- entityMatches.push(entity);
637
- } else {
638
- // otherwise, check that entityIds is a subset of matches
639
- if (intersection(matches[entityId], entityIds).length === entityIds.length) {
640
- entityMatches.push(entity);
641
- }
642
- }
643
- }
644
- }
645
- return entityMatches;
646
- }
647
-
648
- /**
649
- * resolveLinkedEntities
650
- *
651
- * Given an entity and a profile, if the entity matches a resolve configuration
652
- * this method will populate the entities linked from the resolve properties
653
- * defined in the profile.
654
- *
655
- * @param {Object} entity
656
- * @param {Object} profile
657
- *
658
- * @returns an array of associations
659
- * @example
660
-
661
- // given an entity
662
- let entity = {
663
- "@id": "#createAction1",
664
- "@type": ["CreateAction"],
665
- name: "A very long named create action to demonstrate what happens with display of long names",
666
- object: { "@id": "#person2" },
667
- participant: { "@id": "#participant1" },
668
- agent: { "@id": "#agent1" },
669
- };
670
-
671
- // and a profile with a resolve configuration
672
- let profile = {
673
- ...
674
- resolve: [
675
- {
676
- types: [ 'Relationship', 'Related' ],
677
- properties: [ 'source', 'target' ]
678
- },
679
- {
680
- types: [ 'CreateAction', 'EditAction' ],
681
- properties: [ 'object', 'participant', 'agent' ]
682
- }
683
- ]
684
- ...
685
- }
686
-
687
- // get a list of the associated entities
688
- console.log(cm.resolveLinkedEntityAssociations({ entity, profile }))
689
-
690
-
691
- associations === [
692
- {
693
- property: 'object',
694
- '@id': '#person2',
695
- '@type': [ 'Thing' ],
696
- name: '#person2'
697
- },
698
- {
699
- property: 'participant',
700
- '@id': '#participant1',
701
- '@type': [ 'Thing' ],
702
- name: '#participant1'
703
- },
704
- {
705
- property: 'agent',
706
- '@id': '#agent1',
707
- '@type': [ 'Thing' ],
708
- name: '#agent1'
709
- }
710
- ]
711
-
712
- */
713
- resolveLinkedEntityAssociations({
714
- entity,
715
- profile,
716
- }: {
717
- entity: NormalisedEntityDefinition;
718
- profile: NormalisedProfile;
719
- }):
720
- | {
721
- property: string;
722
- "@id": string;
723
- "@type": string[];
724
- name: string;
725
- }[]
726
- | [] {
727
- if (!profile?.resolve) return [];
728
- let resolveConfiguration = profile.resolve;
729
- const resolvers: { [key: string]: any } = {};
730
- resolveConfiguration.forEach((c) => {
731
- c.types.forEach((type) => {
732
- resolvers[type] = c.properties;
733
- });
734
- });
735
-
736
- // does the entity @type overlap with a resolve configuration?
737
- const match = intersection(Object.keys(resolvers), entity["@type"]);
738
- if (!match.length) {
739
- // the current entity does match a resolve definition
740
- return [];
741
- }
742
-
743
- // what properties need to be resolved?
744
- const propertiesToResolve = flattenDeep(match.map((type) => resolvers[type]));
745
-
746
- let associations: Array<{
747
- property: string;
748
- "@id": string;
749
- "@type": string[];
750
- name: string;
751
- }> = [];
752
-
753
- for (let property of Object.keys(entity)) {
754
- // skip core prop's and any prop not specifically configured to resolve
755
- if (this.coreProperties.includes(property)) continue;
756
- if (!propertiesToResolve.includes(property)) continue;
757
-
758
- // resolve away
759
- let values = [].concat(entity[property] as any);
760
- values.forEach((value) => {
761
- if (!isPlainObject(value) || !("@id" in value)) return value;
762
- associations.push({
763
- property,
764
- ...(this.getEntity({
765
- id: value["@id"],
766
- stub: true,
767
- }) as NormalisedEntityDefinition),
768
- });
769
- });
770
- }
771
- return associations;
772
- }
773
-
774
- /**
775
- * Add an entity to the graph
776
- *
777
- * The entity must have '@id' and '@type' defined.
778
- *
779
- * @param {Object} entity - an entity definition to add to the crate
780
- * @returns the entity
781
- * @example
782
-
783
- const cm = new CrateManager({ crate })
784
- let entity = {
785
- "@id": '#e1',
786
- "@type": "Person",
787
- name: 'person1',
788
- };
789
- let r = cm.addEntity(entity);
790
-
791
- */
792
- addEntity(entity: UnverifiedEntityDefinition): NormalisedEntityDefinition {
793
- if (!("@id" in entity)) {
794
- throw new Error(`You can't add an entity without an identifier: '@id'.`);
795
- }
796
- if (!("@type" in entity)) {
797
- throw new Error(`You can't add an entity without defining the type : '@type'.`);
798
- }
799
-
800
- const e = structuredClone(entity);
801
- let normalisedEntity = normalise(e, this.graphLength);
802
- const noClashCheck = this.__confirmNoClash({
803
- entity: normalisedEntity,
804
- }) as NormalisedEntityDefinition;
805
- if (!noClashCheck) {
806
- this.__setWarning("clash", `The entity ${normalisedEntity["@id"]} already exists. Crate set back to it's original state.`);
807
- return this.getEntity({ id: normalisedEntity["@id"] }) as NormalisedEntityDefinition;
808
- } else {
809
- this.warnings.hasWarning = false;
810
- this.warnings.clash.messages = [];
811
- }
812
-
813
- // set all properties, other than core props, to array
814
- for (let property of Object.keys(normalisedEntity)) {
815
- if (this.coreProperties.includes(property)) continue;
816
-
817
- // ensure it's an array
818
- entity[property] = [].concat(normalisedEntity[property] as []);
819
-
820
- // then filter out empty properties with empty strings
821
- normalisedEntity[property] = (entity[property] as []).filter((p) => p !== "");
822
- }
823
-
824
- // manage timestamps
825
- if (this.entityTimestamps) {
826
- normalisedEntity[entityDateCreatedProperty] = [new Date().toISOString()];
827
- normalisedEntity[entityDateUpdatedProperty] = [new Date().toISOString()];
828
- }
829
-
830
- // push it into the graph
831
- this.crate["@graph"].push(normalisedEntity);
832
-
833
- // create the index and reverse lookup entries
834
- this.graphLength = this.crate["@graph"].length;
835
- this.entityIdIndex[normalisedEntity["@id"]] = this.graphLength - 1;
836
- this.reverse[normalisedEntity["@id"]] = {};
837
- this.__storeEntityType(normalisedEntity);
838
-
839
- return normalisedEntity;
840
- }
841
-
842
- /**
843
- * Add an entity with a blank node id ('_:...') to the graph.
844
- *
845
- * Use this when you want to add a non contextual entity to the graph. In thoses
846
- * cases providing an `@id` and name don't really make sense even though those properties are still required
847
- * therefore this method simplifies the process of adding those entity types. For example,
848
- * Actions (e.g. CreateAction), Relationships, GeoShape, GeoCoordinates etc
849
- *
850
- * @param {string} type - the entity type to configure for the new entity
851
- * @returns the entity
852
- * @example
853
-
854
- const cm = new CrateManager({ crate })
855
- let r = cm.addBlankNode('Relationship);
856
-
857
- r === {
858
- '@id': '_:Relationship1',
859
- '@type': [ 'Relationship' ],
860
- name: '_:Relationship1'
861
- }
862
-
863
- */
864
- addBlankNode(type: string): NormalisedEntityDefinition {
865
- const blankNodeTypeMatches = this.blankNodes.filter((n) => n.match(type));
866
- const id = `_:${type}${blankNodeTypeMatches.length + 1}`;
867
- this.blankNodes.push(id);
868
- let entity = {
869
- "@id": id,
870
- "@type": type,
871
- name: id,
872
- };
873
- return this.addEntity(entity as UnverifiedEntityDefinition);
874
- }
875
-
876
- /**
877
- * Add files or folders - use addFile or addFolder in preference to this
878
- *
879
- * This is a helper method specifically for adding files and folders in the crate. This method
880
- * will add the intermediate paths as 'Datasets' (as per the spec) and link everything via the hasPart
881
- * property as required. It is assumed that the path is relative to the root of the folder.
882
- *
883
- * @param {object} params
884
- * @param {string} params.path - a file or folder path
885
- * @param {string} params.type - the type of thing being added - File or Dataset
886
- * @returns the entity
887
- * @example
888
-
889
- const cm = new CrateManager({ crate })
890
- let r = cm.addFileOrFolder('/a/b/c/file.txt);
891
- */
892
- addFileOrFolder({
893
- path,
894
- type = "File",
895
- }: {
896
- path: string;
897
- type: string;
898
- }): NormalisedEntityDefinition {
899
- if (!["File", "Dataset"].includes(type)) {
900
- throw new Error(`'addFileOrFolder' type must be File or Dataset`);
901
- }
902
-
903
- // remove preceding / and ./
904
- if (path.match(/^\.\//)) path = path.replace(/^\.\//, "");
905
- if (path.match(/^\//)) path = path.replace(/^\//, "");
906
-
907
- // ensure folders end in '/'
908
- if (type === "Dataset" && !path.match(/.*\/$/)) {
909
- path = `${path}/`;
910
- }
911
-
912
- // remove initial slash if there is one
913
- if (path.match(/^\//)) {
914
- path = path.substring(1);
915
- }
916
-
917
- // create the file path as individual datasets before joining
918
- // the file into the right place
919
- let paths: string[] = path.split("/").slice(0, -1);
920
- let i = 0;
921
- const entities = [];
922
- for (let p of paths) {
923
- let entity = {
924
- "@id": i > 0 ? `${paths.slice(0, i).join("/")}/${p}/` : `${p}/`,
925
- "@type": ["Dataset"],
926
- name: i > 0 ? `${paths.slice(0, i).join("/")}/${p}/` : `${p}/`,
927
- };
928
- entity["@id"] = encodeURI(entity["@id"]);
929
- if (entity["@id"] === "./") continue;
930
- let normalisedEntity = this.addEntity(entity as UnverifiedEntityDefinition);
931
- i += 1;
932
- entities.push(normalisedEntity);
933
- }
934
- i = 0;
935
- for (let e of entities) {
936
- if (i === 0) {
937
- this.linkEntity({
938
- id: "./",
939
- property: "hasPart",
940
- propertyId: "https://schema.org/hasPart",
941
- value: { "@id": e["@id"] },
942
- });
943
- } else {
944
- this.linkEntity({
945
- id: entities[i - 1]["@id"],
946
- property: "hasPart",
947
- propertyId: "https://schema.org/hasPart",
948
- value: { "@id": e["@id"] },
949
- });
950
- }
951
- i += 1;
952
- }
953
-
954
- let entity = {
955
- "@id": encodeURI(path),
956
- "@type": [type],
957
- name: path,
958
- };
959
- if (type === "File") {
960
- let id = "./";
961
- if (entities.length) {
962
- id = entities.slice(-1)[0]?.["@id"];
963
- }
964
-
965
- const sourceEntity = this.getEntity({
966
- id,
967
- stub: true,
968
- }) as NormalisedEntityDefinition;
969
- this.ingestAndLink({
970
- id: sourceEntity["@id"],
971
- property: "hasPart",
972
- propertyId: "https://schema.org/hasPart",
973
- json: entity as UnverifiedEntityDefinition,
974
- });
975
- }
976
- return this.getEntity({ id: entity["@id"], stub: true }) as NormalisedEntityDefinition;
977
- }
978
-
979
- /**
980
- * Add file
981
- *
982
- * This is a helper method specifically for adding files to the crate. This method
983
- * will add the intermediate paths as 'Datasets' (as per the spec) and link everything via the hasPart
984
- * property as required. It is assumed that the path is relative to the root of the folder.
985
- *
986
- * @param {string} path - a file path to add - ensure the file path is relative to the folder root
987
- * @returns the entity
988
- * @example
989
-
990
- const cm = new CrateManager({ crate })
991
- let r = cm.addFile('/a/b/c/file.txt);
992
- */
993
- addFile(path: string): NormalisedEntityDefinition {
994
- return this.addFileOrFolder({ path, type: "File" });
995
- }
996
-
997
- /**
998
- * Add folder
999
- *
1000
- * This is a helper method specifically for adding folders to the crate. This method
1001
- * will add the intermediate paths as 'Datasets' (as per the spec) and link everything via the hasPart
1002
- * property as required. It is assumed that the path is relative to the root of the folder.
1003
- *
1004
- * @param {string} path - a folder path to add - ensure the folder path is relative to the folder root
1005
- * @returns the entity
1006
- * @example
1007
-
1008
- const cm = new CrateManager({ crate })
1009
- let r = cm.addFolder('/a/b/c);
1010
- */
1011
- addFolder(path: string): NormalisedEntityDefinition {
1012
- return this.addFileOrFolder({ path, type: "Dataset" });
1013
- }
1014
-
1015
- /**
1016
- * Delete an entity
1017
- *
1018
- * @param {Object} options
1019
- * @param {string} options.id - the id of the entity to delete from the crate
1020
- *
1021
- * @returns true if successful
1022
- * @example
1023
-
1024
- const cm = new CrateManager({ crate })
1025
- cm.deleteEntity({ id: '#e1' })
1026
-
1027
- */
1028
- deleteEntity({ id }: { id: string }) {
1029
- if (!id) throw new Error(`'deleteEntity' requires 'id' to be defined`);
1030
- if (
1031
- [
1032
- (this.crate["@graph"][this.rootDescriptor as number] as NormalisedEntityDefinition)[
1033
- "@id"
1034
- ],
1035
- (this.crate["@graph"][this.rootDataset as number] as NormalisedEntityDefinition)[
1036
- "@id"
1037
- ],
1038
- ].includes(id)
1039
- ) {
1040
- throw new Error(`You can't delete the root dataset or the root descriptor.`);
1041
- }
1042
-
1043
- const indexRef = this.entityIdIndex[id];
1044
- const entity = this.crate["@graph"][indexRef];
1045
- if (!entity) return;
1046
-
1047
- this.__removeEntityType(entity);
1048
-
1049
- // get the entity, find what it links to and remove it from the reverse of those linkages
1050
- for (let [property, instances] of Object.entries(entity as NormalisedEntityDefinition)) {
1051
- if (this.coreProperties.includes(property)) continue;
1052
- for (let instance of instances) {
1053
- if (!isEntityReference(instance)) continue;
1054
- if (instance?.["@id"] && this.reverse[instance["@id"]]) {
1055
- this.reverse[instance["@id"]][property] = this.reverse[instance["@id"]][
1056
- property
1057
- ].filter((i: any) => i["@id"] !== id);
1058
-
1059
- // remove the property if it's empty
1060
- if (!this.reverse[instance["@id"]][property].length) {
1061
- delete this.reverse[instance["@id"]][property];
1062
- }
1063
- }
1064
- }
1065
- }
1066
-
1067
- // now do the same by walking the reverse links from this entity
1068
- for (let [property, instances] of Object.entries(
1069
- this.reverse[entity["@id"]] as NormalisedEntityDefinition
1070
- )) {
1071
- if (this.coreProperties.includes(property)) continue;
1072
- for (let instance of instances) {
1073
- if (!isEntityReference(instance)) continue;
1074
- if (instance["@id"]) {
1075
- let linkIndexRef = this.entityIdIndex[instance["@id"]];
1076
- let linkEntity = this.crate["@graph"][linkIndexRef];
1077
- if (linkEntity) {
1078
- linkEntity[property] = linkEntity[property].filter((i) => {
1079
- if (isEntityReference(i)) {
1080
- return i["@id"] !== id;
1081
- } else {
1082
- return i;
1083
- }
1084
- });
1085
-
1086
- // remove the property if it's empty
1087
- if (!linkEntity[property].length) {
1088
- delete linkEntity[property];
1089
- }
1090
- }
1091
- }
1092
- }
1093
- }
1094
-
1095
- delete this.entityIdIndex[id];
1096
- delete this.reverse[id];
1097
- this.crate["@graph"][indexRef] = undefined;
1098
-
1099
- return true;
1100
- }
1101
-
1102
- /**
1103
- * Set a property on an entity
1104
- *
1105
- * @param {Object} options
1106
- * @param {options.id} options.id - the id of the entity to add the property to
1107
- * @param {options.property } options.property - the property to add
1108
- * @param {options.value} options.value - the data to add to that property. Can be string or object with '@id'
1109
- * @returns true if successful
1110
- * @example
1111
-
1112
- const cm = new CrateManager({ crate })
1113
- const authorId = chance.url();
1114
-
1115
- // setting an object reference
1116
- cm.setProperty({ id: "./", property: "author", value: { "@id": authorId } });
1117
-
1118
- // setting a text string
1119
- cm.setProperty({ id: "./", property: "author", value: "text" });
1120
-
1121
- // setting a number
1122
- cm.setProperty({ id: "./", property: "author", value: 3 });
1123
-
1124
- */
1125
- setProperty({
1126
- id,
1127
- property,
1128
- propertyId,
1129
- value,
1130
- }: {
1131
- id: string;
1132
- property: string;
1133
- propertyId?: string;
1134
- value: PrimitiveType | EntityReference;
1135
- }): boolean | undefined {
1136
- if (this.coreProperties.includes(property)) {
1137
- throw new Error(`This method does not operate on ${this.coreProperties.join(", ")}`);
1138
- }
1139
- if (!id) throw new Error(`'setProperty' requires 'id' to be defined`);
1140
- if (!property) throw new Error(`setProperty' requires 'property' to be defined`);
1141
- if (value !== false && !value)
1142
- throw new Error(`'setProperty' requires 'value' to be defined`);
1143
-
1144
- // just don't set an empty object on a property
1145
- if (isPlainObject(value) && isEmpty(value)) return;
1146
-
1147
- const indexRef = this.entityIdIndex[id];
1148
- const entity = this.crate["@graph"][indexRef];
1149
- if (!entity) return;
1150
-
1151
- if (!(property in entity)) {
1152
- entity[property] = [];
1153
- }
1154
- // validate the value's shape - v. important
1155
- if (isString(value) || isNumber(value) || isBoolean(value)) {
1156
- (entity[property] as any[]).push(value);
1157
- } else if (isPlainObject(value) && "@id" in value) {
1158
- // value makes sense
1159
- // but make sure it's only the id and not the whole entity
1160
- value = { "@id": value["@id"] };
1161
-
1162
- // and don't add duplicates
1163
- let ids = entity[property]
1164
- .filter((v) => (v as EntityReference)?.["@id"])
1165
- .map((v) => (v as EntityReference)?.["@id"]);
1166
- if (!ids.includes(value["@id"])) entity[property].push(value as any);
1167
-
1168
- // and add a @reverse link
1169
- this.__addReverse({ id, property, value });
1170
- } else if(isArray(value)) {
1171
- // In case of handlesMultipleValues inputs, like checkbox select the value is an array itself.
1172
- // We need to call toRaw() on it to make it a normal array, ie. get rid of the vue array proxy
1173
- (entity[property] as any[]) = value;
1174
- } else {
1175
- // value doesn't make sense - bail
1176
- throw new Error(`value must be a string, number, boolean or object with '@id'`);
1177
- }
1178
- this.__updateContext({ name: property, id: propertyId });
1179
-
1180
- // manage timestamps
1181
- if (this.entityTimestamps) {
1182
- entity[entityDateUpdatedProperty] = [new Date().toISOString()];
1183
- }
1184
- return true;
1185
- }
1186
-
1187
- /**
1188
- * Update a property on an entity
1189
- *
1190
- * @param {Object} options
1191
- * @param {string} options.id - the id of the entity to add the property to
1192
- * @param {string} options.property - the property to add
1193
- * @param {string} options.idx - the idx of the property array to update
1194
- * @param {string} options.value - the data to add to that property, string or object with '@id'
1195
- * @example
1196
-
1197
- const cm = new CrateManager({ crate })
1198
- cm.updateProperty({ id: "./", property: "@id", value: "something else" });
1199
- cm.updateProperty({ id: "./", property: "author", idx: 1, value: "new" });
1200
-
1201
- */
1202
- updateProperty({
1203
- id,
1204
- property,
1205
- idx,
1206
- value,
1207
- }: {
1208
- id: string;
1209
- property: string;
1210
- idx?: number;
1211
- value: PrimitiveType | PrimitiveType[] | EntityReference;
1212
- }): NormalisedEntityDefinition | undefined | string {
1213
- console.log("updateProperty", { id, property, idx, value });
1214
- if (!id) throw new Error(`'setProperty' requires 'id' to be defined`);
1215
- if (!property) throw new Error(`setProperty' requires 'property' to be defined`);
1216
- if (value !== false && !value)
1217
- throw new Error(`'setProperty' requires 'value' to be defined`);
1218
- console.log("updateProperty", { id, property, idx, value });
1219
- // just don't set an empty object on a property
1220
- if (isPlainObject(value) && isEmpty(value)) return;
1221
-
1222
- let indexRef = this.entityIdIndex[id];
1223
- const entity = this.crate["@graph"][indexRef];
1224
- if (!entity) return;
1225
-
1226
- if (this.coreProperties.includes(property)) {
1227
- if (property === "@id") {
1228
- // First verify the new ID won't clash
1229
- const tempEntity = structuredClone(entity);
1230
- tempEntity["@id"] = value as string;
1231
-
1232
- const noClash = this.__confirmNoClash({
1233
- entity: tempEntity,
1234
- mintNewId: false
1235
- });
1236
-
1237
- if (!noClash) {
1238
- this.__setWarning("clash", `The entity ${tempEntity["@id"]} already exists. Crate set back to it's original state.`);
1239
- return this.getEntity({ id: tempEntity["@id"] }) as NormalisedEntityDefinition;
1240
- } else {
1241
- this.warnings.hasWarning = false;
1242
- this.warnings.clash.messages = [];
1243
- }
1244
-
1245
- // update @id
1246
- this.__updateEntityId({
1247
- oldId: (entity as EntityReference)?.["@id"],
1248
- newId: value as string,
1249
- });
1250
- } else if (property === "@type") {
1251
- // update @type
1252
- // ensure it's an array first though or weird sh*t happens
1253
- value = [].concat(value as any);
1254
-
1255
- // when updating the type we first need to clear out the
1256
- // old types and then set the new so we end up with correct
1257
- // reference counts
1258
- this.__removeEntityType(entity);
1259
- entity["@type"] = uniq(value) as [];
1260
- this.__storeEntityType(entity);
1261
- } else if (property === "name") {
1262
- // update name
1263
- // ensure we're setting a string value for the name property
1264
- if (isArray(value)) value = value.join(", ");
1265
- entity.name = String(value);
1266
- }
1267
- } else {
1268
- // In case of handlesMultipleValues inputs, like checkbox select the value is an array itself.
1269
- // We need to call toRaw() on it to make it a normal array, ie. get rid of the vue array proxy
1270
- if (isArray(value)) {
1271
- (entity[property] as any[]) = value;
1272
- } else {
1273
- if (idx !== 0 && !idx) throw new Error(`setProperty' requires 'idx' to be defined`);
1274
- (entity[property] as any[])[idx] = value;
1275
- }
1276
- }
1277
-
1278
- // manage timestamps
1279
- if (this.entityTimestamps) {
1280
- entity[entityDateUpdatedProperty] = [new Date().toISOString()];
1281
- }
1282
- return entity;
1283
- }
1284
-
1285
- /**
1286
- * Delete a specific property from the entity. That is, an instance of a property.
1287
- *
1288
- * @param {Object}
1289
- * @param {string} options.id - the id of the entity to remove the property value from
1290
- * @param {string} options.property - the property
1291
- * @param {string} options.idx - the idx of the property array to delete
1292
- * @example
1293
-
1294
- const cm = new CrateManager({ crate })
1295
- cm.deleteProperty({ id: "./", property: "author", idx: 1 });
1296
-
1297
- */
1298
- deleteProperty({ id, property, idx }: { id: string; property: string; idx?: number }) {
1299
- if (!id) throw new Error(`'deleteProperty' requires 'id' to be defined`);
1300
- if (!property) throw new Error(`deleteProperty' requires 'property' to be defined`);
1301
- // if (idx !== 0 && !idx) throw new Error(`deleteProperty' requires 'idx' to be defined`);
1302
-
1303
- let entity;
1304
- if (idx || idx === 0) {
1305
- // delete just that property instance
1306
- const indexRef = this.entityIdIndex[id];
1307
- entity = this.crate["@graph"][indexRef];
1308
- if (isUndefined(entity)) return;
1309
-
1310
- let { propertyDefinition } = this.pm.getPropertyDefinition({
1311
- property: property,
1312
- entity: entity,
1313
- });
1314
-
1315
- // In case of input, which handles multiple values on its own, like a checkbox style select, the array is
1316
- // handled as a single value, so we need to delete all values at once
1317
- // For other multivalued inputs, we delete one by one.
1318
- if (propertyDefinition.handlesMultipleValues === true) {
1319
- delete entity[property];
1320
- } else {
1321
- entity[property].splice(idx, 1);
1322
- if (!entity[property].length) delete entity[property];
1323
-
1324
- }
1325
- } else if (idx === undefined) {
1326
- const indexRef = this.entityIdIndex[id];
1327
- entity = this.crate["@graph"][indexRef];
1328
- if (isUndefined(entity)) return;
1329
-
1330
- if (!entity[property]) return;
1331
-
1332
- // iterate over the values and unlink any linked properties
1333
- entity[property].forEach((instance) => {
1334
- if ((instance as EntityReference)?.["@id"])
1335
- this.unlinkEntity({ id, property, value: instance as EntityReference });
1336
- });
1337
-
1338
- // now delete whatever is left
1339
- delete entity[property];
1340
- }
1341
-
1342
- // manage timestamps
1343
- if (this.entityTimestamps && !isUndefined(entity)) {
1344
- entity[entityDateUpdatedProperty] = [new Date().toISOString()];
1345
- }
1346
- }
1347
-
1348
- /**
1349
- * Ingest and link a nested json object
1350
- *
1351
- * @param {Object} options
1352
- * @param {string} options.id - the id of the entity to join the data into
1353
- * @param {string} options.property - the property to join the data into
1354
- * @param {string} options.propertyId - the propertyId of the property - ie the url to the definition
1355
- * @param {json} options.json - the data object to join in
1356
- * @example
1357
-
1358
- const cm = new CrateManager({ crate })
1359
- let json = {
1360
- ...,
1361
- }
1362
- cm.ingestAndLink({
1363
- id: "./",
1364
- property: "language",
1365
- json,
1366
- });
1367
-
1368
- */
1369
- ingestAndLink({
1370
- id = undefined,
1371
- property = undefined,
1372
- propertyId = undefined,
1373
- json = {},
1374
- }: {
1375
- id: string | undefined;
1376
- property: string | undefined;
1377
- propertyId: string | undefined;
1378
- json: UnverifiedEntityDefinition;
1379
- }) {
1380
- if (!id) throw new Error(`ingestAndLink: 'id' must be defined`);
1381
- if (!property) throw new Error(`ingestAndLink: 'property' must be defined`);
1382
-
1383
- let flattened = this.flatten(json);
1384
- let entities: NormalisedEntityDefinition[] = flattened.map(
1385
- (entity: UnverifiedEntityDefinition) => {
1386
- let normalisedEntity: NormalisedEntityDefinition = normalise(
1387
- entity,
1388
- this.graphLength
1389
- );
1390
- normalisedEntity = this.addEntity(normalisedEntity);
1391
- return normalisedEntity;
1392
- }
1393
- );
1394
-
1395
- this.linkEntity({ id, property, propertyId, value: { "@id": entities[0]["@id"] } });
1396
-
1397
- // go through and set all of the reverse links
1398
- for (let entity of entities) {
1399
- for (let property of Object.keys(entity)) {
1400
- if (this.coreProperties.includes(property)) continue;
1401
- (entity[property] as []).forEach((instance) => {
1402
- if (instance?.["@id"]) {
1403
- this.__addReverse({ id: entity["@id"], property, value: instance });
1404
- }
1405
- });
1406
- }
1407
- }
1408
- }
1409
-
1410
- /**
1411
- * Flatten a nested json object to an array
1412
- *
1413
- * @param {Object} json - a potentially nested data blob to flatten into array
1414
- * @returns an array of objects
1415
- * @example
1416
-
1417
- const cm = new CrateManager({ crate })
1418
- let json = {
1419
- ...,
1420
- }
1421
- let arrayOfObjects = cm.flatten(json)
1422
-
1423
- */
1424
- flatten(json: UnverifiedEntityDefinition): NormalisedEntityDefinition[] {
1425
- if (!isPlainObject(json)) {
1426
- throw new Error(`flatten only takes an object.`);
1427
- }
1428
- json = structuredClone(json);
1429
- let flattened = [];
1430
- flattened.push(json);
1431
- for (let property of Object.keys(json)) {
1432
- if (["@id", "@type", "name"].includes(property)) continue;
1433
-
1434
- if (!isArray(json[property])) json[property] = [json[property] as any];
1435
-
1436
- json[property].forEach((instance) => {
1437
- if (isPlainObject(instance)) flattened.push(this.flatten(instance as any));
1438
- });
1439
- json[property] = json[property].map((instance: any) => {
1440
- if (isPlainObject(instance)) return { "@id": instance["@id"] };
1441
- return instance;
1442
- });
1443
- }
1444
- return flattenDeep(flattened as []).map((e) => e);
1445
- }
1446
-
1447
- /**
1448
- * Link two entities
1449
- *
1450
- * Link two entities via a property. If there is a profile defined
1451
- * and it has reverse associations, then they will be added.
1452
- *
1453
- * @param {Object} options
1454
- * @param {string} options.id - the id of the entity to add the association to
1455
- * @param {string} options.property - the property to add the association to
1456
- * @param {string} options.propertyId - the propertyId of the property - ie the url to the definition
1457
- * @param {object} options.value - an object with '@id' defining the association to create
1458
- * @example
1459
-
1460
- const cm = new CrateManager({ crate })
1461
- cm.linkEntity({ id: './', property: 'author', value: { '@id': '#e1' }})
1462
-
1463
- **/
1464
- linkEntity({
1465
- id,
1466
- property,
1467
- propertyId = undefined,
1468
- value,
1469
- }: {
1470
- id: string;
1471
- property: string;
1472
- propertyId?: string;
1473
- value: { "@id": string };
1474
- }): void {
1475
- if (!id) throw new Error(`'linkEntity' requires 'id' to be defined`);
1476
- if (!property) throw new Error(`'linkEntity' requires 'property' to be defined`);
1477
- if (!value) throw new Error(`'linkEntity' requires 'value' to be defined`);
1478
- if (!isPlainObject(value) || !value["@id"]) {
1479
- throw new Error(`value must be an object with '@id' defined`);
1480
- }
1481
- this.setProperty({ id, property, propertyId, value });
1482
-
1483
- // set inverse association if required
1484
- const associations = this.pm?.getPropertyAssociations() ?? {};
1485
- if (associations[property]) {
1486
- let inverse = associations[property];
1487
- this.setProperty({
1488
- id: value["@id"],
1489
- property: inverse.property,
1490
- propertyId: inverse.propertyId,
1491
- value: { "@id": id },
1492
- });
1493
- }
1494
- }
1495
-
1496
- /**
1497
- * Unlink two entities
1498
- *
1499
- * Remove an association between two entities. If there is a profile defined
1500
- * and it has reverse associations, then they will be removed as well.
1501
- *
1502
- * @param {Object} options
1503
- * @param {string} options.id - the id of the entity to remove the association from
1504
- * @param {string} options.property - the property containing the association
1505
- * @param {object} options.value - an object with '@id' defining the association to remove
1506
- * @example
1507
-
1508
- const cm = new CrateManager({ crate })
1509
- const cm = new CrateManager({ crate })
1510
- cm.unlinkEntity({ id: './', property: 'author', value: { '@id': '#e1' }})
1511
-
1512
- **/
1513
- unlinkEntity({
1514
- id = undefined,
1515
- property = undefined,
1516
- value = undefined,
1517
- stop = false,
1518
- }: {
1519
- id: string | undefined;
1520
- property: string | undefined;
1521
- value: { "@id": string } | undefined;
1522
- stop?: boolean;
1523
- }) {
1524
- if (!id) throw new Error(`'unlinkEntity' requires 'id' to be defined`);
1525
- if (!property) throw new Error(`'unlinkEntity' requires 'property' to be defined`);
1526
- if (!value) throw new Error(`'unlinkEntity' requires 'value' to be defined`);
1527
- if (!isPlainObject(value) || !value["@id"]) {
1528
- throw new Error(`value must be an object with '@id' defined`);
1529
- }
1530
-
1531
- // console.log("START", id, property, value["@id"]);
1532
- // get the source entity
1533
- let indexRef = this.entityIdIndex[id];
1534
- let entity = this.crate["@graph"][indexRef];
1535
- if (!entity) return;
1536
- // console.log("SOURCE ENTITY BEFORE ", entity);
1537
-
1538
- // and remove the linked entity from the specified property
1539
- entity[property] = (entity[property] as []).filter((v) => {
1540
- if (v?.["@id"] && v["@id"] === value["@id"]) {
1541
- // do nothing - we don't want it
1542
- } else {
1543
- return v;
1544
- }
1545
- });
1546
- if (!entity[property].length) delete entity[property];
1547
-
1548
- // manage timestamps
1549
- if (this.entityTimestamps) {
1550
- entity[entityDateUpdatedProperty] = [new Date().toISOString()];
1551
- }
1552
- // console.log("SOURCE ENTITY AFTER", entity);
1553
-
1554
- // clean up the reverse mapping back value['@id'] -> id
1555
-
1556
- // console.log(`TARGET ENTITY BEFORE REVERSE`, this.reverse[value["@id"]]);
1557
- // console.log();
1558
- if (this.reverse[value["@id"]]) {
1559
- this.reverse[value["@id"]][property] = this.reverse[value["@id"]][property].filter(
1560
- (v: any) => {
1561
- if (isEntityReference(v)) return v["@id"] !== id;
1562
- }
1563
- );
1564
- if (!this.reverse[value["@id"]][property].length)
1565
- delete this.reverse[value["@id"]][property];
1566
- }
1567
- // console.log(`TARGET ENTITY AFTER REVERSE`, this.reverse[value["@id"]]);
1568
- // remove any inverse associations
1569
- const associations = this.pm?.getPropertyAssociations() ?? {};
1570
- if (associations[property] && !stop) {
1571
- const inverse = associations[property];
1572
- // console.log(inverse, value["@id"], property, id);
1573
- this.unlinkEntity({
1574
- id: value["@id"],
1575
- property: inverse.property,
1576
- value: { "@id": id },
1577
- stop: true, // V. IMPORTANT so we don't get into an infinite loop
1578
- });
1579
- }
1580
- }
1581
-
1582
- /**
1583
- * Purge unlinked entities from the crate
1584
- *
1585
- * Clean up the graph and purge any unlinked entities including disconnected subtrees.
1586
- * @example
1587
-
1588
- const cm = new CrateManager({ crate })
1589
- cm.purgeUnlinkedEntities()
1590
-
1591
- */
1592
- purgeUnlinkedEntities(): void {
1593
- let walker = (entity: NormalisedEntityDefinition) => {
1594
- linkedEntities[entity["@id"]] = true;
1595
- for (let property of Object.keys(entity)) {
1596
- if (this.coreProperties.includes(property)) continue;
1597
- for (let instance of entity[property]) {
1598
- if (!isEntityReference(instance)) continue;
1599
- if (instance?.["@id"] && !linkedEntities[instance["@id"]]) {
1600
- let indexRef = this.entityIdIndex[instance["@id"]];
1601
- if (indexRef !== undefined) {
1602
- let entity = this.crate["@graph"][indexRef];
1603
- walker(entity as NormalisedEntityDefinition);
1604
- }
1605
- }
1606
- }
1607
- }
1608
- };
1609
-
1610
- walker = walker.bind(this);
1611
- let linkedEntities: { [key: string]: boolean } = { "ro-crate-metadata.json": true };
1612
-
1613
- let rootDescriptor = this.getEntity({ id: "ro-crate-metadata.json" });
1614
- if (!rootDescriptor) return;
1615
- // let indexRef = this.entityIdIndex["ro-crate-metadata.json"];
1616
- // we first need to walk the graph from the root descriptor
1617
- // and assemble a list of linked entities that we get to by
1618
- // following forward looking associations
1619
- walker(rootDescriptor);
1620
-
1621
- // then, we walk the entire graph and look for entities
1622
- // that are not already linked. When we find one, we walk
1623
- // it forwards to see if it links to anything already linked
1624
- // and if yes, then we link it
1625
- // think things like relationships and actions that may link
1626
- // to enities in the graph even though they themselves are not linked to
1627
- for (let i = 0; i < this.graphLength; i++) {
1628
- let entity = this.crate["@graph"][i];
1629
- if (!entity) continue;
1630
- if (!linkedEntities[entity["@id"]]) {
1631
- for (let property of Object.keys(entity)) {
1632
- if (this.coreProperties.includes(property)) continue;
1633
- for (let instance of entity[property]) {
1634
- if (!isEntityReference(instance)) continue;
1635
- if (instance?.["@id"] && linkedEntities[instance["@id"]]) {
1636
- linkedEntities[entity["@id"]] = true;
1637
- }
1638
- }
1639
- }
1640
- }
1641
- }
1642
-
1643
- // now we can remove everything we couldn't get to
1644
- for (let i = 0; i < this.graphLength; i++) {
1645
- let entity = this.crate["@graph"][i];
1646
- if (entity && !linkedEntities[entity["@id"]]) {
1647
- let indexRef = this.entityIdIndex[entity["@id"]];
1648
- delete this.entityIdIndex[entity["@id"]];
1649
- delete this.reverse[entity["@id"]];
1650
- this.crate["@graph"][indexRef] = undefined;
1651
- }
1652
- }
1653
- }
1654
-
1655
- /**
1656
- * Export the RO-Crate
1657
- * @returns the complete ro-crate
1658
- * @example
1659
-
1660
- const cm = new CrateManager({ crate })
1661
- let crate = cm.exportCrate()
1662
-
1663
- */
1664
- exportCrate(): NormalisedCrate {
1665
- const t0 = performance.now();
1666
- // const crate = structuredClone(this.crate);
1667
-
1668
- let entities = this.crate["@graph"]
1669
- .filter((e) => {
1670
- return !isUndefined(e);
1671
- })
1672
- .map((e) => {
1673
- const entity = structuredClone(e);
1674
- entity["@reverse"] =
1675
- (structuredClone(this.reverse[e["@id"]]) as {
1676
- [key: string]: { "@id": string }[];
1677
- }) ?? {};
1678
-
1679
- for (let property of Object.keys(entity)) {
1680
- if (property === "@reverse") {
1681
- for (let rp of Object.keys(entity["@reverse"])) {
1682
- if (isArray(entity[property][rp]) && entity[property][rp].length === 1)
1683
- entity[property][rp] = entity[property][rp][0];
1684
- }
1685
- } else {
1686
- if (isArray(entity[property]) && entity[property].length === 1)
1687
- entity[property] = entity[property][0];
1688
- }
1689
- }
1690
- return entity;
1691
- });
1692
-
1693
- const context: NormalisedContext = this.getContext();
1694
- const crate = {
1695
- "@context": context.length === 1 ? context[0] : context,
1696
- "@graph": entities,
1697
- } as NormalisedCrate;
1698
- const t1 = performance.now();
1699
- console.debug(`Crate export: ${round(t1 - t0, 1)}ms`);
1700
- // console.log(JSON.stringify(this.crate, null, 2));
1701
- return crate;
1702
- }
1703
-
1704
- /**
1705
- * exportEntityTemplate
1706
- *
1707
- * Export an entity as a template to be reused.
1708
- * 1. If resolveDepth = 0 then the entity is returned with all associations removed
1709
- * 2. If resolveDepth = 1 then the entity is returned with one level of associations populated but
1710
- * all of their associations will be removed.
1711
- *
1712
- * @param {Object} options
1713
- * @param {String} options.id - the id of the entity to export as a template
1714
- * @param {String} options.resolveDepth - 0 or 1. If 1, linked entities will be joined in
1715
- *
1716
- * @returns entity
1717
- * @example
1718
-
1719
- const cm = new CrateManager({ crate })
1720
- let entity = cm.exportEntityTemplate({ id: '#person' })
1721
- let entity = cm.exportEntityTemplate({ id: '#person', resolveDepth: 1 })
1722
-
1723
- */
1724
- exportEntityTemplate({
1725
- id,
1726
- resolveDepth = 0,
1727
- }: {
1728
- id: string;
1729
- resolveDepth?: number;
1730
- }): NormalisedEntityDefinition {
1731
- if (![0, 1].includes(resolveDepth)) {
1732
- throw new Error(`resolveDepth can only be 0 or 1`);
1733
- }
1734
- let indexRef = this.entityIdIndex[id];
1735
- let entity = structuredClone(this.crate["@graph"][indexRef]);
1736
-
1737
- for (let property of Object.keys(entity)) {
1738
- if (this.coreProperties.includes(property)) continue;
1739
-
1740
- entity[property] = entity[property].map((value: EntityReference) => {
1741
- if (value?.["@id"]) {
1742
- if (resolveDepth === 0) return undefined;
1743
- let linkedIndexRef = this.entityIdIndex[value["@id"]];
1744
- let linkedEntity = structuredClone(this.crate["@graph"][linkedIndexRef]);
1745
- linkedEntity = this.__removeAssociations(linkedEntity);
1746
- delete linkedEntity["@reverse"];
1747
- return linkedEntity;
1748
- } else {
1749
- return value;
1750
- }
1751
- });
1752
- entity[property] = compact(entity[property]);
1753
- if (entity[property].length === 0) {
1754
- delete entity[property];
1755
- } else if (entity[property].length === 1) {
1756
- entity[property] = entity[property][0];
1757
- }
1758
- }
1759
- delete entity["@reverse"];
1760
- return entity;
1761
- }
1762
-
1763
- /**
1764
- * getErrors
1765
- *
1766
- * @returns { errors }
1767
- *
1768
- */
1769
- getErrors(): errorsInterface {
1770
- return this.errors;
1771
- }
1772
-
1773
- /**
1774
- * getWarnings
1775
- *
1776
- * @returns { warnings }
1777
- */
1778
- getWarnings(): warningsInterface {
1779
- return this.warnings;
1780
- }
1781
-
1782
- __updateContext({ name, id }: { name: string; id?: string }): void {
1783
- if (id && !(id in this.contextDefinitions)) {
1784
- // the property or class isn't defined in the context
1785
- // add it in the definitions for lookups later
1786
- // store it in the local context which gets joined
1787
- // in where required.
1788
- this.contextDefinitions[id] = true;
1789
- this.localContext[name] = id;
1790
- }
1791
- }
1792
-
1793
- /**
1794
- * Normalise context
1795
- *
1796
- * Collapse all objects into a single object
1797
- *
1798
- * @param {*} context
1799
- * @returns context
1800
- */
1801
- __normaliseContext(context: UnverifiedContext): NormalisedContext {
1802
- context = [].concat(context);
1803
- let entries = {};
1804
- for (let entry of context) {
1805
- if (isPlainObject(entry)) entries = { ...entries, ...entry };
1806
- }
1807
- context = context.filter((e: string | {}) => isString(e));
1808
- if (!isEmpty(entries)) context = [...context, entries];
1809
- return context;
1810
- }
1811
- __storeEntityType(entity: NormalisedEntityDefinition): void {
1812
- // store the entity type for lookups by type
1813
- entity["@type"].forEach((type) => {
1814
- if (!this.entityTypes[type]) {
1815
- this.entityTypes[type] = 1;
1816
- } else {
1817
- this.entityTypes[type] += 1;
1818
- }
1819
- });
1820
- }
1821
- __removeEntityType(entity: NormalisedEntityDefinition) {
1822
- // update the entity type's store
1823
- entity["@type"].forEach((type) => {
1824
- this.entityTypes[type] -= 1;
1825
- if (this.entityTypes[type] === 0) delete this.entityTypes[type];
1826
- });
1827
- }
1828
- __collectAllDefinitions(context: NormalisedContext): { [key: string]: boolean } {
1829
- let definitions: { [key: string]: boolean } = {};
1830
-
1831
- for (let entry of context) {
1832
- if (isString(entry)) {
1833
- // likely the ro-crate context - see if it is
1834
- let def = getContextDefinition(entry);
1835
- if (def) {
1836
- definitions = { ...definitions, ...def.definitions };
1837
- } else {
1838
- definitions[entry] = true;
1839
- }
1840
- } else if (isPlainObject(entry)) {
1841
- for (let [key, value] of Object.entries(entry)) {
1842
- definitions[value as string] = true;
1843
- }
1844
- }
1845
- }
1846
- // console.log(definitions);
1847
- return definitions;
1848
- }
1849
- __setError(error: keyof typeof this.errors, entity: string | UnverifiedEntityDefinition) {
1850
- if (error === "init") {
1851
- this.errors.init.messages.push(entity as string);
1852
- } else if (error === "clash") {
1853
- this.errors.clash.messages.push(entity as string);
1854
- } else if (error in this.errors) {
1855
- (this.errors[error] as any).entity.push(entity as UnverifiedEntityDefinition);
1856
- }
1857
- this.errors.hasError = true;
1858
- }
1859
- __setWarning(warning: keyof typeof this.warnings, entity: string | UnverifiedEntityDefinition) {
1860
- if (warning in this.warnings) {
1861
- if (isString(entity)) {
1862
- (this.warnings[warning] as any).messages.push(entity as string);
1863
- } else {
1864
- (this.warnings[warning] as any).entity.push(entity as UnverifiedEntityDefinition);
1865
- }
1866
- }
1867
- this.warnings.hasWarning = true;
1868
- }
1869
- __materialiseEntity({ id }: { id: string }): NormalisedEntityDefinition {
1870
- return {
1871
- "@id": id,
1872
- "@type": isURL(id) ? ["URL"] : ["Thing"],
1873
- name: id,
1874
- } as NormalisedEntityDefinition;
1875
- }
1876
- __confirmNoClash({
1877
- entity,
1878
- mintNewId = true,
1879
- }: {
1880
- entity: NormalisedEntityDefinition;
1881
- mintNewId?: boolean;
1882
- }): NormalisedEntityDefinition | boolean {
1883
- // if it looks like the root dataset - throw an error
1884
- // can't have multiple root datasets
1885
- if (entity["@id"] === "./") {
1886
- this.__setError("clash", `You can't add an entity with id: './' as that will clash with the root dataset.`);
1887
- return false;
1888
- // throw new Error(
1889
- // `You can't add an entity with id: './' as that will clash with the root dataset.`
1890
- // );
1891
- } else {
1892
- this.errors.hasError = false;
1893
- this.errors.clash.messages = [];
1894
- }
1895
- // if it looks like the root descriptor - throw an error
1896
- // can't have multiple root descriptors
1897
- if (entity["@id"] === "ro-crate-metadata.json") {
1898
- throw new Error(
1899
- `You can't add an entity with id: 'ro-crate-metadata.json' as that will clash with the root descriptor.`
1900
- );
1901
- }
1902
-
1903
- let idx = this.entityIdIndex[entity["@id"]];
1904
-
1905
- // if there is no id clash, return the entity as is
1906
- if (idx === undefined) return entity;
1907
-
1908
- // if the id is already used, check that the type is different
1909
- // if it is, change the id and return the entity for inclusion
1910
- let entityLookup = this.crate["@graph"][idx];
1911
- if (!difference(entityLookup?.["@type"], entity["@type"]).length) {
1912
- return false;
1913
- } else {
1914
- // if get to here then there's a clash
1915
-
1916
- if (mintNewId) {
1917
- const id = `e${this.graphLength + 1}`;
1918
- entity["@id"] = `#${id}`;
1919
- return entity;
1920
- } else {
1921
- throw new Error("That id is already used on another entity");
1922
- }
1923
- }
1924
- }
1925
- __updateEntityId({ oldId, newId }: { oldId: string; newId: string }) {
1926
- if (!oldId) throw new Error(`You must provide the id to change: oldId`);
1927
- if (!newId) throw new Error(`You must provide the id for the change: newId`);
1928
- // get the original entity and see if we can set this new id on it
1929
- // we clone the original entity as we don't want to set anything yet
1930
- let indexRef = this.entityIdIndex[oldId];
1931
- // console.log("ORIGINAL ENTITY", JSON.stringify(this.crate["@graph"][indexRef], null, 2));
1932
- let entity = structuredClone(this.crate["@graph"][indexRef]);
1933
- entity["@id"] = newId;
1934
- entity = normalise(entity, this.graphLength);
1935
- entity = this.__confirmNoClash({ entity, mintNewId: false });
1936
- if (!entity) {
1937
- this.__setWarning("clash", `The entity ${entity["@id"]} already exists. Crate set back to it's original state.`);
1938
- return this.getEntity({ id: entity[indexRef] }) as NormalisedEntityDefinition;
1939
- } else {
1940
- this.warnings.hasWarning = false;
1941
- this.warnings.clash.messages = [];
1942
- }
1943
- // console.log("NEW ENTITY", JSON.stringify(entity, null, 2));
1944
-
1945
- // get the entity using the original id and then walk the properties forward
1946
- // to find what it links to. For each of those, set the reverse link to the new id
1947
- indexRef = this.entityIdIndex[oldId];
1948
- let oe = this.crate["@graph"][indexRef];
1949
- for (let [property, instances] of Object.entries(oe as NormalisedEntityDefinition)) {
1950
- if (this.coreProperties.includes(property)) continue;
1951
- // for (let instance of instances) {
1952
- // if (instance?.["@id"]) {
1953
- // // console.log(
1954
- // // "FORWARD LINKED ENTITY BEFORE",
1955
- // // property,
1956
- // // this.reverse[instance["@id"]][property]
1957
- // // );
1958
- // this.reverse[instance["@id"]][property].push({ "@id": entity["@id"] });
1959
- // this.reverse[instance["@id"]][property] = this.reverse[instance["@id"]][
1960
- // property
1961
- // ].filter((i) => i["@id"] !== oldId);
1962
- // this.reverse[instance["@id"]][property] = uniqBy(
1963
- // this.reverse[instance["@id"]][property],
1964
- // "@id"
1965
- // );
1966
- // // console.log(
1967
- // // "FORWARD LINKED ENTITY AFTER",
1968
- // // property,
1969
- // // this.reverse[instance["@id"]][property]
1970
- // // );
1971
- // }
1972
- // }
1973
- (instances as []).forEach((instance: EntityReference) => {
1974
- if (instance?.["@id"]) {
1975
- // console.log(
1976
- // "FORWARD LINKED ENTITY BEFORE",
1977
- // property,
1978
- // this.reverse[instance["@id"]][property]
1979
- // );
1980
- this.reverse[instance["@id"]][property].push({ "@id": entity["@id"] });
1981
- this.reverse[instance["@id"]][property] = this.reverse[instance["@id"]][
1982
- property
1983
- ].filter((i: EntityReference) => i["@id"] !== oldId);
1984
- this.reverse[instance["@id"]][property] = uniqBy(
1985
- this.reverse[instance["@id"]][property],
1986
- "@id"
1987
- );
1988
- // console.log(
1989
- // "FORWARD LINKED ENTITY AFTER",
1990
- // property,
1991
- // this.reverse[instance["@id"]][property]
1992
- // );
1993
- }
1994
- });
1995
- }
1996
-
1997
- // walk the reverse links from this entity and update the forrward links
1998
- // now walk the reverse links of the entity to update the references to it
1999
- if (this.reverse[oldId]) {
2000
- for (let [property, links] of Object.entries(this.reverse[oldId])) {
2001
- // for (let link of links) {
2002
- // let linkIndexRef = this.entityIdIndex[link["@id"]];
2003
- // let linkedEntity = this.crate["@graph"][linkIndexRef];
2004
-
2005
- // // console.log("REVERSE LINKED ENTITY BEFORE", property, linkedEntity[property]);
2006
- // linkedEntity[property].push({ "@id": entity["@id"] });
2007
- // linkedEntity[property] = linkedEntity[property].filter(
2008
- // (i) => i["@id"] !== oldId
2009
- // );
2010
- // // console.log("REVERSE LINKED ENTITY AFTER", property, linkedEntity[property]);
2011
- // }
2012
- (links as []).forEach((link: EntityReference) => {
2013
- let linkIndexRef = this.entityIdIndex[link["@id"]];
2014
- let linkedEntity = this.crate["@graph"][linkIndexRef];
2015
-
2016
- if (
2017
- linkedEntity &&
2018
- linkedEntity[property] &&
2019
- Array.isArray(linkedEntity[property])
2020
- ) {
2021
- // console.log("REVERSE LINKED ENTITY BEFORE", property, linkedEntity[property]);
2022
- linkedEntity[property].push({ "@id": entity["@id"] } as EntityReference);
2023
- linkedEntity[property] = linkedEntity[property].filter((i) => {
2024
- if (isEntityReference(i)) return i["@id"] !== oldId;
2025
- });
2026
- }
2027
- // console.log("REVERSE LINKED ENTITY AFTER", property, linkedEntity[property]);
2028
- });
2029
- }
2030
- }
2031
-
2032
- // now we can update the original entity in the graph
2033
- // set the new id on the entity itself
2034
- indexRef = this.entityIdIndex[oldId];
2035
- (this.crate["@graph"][indexRef] as EntityReference)["@id"] = entity["@id"];
2036
- // console.log("NEW ENTITY IN GRAPH", JSON.stringify(this.crate["@graph"][indexRef], null, 2));
2037
-
2038
- // update the index ref's
2039
- delete this.entityIdIndex[oldId];
2040
- // console.log("OLDID lookup", this.entityIdIndex[oldId]);
2041
- this.entityIdIndex[entity["@id"]] = indexRef;
2042
- // console.log("NEWID lookup", this.entityIdIndex[entity["@id"]]);
2043
- // console.log(
2044
- // "NEW ENTITY IN GRAPH",
2045
- // JSON.stringify(this.crate["@graph"][this.entityIdIndex[entity["@id"]]], null, 2)
2046
- // );
2047
-
2048
- // copy the reverse associations to the new id
2049
- // and then remove the original reverse associations
2050
- this.reverse[entity["@id"]] = structuredClone(this.reverse[oldId]);
2051
- delete this.reverse[oldId];
2052
- // console.log(
2053
- // "NEW ENTITY REVERSE CONNECTIONS",
2054
- // entity["@id"],
2055
- // JSON.stringify(this.reverse[entity["@id"]], null, 2)
2056
- // );
2057
-
2058
- // console.log(JSON.stringify(this.crate["@graph"], null, 2));
2059
- // console.log("");
2060
- // console.log("");
2061
- // console.log("");
2062
- // console.log(JSON.stringify(this.reverse, null, 2));
2063
- // console.log("");
2064
- // console.log("");
2065
- // console.log("");
2066
- // console.log(JSON.stringify(this.entityIdIndex, null, 2));
2067
- }
2068
- __addReverse({
2069
- id,
2070
- property,
2071
- value,
2072
- }: {
2073
- id: string;
2074
- property: string;
2075
- value: EntityReference;
2076
- }) {
2077
- const linkIndexRef = this.entityIdIndex[value["@id"]];
2078
-
2079
- if (linkIndexRef) {
2080
- if (!this.reverse[value["@id"]][property]) this.reverse[value["@id"]][property] = [];
2081
-
2082
- let links = this.reverse[value["@id"]][property].map(
2083
- (l: EntityReference) => l?.["@id"]
2084
- );
2085
- if (!links.includes(id)) {
2086
- this.reverse[value["@id"]][property].push({ "@id": id });
2087
- }
2088
- // this.reverse[value["@id"]][property].push({ "@id": id });
2089
- // this.reverse[value["@id"]][property] = uniqBy(
2090
- // this.reverse[value["@id"]][property],
2091
- // "@id"
2092
- // );
2093
- }
2094
- }
2095
- __removeAssociations(entity: NormalisedEntityDefinition): NormalisedEntityDefinition {
2096
- for (let property of Object.keys(entity)) {
2097
- if (this.coreProperties.includes(property)) continue;
2098
- entity[property] = entity[property].filter((value) => {
2099
- if (isEntityReference(value)) return !value["@id"];
2100
- });
2101
- if (!entity[property].length) delete entity[property];
2102
- }
2103
- return entity;
2104
- }
2105
- }
2106
-
2107
- function isEntityReference(obj: any): obj is EntityReference {
2108
- return (
2109
- typeof obj === "object" && obj !== null && "@id" in obj && typeof obj["@id"] === "string"
2110
- );
2111
- }