X-Git-Url: https://git.ladys.computer/Etiquette/blobdiff_plain/771a1ca8d8302b01060db00de7f588093e8805ec..e20983aca1b36dd368d52e02ee018c2eaf0ca55f:/schema.js diff --git a/schema.js b/schema.js new file mode 100644 index 0000000..c708cfe --- /dev/null +++ b/schema.js @@ -0,0 +1,114 @@ +// 📧🏷️ Étiquette ∷ schema.js +// ==================================================================== +// +// Copyright © 2023 Lady [@ Lady’s Computer]. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at . +// +// ___ +// +// ※ The definitions in this file are minimal and only really geared +// towards supporting the functionality that 📧🏷️ Étiquette needs. + +/** + * Supported class types. + * + * The class `"Thing"` is not defined, but is used as a generic + * superclass. + */ +const classes = { + Tag: { subClassOf: "Thing" }, + CanonTag: { subClassOf: "Tag" }, + ConceptualTag: { subClassOf: "Tag" }, + RelationshipTag: { + subClassOf: ["ConceptualTag", { + onProperty: "involves", + allValuesFrom: { + unionOf: ["CharacterTag", "RelationshipTag"], + }, + }], + }, + FriendshipTag: { subClassOf: "RelationshipTag" }, + RivalryTag: { subClassOf: "RelationshipTag" }, + FamilialRelationshipTag: { subClassOf: "RelationshipTag" }, + RomanticRelationshipTag: { subClassOf: "RelationshipTag" }, + SexualRelationshipTag: { subClassOf: "RelationshipTag" }, + EntityTag: { subClassOf: "Tag" }, + CharacterTag: { subClassOf: "EntityTag" }, + InanimateEntityTag: { subClassOf: "EntityTag" }, + GenreTag: { subClassOf: "Tag" }, + SettingTag: { subClassOf: "Tag" }, + LocationTag: { subClassOf: "SettingTag" }, + TimePeriodTag: { subClassOf: "SettingTag" }, + UniverseTag: { subClassOf: "SettingTag" }, +}; + +/** Supported transitive object properties. */ +const transitiveProperties = { + broaderTransitive: { domain: "Tag", range: "Tag" }, + narrowerTransitive: { inverseOf: "broaderTransitive" }, +}; + +/** Supported object properties. */ +const objectProperties = { + broader: { subPropertyOf: "broaderTransitive" }, + narrower: { + inverseOf: "broader", + subPropertyOf: "narrowerTransitive", + }, + inCanon: { + domain: { unionOf: ["EntityTag", "SettingTag"] }, + range: "CanonTag", + }, + hasInCanon: { inverseOf: "inCanon" }, + involves: { domain: "ConceptualTag", range: "Tag" }, + involvedIn: { inverseOf: "involves" }, + ...transitiveProperties, +}; + +/** Supported data properties. */ +const dataProperties = { + prefLabel: { domain: "Thing", range: "PlainLiteral" }, + altLabel: { domain: "Thing", range: "PlainLiteral" }, + hiddenLabel: { domain: "Thing", range: "PlainLiteral" }, +}; + +/** + * Returns an immutable, null‐prototype object deeply derived from the + * provided one. + * + * ※ Once records and tuples are added to Ecmascript, the schema + * should be defined in terms of those primitives. In the meantime, + * this function at least ensures the schema is Very Immutable. + */ +const makeRecord = ($) => { + return Object.preventExtensions( + Object.create( + null, + Object.fromEntries([...function* () { + for (const [key, value] of Object.entries($)) { + if (Object(value) === value) { + const recordValue = makeRecord(value); + yield [key, { enumerable: true, value: recordValue }]; + } else { + yield [key, { enumerable: true, value }]; + } + } + if (Array.isArray($)) { + yield ["length", { value: $.length }]; + } else { + /* do nothing */ + } + }()]), + ), + ); +}; + +export default makeRecord({ + classes, + objectProperties, + transitiveProperties, + dataProperties, +});