@unblocks/registry
Version:
All-purpose map data structure on steroids. Successor of @encodable/registry
290 lines (283 loc) • 7.69 kB
JavaScript
'use client'
// src/models/Registry.ts
import { globalBox } from "global-box";
// src/models/OverwritePolicy.ts
var OverwritePolicy = {
ALLOW: "ALLOW",
PROHIBIT: "PROHIBIT",
WARN: "WARN"
};
var OverwritePolicy_default = OverwritePolicy;
// src/models/createRegistryState.ts
function createRegistryState({
globalId,
name,
version = "0.x.x",
defaultKey,
setFirstItemAsDefault = false,
overwritePolicy = OverwritePolicy_default.ALLOW
}) {
return {
globalId,
name,
version,
defaultKey,
initialDefaultKey: defaultKey,
setFirstItemAsDefault,
overwritePolicy,
items: {},
promises: {}
};
}
// src/models/Registry.ts
var Registry = class {
constructor(config = {}) {
if (typeof config.globalId === "undefined") {
this.state = createRegistryState(config);
} else {
this.state = globalBox().getOrCreate(config.globalId, () => createRegistryState(config));
}
}
/**
* Clear all item in the registry.
* Reset default key to initial default key (if any)
* @returns the registry itself
*/
clear() {
this.state.items = {};
this.state.promises = {};
this.state.defaultKey = this.state.initialDefaultKey;
return this;
}
/**
* Apply a function to this registry.
* @param func Function that takes this registry as an argument
* @returns the registry itself
*/
apply(func) {
func(this);
return this;
}
/**
* Check if item with the given key exists
* @param key the key to look for
* @returns true if the item exists, false otherwise
*/
has(key) {
const item = this.state.items[key];
return item !== null && item !== void 0;
}
/**
* Register key with a value
* @param key
* @param value
* @returns the registry itself
*/
registerValue(key, value) {
const item = this.state.items[key];
const willOverwrite = this.has(key) && ("value" in item && item.value !== value || "loader" in item);
if (willOverwrite) {
if (this.state.overwritePolicy === OverwritePolicy_default.WARN) {
console.warn(`Item with key "${key}" already exists. You are assigning a new value.`);
} else if (this.state.overwritePolicy === OverwritePolicy_default.PROHIBIT) {
throw new Error(`Item with key "${key}" already exists. Cannot overwrite.`);
}
}
if (!item || willOverwrite) {
this.state.items[key] = { value };
delete this.state.promises[key];
}
if (this.state.setFirstItemAsDefault && !this.state.defaultKey) {
this.state.defaultKey = key;
}
return this;
}
/**
* Register key with a loader, a function that returns a value.
* @param key
* @param loader
* @returns the registry itself
*/
registerLoader(key, loader) {
const item = this.state.items[key];
const willOverwrite = this.has(key) && ("loader" in item && item.loader !== loader || "value" in item);
if (willOverwrite) {
if (this.state.overwritePolicy === OverwritePolicy_default.WARN) {
console.warn(`Item with key "${key}" already exists. You are assigning a new value.`);
} else if (this.state.overwritePolicy === OverwritePolicy_default.PROHIBIT) {
throw new Error(`Item with key "${key}" already exists. Cannot overwrite.`);
}
}
if (!item || willOverwrite) {
this.state.items[key] = { loader };
delete this.state.promises[key];
}
if (this.state.setFirstItemAsDefault && !this.state.defaultKey) {
this.state.defaultKey = key;
}
return this;
}
/**
* Get value from the specified key.
* If the item contains a loader, invoke the loader and return its output.
* @param key
*/
get(key) {
const targetKey = key != null ? key : this.state.defaultKey;
if (typeof targetKey === "undefined") return void 0;
const item = this.state.items[targetKey];
if (item !== void 0) {
if ("loader" in item) {
return item.loader && item.loader();
}
return item.value;
}
return void 0;
}
/**
* Similar to `.get()` but wrap results in a `Promise`.
* This is useful when some items are async loaders to provide uniform output.
* @param key
*/
getAsPromise(key) {
const promise = this.state.promises[key];
if (typeof promise !== "undefined") {
return promise;
}
const item = this.get(key);
if (item !== void 0) {
const newPromise = Promise.resolve(item);
this.state.promises[key] = newPromise;
return newPromise;
}
return Promise.reject(new Error(`Item with key "${key}" is not registered.`));
}
/**
* Return the current default key.
* Default key is a fallback key to use if `.get()` was called without a key.
*/
getDefaultKey() {
return this.state.defaultKey;
}
/**
* Set default key to the specified key
* Default key is a fallback key to use if `.get()` was called without a key.
* @param key
*/
setDefaultKey(key) {
this.state.defaultKey = key;
return this;
}
/**
* Remove default key.
* Default key is a fallback key to use if `.get()` was called without a key.
*/
clearDefaultKey() {
this.state.defaultKey = void 0;
return this;
}
/**
* Return a map of all key-values in this registry.
*/
getMap() {
return this.keys().reduce((prev, key) => {
const map = prev;
map[key] = this.get(key);
return map;
}, {});
}
/**
* Same with `.getMap()` but return a `Promise` that resolves when all values are resolved.
*/
getMapAsPromise() {
const keys = this.keys();
return Promise.all(keys.map((key) => this.getAsPromise(key))).then(
(values) => values.reduce((prev, value, i) => {
const map = prev;
map[keys[i]] = value;
return map;
}, {})
);
}
/**
* Return all keys in this registry.
*/
keys() {
return Object.keys(this.state.items);
}
/**
* Return all values in this registry.
* For loaders, they are invoked and their outputs are returned.
*/
values() {
return this.keys().map((key) => this.get(key));
}
/**
* Same with `.values()` but return a `Promise` that resolves when all values are resolved.
*/
valuesAsPromise() {
return Promise.all(this.keys().map((key) => this.getAsPromise(key)));
}
/**
* Return all key-value entries in this registry.
*/
entries() {
return this.keys().map((key) => ({
key,
value: this.get(key)
}));
}
/**
* Same with `.entries()` but return a `Promise` that resolves when all values are resolved.
*/
entriesAsPromise() {
const keys = this.keys();
return Promise.all(keys.map((key) => this.getAsPromise(key))).then(
(values) => values.map((value, i) => ({
key: keys[i],
value
}))
);
}
/**
* Remove the item with the specified key.
* Do nothing if an item with the given key does not exist.
* @param key
*/
remove(key) {
delete this.state.items[key];
delete this.state.promises[key];
return this;
}
/**
* Get number of items in the registry
*/
size() {
return this.keys().length;
}
/**
* Returns true if there is no item in the registry
*/
isEmpty() {
return this.size() === 0;
}
};
// src/models/SyncRegistry.ts
var SyncRegistry = class extends Registry {
};
// src/utils/makeSingleton.ts
function makeSingleton(create) {
let singleton;
return function getInstance() {
if (typeof singleton === "undefined") {
singleton = create();
}
return singleton;
};
}
export {
OverwritePolicy_default as OverwritePolicy,
Registry,
SyncRegistry,
makeSingleton
};