X-Git-Url: https://git.ladys.computer/Etiquette/blobdiff_plain/ce40db353c27887f4345c88fc70a7251bf688bbb..f8903c5b3bd12d02af174e5043d906d63da0b0d1:/memory.js
diff --git a/memory.js b/memory.js
index 6773bdd..f4a861b 100644
--- a/memory.js
+++ b/memory.js
@@ -1,14 +1,19 @@
-// 📧🏷️ Étiquette ∷ memory.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 ∷ memory.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 { wrmgBase32Binary, wrmgBase32String } from "./deps.js";
+const ÉTIQUETTE = "📧🏷️ Étiquette";
+
/**
* A symbol which is used internally to identify the constructor
* associated with a stored object.
@@ -23,33 +28,32 @@ const constructorSymbol = Symbol("constructor");
* If an argument is provided, it is used as the underlying numeric
* value for the identifier.
*
- * The return value is a `String` *object* with a `.value` own property
+ * The return value is a `String´ ⹐object⹑ with a `.value´ own property
* giving the underlying numeric value for the string.
*
* ※ This function is not exposed.
*/
const mintID = ($ = null) => {
- const [number, buffer] = $ == null
- ? (() => {
+ const { number, buffer } = (() => {
+ if ($ == null) {
// No value was provided; generate a random one and store its
// buffer.
const values = crypto.getRandomValues(new Uint32Array(1));
- const view = new DataView(values.buffer);
- return [
- view.getUint32(0) >>> 2, // drop the final 2 bits
- view.buffer,
- ];
- })()
- : [$ >>> 0 & -1 >>> 2, null];
+ const { buffer } = values;
+ return {
+ number: new DataView(buffer).getUint32(0) >>> 2,
+ buffer,
+ };
+ } else {
+ // A value was provided, so a buffer needs to be generated.
+ const number = $ & -1 >>> 2;
+ const buffer = new ArrayBuffer(4);
+ new DataView(buffer).setUint32(0, number << 2, false);
+ return { number, buffer };
+ }
+ })();
const checksum = number % 37;
- const wrmg = wrmgBase32String(
- buffer ?? (() => {
- // A value was provided, so a buffer still needs to be generated.
- const view = new DataView(new ArrayBuffer(4));
- view.setUint32(0, number << 2, false);
- return view.buffer;
- })(),
- );
+ const wrmg = wrmgBase32String(buffer);
return Object.assign(
new String(
`${"0123456789ABCDEFGHJKMNPQRSTVWXYZ*~$=U"[checksum]}${
@@ -61,9 +65,12 @@ const mintID = ($ = null) => {
};
/**
- * Validates the checksum prefixing the provided W·R·M·G base32
+ * Validates the checksum prefixing the provided 30‐bit W·R·M·G base32
* identifier and then returns that same identifier in normalized form.
*
+ * ☡ This function will throw if the provided value is not a 30‐bit
+ * W·R·M·G base32 value with a leading checksum.
+ *
* ※ This function is not exposed.
*/
const normalizeID = ($) => {
@@ -72,34 +79,36 @@ const normalizeID = ($) => {
identifier[0].toUpperCase(),
);
if (checksum == -1) {
- // The checksum character is invalid.
+ // ☡ The checksum character is invalid.
throw new RangeError(`Invalid checksum: "${identifier[0]}".`);
} else {
// There is a valid checksum.
const binary = wrmgBase32Binary`${identifier.substring(1)}0`;
const { byteLength } = binary;
if (byteLength != 4) {
- // The identifier was of unexpected size.
+ // ☡ The identifier was of unexpected size.
throw new RangeError(
- `Expected id to fit within 4 bytes, but got ${byteLength}.`,
+ `${ÉTIQUETTE}: Expected id to fit within 4 bytes, but got ${byteLength}.`,
);
} else {
// The identifier was correctly‐sized.
const value = new DataView(binary).getUint32(0, false);
if (value & 0b11) {
- // The final two bits, which should have been padded with
+ // ☡ The final two bits, which should have been padded with
// zeroes, have a nonzero value.
//
- // This should be impossible and indicates something went very
- // wrong in base32 decoding.
- throw new RangeError("Unexpected values in lower two bits");
+ // ※ This should be impossible and indicates something went
+ // very wrong in base32 decoding.
+ throw new RangeError(
+ `${ÉTIQUETTE}: Unexpected values in lower two bits`,
+ );
} else {
// The final two bits are zero as expected.
const number = value >>> 2;
if (checksum != number % 37) {
- // The checksum does not match the number.
+ // ☡ The checksum does not match the number.
throw new RangeError(
- `Invalid checksum for id: ${identifier} (${number})`,
+ `${ÉTIQUETTE}: Invalid checksum for id: ${identifier} (${number})`,
);
} else {
// The checksum matches. Mint a new identifier with the same
@@ -115,7 +124,7 @@ const normalizeID = ($) => {
* A symbol which is used to identify the method for constructing a new
* instance of a constructor based on stored data.
*
- * ※ This value is exposed as `Storage.toInstance`.
+ * ※ This value is exposed as `Storage.toInstance´.
*/
const toInstanceSymbol = Symbol("Storage.toInstance");
@@ -124,7 +133,7 @@ const toInstanceSymbol = Symbol("Storage.toInstance");
* instance into an object of enumerable own properties suitable for
* persistence.
*
- * ※ This value is exposed as `Storage.toObject`.
+ * ※ This value is exposed as `Storage.toObject´.
*/
const toObjectSymbol = Symbol("Storage.toObject");
@@ -134,7 +143,7 @@ const toObjectSymbol = Symbol("Storage.toObject");
*/
export class Storage {
static {
- // Define `Storage.toInstance` and `Storage.toObject` as
+ // Define `Storage.toInstance´ and `Storage.toObject´ as
// nonconfigurable, non·enumerable, read·only properties with the
// appropriate values.
Object.defineProperties(this, {
@@ -154,12 +163,15 @@ export class Storage {
}
/**
- * A `Set` of deleted identifiers, to ensure they are not
+ * A `Set´ of deleted identifiers, to ensure they are not
* re·assigned.
+ *
+ * The identifier `000-0000´ is deleted from the start and can only
+ * be manually set.
*/
- #deleted = new Set();
+ #deleted = new Set([`${mintID(0)}`]);
- /** The `Map` used to actually store the data internally. */
+ /** The `Map´ used to actually store the data internally. */
#store = new Map();
/**
@@ -172,10 +184,10 @@ export class Storage {
...object
} = data;
if (!(toInstanceSymbol in constructor)) {
- // There is no method on the constructor for generating an
+ // ☡ There is no method on the constructor for generating an
// instance.
throw new TypeError(
- "Constructor must implement Storage.toInstance for object to be retrieved.",
+ `${ÉTIQUETTE}: Constructor must implement Storage.toInstance for object to be retrieved.`,
);
} else {
// Generate an instance and return it.
@@ -199,7 +211,7 @@ export class Storage {
// The provided value does not have a method for generating an
// object to store.
throw new TypeError(
- "Object must implement Storage.toObject to be stored.",
+ `${ÉTIQUETTE}: Object must implement Storage.toObject to be stored.`,
);
} else {
// The provided value has a method for generating a storage
@@ -217,12 +229,18 @@ export class Storage {
) {
// Generate successive identifiers until an available one
// is reached.
+ //
+ // ※ Successive identifiers are used in order to guarantee
+ // an eventual result; continuing to generate random
+ // identifiers could go on forever.
id = mintID(id.value + 1);
literalValue = `${id}`;
}
return literalValue;
} else {
- // There are no identifiers left to assign.
+ // ☡ There are no identifiers left to assign.
+ //
+ // ※ This is unlikely to ever be possible in practice.
throw new TypeError("Out of room.");
}
})();
@@ -259,9 +277,9 @@ export class Storage {
*/
delete(id) {
const store = this.#store;
- const validID = normalizeID(id);
- this.#deleted.add(validID);
- return store.delete(validID);
+ const normalized = normalizeID(id);
+ this.#deleted.add(normalized);
+ return store.delete(normalized);
}
/** Yields successive identifier~instance pairs from storage. */
@@ -278,9 +296,9 @@ export class Storage {
* constructed from data in storage.
*
* The callback function will be called with the constructed
- * instance, its identifier, and this `Storage` instance.
+ * instance, its identifier, and this `Storage´ instance.
*
- * If a second argument is provided, it will be used as the `this`
+ * If a second argument is provided, it will be used as the `this´
* value.
*/
forEach(callback, thisArg = undefined) {
@@ -293,19 +311,19 @@ export class Storage {
/**
* Returns an instance constructed from the data stored at the
- * provided identifier, or `null` if the identifier has no data.
+ * provided identifier, or `null´ if the identifier has no data.
*/
get(id) {
const store = this.#store;
- const validID = normalizeID(id);
- const data = store.get(validID);
+ const normalized = normalizeID(id);
+ const data = store.get(normalized);
if (data == null) {
// No object was at the provided identifier.
return null;
} else {
// The provided identifier had a stored object; return the
// constructed instance.
- return this.#construct(data, validID);
+ return this.#construct(data, normalized);
}
}
@@ -325,7 +343,7 @@ export class Storage {
/**
* Sets the data for the provided identifier to be that generated
- * from the provided instance, then returns this `Storage` object.
+ * from the provided instance, then returns this `Storage´ object.
*/
set(id, instance) {
this.#persist(instance, normalizeID(id));
@@ -336,7 +354,7 @@ export class Storage {
* Returns the number of identifiers with data in storage.
*
* ☡ This number may be smaller than the actual number of used
- * identifiers, as deleted identifiers are *not* freed up for re·use.
+ * identifiers, as deleted identifiers are ⹐not⹑ freed up for re·use.
*/
get size() {
return this.#store.size;