--- /dev/null
+// 📧🏷️ É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 <https://mozilla.org/MPL/2.0/>.
+//
+// ___
+//
+// ※ 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,
+});