Last active
April 20, 2024 05:41
-
-
Save nyteshade/940fca6e64533a93a53c626aa2dc66f4 to your computer and use it in GitHub Desktop.
ParamParser and HTML
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function ship(code) { | |
const _ne = Object.entries(code) | |
.reduce((a, [k,v]) => ({ ...a, [k]:v }), {}); | |
const { defaults = {} } = code; | |
const [_den, _de] = Object.entries(defaults)?.[0]; | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = _ne || {}; | |
} | |
else if (_den && _de) { globalThis[_den] = _de; } | |
} | |
let {Symkeys} = require('./symkeys'); | |
let {ParamParser} = require('./param.parser'); | |
const HTML = new Proxy( | |
class HTML { | |
/** | |
* Creates an HTML element based on provided arguments, which can | |
* include element name, content, styles, attributes, | |
* webComponentName, useDocument, and children. This method | |
* dynamically parses the arguments using the configured parsers | |
* and constructs the element accordingly. | |
* | |
* @param {...any} args - Arguments that can include various | |
* configurations for the element creation such as name, content, | |
* styles, attributes, webComponentName, useDocument, and children. | |
* @returns {Element} The newly created HTML element. | |
* | |
* @example | |
* // There are three primary signatures for this function; each | |
* // has its pros and cons but is most ideal with the Proxy | |
* // getter applied. | |
* | |
* // - tagName is the only required parameter | |
* // - content becomes a TextNode element | |
* // - styles is a JavaScript style CSS object | |
* // - attributes is a JavaScript object with attribute key/value | |
* // pairs defined inside | |
* // - webComponentName is the name of the custom registered HTML | |
* // element if that is the type of element being created. | |
* // - document defaults to `top.document` but can be pointed to | |
* // another such as one from within an `<iframe>` | |
* HTML.create( | |
* tagName, content, styles, attributes, | |
* webComponentName, document | |
* ) | |
* | |
* HTML.create(tagName, { | |
* content, styles, attributes, webComponentName, document | |
* }) | |
* | |
* HTML.create( | |
* tagName, [Element, Element, Element, ...], styles, | |
* attributes, webComponentName, document | |
* ) | |
* | |
* @example | |
* // With the Proxy in place, however, HTML becomes even more | |
* // exciting. | |
* HTML.div([ | |
* HTML.h1('Title'), | |
* HTML.p('lorem ipsum dolor...'), | |
* ]) | |
* // creates <div><h1>Title</h1><p>lorem ipsum dolor...</p></div> | |
*/ | |
static create(...args) { | |
const parsers = HTML.Parsers | |
const { success, _opts } = ParamParser.tryParsers(args, parsers) | |
const doc = top.document; | |
const options = _opts.webComponentName | |
? { is: _opts.webComponentName } | |
: undefined; | |
const element = doc.createElement(name, options); | |
for (const [key, value] of Object.entries(_opts.attributes)) { | |
element.setAttribute(key, value); | |
} | |
for (const [key, value] of Object.entries(_opts.style)) { | |
element.style[key] = value; | |
} | |
if (typeof _opts.content === 'string' && _opts.content) { | |
element.append(doc.createTextNode(_opts.content)); | |
} | |
for (const child of _opts.children) { | |
element.append(child); | |
} | |
return element; | |
} | |
/** | |
* Appends a newly created HTML element to a specified target element | |
* identified by a CSS selector. This method provides a fluent interface | |
* allowing for the chaining of `create` and `appendTo` calls. | |
* | |
* The `create` method dynamically creates an HTML element based on | |
* provided arguments, which can include element name, content, styles, | |
* attributes, webComponentName, useDocument, and children. The created | |
* element is then appended to the target element. | |
* | |
* The `appendTo` method allows for appending additional elements to the | |
* target, facilitating the creation of complex DOM structures in a | |
* declarative manner. | |
* | |
* @param {string} selector - The CSS selector of the target element to | |
* which the created element will be appended. | |
* @returns {Object} An object containing the `create` and `appendTo` | |
* methods for chaining. | |
* | |
* @example | |
* HTML.appendTo('#container').create('div', { | |
* content: 'Hello, World!', | |
* style: { color: 'blue' } | |
* }); | |
* // This will find the element with id 'container' and append a new | |
* // div element with the text 'Hello, World!' and text color blue. | |
*/ | |
static appendTo(selector) { | |
const deferred = { promise: null, resolve: null, reject: null } | |
deferred.promise = new Promise((resolve, reject) => | |
Object.assign(deferred, { resolve, reject }) | |
) | |
const target = document.querySelector(selector) | |
return { | |
create(...args) { | |
const element = HTML.create(...args); | |
target?.append(element); | |
return element; | |
}, | |
appendTo(...args) { | |
return HTML.appendTo(...args) | |
}, | |
} | |
} | |
static get HTMLInvalidParametersError() { | |
return class HTMLInvalidParametersError extends Error { } | |
} | |
static get Parsers() { | |
return [ | |
this.OrderedParser, | |
this.ObjectLiteralParser, | |
this.ChildrenArrayParser, | |
] | |
} | |
/** | |
* Represents a parser that processes parameters in a predefined | |
* order. This class extends `ParamParser` to implement validation | |
* and parsing logic specific to ordered parameter sets for HTML | |
* element creation. | |
*/ | |
static OrderedParser = class extends ParamParser { | |
/** | |
* Validates the parameters for creating an HTML element to | |
* ensure they meet the expected criteria. Specifically, it | |
* checks that the name is a non-empty string and that the | |
* content is neither an object nor an array, which aligns with | |
* the requirements for simple text or HTML content. | |
* | |
* @param {Array} params - An array containing the parameters | |
* to validate, structured as [name, content, style, attributes, | |
* children, webComponentName, useDocument]. | |
* @returns {boolean} - True if the parameters meet the | |
* validation criteria, false otherwise. | |
* | |
* @example | |
* validate([ | |
* 'div', 'Hello, World!', {}, {}, [], | |
* 'my-web-component', document | |
* ]); | |
* // returns true if 'name' is a non-empty string and | |
* // 'content' is not an object or array, false otherwise. | |
*/ | |
validate([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
return ( | |
(typeof name === 'string' && name.length) && | |
(typeof content !== 'object' && !Array.isArray(content)) | |
) | |
} | |
/** | |
* Parses the provided parameters into an object suitable for HTML | |
* element creation. This method organizes the parameters into a | |
* structured format, facilitating their use in element creation | |
* processes. | |
* | |
* @param {Array} params - The parameters for HTML element creation, | |
* structured as [name, content, style, attributes, children, | |
* webComponentName, useDocument]. | |
* @returns {Object} An object containing the parsed parameters. | |
* | |
* @example | |
* const parsedParams = parse([ | |
* 'div', 'Hello, World!', {color: 'red'}, {'id': 'greeting'}, | |
* [], 'my-web-component', document | |
* ]); | |
* // Returns: | |
* // { | |
* // name: 'div', | |
* // content: 'Hello, World!', | |
* // style: {color: 'red'}, | |
* // attributes: {'id': 'greeting'}, | |
* // children: [], | |
* // webComponentName: 'my-web-component', | |
* // useDocument: document | |
* // } | |
*/ | |
parse([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
return { | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
} | |
} | |
} | |
static ObjectLiteralParser = class extends ParamParser { | |
validate([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
// list of valid and expected parameters for the HTML instance | |
const params = [ | |
'name', 'style', 'attributes', 'webComponentName', 'content', | |
'useDocument', 'children', | |
]; | |
return ( | |
// validate name is supplied and is a valid string | |
(typeof name === 'string' && name.length) && | |
// validate content was supplied as an object and not a string | |
(typeof content === 'object' && content !== null) && | |
// validate content object contains at least some of the | |
// expected ordered parameters | |
params.some(param => Reflect.has(content, param)) | |
) | |
} | |
parse([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
// validate the values supplied to the parser for | |
// further consumption. | |
const _style = style || {} | |
const _attrs = attributes || {} | |
const _children = children || [] | |
const _doc = useDocument || top.document; | |
return { | |
name, content, style: _style, attributes: _attrs, | |
children: _children, webComponentName, useDocument: _doc | |
} | |
} | |
} | |
static ChildrenArrayParser = class extends ParamParser { | |
validate([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
// For this parser to succeed, name must be valid | |
const validName = | |
typeof name === 'string' && name.length; | |
const contentsAreChildren = ( | |
// The supplied content parameter must be an array | |
(Array.isArray(content) && content.length) && | |
// And at least one of the elements must be of type Element | |
(content.filter(e => e instanceof Element)?.length) | |
); | |
return validName && contentsAreChildren | |
} | |
parse([ | |
name, content, style, attributes, | |
children, webComponentName, useDocument | |
]) { | |
return { | |
name, | |
content: undefined, | |
style: style || {}, | |
attributes: attributes || {}, | |
children: content.filter(e => e instanceof Element), | |
webComponentName, | |
useDocument: useDocument || top.document | |
} | |
} | |
} | |
}, | |
{ | |
get(target, property, receiver) { | |
if (typeof property === 'string' && property !== 'create') { | |
return target.create?.bind(target, property); | |
} | |
if (property === 'prototype') { | |
return Object.getPrototypeOf(_TagBoundProxy) | |
} | |
return Reflect.get(target, property, receiver); | |
} | |
} | |
) | |
console.log('Trying to ship ', {HTML}) | |
ship({ | |
HTML, | |
defaults: { HTML }, | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ParamParser { | |
/** | |
* | |
* | |
* @param {any[]} parameters arguments passed in by the process. | |
* @param {((any[]) => boolean)?} validator an optional way to | |
* specify a validator without subclassing ParamParser | |
* @param {((any[]) => object)?} parser an optional way to specify | |
* a parser without subclassing ParamParser | |
*/ | |
constructor(parameters, validator = () => {}, parser = () => {}) { | |
this.args = parameters | |
this.parser = parser | |
this.validator = validator | |
this.result = undefined | |
this.success = this.validate(this.args) | |
if (this.success) { | |
this.results = this.parse(this.args) | |
} | |
} | |
/** | |
* @param {object} args arguments that were previously validated | |
* by either the overloaded validate() method or the supplied | |
* validator closure. | |
* @returns {object} returns the output object, or an empty | |
* object, after parsing the input arguments or parameters. | |
*/ | |
parse(args) { | |
return this.parser?.(args); | |
} | |
/** | |
* Walk the arguments and determine if the supplied input is | |
* a valid parsing. | |
* | |
* @param {any[]} args arguments supplied by the process. | |
* @returns {boolean} `true` if the validation is successful, | |
* `false` otherwise. | |
*/ | |
validate(args) { | |
return this.validator?.(args); | |
} | |
/** | |
* Attempts to parse the given parameters using the provided parsers, throwing an | |
* error if no valid parser is found. This method serves as a convenience wrapper | |
* around `safeTryParsers`, enforcing strict parsing by automatically enabling | |
* error throwing on failure. | |
* | |
* @param {any[]} parameters - The parameters to be parsed. | |
* @param {Function[]} parsers - An array of `ParamParser` subclasses to attempt | |
* parsing with. | |
* @returns {Object} An object containing the parsing result, with a `success` | |
* property indicating if parsing was successful, and a `data` property containing | |
* the parsed data if successful. | |
* @example | |
* const parameters = ['param1', 'param2']; | |
* const parsers = [Parser1, Parser2]; | |
* const result = ParamParser.tryParsers(parameters, parsers); | |
* if (result.success) { | |
* console.log('Parsing successful:', result.data); | |
* } else { | |
* console.error('Parsing failed.'); | |
* } | |
*/ | |
static tryParsers(parameters, parsers) { | |
return this.safeTryParsers(parameters, parsers, true) | |
} | |
/** | |
* Tries parsing `parameters` with each parser in `parsers`. If | |
* `throwOnFail` is true, throws an error when validation fails or | |
* no valid parser is found. | |
* | |
* This method attempts to parse the given parameters using the | |
* provided list of parsers. It validates the input to ensure both | |
* `parameters` and `parsers` are arrays and that `parsers` | |
* contains at least one valid `ParamParser` subclass. If | |
* `throwOnFail` is set to true, it will throw specific errors for | |
* invalid inputs or when no parser succeeds. Otherwise, it returns | |
* an object indicating the success status and the result of | |
* parsing, if successful. | |
* | |
* @param {any[]} parameters - The parameters to be parsed. | |
* @param {Function[]} parsers - An array of `ParamParser` | |
* subclasses to attempt parsing with. | |
* @param {boolean} [throwOnFail=false] - Whether to throw an | |
* error on failure. | |
* @returns {{success: boolean, data: any}} An object with a | |
* `success` flag and `data` containing the parsing result, if | |
* successful. | |
* @throws {ParametersMustBeArrayError} If `parameters` or | |
* `parsers` are not arrays when `throwOnFail` is true. | |
* @throws {ParsersArrayMustContainParsersError} If `parsers` | |
* does not contain at least one valid `ParamParser` subclass | |
* when `throwOnFail` is true. | |
* @throws {NoValidParsersFound} If no valid parser is found | |
* and `throwOnFail` is true. | |
* @example | |
* const parameters = ['param1', 'param2']; | |
* const parsers = [Parser1, Parser2]; | |
* const result = ParamParser.safeTryParsers( | |
* parameters, parsers, true | |
* ); | |
* | |
* if (result.success) { | |
* console.log('Parsing successful:', result.data); | |
* } else { | |
* console.error('Parsing failed.'); | |
* } | |
*/ | |
static safeTryParsers(parameters, parsers, throwOnFail = false) { | |
if (!Array.isArray(parameters) || !Array.isArray(parsers)) { | |
if (throwOnFail) { | |
throw new this.ParametersMustBeArrayError( | |
`${this.name}.tryParsers must receive two arrays as args` | |
); | |
} | |
} | |
if (!parsers.some(parser => parser?.prototype instanceof ParamParser && | |
typeof parser === 'function')) { | |
if (throwOnFail) { | |
throw new this.ParsersArrayMustContainParsersError( | |
`${this.name}.tryParsers parsers argument must contain at least one ` + | |
`ParamParser derived class` | |
); | |
} | |
} | |
let success = false; | |
let result = undefined; | |
for (let Parser of parsers) { | |
const parser = new Parser(parameters); | |
if (parser.success) { | |
success = true; | |
result = parser.result; | |
break; | |
} | |
} | |
if (!success && throwOnFail) { | |
throw new this.NoValidParsersFound('No valid parsers found'); | |
} | |
return { success, data: result }; | |
} | |
/** | |
* A custom error class that signifies no valid parsers were found | |
* during the parsing process. This error is thrown when all | |
* parsers fail to parse the given parameters and the `throwOnFail` | |
* flag is set to true in the `safeTryParsers` method. | |
* | |
* @returns {Function} A class extending Error, representing a | |
* specific error when no valid parsers are found.ound. | |
* | |
* @example | |
* try { | |
* const result = ParamParser.safeTryParsers( | |
* parameters, parsers, true | |
* ); | |
* } catch (error) { | |
* if (error instanceof ParamParser.NoValidParsersFound) { | |
* console.error( | |
* 'No valid parsers could process the parameters.' | |
* ); | |
* } | |
* } | |
*/ | |
static get NoValidParsersFound() { | |
return class NoValidParsersFound extends Error { } | |
} | |
/** | |
* Represents an error thrown when the parameters provided to a method | |
* are not in an array format as expected. This class extends the | |
* native JavaScript `Error` class, allowing for instances of this | |
* error to be thrown and caught using standard error handling | |
* mechanisms in JavaScript. | |
* | |
* This error is specifically used in scenarios where a method | |
* expects its arguments to be provided as an array, and the | |
* validation of those arguments fails because they were not | |
* provided in an array format. It serves as a clear indicator | |
* of the nature of the error to developers, enabling them to | |
* quickly identify and rectify the issue in their code. | |
* | |
* @example | |
* try { | |
* ParamParser.safeTryParsers(nonArrayParameters, parsers, true); | |
* } catch (error) { | |
* if (error instanceof ParamParser.ParametersMustBeArrayError) { | |
* console.error('Parameters must be provided as an array.'); | |
* } | |
* } | |
*/ | |
static get ParametersMustBeArrayError() { | |
return class ParametersMustBeArrayError extends Error { } | |
} | |
/** | |
* A custom error class indicating that the parsers array does not | |
* contain valid parser functions. This error is thrown when the | |
* validation of parsers within `ParamParser.safeTryParsers` fails | |
* to find any instance that is a subclass of `ParamParser`. It | |
* extends the native JavaScript `Error` class, allowing it to be | |
* thrown and caught using standard error handling mechanisms. | |
* | |
* This error serves as a clear indicator to developers that the | |
* provided array of parsers does not meet the expected criteria, | |
* specifically that it must contain at least one valid parser | |
* that extends `ParamParser`. This ensures that the parsing | |
* process can be executed with at least one valid parser function. | |
* | |
* @example | |
* try { | |
* ParamParser.safeTryParsers(parameters, [], true); | |
* } catch (error) { | |
* const { ParsersArrayMustContainParsersError } = ParamParser | |
* if (error instanceof ParsersArrayMustContainParsersError) { | |
* console.error( | |
* 'The parsers array must contain at least one valid parser.' | |
* ); | |
* } | |
* } | |
*/ | |
static get ParsersArrayMustContainParsersError() { | |
return class ParsersArrayMustContainParsersError extends Error { } | |
} | |
} | |
ship({ ParamParser, defaults: { ParamParser } }) | |
function ship(code) { | |
const _ne = Object.entries(code) | |
.reduce((a, [k,v]) => ({ ...a, [k]:v }), {}); | |
const { defaults = {} } = code; | |
const [_den, _de] = Object.entries(defaults)?.[0]; | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = _ne || {}; | |
} | |
else if (_den && _de) { globalThis[_den] = _de; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Represents a secure container for storing and retrieving unique symbols | |
* associated with data. This class provides methods to add new symbols to | |
* the Symkeys and to retrieve data associated with a particular symbol. | |
* | |
* @example | |
* // Create a new Symkeys instance | |
* const symkeys = new Symkeys(); | |
* | |
* // Add a symbol with associated data to the Symkeys | |
* const mySymbol = Symkeys.add('myIdentifier', { foo: 'bar' }); | |
* | |
* // Retrieve the data using the symbol | |
* const myData = Symkeys.dataFor(mySymbol); | |
* console.log(myData); // Output: { foo: 'bar' } | |
*/ | |
class Symkeys { | |
/** | |
* Adds a new entry to the Symkeys with a unique symbol based on the provided | |
* name and associates it with the given data. | |
* | |
* @param named - The base name for the symbol to be created. | |
* @param [associatedData={}] - The data to associate with the symbol. | |
* @returns The unique symbol created for the entry. | |
* | |
* @example | |
* // Add an entry with associated data | |
* const symbol = Symkeys.add('myEntry', { foo: 'bar' }); | |
* // Retrieve the associated data using the symbol | |
* const data = Symkeys.dataFor(symbol); | |
* console.log(data); // Output: { foo: 'bar' } | |
*/ | |
add(named, associatedData = {}) { | |
// Generate a unique token for the symbol | |
const token = Symkeys.token; | |
// Calculate a name (optionally with domain and separator) | |
const symName = this.calculateName(named) | |
// Create a symbol using the provided name and the unique token | |
const symbol = Symbol.for(`${symName} #${token}`); | |
// Store the symbol and associated data in the Symkeys's internal map | |
this[Symkeys.kDataKey].set(symbol, associatedData); | |
// Return the unique symbol for external use | |
return symbol; | |
} | |
/** | |
* Retrieves the data associated with a given symbol from the Symkeys. | |
* | |
* This method allows access to the data that has been associated with a | |
* particular symbol in the Symkeys. It is useful for retrieving stored | |
* information when only the symbol is known. | |
* | |
* @param symbol - The symbol whose associated data is to be | |
* retrieved. | |
* @returns The data associated with the symbol, or undefined if | |
* the symbol is not found in the Symkeys. | |
* | |
* @example | |
* // Assuming 'mySymbol' is a symbol that has been added to the Symkeys | |
* // with associated data | |
* const data = Symkeys.dataFor(mySymbol); | |
* console.log(data); // Output: The data associated with 'mySymbol' | |
*/ | |
data(forSymbol) { | |
return this[Symkeys.kDataKey].get(forSymbol); | |
} | |
/** | |
* Extracts the token part from a symbol created by the `add` method. | |
* | |
* This method parses the string representation of a symbol to retrieve | |
* the unique token that was appended to the symbol's name upon creation. | |
* It is useful for debugging or for operations that require knowledge of | |
* the token associated with a symbol. | |
* | |
* @param symbol - The symbol whose token is to be extracted. | |
* @returns The extracted token or undefined if the | |
* token cannot be extracted. | |
* | |
* @example | |
* // Assuming 'mySymbol' is a symbol created with the name 'myEntry' | |
* // and a token 'agftofxob6f' | |
* const token = Symkeys.tokenFor(mySymbol); | |
* console.log(token); // Output: 'agftofxob6f' | |
*/ | |
token(forSymbol) { | |
// Use a regular expression to match the token pattern in the symbol | |
// description exists on symbol but our JS output target is too old | |
return /^.* \#(.*?)$/.exec(forSymbol).description?.[1]; | |
} | |
/** | |
* Retrieves an iterator for the symbols stored in the Symkeys. | |
* | |
* This method provides access to the symbols that have been stored in | |
* the Symkeys. It returns an iterator which can be used to loop over | |
* all the symbols. This is particularly useful for iterating through | |
* all stored data without knowing the individual symbols in advance. | |
* | |
* @returns An iterator that yields all the symbols | |
* stored in the Symkeys. | |
* | |
* @example | |
* // Assuming the Symkeys has symbols stored | |
* for (const symbol of Symkeys.symbols()) { | |
* console.log(symbol); | |
* } | |
*/ | |
symbols() { | |
// Retrieve the keys (symbols) from the Symkeys data map and return | |
// the iterator. | |
return this[Symkeys.kDataKey].keys(); | |
} | |
calculateName(providedName, useDomain, useSeparator) { | |
let domain = String(useDomain || this[Symkeys.kDomain]) | |
let separator = String(useSeparator || this[Symkeys.kSeparator]) | |
let postfix = (String(providedName).startsWith(separator) | |
? providedName.substring(1) | |
: providedName | |
) | |
if (domain.length) { | |
if (domain.endsWith(separator)) { | |
domain = domain.substring(0, domain.length - 1) | |
} | |
} | |
else { | |
separator = '' | |
} | |
return `${domain}${separator}${postfix}` | |
} | |
/** | |
* Constructs a new instance of the Symkeys, setting up a proxy to | |
* intercept and manage access to properties. | |
* | |
* This constructor initializes the Symkeys with a proxy that | |
* overrides the default behavior for property access, checking, and | |
* enumeration. It allows the Symkeys to behave like a map for its | |
* own properties, while also maintaining the prototype chain. | |
* | |
* @param {string} domain an optional prefix string, to which the | |
* `separator` parameter value will be guaranteed to have in between | |
* the domain (if truthy) and the name of the added key. | |
* @param {string} separator defaults to a period. So if your domain | |
* is 'symkeys.internal' then calling {@link add()} with a name of | |
* `"feature"` will result in the full name being | |
* `"symkeys.internal.feature"` | |
* | |
* @example | |
* const Symkeys = new Symkeys(); | |
* Symkeys[Symbol.for('myProperty')] = 'myValue'; | |
* console.log(Symkeys[Symbol.for('myProperty')]); // 'myValue' | |
*/ | |
constructor(domain = '', separator = '.') { | |
// Create a prototype from the parent class to maintain the chain. | |
const prototype = Object.create(Object.getPrototypeOf(this)) | |
// Store the original prototype for potential future use. | |
this[Symkeys.kPrototype] = prototype | |
// Create map for this instance | |
this[Symkeys.kDataKey] = new Map() | |
// Store the domain | |
this[Symkeys.kDomain] = (typeof domain === 'string' && domain) | |
// Store the separator | |
this[Symkeys.kSeparator] = separator | |
// Access the internal map that stores Symkeys data. | |
const map = this[Symkeys.kDataKey]; | |
// Replace the instance's prototype with a proxy that manages | |
// property access and manipulation. | |
Object.setPrototypeOf( | |
this, | |
new Proxy(Object.create(prototype), { | |
// Return the stored prototype for the target. | |
getPrototypeOf(_) { | |
return prototype; | |
}, | |
// Intercept property access. | |
get(target, property, receiver) { | |
// If the property exists in the Symkeys map, return its value. | |
if (map.has(property)) { | |
return map.get(property); | |
} | |
// Otherwise, perform the default behavior. | |
return Reflect.get(target, property, receiver); | |
}, | |
// Check for property existence. Check both the Symkeys map and the target for | |
// the property. | |
has(target, property) { | |
return map.has(property) || Reflect.has(target, property); | |
}, | |
// Retrieve all property keys. Combine keys from the Symkeys map and the target. | |
ownKeys(target) { | |
return [...Array.from(map.keys()), ...Reflect.ownKeys(target)]; | |
}, | |
// Intercept property assignment. | |
set(_, property, value, __) { | |
// If the property exists in the Symkeys map, set its value. | |
if (map.has(property)) { | |
map.set(property, value); | |
return true; | |
} | |
// If not, the operation is not allowed. | |
return false; | |
}, | |
// Retrieve property descriptors. | |
getOwnPropertyDescriptor(_, property) { | |
// Convert the Symkeys map to an object to use with | |
// Object.getOwnPropertyDescriptor. | |
const object = [...map.entries()].reduce( | |
(a, e) => Object.assign(a, { [e[0]]: e[1] }), | |
{}, | |
); | |
// Retrieve the descriptor from the object. | |
return Object.getOwnPropertyDescriptor(object, property); | |
}, | |
}), | |
); | |
} | |
/** | |
* Generates a random token string. | |
* | |
* This method creates a pseudo-random token that can be used for various | |
* purposes within the library, such as generating unique identifiers or | |
* keys. The token is generated using a base 36 encoding, which includes | |
* numbers and lowercase letters. | |
* | |
* @returns A random token string. | |
* | |
* @example | |
* // Example of getting a random token: | |
* const token = MyClass.token; | |
* console.log(token); // Outputs a string like 'qg6k1zr0is' | |
*/ | |
static get token() { | |
return Math.random().toString(36).slice(2); | |
} | |
/** | |
* Reusable publicly static key for identifying where data is stored. | |
*/ | |
static get kDataKey() { | |
return Symbol.for('symkeys.data'); | |
} | |
/** | |
* Reusable publicly static key for identifying where data is stored. | |
*/ | |
static get kPrototype() { | |
return Symbol.for('symkeys.prototype') | |
} | |
static get kDomain() { | |
return Symbol.for('symkeys.domain') | |
} | |
static get kSeparator() { | |
return Symbol.for('symkeys.separator') | |
} | |
} | |
// Prevent the need for transpiler/packer or ES modules | |
ship({ Symkeys, defaults: { Symkeys }}) | |
function ship(code) { | |
const _ne = Object.entries(code) | |
.reduce((a, [k,v]) => ({ ...a, [k]:v }), {}); | |
const { defaults = {} } = code; | |
const [_den, _de] = Object.entries(defaults)?.[0]; | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = _ne || {}; | |
} | |
else if (_den && _de) { globalThis[_den] = _de; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment