diff --git a/src/schema_to_frame.mjs b/src/schema_to_frame.mjs index 1dca145..1917b88 100644 --- a/src/schema_to_frame.mjs +++ b/src/schema_to_frame.mjs @@ -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 @@ -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"]); @@ -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])), ); } diff --git a/test/schema_to_frame.test.mjs b/test/schema_to_frame.test.mjs index 22e7a04..0917e2d 100644 --- a/test/schema_to_frame.test.mjs +++ b/test/schema_to_frame.test.mjs @@ -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.