1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | import { NamedNode, Quad } from "rdf-js";
|
23 | import { dataset, filter, clone } from "./rdfjs";
|
24 | import {
|
25 | isLocalNode,
|
26 | isEqual,
|
27 | isNamedNode,
|
28 | getLocalNode,
|
29 | asNamedNode,
|
30 | resolveLocalIri,
|
31 | } from "./datatypes";
|
32 | import {
|
33 | LitDataset,
|
34 | UrlString,
|
35 | Thing,
|
36 | Url,
|
37 | ThingLocal,
|
38 | LocalNode,
|
39 | ThingPersisted,
|
40 | ChangeLog,
|
41 | DatasetInfo,
|
42 | hasChangelog,
|
43 | hasDatasetInfo,
|
44 | } from "./interfaces";
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | export interface GetThingOptions {
|
50 | |
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | scope?: Url | UrlString;
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | export function getThingOne(
|
66 | litDataset: LitDataset,
|
67 | thingUrl: UrlString | Url | LocalNode,
|
68 | options: GetThingOptions = {}
|
69 | ): Thing {
|
70 | const subject = isLocalNode(thingUrl) ? thingUrl : asNamedNode(thingUrl);
|
71 | const scope: NamedNode | null = options.scope
|
72 | ? asNamedNode(options.scope)
|
73 | : null;
|
74 |
|
75 | const thingDataset = litDataset.match(subject, null, null, scope);
|
76 |
|
77 | if (isLocalNode(subject)) {
|
78 | const thing: ThingLocal = Object.assign(thingDataset, {
|
79 | name: subject.name,
|
80 | });
|
81 |
|
82 | return thing;
|
83 | } else {
|
84 | const thing: Thing = Object.assign(thingDataset, {
|
85 | url: subject.value,
|
86 | });
|
87 |
|
88 | return thing;
|
89 | }
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | export function getThingAll(
|
99 | litDataset: LitDataset,
|
100 | options: GetThingOptions = {}
|
101 | ): Thing[] {
|
102 | const subjectNodes = new Array<Url | LocalNode>();
|
103 | for (const quad of litDataset) {
|
104 |
|
105 |
|
106 |
|
107 | const quadSubject = quad.subject;
|
108 | if (
|
109 | isNamedNode(quadSubject) &&
|
110 | !subjectNodes.some((subjectNode) => isEqual(subjectNode, quadSubject))
|
111 | ) {
|
112 | subjectNodes.push(quadSubject);
|
113 | }
|
114 | if (
|
115 | isLocalNode(quadSubject) &&
|
116 | !subjectNodes.some((subjectNode) => isEqual(subjectNode, quadSubject))
|
117 | ) {
|
118 | subjectNodes.push(quadSubject);
|
119 | }
|
120 | }
|
121 |
|
122 | const things: Thing[] = subjectNodes.map((subjectNode) =>
|
123 | getThingOne(litDataset, subjectNode, options)
|
124 | );
|
125 |
|
126 | return things;
|
127 | }
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | export function setThing<Dataset extends LitDataset>(
|
137 | litDataset: Dataset,
|
138 | thing: Thing
|
139 | ): Dataset & ChangeLog {
|
140 | const newDataset = removeThing(litDataset, thing);
|
141 |
|
142 | for (const quad of thing) {
|
143 | newDataset.add(quad);
|
144 | newDataset.changeLog.additions.push(quad);
|
145 | }
|
146 |
|
147 | return newDataset;
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | export function removeThing<Dataset extends LitDataset>(
|
158 | litDataset: Dataset,
|
159 | thing: UrlString | Url | LocalNode | Thing
|
160 | ): Dataset & ChangeLog {
|
161 | const newLitDataset = withChangeLog(cloneLitStructs(litDataset));
|
162 | const resourceIri: UrlString | undefined = hasDatasetInfo(newLitDataset)
|
163 | ? newLitDataset.datasetInfo.fetchedFrom
|
164 | : undefined;
|
165 |
|
166 | const thingSubject = toNode(thing);
|
167 | for (const quad of litDataset) {
|
168 | if (!isNamedNode(quad.subject) && !isLocalNode(quad.subject)) {
|
169 |
|
170 | newLitDataset.add(quad);
|
171 | } else if (
|
172 | !isEqual(thingSubject, quad.subject, { resourceIri: resourceIri })
|
173 | ) {
|
174 | newLitDataset.add(quad);
|
175 | } else {
|
176 | newLitDataset.changeLog.deletions.push(quad);
|
177 | }
|
178 | }
|
179 | return newLitDataset;
|
180 | }
|
181 |
|
182 | function withChangeLog<Dataset extends LitDataset>(
|
183 | litDataset: Dataset
|
184 | ): Dataset & ChangeLog {
|
185 | const newLitDataset: Dataset & ChangeLog = hasChangelog(litDataset)
|
186 | ? litDataset
|
187 | : Object.assign(litDataset, {
|
188 | changeLog: { additions: [], deletions: [] },
|
189 | });
|
190 | return newLitDataset;
|
191 | }
|
192 |
|
193 | function cloneLitStructs<Dataset extends LitDataset>(
|
194 | litDataset: Dataset
|
195 | ): Dataset {
|
196 | const freshDataset = dataset();
|
197 | if (hasChangelog(litDataset)) {
|
198 | (freshDataset as LitDataset & ChangeLog).changeLog = {
|
199 | additions: [...litDataset.changeLog.additions],
|
200 | deletions: [...litDataset.changeLog.deletions],
|
201 | };
|
202 | }
|
203 | if (hasDatasetInfo(litDataset)) {
|
204 | (freshDataset as LitDataset & DatasetInfo).datasetInfo = {
|
205 | ...litDataset.datasetInfo,
|
206 | };
|
207 | }
|
208 |
|
209 | return freshDataset as Dataset;
|
210 | }
|
211 |
|
212 | interface CreateThingLocalOptions {
|
213 | |
214 |
|
215 |
|
216 |
|
217 |
|
218 | name?: string;
|
219 | }
|
220 | interface CreateThingPersistedOptions {
|
221 | |
222 |
|
223 |
|
224 | url: UrlString;
|
225 | }
|
226 | export type CreateThingOptions =
|
227 | | CreateThingLocalOptions
|
228 | | CreateThingPersistedOptions;
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | export function createThing(
|
235 | options: CreateThingPersistedOptions
|
236 | ): ThingPersisted;
|
237 | export function createThing(options?: CreateThingLocalOptions): ThingLocal;
|
238 | export function createThing(options: CreateThingOptions = {}): Thing {
|
239 | if (typeof (options as CreateThingPersistedOptions).url !== "undefined") {
|
240 | const url = (options as CreateThingPersistedOptions).url;
|
241 |
|
242 | if (typeof URL !== "undefined") {
|
243 |
|
244 | new URL(url);
|
245 | }
|
246 | const thing: ThingPersisted = Object.assign(dataset(), { url: url });
|
247 | return thing;
|
248 | }
|
249 | const name = (options as CreateThingLocalOptions).name ?? generateName();
|
250 | const thing: ThingLocal = Object.assign(dataset(), { name: name });
|
251 | return thing;
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 | export function asUrl(thing: ThingLocal, baseUrl: UrlString): UrlString;
|
261 | export function asUrl(thing: ThingPersisted): UrlString;
|
262 | export function asUrl(thing: Thing, baseUrl?: UrlString): UrlString {
|
263 | if (isThingLocal(thing)) {
|
264 | if (typeof baseUrl === "undefined") {
|
265 | throw new Error(
|
266 | "The URL of a Thing that has not been persisted cannot be determined without a base URL."
|
267 | );
|
268 | }
|
269 | return resolveLocalIri(thing.name, baseUrl);
|
270 | }
|
271 |
|
272 | return thing.url;
|
273 | }
|
274 |
|
275 | export const asIri = asUrl;
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 | export function isThingLocal(
|
282 | thing: ThingPersisted | ThingLocal
|
283 | ): thing is ThingLocal {
|
284 | return (
|
285 | typeof (thing as ThingLocal).name === "string" &&
|
286 | typeof (thing as ThingPersisted).url === "undefined"
|
287 | );
|
288 | }
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | export function toNode(
|
295 | thing: UrlString | Url | LocalNode | Thing
|
296 | ): NamedNode | LocalNode {
|
297 | if (isNamedNode(thing) || isLocalNode(thing)) {
|
298 | return thing;
|
299 | }
|
300 | if (typeof thing === "string") {
|
301 | return asNamedNode(thing);
|
302 | }
|
303 | if (isThingLocal(thing)) {
|
304 | return getLocalNode(thing.name);
|
305 | }
|
306 | return asNamedNode(asUrl(thing));
|
307 | }
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 | export function cloneThing<T extends Thing>(
|
315 | thing: T
|
316 | ): T extends ThingLocal ? ThingLocal : ThingPersisted;
|
317 | export function cloneThing(thing: Thing): Thing {
|
318 | const cloned = clone(thing);
|
319 | if (isThingLocal(thing)) {
|
320 | (cloned as ThingLocal).name = thing.name;
|
321 | return cloned as ThingLocal;
|
322 | }
|
323 | (cloned as ThingPersisted).url = thing.url;
|
324 | return cloned as ThingPersisted;
|
325 | }
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 | export function filterThing<T extends Thing>(
|
334 | thing: T,
|
335 | callback: (quad: Quad) => boolean
|
336 | ): T extends ThingLocal ? ThingLocal : ThingPersisted;
|
337 | export function filterThing(
|
338 | thing: Thing,
|
339 | callback: (quad: Quad) => boolean
|
340 | ): Thing {
|
341 | const filtered = filter(thing, callback);
|
342 | if (isThingLocal(thing)) {
|
343 | (filtered as ThingLocal).name = thing.name;
|
344 | return filtered as ThingLocal;
|
345 | }
|
346 | (filtered as ThingPersisted).url = thing.url;
|
347 | return filtered as ThingPersisted;
|
348 | }
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | const generateName = () => {
|
361 | return (
|
362 | Date.now().toString() + Math.random().toString().substring("0.".length)
|
363 | );
|
364 | };
|