import { Codec, metadata, withMetadata } from "../common/mod.js" import { iterable } from "./iterable.js" import { encodeHexPrefixed } from "./mod.js" import { tuple } from "./tuple.js" export function map( $key: Codec, $value: Codec, ): Codec, ScaleMap> { return withMetadata, ScaleMap>( metadata("$.map", map, $key, $value), iterable({ $el: tuple($key, $value), calcLength: (map) => map.size, rehydrate: (values) => new ScaleMap($key, values), assert(assert) { assert.instanceof(this, ScaleMap) }, }), ) } export function set($value: Codec): Codec, ScaleSet> { return withMetadata( metadata("$.set", set, $value), iterable({ $el: $value, calcLength: (set) => set.size, rehydrate: (values) => new ScaleSet($value, values), assert(assert) { assert.instanceof(this, ScaleSet) }, }), ) } type Primitive = undefined | null | string | number | boolean | bigint | symbol export class ScaleMap implements Map { #inner = new Map() #hexMemo = new WeakMap() constructor(readonly $key: Codec, entries?: Iterable<[K, V]>) { if (entries) { for (const [key, value] of entries) { this.set(key, value) } } } #transformKey(key: K): Primitive { return transformKey(this.$key, this.#hexMemo, key) } get size() { return this.#inner.size } get [Symbol.toStringTag]() { return "ScaleMap" } clear(): void { this.#inner.clear() } delete(key: K): boolean { return this.#inner.delete(this.#transformKey(key)) } forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { this.#inner.forEach(([key, value]) => callbackfn.call(thisArg, value, key, this)) } get(key: K): V | undefined { return this.#inner.get(this.#transformKey(key))?.[1] } has(key: K): boolean { return this.#inner.has(this.#transformKey(key)) } set(key: K, value: V): this { this.#inner.set(this.#transformKey(key), [key, value]) return this } [Symbol.iterator](): IterableIterator<[K, V]> { return this.#inner.values() } entries(): IterableIterator<[K, V]> { return this.#inner.values() } *keys(): IterableIterator { for (const { 0: key } of this) { yield key } } *values(): IterableIterator { for (const { 1: value } of this) { yield value } } } export class ScaleSet implements Set { #inner = new Map() #hexMemo = new WeakMap() constructor(readonly $value: Codec, values?: Iterable) { if (values) { for (const value of values) { this.add(value) } } } #transformValue(value: T): Primitive { return transformKey(this.$value, this.#hexMemo, value) } get size() { return this.#inner.size } get [Symbol.toStringTag]() { return "ScaleSet" } clear(): void { this.#inner.clear() } delete(value: T): boolean { return this.#inner.delete(this.#transformValue(value)) } forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { this.#inner.forEach((value) => callbackfn.call(thisArg, value, value, this)) } has(key: T): boolean { return this.#inner.has(this.#transformValue(key)) } add(value: T): this { this.#inner.set(this.#transformValue(value), value) return this } [Symbol.iterator](): IterableIterator { return this.#inner.values() } *entries(): IterableIterator<[T, T]> { for (const value of this) { yield [value, value] } } keys(): IterableIterator { return this.#inner.values() } values(): IterableIterator { return this.#inner.values() } } function transformKey($key: Codec, hexMemo: WeakMap, key: K): Primitive { if (typeof key === "string") { // This ensures that the hexes won't ever clash with regular string keys, // but leaves most string keys unchanged for performance return key.startsWith("0x") ? "0x" + key : key } if (typeof key !== "object" && typeof key !== "function" || !key) { return key as K & Primitive } const existingHex = hexMemo.get(key) if (existingHex) return existingHex const hex = encodeHexPrefixed($key.encode(key)) hexMemo.set(key, hex) return hex }