@muze-nl/oldm-core 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/package.json +1 -1
- package/src/oldm.mjs +133 -56
package/README.md
CHANGED
|
@@ -6,11 +6,12 @@ This package contains the object/graph mapping layer only. It has explicit ESM e
|
|
|
6
6
|
|
|
7
7
|
```javascript
|
|
8
8
|
import oldm, { one, many, first, Collection } from '@muze-nl/oldm-core'
|
|
9
|
-
import { n3Parser, n3Writer } from '@muze-nl/oldm-n3'
|
|
9
|
+
import { n3Parser, n3PatchWriter, n3Writer } from '@muze-nl/oldm-n3'
|
|
10
10
|
|
|
11
11
|
const context = oldm({
|
|
12
12
|
parser: n3Parser,
|
|
13
|
-
writer: n3Writer
|
|
13
|
+
writer: n3Writer,
|
|
14
|
+
patchWriter: n3PatchWriter
|
|
14
15
|
})
|
|
15
16
|
```
|
|
16
17
|
|
|
@@ -97,6 +98,12 @@ delete me.vcard$nickname
|
|
|
97
98
|
|
|
98
99
|
If there is no obvious source graph, OLDM throws and asks you to choose one explicitly with `context.set/add/delete(..., { graph })` or `graph.set/add/delete(...)`.
|
|
99
100
|
|
|
101
|
+
## PATCH output
|
|
102
|
+
|
|
103
|
+
`Graph#patch()` delegates to the configured `patchWriter`, just like `Graph#write()` delegates to `writer`. Core stays parser/writer agnostic; use `n3PatchWriter` from `@muze-nl/oldm-n3` when you want Solid N3 Patch output.
|
|
104
|
+
|
|
105
|
+
Patch writers can support owned anonymous values conservatively: fresh blank nodes and RDF collections can be inserted, while changed or deleted existing anonymous values are replaced as complete closures. Shared or cyclic anonymous values should fall back to full document replacement.
|
|
106
|
+
|
|
100
107
|
## Public exports
|
|
101
108
|
|
|
102
109
|
- default `oldm(options)` context factory
|
package/package.json
CHANGED
package/src/oldm.mjs
CHANGED
|
@@ -195,6 +195,7 @@ export class Context {
|
|
|
195
195
|
}
|
|
196
196
|
this.parser = options?.parser
|
|
197
197
|
this.writer = options?.writer
|
|
198
|
+
this.patchWriter = options?.patchWriter
|
|
198
199
|
this.graphs = []
|
|
199
200
|
this.graphsByUrl = Object.create(null)
|
|
200
201
|
this.defaultGraph = options?.defaultGraph ?? null
|
|
@@ -233,7 +234,7 @@ export class Context {
|
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
|
-
return this.addGraph(new Graph(quads, url, type, prefixes, this))
|
|
237
|
+
return this.addGraph(new Graph(quads, url, type, prefixes, this, input))
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
addGraph(graph)
|
|
@@ -262,21 +263,21 @@ export class Context {
|
|
|
262
263
|
|
|
263
264
|
set(subject, predicate, value, options={})
|
|
264
265
|
{
|
|
265
|
-
return this.resolveGraph(subject, options).set(subject, predicate, value)
|
|
266
|
+
return this.resolveGraph(subject, options).set(subject, predicate, value, {prefixPreference: 'context'})
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
add(subject, predicate, value, options={})
|
|
269
270
|
{
|
|
270
|
-
return this.resolveGraph(subject, options).add(subject, predicate, value)
|
|
271
|
+
return this.resolveGraph(subject, options).add(subject, predicate, value, {prefixPreference: 'context'})
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
delete(subject, predicate=null, value=undefined, options={})
|
|
274
275
|
{
|
|
275
276
|
const graph = this.resolveGraph(subject, options)
|
|
276
277
|
if (arguments.length < 3) {
|
|
277
|
-
return graph.delete(subject, predicate)
|
|
278
|
+
return graph.delete(subject, predicate, undefined, {prefixPreference: 'context', hasValue: false})
|
|
278
279
|
}
|
|
279
|
-
return graph.delete(subject, predicate, value)
|
|
280
|
+
return graph.delete(subject, predicate, value, {prefixPreference: 'context', hasValue: true})
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
resolveGraph(subject, options={})
|
|
@@ -388,7 +389,9 @@ export class Context {
|
|
|
388
389
|
return true
|
|
389
390
|
}
|
|
390
391
|
|
|
391
|
-
const property =
|
|
392
|
+
const property = subject.graph instanceof Graph
|
|
393
|
+
? subject.graph.propertyName(this.fullURI(predicate), 'context')
|
|
394
|
+
: this.propertyName(predicate)
|
|
392
395
|
if (!(property in subject)) {
|
|
393
396
|
return false
|
|
394
397
|
}
|
|
@@ -458,8 +461,12 @@ export class Context {
|
|
|
458
461
|
if (predicate == 'id') {
|
|
459
462
|
continue
|
|
460
463
|
}
|
|
461
|
-
|
|
462
|
-
|
|
464
|
+
|
|
465
|
+
const contextPredicate = predicate == 'a'
|
|
466
|
+
? 'a'
|
|
467
|
+
: this.propertyName(source.graph.fullURI(predicate, null, 'source'))
|
|
468
|
+
target[contextPredicate] = mergeValue(
|
|
469
|
+
target[contextPredicate],
|
|
463
470
|
resolveValue(value, subjects, this)
|
|
464
471
|
)
|
|
465
472
|
}
|
|
@@ -556,12 +563,13 @@ export class Graph
|
|
|
556
563
|
{
|
|
557
564
|
#blankNodes = Object.create(null)
|
|
558
565
|
|
|
559
|
-
constructor(quads, url, mimetype, prefixes, context)
|
|
566
|
+
constructor(quads, url, mimetype, prefixes, context, originalSource=null)
|
|
560
567
|
{
|
|
561
568
|
this.mimetype = mimetype
|
|
562
569
|
this.url = url
|
|
563
570
|
this.prefixes = prefixes
|
|
564
571
|
this.context = context
|
|
572
|
+
this.originalSource = originalSource
|
|
565
573
|
this.subjects = Object.create(null)
|
|
566
574
|
for (let quad of quads) {
|
|
567
575
|
let subject
|
|
@@ -634,39 +642,109 @@ export class Graph
|
|
|
634
642
|
return this.context.writer(this)
|
|
635
643
|
}
|
|
636
644
|
|
|
645
|
+
patch()
|
|
646
|
+
{
|
|
647
|
+
if (!this.context.patchWriter) {
|
|
648
|
+
throw new Error('Cannot generate a patch without a configured patchWriter')
|
|
649
|
+
}
|
|
650
|
+
return this.context.patchWriter(this)
|
|
651
|
+
}
|
|
652
|
+
|
|
637
653
|
get(shortID)
|
|
638
654
|
{
|
|
639
655
|
return this.subjects[this.fullURI(shortID)]
|
|
640
656
|
}
|
|
641
657
|
|
|
642
|
-
|
|
658
|
+
prefixEntries(preference='source')
|
|
659
|
+
{
|
|
660
|
+
const sourcePrefixes = this.prefixes ?? {}
|
|
661
|
+
const sourceOrder = Object.keys(sourcePrefixes)
|
|
662
|
+
const contextPrefixes = this.context.prefixes ?? {}
|
|
663
|
+
const contextOrder = this.context.prefixOrder ?? Object.keys(contextPrefixes)
|
|
664
|
+
const entries = []
|
|
665
|
+
const seen = new Set()
|
|
666
|
+
const seenIRIs = new Set()
|
|
667
|
+
const add = (prefixes, order, skipKnownIRIs=false) => {
|
|
668
|
+
for (const prefix of order) {
|
|
669
|
+
if (!Object.prototype.hasOwnProperty.call(prefixes, prefix)) {
|
|
670
|
+
continue
|
|
671
|
+
}
|
|
672
|
+
const iri = prefixes[prefix]
|
|
673
|
+
if (!seen.has(prefix) && (!skipKnownIRIs || !seenIRIs.has(iri))) {
|
|
674
|
+
entries.push([prefix, iri])
|
|
675
|
+
seen.add(prefix)
|
|
676
|
+
seenIRIs.add(iri)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
for (const prefix of Object.keys(prefixes)) {
|
|
680
|
+
const iri = prefixes[prefix]
|
|
681
|
+
if (!seen.has(prefix) && (!skipKnownIRIs || !seenIRIs.has(iri))) {
|
|
682
|
+
entries.push([prefix, iri])
|
|
683
|
+
seen.add(prefix)
|
|
684
|
+
seenIRIs.add(iri)
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (preference == 'context') {
|
|
690
|
+
add(contextPrefixes, contextOrder)
|
|
691
|
+
add(sourcePrefixes, sourceOrder, true)
|
|
692
|
+
} else {
|
|
693
|
+
add(sourcePrefixes, sourceOrder)
|
|
694
|
+
add(contextPrefixes, contextOrder, true)
|
|
695
|
+
}
|
|
696
|
+
return entries
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
prefixDeclarations(preference='source')
|
|
700
|
+
{
|
|
701
|
+
return Object.fromEntries(this.prefixEntries(preference))
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
propertyName(predicate, preference='source')
|
|
705
|
+
{
|
|
706
|
+
if (predicate?.id) {
|
|
707
|
+
predicate = predicate.id
|
|
708
|
+
}
|
|
709
|
+
const fullPredicate = this.fullURI(predicate, null, preference)
|
|
710
|
+
if (predicate == 'a' || fullPredicate == rdfType) {
|
|
711
|
+
return 'a'
|
|
712
|
+
}
|
|
713
|
+
return this.shortURI(fullPredicate, null, 'source')
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
set(subject, predicate, value, options={})
|
|
643
717
|
{
|
|
644
|
-
const
|
|
645
|
-
const
|
|
718
|
+
const preference = options.prefixPreference ?? 'source'
|
|
719
|
+
const node = this.ensureSubject(subject, preference)
|
|
720
|
+
const property = this.propertyName(predicate, preference)
|
|
646
721
|
|
|
647
722
|
if (property == 'a') {
|
|
648
|
-
node.a = this.normalizeTypeValues(value)
|
|
723
|
+
node.a = this.normalizeTypeValues(value, preference)
|
|
649
724
|
} else {
|
|
650
|
-
node[property] = this.normalizeValues(value)
|
|
725
|
+
node[property] = this.normalizeValues(value, preference)
|
|
651
726
|
}
|
|
652
727
|
return node
|
|
653
728
|
}
|
|
654
729
|
|
|
655
|
-
add(subject, predicate, value)
|
|
730
|
+
add(subject, predicate, value, options={})
|
|
656
731
|
{
|
|
657
|
-
const
|
|
658
|
-
const
|
|
732
|
+
const preference = options.prefixPreference ?? 'source'
|
|
733
|
+
const node = this.ensureSubject(subject, preference)
|
|
734
|
+
const property = this.propertyName(predicate, preference)
|
|
659
735
|
const newValue = property == 'a'
|
|
660
|
-
? this.normalizeTypeValues(value)
|
|
661
|
-
: this.normalizeValues(value)
|
|
736
|
+
? this.normalizeTypeValues(value, preference)
|
|
737
|
+
: this.normalizeValues(value, preference)
|
|
662
738
|
|
|
663
739
|
node[property] = mergeValue(node[property], newValue)
|
|
664
740
|
return node
|
|
665
741
|
}
|
|
666
742
|
|
|
667
|
-
delete(subject, predicate=null, value=undefined)
|
|
743
|
+
delete(subject, predicate=null, value=undefined, options={})
|
|
668
744
|
{
|
|
669
|
-
const
|
|
745
|
+
const preference = options.prefixPreference ?? 'source'
|
|
746
|
+
const hasValue = options.hasValue ?? arguments.length >= 3
|
|
747
|
+
const node = this.findSubject(subject, preference)
|
|
670
748
|
if (!node) {
|
|
671
749
|
return false
|
|
672
750
|
}
|
|
@@ -681,19 +759,19 @@ export class Graph
|
|
|
681
759
|
return true
|
|
682
760
|
}
|
|
683
761
|
|
|
684
|
-
const property = this.
|
|
762
|
+
const property = this.propertyName(predicate, preference)
|
|
685
763
|
if (!(property in node)) {
|
|
686
764
|
return false
|
|
687
765
|
}
|
|
688
766
|
|
|
689
|
-
if (
|
|
767
|
+
if (!hasValue) {
|
|
690
768
|
delete node[property]
|
|
691
769
|
return true
|
|
692
770
|
}
|
|
693
771
|
|
|
694
772
|
const deleteValues = property == 'a'
|
|
695
|
-
? values(this.normalizeTypeValues(value))
|
|
696
|
-
: values(this.normalizeValues(value))
|
|
773
|
+
? values(this.normalizeTypeValues(value, preference))
|
|
774
|
+
: values(this.normalizeValues(value, preference))
|
|
697
775
|
const remaining = values(node[property])
|
|
698
776
|
.filter(item => !deleteValues.some(deleteValue => sameValue(item, deleteValue)))
|
|
699
777
|
|
|
@@ -710,7 +788,7 @@ export class Graph
|
|
|
710
788
|
return true
|
|
711
789
|
}
|
|
712
790
|
|
|
713
|
-
ensureSubject(subject)
|
|
791
|
+
ensureSubject(subject, preference='source')
|
|
714
792
|
{
|
|
715
793
|
if (subject instanceof BlankNode && !(subject instanceof NamedNode)) {
|
|
716
794
|
if (subject.graph !== this) {
|
|
@@ -723,32 +801,32 @@ export class Graph
|
|
|
723
801
|
return this.addNamedNode(subject.id)
|
|
724
802
|
}
|
|
725
803
|
|
|
726
|
-
return this.addNamedNode(this.fullURI(subject))
|
|
804
|
+
return this.addNamedNode(this.fullURI(subject, null, preference))
|
|
727
805
|
}
|
|
728
806
|
|
|
729
|
-
findSubject(subject)
|
|
807
|
+
findSubject(subject, preference='source')
|
|
730
808
|
{
|
|
731
809
|
if (subject instanceof BlankNode && !(subject instanceof NamedNode)) {
|
|
732
810
|
return subject.graph === this ? subject : null
|
|
733
811
|
}
|
|
734
|
-
const id = subject?.id ? subject.id : this.fullURI(subject)
|
|
812
|
+
const id = subject?.id ? subject.id : this.fullURI(subject, null, preference)
|
|
735
813
|
return this.subjects[id]
|
|
736
814
|
}
|
|
737
815
|
|
|
738
|
-
normalizeValues(value)
|
|
816
|
+
normalizeValues(value, preference='source')
|
|
739
817
|
{
|
|
740
818
|
if (Array.isArray(value) && !(value instanceof Collection)) {
|
|
741
|
-
return value.map(item => this.normalizeValue(item))
|
|
819
|
+
return value.map(item => this.normalizeValue(item, preference))
|
|
742
820
|
}
|
|
743
|
-
return this.normalizeValue(value)
|
|
821
|
+
return this.normalizeValue(value, preference)
|
|
744
822
|
}
|
|
745
823
|
|
|
746
|
-
normalizeValue(value)
|
|
824
|
+
normalizeValue(value, preference='source')
|
|
747
825
|
{
|
|
748
826
|
if (value instanceof Collection) {
|
|
749
827
|
const collection = new Collection(this)
|
|
750
828
|
for (const item of value) {
|
|
751
|
-
collection.push(this.normalizeValue(item))
|
|
829
|
+
collection.push(this.normalizeValue(item, preference))
|
|
752
830
|
}
|
|
753
831
|
return collection
|
|
754
832
|
}
|
|
@@ -761,29 +839,29 @@ export class Graph
|
|
|
761
839
|
}
|
|
762
840
|
return value
|
|
763
841
|
}
|
|
764
|
-
if (this.looksLikeURI(value)) {
|
|
765
|
-
return this.addNamedNode(this.fullURI(value))
|
|
842
|
+
if (this.looksLikeURI(value, preference)) {
|
|
843
|
+
return this.addNamedNode(this.fullURI(value, null, preference))
|
|
766
844
|
}
|
|
767
845
|
return value
|
|
768
846
|
}
|
|
769
847
|
|
|
770
|
-
normalizeTypeValues(value)
|
|
848
|
+
normalizeTypeValues(value, preference='source')
|
|
771
849
|
{
|
|
772
850
|
if (Array.isArray(value) && !(value instanceof Collection)) {
|
|
773
|
-
return value.map(item => this.normalizeTypeValue(item))
|
|
851
|
+
return value.map(item => this.normalizeTypeValue(item, preference))
|
|
774
852
|
}
|
|
775
|
-
return this.normalizeTypeValue(value)
|
|
853
|
+
return this.normalizeTypeValue(value, preference)
|
|
776
854
|
}
|
|
777
855
|
|
|
778
|
-
normalizeTypeValue(value)
|
|
856
|
+
normalizeTypeValue(value, preference='source')
|
|
779
857
|
{
|
|
780
858
|
if (value instanceof NamedNode) {
|
|
781
|
-
return this.shortURI(value.id)
|
|
859
|
+
return this.shortURI(value.id, null, 'source')
|
|
782
860
|
}
|
|
783
|
-
return this.shortURI(this.fullURI(value))
|
|
861
|
+
return this.shortURI(this.fullURI(value, null, preference), null, 'source')
|
|
784
862
|
}
|
|
785
863
|
|
|
786
|
-
looksLikeURI(value)
|
|
864
|
+
looksLikeURI(value, preference='source')
|
|
787
865
|
{
|
|
788
866
|
if (typeof value != 'string') {
|
|
789
867
|
return false
|
|
@@ -792,34 +870,33 @@ export class Graph
|
|
|
792
870
|
return true
|
|
793
871
|
}
|
|
794
872
|
const [prefix, path] = value.split(this.context.separator)
|
|
795
|
-
return Boolean(path && this.
|
|
873
|
+
return Boolean(path && this.prefixEntries(preference).some(([candidate]) => candidate == prefix))
|
|
796
874
|
}
|
|
797
875
|
|
|
798
|
-
fullURI(shortURI, separator=null)
|
|
876
|
+
fullURI(shortURI, separator=null, preference='source')
|
|
799
877
|
{
|
|
800
878
|
if (!separator) {
|
|
801
879
|
separator = this.context.separator
|
|
802
880
|
}
|
|
803
|
-
const [prefix, path] = shortURI.split(separator)
|
|
881
|
+
const [prefix, path] = String(shortURI).split(separator)
|
|
804
882
|
if (path) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return this.prefixes[prefix]+path
|
|
883
|
+
for (const [candidate, iri] of this.prefixEntries(preference)) {
|
|
884
|
+
if (candidate == prefix) {
|
|
885
|
+
return iri+path
|
|
886
|
+
}
|
|
810
887
|
}
|
|
811
888
|
}
|
|
812
889
|
return shortURI
|
|
813
890
|
}
|
|
814
891
|
|
|
815
|
-
shortURI(fullURI, separator=null)
|
|
892
|
+
shortURI(fullURI, separator=null, preference='source')
|
|
816
893
|
{
|
|
817
894
|
if (!separator) {
|
|
818
895
|
separator = this.context.separator
|
|
819
896
|
}
|
|
820
|
-
for (const prefix of this.
|
|
821
|
-
if (fullURI.startsWith(
|
|
822
|
-
return prefix + separator + fullURI.substring(
|
|
897
|
+
for (const [prefix, iri] of this.prefixEntries(preference)) {
|
|
898
|
+
if (fullURI.startsWith(iri)) {
|
|
899
|
+
return prefix + separator + fullURI.substring(iri.length)
|
|
823
900
|
}
|
|
824
901
|
}
|
|
825
902
|
if (this.url && fullURI.startsWith(this.url)) {
|
|
@@ -958,4 +1035,4 @@ export class Collection extends Array
|
|
|
958
1035
|
enumerable: false
|
|
959
1036
|
})
|
|
960
1037
|
}
|
|
961
|
-
}
|
|
1038
|
+
}
|