X-Git-Url: https://git.ladys.computer/Etiquette/blobdiff_plain/a3aca34a22550e1555bcdf8a571492213bfa83b0..f8903c5b3bd12d02af174e5043d906d63da0b0d1:/model.js
diff --git a/model.js b/model.js
index d64f6c4..d2990eb 100644
--- a/model.js
+++ b/model.js
@@ -1,67 +1,72 @@
-// 📧🏷️ Étiquette ∷ model.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 .
+// SPDX-FileCopyrightText: 2023, 2025 Lady
+// SPDX-License-Identifier: MPL-2.0
+/**
+ * ⁌ 📧🏷️ Étiquette ∷ model.js
+ *
+ * Copyright © 2023, 2025 Lady [@ Ladys 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 .
+ */
import { identity } from "./deps.js";
import { Storage } from "./memory.js";
import { taggingDiscoveryContext } from "./names.js";
import schema from "./schema.js";
+const ÉTIQUETTE = "📧🏷️ Étiquette";
+
/**
* A tag.
*
- * `Tag`s are not assigned identifiers and do not have side·effects on
- * other tags in the `TagSystem` until they are persisted with
- * `::persist`, at which point changes to their relationships are
+ * `Tag´s are not assigned identifiers and do not have side·effects on
+ * other tags in the `TagSystem´ until they are persisted with
+ * `::persist´, at which point changes to their relationships are
* applied.
*
- * `Tag`s are also not kept up‐to‐date, but persisting an outdated
- * `Tag` will *not* undo subsequent changes.
+ * `Tag´s are also not kept up‐to‐date, but persisting an outdated
+ * `Tag´ will ⹐not⹑ undo subsequent changes.
*
* ※ This class is not itself directly exposed, although bound
- * versions of it are via `TagSystem::Tag`.
+ * versions of it are via `TagSystem::Tag´.
*/
class Tag {
- /** The `TagSystem` this `Tag` belongs to. */
+ /** The `TagSystem´ this `Tag´ belongs to. */
#system;
- /** The `Storage` managed by this `Tag`’s `TagSystem`. */
+ /** The `Storage´ managed by this `Tag´s `TagSystem´. */
#storage;
- /** The schema in use for this `Tag`. */
+ /** The schema in use for this `Tag´. */
#schema;
/**
* The 30‐bit W·R·M·G base32 identifier with leading checksum which
- * has been assigned to this `Tag`.
+ * has been assigned to this `Tag´.
*
- * Will be `null` if this `Tag` has not been persisted. Otherwise,
- * the format is `cxx-xxxx` (`c` = checksum; `x` = digit).
+ * Will be `null´ if this `Tag´ has not been persisted. Otherwise,
+ * the format is `cxx-xxxx´ (`c´ = checksum; `x´ = digit).
*/
#identifier = null;
- /** The kind of this `Tag`. */
+ /** The kind of this `Tag´. */
#kind = "Tag";
/**
- * The data which was attached to this `Tag` the last time it was
+ * The data which was attached to this `Tag´ the last time it was
* persisted or retrieved from storage.
*
* Diffing with this will reveal changes.
*/
#persistedData = null;
- /** The current (modified) data associated with this `Tag`. */
+ /** The current (modified) data associated with this `Tag´. */
#data = tagData();
/**
- * Adds the provided label(s) to this `Tag` as the provided
- * predicate, then returns this `Tag`.
+ * Adds the provided label(s) to this `Tag´ as the provided
+ * predicate, then returns this `Tag´.
*/
#addLabel(predicate, ...labels) {
const values = this.#data[predicate];
@@ -74,11 +79,11 @@ class Tag {
}
/**
- * Adds the provided tags to the list of tags that this `Tag` is
- * related to by the provided predicate, then returns this `Tag`.
+ * Adds the provided tags to the list of tags that this `Tag´ is
+ * related to by the provided predicate, then returns this `Tag´.
*
* Arguments may be string identifiers or objects with an
- * `.identifier` property.
+ * `.identifier´ property.
*/
#addTag(predicate, ...tags) {
const storage = this.#storage;
@@ -89,26 +94,26 @@ class Tag {
if (identifier == null) {
// ☡ The current tag has no identifier.
throw new TypeError(
- `Cannot state ${predicate} of Tag: Identifier must not be nullish.`,
+ `${ÉTIQUETTE}: Cannot state ${predicate} of Tag: Identifier must not be nullish.`,
);
} else if (values.has(identifier)) {
// Short‐circuit: The identifier has already been stated with
// this predicate.
/* do nothing */
} else {
- // The current tag has an identifier, but it hasn’t been stated
+ // The current tag has an identifier, but it hasn¦t been stated
// with this predicate yet.
const tag = storage.get(identifier);
if (tag == null) {
- // ☡ The current tag has not been persisted to this `Tag`’s
+ // ☡ The current tag has not been persisted to this `Tag´s
// storage.
throw new RangeError(
- `Cannot state ${predicate} of Tag: Identifier is not persisted: ${identifier}.`,
+ `${ÉTIQUETTE}: Cannot state ${predicate} of Tag: Identifier is not persisted: ${identifier}.`,
);
} else if (!this.#isTagInStorage(tag)) {
// ☡ The current tag is not a tag in the correct tag system.
throw new TypeError(
- `Cannot state ${predicate} of Tag: Tags must be from the same Tag System, but got: ${identifier}.`,
+ `${ÉTIQUETTE}: Cannot state ${predicate} of Tag: Tags must be from the same Tag System, but got: ${identifier}.`,
);
} else if (
!isObjectPredicateOK(
@@ -121,7 +126,7 @@ class Tag {
// ☡ This tag and the current tag form an invalid pair for
// this predicate.
throw new TypeError(
- `Cannot state ${predicate} of Tag: Not valid for domain and range: ${this.#kind}, ${tag.#kind}.`,
+ `${ÉTIQUETTE}: Cannot state ${predicate} of Tag: Not valid for domain and range: ${this.#kind}, ${tag.#kind}.`,
);
} else {
// The current tag is a tag in the correct tag system; add
@@ -134,8 +139,8 @@ class Tag {
}
/**
- * Removes the provided string label(s) from this `Tag` as the
- * provided predicate, then returns this `Tag`.
+ * Removes the provided string label(s) from this `Tag´ as the
+ * provided predicate, then returns this `Tag´.
*/
#deleteLabel(predicate, ...labels) {
const values = this.#data[predicate];
@@ -148,11 +153,11 @@ class Tag {
}
/**
- * Removes the provided tags from the list of tags that this `Tag` is
- * related to by the provided predicate, then returns this `Tag`.
+ * Removes the provided tags from the list of tags that this `Tag´ is
+ * related to by the provided predicate, then returns this `Tag´.
*
* Arguments may be string identifiers or objects with an
- * `.identifier` property.
+ * `.identifier´ property.
*/
#deleteTag(predicate, ...tags) {
const values = this.#data[predicate];
@@ -167,11 +172,11 @@ class Tag {
* Returns whether or not the provided value is a tag which shares a
* storage with this tag.
*
- * Sharing a storage also implies sharing a `TagSystem`.
+ * Sharing a storage also implies sharing a `TagSystem´.
*/
#isTagInStorage($) {
try {
- // Try to compare the provided value’s internal store with
+ // Try to compare the provided values internal store with
// the provided storage.
return $.#storage == this.#storage;
} catch {
@@ -181,7 +186,7 @@ class Tag {
}
/**
- * Yields the labels of this `Tag` according to the provided
+ * Yields the labels of this `Tag´ according to the provided
* predicate.
*/
*#yieldLabels(predicate) {
@@ -189,7 +194,7 @@ class Tag {
}
/**
- * Yields the tags that this `Tag` is related to by the provided
+ * Yields the tags that this `Tag´ is related to by the provided
* predicate.
*/
*#yieldTags(predicate) {
@@ -217,7 +222,7 @@ class Tag {
}
/**
- * Yields the tags that this `Tag` is related to by the provided
+ * Yields the tags that this `Tag´ is related to by the provided
* predicate, figured transitively.
*/
*#yieldTransitiveTags(transitivePredicate, basePredicate) {
@@ -269,11 +274,11 @@ class Tag {
}
/**
- * Constructs a new `Tag` of the provided kind and with the provided
+ * Constructs a new `Tag´ of the provided kind and with the provided
* preferred label.
*
* ※ The first two arguments of this constructor are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
+ * generating the value of `TagSystem::Tag´. It isn¦t possible to
* access this constructor in its unbound form from outside this
* module.
*
@@ -287,7 +292,7 @@ class Tag {
if (!(kindString in schema.classes)) {
// The provided kind is not supported.
throw new RangeError(
- `Cannot construct Tag: Unrecognized kind: ${kind}.`,
+ `${ÉTIQUETTE}: Cannot construct Tag: Unrecognized kind: ${kind}.`,
);
} else {
// The provided kind is one of the recognized tag kinds.
@@ -297,7 +302,7 @@ class Tag {
}
/**
- * Returns a new `Tag` constructor for the provided system, storage,
+ * Returns a new `Tag´ constructor for the provided system, storage,
* schema, created with an appropriate prototype for the properties
* so defined.
*
@@ -348,8 +353,8 @@ class Tag {
// The current key does not indicate an inverse
// property, so add and delete methods are also
// added.
- const cased = key[0].toUpperCase() +
- key.substring(1);
+ const cased = key[0].toUpperCase()
+ + key.substring(1);
yield [`add${cased}Tag`, function (...tags) {
return this.#addTag(key, ...tags);
}];
@@ -362,8 +367,8 @@ class Tag {
/* do nothing */
}
if (
- subPropertyOf != null &&
- subPropertyOf in transitiveProperties
+ subPropertyOf != null
+ && subPropertyOf in transitiveProperties
) {
// The current key indicates a subproperty of a
// transitive property; its method is also added.
@@ -384,9 +389,9 @@ class Tag {
// Iterate over each data property and yield any
// necessary method definitions.
if (key != "prefLabel") {
- // The current key is not `"prefLabel"`.
- const cased = key[0].toUpperCase() +
- key.substring(1);
+ // The current key is not `"prefLabel"´.
+ const cased = key[0].toUpperCase()
+ + key.substring(1);
yield [`${key}s`, function* () {
yield* this.#yieldLabels(key);
}];
@@ -397,7 +402,7 @@ class Tag {
return this.#deleteLabel(key, ...labels);
}];
} else {
- // The current key is `"prefLabel"`. This is a
+ // The current key is `"prefLabel"´. This is a
// special case which is not handled by the schema.
/* do nothing */
}
@@ -422,7 +427,7 @@ class Tag {
/**
* Assigns the provided data and identifier to the provided tag.
*
- * ☡ This function throws if the provided tag is not a `Tag`.
+ * ☡ This function throws if the provided tag is not a `Tag´.
*
* ※ This function is not exposed.
*/
@@ -434,11 +439,40 @@ class Tag {
}
/**
- * Returns the `TagSystem` that the provided value belongs to.
+ * Returns a new `Tag´ with the provided identifier, kind, and
+ * prefLabel.
+ *
+ * ※ This function exists to enable `TagSystem´s to replay Create
+ * activities, maintaining the identifier of the original.
+ *
+ * ☡ This function throws if the provided identifier is already in
+ * use.
+ *
+ * ※ This function is not exposed.
+ */
+ static new(system, identifier, kind = "Tag", prefLabel = "") {
+ const storage = (new system.Tag()).#storage;
+ if (storage.has(identifier)) {
+ throw new RangeError(
+ `${ÉTIQUETTE}: Cannot create Tag: Identifier already in use: ${identifier}.`,
+ );
+ } else {
+ const createdTag = new system.Tag(kind, prefLabel);
+ createdTag.#identifier = identifier;
+ createdTag.persist(true);
+ return createdTag;
+ }
+ }
+
+ /**
+ * Returns the `TagSystem´ that the provided value belongs to.
*
* ※ This function can be used to check if the provided value has
* private tag features.
*
+ * ※ `Tag::system´ is an overridable, publicly‐accessible means of
+ * accessing the system.
+ *
* ※ This function is not exposed.
*/
static getSystem($) {
@@ -446,16 +480,16 @@ class Tag {
}
static {
- // Overwrite the default `::constructor` method to instead give the
+ // Overwrite the default `::constructor´ method to instead give the
// actual (bound) constructor which was used to generate a given
- // `Tag`.
+ // `Tag´.
Object.defineProperties(this.prototype, {
constructor: {
configurable: true,
enumerable: false,
get() {
- // All `Tag`s are constructed via the `.Tag` constructor
- // available in their `TagSystem`; return it.
+ // All `Tag´s are constructed via the `.Tag´ constructor
+ // available in their `TagSystem´; return it.
return this.#system.Tag;
},
set: undefined,
@@ -463,38 +497,47 @@ class Tag {
});
}
- /** Returns the authority (domain) name for this `Tag`. */
+ /** Returns the authority (domain) name for this `Tag´. */
get authorityName() {
return this.#system.authorityName;
}
- /** Returns the identifier of this `Tag`. */
+ /** Returns the identifier of this `Tag´. */
get identifier() {
return this.#identifier;
}
- /** Returns the I·R·I for this `Tag`. */
+ /** Returns the I·R·I for this `Tag´. */
get iri() {
const { identifier, iriSpace } = this;
return identifier == null ? null : `${iriSpace}${identifier}`;
}
- /** Returns the I·R·I space for this `Tag`. */
+ /** Returns the I·R·I space for this `Tag´. */
get iriSpace() {
return this.#system.iriSpace;
}
- /** Returns the kind of this `Tag`. */
+ /** Returns the kind of this `Tag´. */
get kind() {
return this.#kind;
}
/**
- * Persist this `Tag` to storage and return an ActivityStreams
+ * Returns the `TagSystem´ for this `Tag´.
+ *
+ * ※ Internally, `Tag.getSystem´ is preferred.
+ */
+ get system() {
+ return this.#system;
+ }
+
+ /**
+ * Persist this `Tag´ to storage and return an ActivityStreams
* serialization of a Tag Activity representing any changes, or
- * `null` if no changes were made.
+ * `null´ if no changes were made.
*
- * If the second argument is `true`, the `Tag` will be persisted but
+ * If the second argument is `true´, the `Tag´ will be persisted but
* no serialization will be made. This is somewhat more efficient.
*
* ※ Persistence can imply side‐effects on other objects, which are
@@ -520,8 +563,8 @@ class Tag {
// Iterate over each entry of the tag data and create a diff
// with the last persisted information.
if (
- objectProperties[key]?.inverseOf != null ||
- silent && key in dataProperties
+ objectProperties[key]?.inverseOf != null
+ || silent && key in dataProperties
) {
// The current property is one which is skipped in diffs.
//
@@ -558,8 +601,8 @@ class Tag {
}
diffs[key] = { old: oldValues, new: newValues };
} else if (
- `${value}` != `${persisted}` ||
- value.language != persisted.language
+ `${value}` != `${persisted}`
+ || value.language != persisted.language
) {
// The current property is (optionally language‐tagged)
// string‐valued and the value changed.
@@ -575,11 +618,11 @@ class Tag {
}
const identifier = this.#identifier;
if (identifier != null) {
- // This `Tag` has already been persisted; use its existing
+ // This `Tag´ has already been persisted; use its existing
// identifier and persist.
storage.set(identifier, this);
} else {
- // This `Tag` has not been persisted yet; save the new
+ // This `Tag´ has not been persisted yet; save the new
// identifier after persisting.
this.#identifier = storage.add(this);
}
@@ -597,11 +640,11 @@ class Tag {
// The current property is the inverse of a non‐transitive
// property.
for (const referencedIdentifier of diffs[term].old) {
- // Iterate over the removed tags and remove this `Tag` from
+ // Iterate over the removed tags and remove this `Tag´ from
// their inverse property.
const referenced = storage.get(referencedIdentifier);
try {
- // Try removing this `Tag`.
+ // Try removing this `Tag´.
referenced.#data[inverse].delete(persistedIdentifier);
storage.set(referencedIdentifier, referenced);
} catch {
@@ -617,7 +660,8 @@ class Tag {
referenced.#data[inverse].add(persistedIdentifier);
storage.set(referencedIdentifier, referenced);
} catch {
- // Adding failed, possibly because the other tag was deleted.
+ // Adding failed, possibly because the other tag was
+ // deleted.
/* do nothing */
}
}
@@ -684,8 +728,8 @@ class Tag {
});
}
} catch {
- // Value resolution failed for some reason; perhaps the
- // tag was deleted.
+ // Value resolution failed for some reason; perhaps
+ // the tag was deleted.
/* do nothing */
}
}
@@ -715,8 +759,8 @@ class Tag {
});
}
} catch {
- // Value resolution failed for some reason; perhaps the
- // tag was deleted.
+ // Value resolution failed for some reason; perhaps
+ // the tag was deleted.
/* do nothing */
}
}
@@ -740,29 +784,30 @@ class Tag {
})(),
};
if (
- !Object.hasOwn(activity, "states") &&
- !Object.hasOwn(activity, "unstates")
+ !Object.hasOwn(activity, "states")
+ && !Object.hasOwn(activity, "unstates")
) {
// No meaningful changes were actually persisted.
return null;
} else {
- // There were meaningful changes persisted regarding this `Tag`.
+ // There were meaningful changes persisted regarding this
+ // `Tag´.
return activity;
}
}
}
- /** Returns the preferred label for this `Tag`. */
+ /** Returns the preferred label for this `Tag´. */
get prefLabel() {
return this.#data.prefLabel;
}
- /** Sets the preferred label of this `Tag` to the provided label. */
+ /** Sets the preferred label of this `Tag´ to the provided label. */
set prefLabel($) {
this.#data.prefLabel = langString($);
}
- /** Returns the Tag U·R·I for this `Tag`. */
+ /** Returns the Tag U·R·I for this `Tag´. */
get tagURI() {
const { identifier } = this;
return identifier == null
@@ -770,12 +815,12 @@ class Tag {
: `tag:${this.taggingEntity}:${identifier}`;
}
- /** Returns the tagging entity (domain and date) for this `Tag`. */
+ /** Returns the tagging entity (domain and date) for this `Tag´. */
get taggingEntity() {
return this.#system.taggingEntity;
}
- /** Returns the string form of the preferred label of this `Tag`. */
+ /** Returns the string form of the preferred label of this `Tag´. */
toString() {
return `${this.#data.prefLabel}`;
}
@@ -797,14 +842,14 @@ class Tag {
const {
/**
- * A `Tag` constructor function.
+ * A `Tag´ constructor function.
*
* This class extends the identity function, meaning that the object
* provided as the constructor is used verbatim (with new private
* fields added).
*
* ※ The instance methods of this class are provided as static
- * methods on the superclass which all `Tag` constructors inherit
+ * methods on the superclass which all `Tag´ constructors inherit
* from.
*
* ※ This class is not exposed.
@@ -812,7 +857,7 @@ const {
TagConstructor,
/**
- * The exposed constructor function from which all `Tag` constructors
+ * The exposed constructor function from which all `Tag´ constructors
* inherit.
*
* ☡ This constructor always throws.
@@ -823,26 +868,26 @@ const {
return {
TagConstructor: class extends identity {
/**
- * The `TagSystem` used for `Tag`s constructed by this
+ * The `TagSystem´ used for `Tag´s constructed by this
* constructor.
*/
#system;
- /** The `Storage` managed by this constructor’s `TagSystem`. */
+ /** The `Storage´ managed by this constructors `TagSystem´. */
#storage;
/** The schema in use for this constructor. */
#schema;
/**
- * Constructs a new `Tag` constructor by adding the appropriate
+ * Constructs a new `Tag´ constructor by adding the appropriate
* private fields to the provided constructor, setting its
* prototype, and then returning it.
*
- * ※ This constructor does not modify the `name` or `prototype`
+ * ※ This constructor does not modify the `name´ or `prototype´
* properties of the provided constructor.
*
- * ※ See `Tag.For`, where this constructor is used.
+ * ※ See `Tag.For´, where this constructor is used.
*/
constructor(constructor, system, storage, schema) {
super(constructor);
@@ -853,14 +898,14 @@ const {
}
static {
- // Define the superclass constructor which all `Tag`
+ // Define the superclass constructor which all `Tag´
// constructors will inherit from.
const superclass = tagConstructorBehaviours.TagSuper =
function Tag() {
throw new TypeError("Tags must belong to a System.");
};
- const { prototype: methods } = this;
- delete methods.constructor;
+ const { prototype: staticFeatures } = this;
+ delete staticFeatures.constructor;
Object.defineProperty(superclass, "prototype", {
configurable: false,
enumerable: false,
@@ -869,12 +914,12 @@ const {
});
Object.defineProperties(
superclass,
- Object.getOwnPropertyDescriptors(methods),
+ Object.getOwnPropertyDescriptors(staticFeatures),
);
}
/**
- * Yields the tags in the `TagSystem` associated with this
+ * Yields the tags in the `TagSystem´ associated with this
* constructor.
*/
*all() {
@@ -882,26 +927,26 @@ const {
const storage = this.#storage;
for (const instance of storage.values()) {
// Iterate over the entries and yield the ones which are
- // `Tag`s in this `TagSystem`.
+ // `Tag´s in this `TagSystem´.
if (Tag.getSystem(instance) == system) {
- // The current instance is a `Tag` in this `TagSystem`.
+ // The current instance is a `Tag´ in this `TagSystem´.
yield instance;
} else {
- // The current instance is not a `Tag` in this
- // `TagSystem`.
+ // The current instance is not a `Tag´ in this
+ // `TagSystem´.
/* do nothing */
}
}
}
/**
- * Returns a new `Tag` resolved from the provided I·R·I.
+ * Returns a new `Tag´ resolved from the provided I·R·I.
*
- * ☡ This function throws if the I·R·I is not in the `.iriSpace`
- * of the `TagSystem` associated with this constructor.
+ * ☡ This function throws if the I·R·I is not in the `.iriSpace´
+ * of the `TagSystem´ associated with this constructor.
*
* ※ If the I·R·I is not recognized, this function returns
- * `null`.
+ * `undefined`.
*/
fromIRI(iri) {
const system = this.#system;
@@ -911,7 +956,7 @@ const {
if (!name.startsWith(prefix)) {
// The I·R·I does not begin with the expected prefix.
throw new RangeError(
- `I·R·I did not begin with the expected prefix: ${iri}`,
+ `${ÉTIQUETTE}: I·R·I did not begin with the expected prefix: ${iri}`,
);
} else {
// The I·R·I begins with the expected prefix.
@@ -919,37 +964,41 @@ const {
try {
// Attempt to resolve the identifier.
const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
} catch {
// Do not throw for bad identifiers.
- return null;
+ return undefined;
}
}
}
/**
- * Returns a new `Tag` resolved from the provided identifier.
+ * Returns a new `Tag´ resolved from the provided identifier.
*
* ☡ This function throws if the identifier is invalid.
*
* ※ If the identifier is valid but not recognized, this
- * function returns `null`.
+ * function returns `undefined´.
*/
fromIdentifier(identifier) {
const system = this.#system;
const storage = this.#storage;
const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
}
/**
- * Returns a new `Tag` resolved from the provided Tag U·R·I.
+ * Returns a new `Tag´ resolved from the provided Tag U·R·I.
*
* ☡ This function throws if the provided Tag U·R·I does not
- * match the tagging entity of this constructor’s `TagSystem`.
+ * match the tagging entity of this constructors `TagSystem´.
*
* ※ If the specific component of the Tag U·R·I is not
- * recognized, this function returns `null`.
+ * recognized, this function returns `undefined´.
*/
fromTagURI(tagURI) {
const system = this.#system;
@@ -959,7 +1008,7 @@ const {
if (!tagName.startsWith(tagPrefix)) {
// The Tag U·R·I does not begin with the expected prefix.
throw new RangeError(
- `Tag U·R·I did not begin with the expected prefix: ${tagURI}`,
+ `${ÉTIQUETTE}: Tag U·R·I did not begin with the expected prefix: ${tagURI}`,
);
} else {
// The I·R·I begins with the expected prefix.
@@ -967,16 +1016,18 @@ const {
try {
// Attempt to resolve the identifier.
const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
} catch {
// Do not throw for bad identifiers.
- return null;
+ return undefined;
}
}
}
/**
- * Yields the tag identifiers in the `TagSystem` associated with
+ * Yields the tag identifiers in the `TagSystem´ associated with
* this constructor.
*/
*identifiers() {
@@ -984,19 +1035,24 @@ const {
const storage = this.#storage;
for (const [identifier, instance] of storage.entries()) {
// Iterate over the entries and yield the ones which are
- // `Tag`s in this `TagSystem`.
+ // `Tag´s in this `TagSystem´.
if (Tag.getSystem(instance) == system) {
- // The current instance is a `Tag` in this `TagSystem`.
+ // The current instance is a `Tag´ in this `TagSystem´.
yield identifier;
} else {
- // The current instance is not a `Tag` in this `TagSystem`.
+ // The current instance is not a `Tag´ in this `TagSystem´.
/* do nothing */
}
}
}
+ /** Returns the `TagSystem´ for this `Tag´ constructor. */
+ get system() {
+ return this.#system;
+ }
+
/**
- * Returns a new `Tag` constructed from the provided data and
+ * Returns a new `Tag´ constructed from the provided data and
* with the provided identifier.
*
* ※ This function is not really intended for public usage.
@@ -1222,8 +1278,8 @@ const {
domainUnion.some((domain) =>
domain == "Thing" || domains.has(domain)
)
- ) &&
- rangeIntersection.every((rangeUnion) =>
+ )
+ && rangeIntersection.every((rangeUnion) =>
rangeUnion.some((range) =>
range == "Thing" || ranges.has(range)
)
@@ -1234,8 +1290,8 @@ const {
const {
/**
- * Returns the provided value converted into a `String` object with
- * `.["@value"]` and `.["@language"]` properties.
+ * Returns the provided value converted into a `String´ object with
+ * `.["@value"]´ and `.["@language"]´ properties.
*
* The same object will be returned for every call with an equivalent
* value.
@@ -1283,7 +1339,7 @@ const {
}
};
- /** Returns the `.["@language"]` of this object. */
+ /** Returns the `.["@language"]´ of this object. */
const getLanguage = Object.defineProperty(
function () {
return this["@language"] || null;
@@ -1293,9 +1349,9 @@ const {
);
/**
- * A `FinalizationRegistry` for language string objects.
+ * A `FinalizationRegistry´ for language string objects.
*
- * This simply cleans up the corresponding `WeakRef` in the language
+ * This simply cleans up the corresponding `WeakRef´ in the language
* map.
*/
const langStringRegistry = new FinalizationRegistry(
@@ -1315,14 +1371,14 @@ const {
*/
const languageMap = Object.create(null);
- /** Returns the `.["@value"]` of this object. */
+ /** Returns the `.["@value"]´ of this object. */
const toString = function () {
return this["@value"];
};
/**
- * Returns this object if it has a `.["@language"]`; otherwise, its
- * `.["@value"]`.
+ * Returns this object if it has a `.["@language"]´; otherwise, its
+ * `.["@value"]´.
*/
const valueOf = function () {
return this["@language"] ? this : this["@value"];
@@ -1446,7 +1502,7 @@ const tagData = ($) => {
/**
* Returns an identifier corresponding to the provided object.
*
- * This is either the value of its `.identifier` or its string value.
+ * This is either the value of its `.identifier´ or its string value.
*
* ※ This function is not exposed.
*/
@@ -1460,31 +1516,34 @@ const toIdentifier = ($) =>
/**
* A tag system, with storage.
*
- * The `::Tag` constructor available on any `TagSystem` instance can be
- * used to create new `Tag`s within the system.
+ * The `::Tag´ constructor available on any `TagSystem´ instance can be
+ * used to create new `Tag´s within the system.
*/
export class TagSystem {
- /** The cached bound `Tag` constructor for this `TagSystem`. */
+ /** The cached bound `Tag´ constructor for this `TagSystem´. */
#Tag = null;
- /** The domain of this `TagSystem`. */
+ /** The domain of this `TagSystem´. */
#domain;
- /** The date of this `TagSystem`. */
+ /** The date of this `TagSystem´. */
#date;
- /** The identifier of this `TagSystem`. */
+ /** The identifier of this `TagSystem´. */
#identifier;
- /** The internal `Storage` of this `TagSystem`. */
+ /** The schema used by this `TagSystem´. */
+ #schema = schema;
+
+ /** The internal `Storage` of this `TagSystem´. */
#storage = new Storage();
/**
- * Constructs a new `TagSystem` with the provided domain and date.
+ * Constructs a new `TagSystem´ with the provided domain and date.
*
* Only actual, lowercased domain names are allowed for the domain,
* and the date must be “full” (include month and day components).
- * This is for alignment with general best practices for Tag URI’s.
+ * This is for alignment with general best practices for Tag U·R·I¦s.
*
* ☡ This constructor throws if provided with an invalid date.
*/
@@ -1506,8 +1565,8 @@ export class TagSystem {
// ☡ The domain is invalid.
throw new RangeError(`Invalid domain: ${domain}.`);
} else if (
- !/^\d{4}-\d{2}-\d{2}$/u.test(dateString) ||
- dateString != new Date(dateString).toISOString().split("T")[0]
+ !/^\d{4}-\d{2}-\d{2}$/u.test(dateString)
+ || dateString != new Date(dateString).toISOString().split("T")[0]
) {
// ☡ The date is invalid.
throw new RangeError(`Invalid date: ${date}.`);
@@ -1519,8 +1578,8 @@ export class TagSystem {
}
/**
- * Returns a bound constructor for constructing `Tags` in this
- * `TagSystem`.
+ * Returns a bound constructor for constructing `Tags´ in this
+ * `TagSystem´.
*/
get Tag() {
if (this.#Tag != null) {
@@ -1529,32 +1588,191 @@ export class TagSystem {
} else {
// No bound constructor has been created yet.
const storage = this.#storage;
- return this.#Tag = Tag.For(this, storage, schema);
+ return this.#Tag = Tag.For(this, storage, this.#schema);
+ }
+ }
+
+ /**
+ * Applies the provided activity to this `TagSystem´ by replaying its
+ * statement changes.
+ *
+ * ※ This method assumes that the provided activity conforms to the
+ * assumptions made by this module; i·e that its tags use the same
+ * identifier format and its activities do not use statements with
+ * inverse property predicates. It is not intended for the generic
+ * playback of activities produced by other scripts or mechanisms,
+ * for which a more sophisticated solution is required.
+ *
+ * ☡ This method throws an error if the provided activity cannot be
+ * processed.
+ */
+ apply(activity) {
+ const { Tag: TagConstructor } = this;
+ const {
+ classes,
+ objectProperties,
+ transitiveProperties,
+ dataProperties,
+ } = this.#schema;
+ const { object, states, unstates } = activity;
+ const activityTypes = [].concat(activity["@type"]);
+ if (!object) {
+ // ☡ The provided activity has no object.
+ throw new TypeError(
+ `${ÉTIQUETTE}: Cannot apply activity: Activity lacks an object.`,
+ );
+ } else {
+ // The provided activity has an object.
+ const iri = `${object}`;
+ const iriSpace = `${this.iriSpace}`;
+ const identifier = (() => {
+ // Extract the identifier from the object I·R·I.
+ if (!iri.startsWith(iriSpace)) {
+ // ☡ The object of the provided activity is not in the I·R·I
+ // space of this `TagSystem´.
+ throw new RangeError(
+ `Cannot apply activity: Object is not in I·R·I space: ${object}`,
+ );
+ } else {
+ // ☡ The object of the provided activity is in the I·R·I
+ // space of this `TagSystem´.
+ return iri.substring(iriSpace.length);
+ }
+ })();
+ const tag = (() => {
+ // Either resolve the identifier to an existing tag or create
+ // a new one.
+ if (activityTypes.includes("Create")) {
+ // The provided activity is a Create activity.
+ const kind = states.findLast(
+ ({ predicate, object }) =>
+ predicate == "a" && `${object}` in classes,
+ )?.object;
+ if (kind == null) {
+ // ☡ There is no recognized tag class provided for the tag;
+ // it cannot be created.
+ throw new RangeError(
+ `Cannot apply activity: Tag type not recognized.`,
+ );
+ } else {
+ // There is a recognized tag class provided for the tag.
+ return Tag.new(this, identifier, kind);
+ }
+ } else {
+ // The provided activity is not a Create activity.
+ return TagConstructor.fromIdentifier(identifier);
+ }
+ })();
+ if (!tag) {
+ // ☡ Resolving the tag identifier failed.
+ throw new RangeError(
+ `${ÉTIQUETTE}: Cannot apply activity: No tag for identifier: ${identifier}.`,
+ );
+ } else {
+ // Resolving the identifier succeeded; apply the changes to the
+ // tag and then silently persist it.
+ for (
+ const [statements, mode] of [
+ [unstates ?? [], "delete"],
+ [states ?? [], "add"],
+ ]
+ ) {
+ // Delete unstatements, then add statements.
+ for (const { predicate: $p, object: $o } of statements) {
+ // Iterate over the statements and apply them.
+ const predicate = `${$p}`;
+ const term = predicate in dataProperties
+ ? langString($o)
+ : predicate in objectProperties
+ && !(predicate in transitiveProperties
+ || objectProperties[predicate].inverseOf != null)
+ ? `${$o}`
+ : null;
+ if (term == null) {
+ // The provided predicate is not recognized; ignore it.
+ /* do nothing */
+ } else if (predicate == "prefLabel") {
+ // Preflabels are handled specially.
+ if (mode == "delete") {
+ // Unstating a preflabel has no effect unless a new one
+ // is also stated.
+ /* do nothing */
+ } else {
+ // Update the preflabel.
+ tag.prefLabel = term;
+ }
+ } else {
+ // The predicate is not `"prefLabel"´.
+ const related = (() => {
+ // If the predicate is an object property, attempt to
+ // resolve the object.
+ if (!(predicate in objectProperties)) {
+ // The predicate is not an object property; return
+ // null.
+ return null;
+ } else {
+ // The predicate is an object property.
+ try {
+ // Attempt to resolve the object.
+ return TagConstructor.fromIRI(term);
+ } catch {
+ // Resolving failed; return undefined.
+ return undefined;
+ }
+ }
+ })();
+ if (related === undefined) {
+ // The predicate is an object property, but its object
+ // was not resolvable.
+ //
+ // ☡ This is a silent error to allow for selective
+ // replay of activities while ignoring terms which are
+ // not covered.
+ /* do nothing */
+ } else {
+ // The predicate is not an object property or has a
+ // resolvable object.
+ //
+ // Apply the statement.
+ tag[
+ mode.concat(
+ predicate[0].toUpperCase(),
+ predicate.substring(1),
+ predicate in objectProperties ? "Tag" : "",
+ )
+ ](related ?? term);
+ }
+ }
+ }
+ }
+ tag.persist(true);
+ return tag;
+ }
}
}
- /** Returns the authority name (domain) for this `TagSystem`. */
+ /** Returns the authority name (domain) for this `TagSystem´. */
get authorityName() {
return this.#domain;
}
- /** Returns the date of this `TagSystem`, as a string. */
+ /** Returns the date of this `TagSystem´, as a string. */
get date() {
return this.#date;
}
/**
- * Yields the entities in this `TagSystem`.
+ * Yields the entities in this `TagSystem´.
*
* ※ Entities can hypothetically be anything. If you specifically
- * want the `Tag`s, use `::Tag.all` instead.
+ * want the `Tag´s, use `::Tag.all´ instead.
*/
*entities() {
yield* this.#storage.values();
}
/**
- * Returns the identifier of this `TagSystem`.
+ * Returns the identifier of this `TagSystem´.
*
* ※ Often this is just the empty string.
*/
@@ -1562,30 +1780,30 @@ export class TagSystem {
return this.#identifier;
}
- /** Yields the identifiers in use in this `TagSystem`. */
+ /** Yields the identifiers in use in this `TagSystem´. */
*identifiers() {
yield* this.#storage.keys();
}
- /** Returns the I·R·I for this `TagSystem`. */
+ /** Returns the I·R·I for this `TagSystem´. */
get iri() {
return `${this.iriSpace}${this.identifier}`;
}
/**
- * Returns the prefix used for I·R·I’s of `Tag`s in this `TagSystem`.
+ * Returns the prefix used for I·R·I¦s of `Tag´s in this `TagSystem´.
*/
get iriSpace() {
return `https://${this.authorityName}/tag:${this.taggingEntity}:`;
}
- /** Returns the Tag U·R·I for this `TagSystem`. */
+ /** Returns the Tag U·R·I for this `TagSystem´. */
get tagURI() {
return `tag:${this.taggingEntity}:${this.identifier}`;
}
/**
- * Returns the tagging entity (domain and date) for this `TagSystem`.
+ * Returns the tagging entity (domain and date) for this `TagSystem´.
*/
get taggingEntity() {
return `${this.authorityName},${this.date}`;