@arpproject/recrate 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -2
- package/dist/recrate.css +4 -4
- package/dist/recrate.es.js +34674 -114061
- package/package.json +41 -32
- package/.eslintrc.json +0 -37
- package/.storybook/main.ts +0 -40
- package/.storybook/preview.tsx +0 -46
- package/babel.config.json +0 -5
- package/docker-compose.yml +0 -30
- package/docs/.nojekyll +0 -1
- package/docs/assets/highlight.css +0 -99
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1448
- package/docs/classes/src_crate_builder_CrateManager_crate_manager.CrateManager.html +0 -240
- package/docs/classes/src_crate_builder_CrateManager_profile_manager.ProfileManager.html +0 -42
- package/docs/classes/src_crate_builder_editor_state.EditorState.html +0 -28
- package/docs/classes/src_crate_builder_types.CrateManagerType.html +0 -57
- package/docs/classes/src_crate_builder_types.ProfileManagerType.html +0 -13
- package/docs/functions/src_crate_builder_CrateManager_lib.isURL.html +0 -2
- package/docs/functions/src_crate_builder_CrateManager_lib.mintNewCrate.html +0 -3
- package/docs/functions/src_crate_builder_CrateManager_lib.normalise.html +0 -5
- package/docs/functions/src_crate_builder_CrateManager_lib.normaliseEntityType.html +0 -1
- package/docs/index.html +0 -58
- package/docs/interfaces/src_crate_builder_types.NormalisedCrate.html +0 -3
- package/docs/interfaces/src_crate_builder_types.NormalisedEntityDefinition.html +0 -4
- package/docs/interfaces/src_crate_builder_types.NormalisedProfile.html +0 -9
- package/docs/interfaces/src_crate_builder_types.ProfileLayout.html +0 -2
- package/docs/interfaces/src_crate_builder_types.ProfileLayoutGroup.html +0 -9
- package/docs/interfaces/src_crate_builder_types.UnverifiedCrate.html +0 -3
- package/docs/interfaces/src_crate_builder_types.UnverifiedEntityDefinition.html +0 -4
- package/docs/modules/src_crate_builder_CrateManager_crate_manager.html +0 -2
- package/docs/modules/src_crate_builder_CrateManager_lib.html +0 -6
- package/docs/modules/src_crate_builder_CrateManager_profile_manager.html +0 -2
- package/docs/modules/src_crate_builder_editor_state.html +0 -2
- package/docs/modules/src_crate_builder_types.html +0 -16
- package/docs/types/src_crate_builder_types.EntityReference.html +0 -1
- package/docs/types/src_crate_builder_types.NormalisedContext.html +0 -1
- package/docs/types/src_crate_builder_types.PrimitiveType.html +0 -1
- package/docs/types/src_crate_builder_types.ProfileAssociation.html +0 -1
- package/docs/types/src_crate_builder_types.ProfileInput.html +0 -1
- package/docs/types/src_crate_builder_types.UnverifiedContext.html +0 -1
- package/docs/variables/src_crate_builder_CrateManager_lib.urlProtocols.html +0 -1
- package/index.html +0 -13
- package/load-data-packs.cjs +0 -38
- package/postcss.config.cjs +0 -6
- package/public/favicon.ico +0 -0
- package/public/index.html +0 -43
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/marker-icon.png +0 -0
- package/public/marker-shadow.png +0 -0
- package/public/robots.txt +0 -3
- package/react-app-env.d.ts +0 -1
- package/rollup.config.js +0 -26
- package/src/app/App.tsx +0 -13
- package/src/app/EmbeddedComponent.tsx +0 -432
- package/src/app/index.html +0 -20
- package/src/app/index.tsx +0 -19
- package/src/app/lookup.ts +0 -141
- package/src/app/override-styles.css +0 -96
- package/src/crate-builder/CrateManager/contexts/1.1-context.jsonld +0 -2660
- package/src/crate-builder/CrateManager/contexts/1.2-DRAFT-context.jsonld +0 -2918
- package/src/crate-builder/CrateManager/contexts.ts +0 -42
- package/src/crate-builder/CrateManager/crate-manager-benchmarking.spec.ts +0 -31
- package/src/crate-builder/CrateManager/crate-manager-loading-exporting.spec.ts +0 -431
- package/src/crate-builder/CrateManager/crate-manager-operations.spec.ts +0 -298
- package/src/crate-builder/CrateManager/crate-manager.spec.ts +0 -2336
- package/src/crate-builder/CrateManager/crate-manager.ts +0 -2111
- package/src/crate-builder/CrateManager/lib.spec.ts +0 -133
- package/src/crate-builder/CrateManager/lib.ts +0 -170
- package/src/crate-builder/CrateManager/profile-manager.spec.ts +0 -593
- package/src/crate-builder/CrateManager/profile-manager.ts +0 -367
- package/src/crate-builder/CrateManager/schema-type-definitions.json +0 -35122
- package/src/crate-builder/CrateManager/validate-identifier.spec.ts +0 -82
- package/src/crate-builder/CrateManager/validate-identifier.ts +0 -65
- package/src/crate-builder/RenderEntity/Add.tsx +0 -249
- package/src/crate-builder/RenderEntity/AddControl.stories.tsx +0 -126
- package/src/crate-builder/RenderEntity/AddControl.tsx +0 -84
- package/src/crate-builder/RenderEntity/AutoComplete.tsx +0 -215
- package/src/crate-builder/RenderEntity/BulkAdd.tsx +0 -136
- package/src/crate-builder/RenderEntity/DeleteProperty.tsx +0 -33
- package/src/crate-builder/RenderEntity/DialogAddProperty.tsx +0 -83
- package/src/crate-builder/RenderEntity/DialogBrowseEntities.tsx +0 -136
- package/src/crate-builder/RenderEntity/DialogEditContext.tsx +0 -107
- package/src/crate-builder/RenderEntity/DialogPreviewCrate.tsx +0 -54
- package/src/crate-builder/RenderEntity/DialogSaveCrateAsTemplate.tsx +0 -65
- package/src/crate-builder/RenderEntity/DialogSaveEntityTemplate.tsx +0 -87
- package/src/crate-builder/RenderEntity/DisplayPropertyName.stories.tsx +0 -30
- package/src/crate-builder/RenderEntity/DisplayPropertyName.tsx +0 -21
- package/src/crate-builder/RenderEntity/EntityId.tsx +0 -75
- package/src/crate-builder/RenderEntity/EntityName.tsx +0 -49
- package/src/crate-builder/RenderEntity/EntityProperty.tsx +0 -188
- package/src/crate-builder/RenderEntity/EntityPropertyInstance.tsx +0 -255
- package/src/crate-builder/RenderEntity/EntityType.tsx +0 -95
- package/src/crate-builder/RenderEntity/ItemLink.tsx +0 -37
- package/src/crate-builder/RenderEntity/PaginateLinkedEntities.stories.tsx +0 -43
- package/src/crate-builder/RenderEntity/PaginateLinkedEntities.tsx +0 -141
- package/src/crate-builder/RenderEntity/PropertyHelp.tsx +0 -39
- package/src/crate-builder/RenderEntity/RenderControls.tsx +0 -278
- package/src/crate-builder/RenderEntity/RenderLinkedItem.tsx +0 -139
- package/src/crate-builder/RenderEntity/RenderPropertyHelp.tsx +0 -41
- package/src/crate-builder/RenderEntity/RenderReverseConnections.tsx +0 -150
- package/src/crate-builder/RenderEntity/RenderTypes.tsx +0 -102
- package/src/crate-builder/RenderEntity/Shell2.tsx +0 -576
- package/src/crate-builder/RenderEntity/UnlinkEntity.tsx +0 -30
- package/src/crate-builder/RenderEntity/auto-complete.lib.ts +0 -184
- package/src/crate-builder/RenderEntity/keys.ts +0 -4
- package/src/crate-builder/RenderEntity/layout.spec.js +0 -593
- package/src/crate-builder/RenderEntity/layout.ts +0 -220
- package/src/crate-builder/Shell.tsx +0 -337
- package/src/crate-builder/component.css +0 -65
- package/src/crate-builder/editor-state.ts +0 -114
- package/src/crate-builder/emotionCache.ts +0 -8
- package/src/crate-builder/helpers.ts +0 -16
- package/src/crate-builder/i18n.ts +0 -22
- package/src/crate-builder/lib/validate-iri.js +0 -69
- package/src/crate-builder/lib/validate-iri.ts +0 -57
- package/src/crate-builder/locales/en.js +0 -149
- package/src/crate-builder/locales/hu.js +0 -147
- package/src/crate-builder/primitives/Boolean.stories.tsx +0 -33
- package/src/crate-builder/primitives/Boolean.tsx +0 -67
- package/src/crate-builder/primitives/Date.stories.tsx +0 -32
- package/src/crate-builder/primitives/Date.tsx +0 -58
- package/src/crate-builder/primitives/DateTime.stories.tsx +0 -32
- package/src/crate-builder/primitives/DateTime.tsx +0 -64
- package/src/crate-builder/primitives/Geo.stories.tsx +0 -57
- package/src/crate-builder/primitives/Geo.tsx +0 -225
- package/src/crate-builder/primitives/Map.SelectArea.js +0 -359
- package/src/crate-builder/primitives/Map.stories.tsx +0 -61
- package/src/crate-builder/primitives/Map.tsx +0 -124
- package/src/crate-builder/primitives/Number.stories.tsx +0 -74
- package/src/crate-builder/primitives/Number.tsx +0 -166
- package/src/crate-builder/primitives/Select.stories.tsx +0 -66
- package/src/crate-builder/primitives/Select.tsx +0 -121
- package/src/crate-builder/primitives/SelectObject.stories.tsx +0 -29
- package/src/crate-builder/primitives/SelectObject.tsx +0 -105
- package/src/crate-builder/primitives/SelectUrl.stories.tsx +0 -42
- package/src/crate-builder/primitives/SelectUrl.tsx +0 -110
- package/src/crate-builder/primitives/Text.stories.tsx +0 -106
- package/src/crate-builder/primitives/Text.tsx +0 -197
- package/src/crate-builder/primitives/Time.stories.tsx +0 -38
- package/src/crate-builder/primitives/Time.tsx +0 -71
- package/src/crate-builder/primitives/Url.stories.tsx +0 -43
- package/src/crate-builder/primitives/Url.tsx +0 -75
- package/src/crate-builder/primitives/Value.stories.tsx +0 -37
- package/src/crate-builder/primitives/Value.tsx +0 -30
- package/src/crate-builder/primitives/date-libs.ts +0 -12
- package/src/crate-builder/profile-schema.json +0 -145
- package/src/crate-builder/property-definitions.ts +0 -78
- package/src/crate-builder/recrate.css +0 -3
- package/src/crate-builder/store.ts +0 -14
- package/src/crate-builder/tailwind.css +0 -5
- package/src/crate-builder/types.d.ts +0 -318
- package/src/examples/collection/collections-entity-example.json +0 -131
- package/src/examples/collection/crate-builder-entity-example.json +0 -33
- package/src/examples/item/complex-collection/ro-crate-metadata.json +0 -174
- package/src/examples/item/complex-item/ro-crate-metadata.json +0 -769
- package/src/examples/item/crate-with-language.json +0 -38
- package/src/examples/item/empty/ro-crate-metadata.json +0 -20
- package/src/examples/item/item-with-relationship-and-action/ro-crate-metadata.json +0 -66
- package/src/examples/item/large-crate/ro-crate-metadata.json +0 -5762
- package/src/examples/item/multiple-types/ro-crate-metadata.json +0 -20
- package/src/examples/item/ridiculously-big-collection/ro-crate-metadata.json +0 -162977
- package/src/examples/profile/aroma.complex.profile.json +0 -11098
- package/src/examples/profile/aroma.profile.json +0 -9158
- package/src/examples/profile/nyingarn-item-profile.json +0 -426
- package/src/examples/profile/profile-to-test-inverse-associations.json +0 -73
- package/src/examples/profile/profile-to-test-multiple-types.json +0 -31
- package/src/examples/profile/profile-with-all-primitives-and-groups.json +0 -207
- package/src/examples/profile/profile-with-all-primitives.json +0 -244
- package/src/examples/profile/profile-with-constraints.json +0 -446
- package/src/examples/profile/profile-with-resolve.json +0 -57
- package/src/examples/profile/vocabulary-creation-profile.json +0 -231
- package/src/images.d.ts +0 -5
- package/src/index.ts +0 -12
- package/src/types.ts +0 -104
- package/tailwind.config.js +0 -21
- package/tsconfig.app.json +0 -31
- package/tsconfig.json +0 -26
- package/typedoc.json +0 -11
- package/update-deps.sh +0 -4
- package/vite-env.d.ts +0 -1
- package/vite.config.ts +0 -46
|
@@ -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
|
-
}
|