// 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/>.
+import { identity } from "./deps.js";
import { Storage } from "./memory.js";
import { taggingDiscoveryContext } from "./names.js";
import schema from "./schema.js";
);
};
Object.defineProperties(constructor, {
+ name: { value: "TagSystem::Tag" },
prototype: {
configurable: false,
enumerable: false,
Object.fromEntries(Array.from(
function* () {
for (const key in objectProperties) {
+ // Iterate over each object property and yield any
+ // necessary method definitions.
const {
inverseOf,
subPropertyOf,
} = objectProperties[key];
if (key in transitiveProperties) {
+ // The current key indicates a transitive property.
+ //
// Transitive property methods are added by their
// nontransitive subproperties.
/* do nothing */
} else {
+ // The current key does not indicate a transitive
+ // property.
yield [`${key}Tags`, function* () {
yield* this.#yieldTags(key);
}];
if (inverseOf == null) {
+ // 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);
yield [`add${cased}Tag`, function (...tags) {
return this.#deleteTag(key, ...tags);
}];
} else {
+ // The current key indicates an inverse property,
+ // so no add and delete methods are necessary.
/* do nothing */
}
if (
subPropertyOf != null &&
subPropertyOf in transitiveProperties
) {
+ // The current key indicates a subproperty of a
+ // transitive property; its method is also added.
yield [`${subPropertyOf}Tags`, function* () {
yield* this.#yieldTransitiveTags(
subPropertyOf,
);
}];
} else {
+ // The current key does not indicate a subproperty
+ // of a transitive property.
/* do nothing */
}
}
}
for (const key in dataProperties) {
+ // 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);
yield [`${key}s`, function* () {
return this.#deleteLabel(key, ...labels);
}];
} else {
+ // The current key is `"prefLabel"`. This is a
+ // special case which is not handled by the schema.
/* do nothing */
}
}
writable: false,
},
});
- return Object.defineProperties(
- constructor,
- Object.fromEntries([
- ["name", { value: "TagSystem::Tag" }],
- ...[
- "all",
- "fromIRI",
- "fromIdentifier",
- "fromTagURI",
- "identifiers",
- Storage.toInstance,
- ].map((key) => [key, {
- configurable: true,
- enumerable: false,
- value: Object.defineProperty(
- Tag[key].bind(constructor, system, storage),
- "name",
- { value: String(key) },
- ),
- writable: true,
- }]),
- ]),
- );
- }
-
- /**
- * Yields the tags in the `TagSystem` associated with this
- * constructor.
- *
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- */
- static *all(system, storage) {
- for (const instance of storage.values()) {
- // Iterate over the entries and yield the ones which are `Tag`s
- // in this `TagSystem`.
- if (Tag.getSystem(instance) == system) {
- // The current instance is a `Tag` in this `TagSystem`.
- yield instance;
- } else {
- // The current instance is not a `Tag` in this `TagSystem`.
- /* do nothing */
- }
- }
- }
-
- /**
- * Returns a new `Tag` resolved from the provided I·R·I.
- *
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- *
- * ☡ 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`.
- */
- static fromIRI(system, storage, iri) {
- const name = `${iri}`;
- const prefix = `${system.iriSpace}`;
- 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}`,
- );
- } else {
- // The I·R·I begins with the expected prefix.
- const identifier = name.substring(prefix.length);
- try {
- // Attempt to resolve the identifier.
- const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
- } catch {
- // Do not throw for bad identifiers.
- return null;
- }
- }
+ return new TagConstructor(constructor, system, storage, schema);
}
/**
- * Returns a new `Tag` resolved from the provided identifier.
+ * Assigns the provided data and identifier to the provided tag.
*
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- *
- * ☡ This function throws if the identifier is invalid.
- *
- * ※ If the identifier is valid but not recognized, this function
- * returns `null`.
- */
- static fromIdentifier(system, storage, identifier) {
- const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
- }
-
- /**
- * Returns a new `Tag` resolved from the provided Tag U·R·I.
+ * ☡ This function throws if the provided tag is not a `Tag`.
*
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- *
- * ☡ This function throws if the provided Tag U·R·I does not match
- * the tagging entity of this constructor’s `TagSystem`.
- *
- * ※ If the specific component of the Tag U·R·I is not recognized,
- * this function returns `null`.
+ * ※ This function is not exposed.
*/
- static fromTagURI(system, storage, tagURI) {
- const tagName = `${tagURI}`;
- const tagPrefix = `tag:${system.taggingEntity}:`;
- 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}`,
- );
- } else {
- // The I·R·I begins with the expected prefix.
- const identifier = tagName.substring(tagPrefix.length);
- try {
- // Attempt to resolve the identifier.
- const instance = storage.get(identifier);
- return Tag.getSystem(instance) == system ? instance : null;
- } catch {
- // Do not throw for bad identifiers.
- return null;
- }
- }
+ static assignData(tag, data, identifier) {
+ tag.#identifier = `${identifier}`;
+ tag.#persistedData = tagData(data);
+ tag.#data = tagData(data);
+ return tag;
}
/**
* ※ 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($) {
return !(#system in Object($)) ? null : $.#system;
}
- /**
- * Yields the tag identifiers in the `TagSystem` associated with this
- * constructor.
- *
- * ※ The first two arguments of this function are bound when
- * generating the value of `TagSystem::Tag`. It isn’t possible to
- * access this function in its unbound form from outside this module.
- */
- static *identifiers(system, storage) {
- for (const [identifier, instance] of storage.entries()) {
- // Iterate over the entries and yield the ones which are `Tag`s
- // in this `TagSystem`.
- if (Tag.getSystem(instance) == system) {
- // The current instance is a `Tag` in this `TagSystem`.
- yield identifier;
- } else {
- // The current instance is not a `Tag` in this `TagSystem`.
- /* do nothing */
- }
- }
- }
-
- /**
- * Returns a new `Tag` constructed from the provided data and with
- * the provided identifier.
- *
- * ※ This function will not work if called directly from `Tag` (and
- * nor is it available *to* be called as such from outside this
- * module). It must be called from a `TagSystem::Tag` bound
- * constructor.
- *
- * ※ This function is not really intended for public usage.
- */
- static [Storage.toInstance](_system, _storage, data, identifier) {
- const tag = new this(data.kind);
- tag.#identifier = `${identifier}`;
- tag.#persistedData = tagData(data);
- tag.#data = tagData(data);
- return tag;
- }
-
static {
// Overwrite the default `::constructor` method to instead give the
// actual (bound) constructor which was used to generate a given
return this.#kind;
}
+ /**
+ * 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
}
}
+const {
+ /**
+ * 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
+ * from.
+ *
+ * ※ This class is not exposed.
+ */
+ TagConstructor,
+
+ /**
+ * The exposed constructor function from which all `Tag` constructors
+ * inherit.
+ *
+ * ☡ This constructor always throws.
+ */
+ TagSuper,
+} = (() => {
+ const tagConstructorBehaviours = Object.create(null);
+ return {
+ TagConstructor: class extends identity {
+ /**
+ * The `TagSystem` used for `Tag`s constructed by this
+ * constructor.
+ */
+ #system;
+
+ /** The `Storage` managed by this constructor’s `TagSystem`. */
+ #storage;
+
+ /** The schema in use for this constructor. */
+ #schema;
+
+ /**
+ * 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`
+ * properties of the provided constructor.
+ *
+ * ※ See `Tag.For`, where this constructor is used.
+ */
+ constructor(constructor, system, storage, schema) {
+ super(constructor);
+ Object.setPrototypeOf(this, TagSuper);
+ this.#system = system;
+ this.#storage = storage;
+ this.#schema = schema;
+ }
+
+ static {
+ // 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: staticFeatures } = this;
+ delete staticFeatures.constructor;
+ Object.defineProperty(superclass, "prototype", {
+ configurable: false,
+ enumerable: false,
+ value: Tag.prototype,
+ writable: false,
+ });
+ Object.defineProperties(
+ superclass,
+ Object.getOwnPropertyDescriptors(staticFeatures),
+ );
+ }
+
+ /**
+ * Yields the tags in the `TagSystem` associated with this
+ * constructor.
+ */
+ *all() {
+ const system = this.#system;
+ 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`.
+ if (Tag.getSystem(instance) == system) {
+ // The current instance is a `Tag` in this `TagSystem`.
+ yield instance;
+ } else {
+ // The current instance is not a `Tag` in this
+ // `TagSystem`.
+ /* do nothing */
+ }
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * ※ If the I·R·I is not recognized, this function returns
+ * `undefined`.
+ */
+ fromIRI(iri) {
+ const system = this.#system;
+ const storage = this.#storage;
+ const name = `${iri}`;
+ const prefix = `${system.iriSpace}`;
+ 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}`,
+ );
+ } else {
+ // The I·R·I begins with the expected prefix.
+ const identifier = name.substring(prefix.length);
+ try {
+ // Attempt to resolve the identifier.
+ const instance = storage.get(identifier);
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
+ } catch {
+ // Do not throw for bad identifiers.
+ return undefined;
+ }
+ }
+ }
+
+ /**
+ * 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 `undefined`.
+ */
+ fromIdentifier(identifier) {
+ const system = this.#system;
+ const storage = this.#storage;
+ const instance = storage.get(identifier);
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
+ }
+
+ /**
+ * 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`.
+ *
+ * ※ If the specific component of the Tag U·R·I is not
+ * recognized, this function returns `undefined`.
+ */
+ fromTagURI(tagURI) {
+ const system = this.#system;
+ const storage = this.#storage;
+ const tagName = `${tagURI}`;
+ const tagPrefix = `tag:${system.taggingEntity}:`;
+ 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}`,
+ );
+ } else {
+ // The I·R·I begins with the expected prefix.
+ const identifier = tagName.substring(tagPrefix.length);
+ try {
+ // Attempt to resolve the identifier.
+ const instance = storage.get(identifier);
+ return Tag.getSystem(instance) == system
+ ? instance
+ : undefined;
+ } catch {
+ // Do not throw for bad identifiers.
+ return undefined;
+ }
+ }
+ }
+
+ /**
+ * Yields the tag identifiers in the `TagSystem` associated with
+ * this constructor.
+ */
+ *identifiers() {
+ const system = this.#system;
+ 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`.
+ if (Tag.getSystem(instance) == system) {
+ // The current instance is a `Tag` in this `TagSystem`.
+ yield identifier;
+ } else {
+ // 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
+ * with the provided identifier.
+ *
+ * ※ This function is not really intended for public usage.
+ */
+ [Storage.toInstance](data, identifier) {
+ const tag = new this(data.kind);
+ return Tag.assignData(tag, data, identifier);
+ }
+ },
+ TagSuper: tagConstructorBehaviours.TagSuper,
+ };
+})();
+
const {
/**
* Returns whether the provided schema, subject class, object