Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/schema_to_frame.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ export function instanceRdfTypes(schema) {
}

// Properties whose value is a reference, so framing must leave it an IRI rather than
// pull the referenced node's triples into this document. Three signals, per
// OOLD-EXT-68fa: an x-oold-range on a string-typed value, an IRI-family format, or a
// context term mapped "@type": "@id".
// pull the referenced node's triples into this document. Four signals, per
// OOLD-EXT-6d10: an x-oold-range on a string-typed value, an IRI-family format, a
// context term mapped "@type": "@id", or one mapped with @reverse.
//
// @reverse stands on its own. A reverse term's values are node references by definition
// (JSON-LD 1.1 4.1.10), so "@type": "@id" beside it is redundant and authors omit it;
// keying only on @type would miss the idiomatic spelling and embed the targets.
//
// Without this, a referenced node that happens to carry triples in the same graph is
// embedded as an object, and the framed document no longer validates against the schema
Expand Down Expand Up @@ -138,6 +142,9 @@ export function keywordAliasKeys(schema) {
return found;
}

const isReferenceTerm = (def) =>
!!def && (def["@type"] === "@id" || "@reverse" in def);

export function referenceProperties(schema) {
const props = collectProps(schema);
const terms = contextTerms(schema["@context"]);
Expand All @@ -158,7 +165,7 @@ export function referenceProperties(schema) {
(k) =>
!aliases.has(k) &&
!isEmbed(props[k]) &&
(isReference(props[k]) || terms[k]?.["@type"] === "@id"),
(isReference(props[k]) || isReferenceTerm(terms[k])),
);
}

Expand Down
13 changes: 13 additions & 0 deletions test/schema_to_frame.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ test('embedding wins where a property carries both signals', () => {
assert.ok(!referenceProperties(organization).includes('address'));
});

test('a bare @reverse term is reference-valued', () => {
// A reverse term's values are node references by definition (JSON-LD 1.1 4.1.10), so
// "@type": "@id" beside it is redundant. The specification's own worked example writes
// both, which is why keying only on @type passed every fixture while missing the
// idiomatic spelling and embedding the targets.
const schema = {
'@context': { employees: { '@reverse': 'schema:worksFor' } },
properties: { employees: { type: 'array', items: { type: 'string' } } },
};
assert.deepEqual(referenceProperties(schema), ['employees']);
assert.deepEqual(schemaToFrame(schema).employees, { '@embed': '@never' });
});

test('a keyword alias never gets a subframe', () => {
// `id` names the node, it is not a predicate. It carries an IRI format, so the reference
// signals match, but a subframe there writes { "@id": {...} }, which a processor rejects.
Expand Down
Loading