]> Lady’s Gitweb - Etiquette/commitdiff
Small improvements to storage
authorLady <redacted>
Mon, 29 May 2023 05:09:47 +0000 (22:09 -0700)
committerLady <redacted>
Wed, 14 Jun 2023 02:07:05 +0000 (19:07 -0700)
- Restructuring of I·D minting for readability.

- Test to ensure `Storage` must be called as a constructor.

- `000-0000` is now reserved (deleted) by default.

- Formatting and documentation improvements.

memory.js
memory.test.js

index 6773bddfc91b3ee533f7cb5bef46fd864a753534..252418b3f85e4e5c2493b8263399e0acb900267f 100644 (file)
--- a/memory.js
+++ b/memory.js
@@ -29,27 +29,26 @@ const constructorSymbol = Symbol("constructor");
  * ※ 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 +60,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,14 +74,14 @@ 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}.`,
       );
@@ -87,17 +89,17 @@ const normalizeID = ($) => {
       // 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.
+        // ※ This should be impossible and indicates something went
+        // very wrong in base32 decoding.
         throw new RangeError("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})`,
           );
@@ -156,8 +158,11 @@ export class Storage {
   /**
    * 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. */
   #store = new Map();
@@ -172,7 +177,7 @@ 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.",
@@ -217,12 +222,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 +270,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. */
@@ -297,15 +308,15 @@ export class Storage {
    */
   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);
     }
   }
 
index 8187635a7cee18320258727de6b027b4674da56b..b9217a6872ca81d5d7775edc6736ae9b8b5b6179 100644 (file)
@@ -40,6 +40,12 @@ class Storable {
 }
 
 describe("Storage", () => {
+  it("[[Call]] throws", () => {
+    assertThrows(() => {
+      Storage();
+    });
+  });
+
   it("[[Construct]] creates a new Storage", () => {
     assertStrictEquals(
       Object.getPrototypeOf(new Storage()),
@@ -155,7 +161,7 @@ describe("Storage", () => {
       assertStrictEquals(instance.has("000-0000"), false);
       instance.set("000-0000", new Storable());
       assertStrictEquals(instance.has("000-0000"), true);
-      instance.delete("000-0000")
+      instance.delete("000-0000");
       assertStrictEquals(instance.has("000-0000"), false);
     });
 
@@ -191,7 +197,7 @@ describe("Storage", () => {
       const data = { my: "data" };
       const storable = new Storable(data);
       instance.set(newID, storable);
-      data.my = "new data"
+      data.my = "new data";
       instance.set(newID, storable);
       assertEquals(instance.get(newID).data, data);
     });
This page took 0.028551 seconds and 4 git commands to generate.