UNPKG

@oazmi/kitchensink

Version:

a collection of personal utility functions

228 lines 9.74 kB
/** utility functions for querying/traversing a nested object through the use of either: * - a dot-separated string query (`dot-path`) * - or an array of keys (`key-path`) * * this submodule is made to be somewhat strictly typed, * so you can expect to get type hints when accessing narrowly typed data via `key-path` or `dot-path` get/set functions. * think of it as analogous to `xpath` for xml, and `document.querySelector` for html. * * TODO: consider allowing `getKeyPath` and `setKeyPath` to accept `create_missing: boolean = false` option to create missing intermediate keys/entires * * @module */ import type { NonSymbolKeys } from "./typedefs.js"; /** get an array of all possible `key-path`s. * * @example * ```ts * let data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } as const * let path_to_noice: KeyPathsOf<typeof data> = ["kill", "your", "self", 2, 1, "noice"] * ``` */ export type KeyPathsOf<T> = KeyPathTree<T>[NonSymbolKeys<T>]; type KeyPathTree<T> = { [P in NonSymbolKeys<T>]-?: T[P] extends object ? [P] | [P, ...KeyPathsOf<T[P]>] : [P]; }; type SerializableKey = string | number; /** this is just an alias for `string`, but it's here just to create a distinction between regular strings and `dot-path`s. * * @example * ```ts * const * my_obj = { hello: ["world", { za: "warudo", toki: "wa tomare" }], yare: "yare daze" }, * dotpath_to_the_warudo: DotPath = "hello.1.za", * dotpath_to_yare_daze: DotPath = "yare" * ``` */ export type DotPath = `${string}.${string}` | string; /** a `key-path` is an array of one or more string keys (number keys work too). * they are not strongly typed with respect to whatever structure you are trying to traverse. * * TODO: why not also permit `symbol`s? one issue it might raise is the inability to convert a `KeyPath` to a `DotPath`. * * @example * ```ts * const data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } * const possible_keypath: KeyPath = ["kill", "your", "self", 2, "1"] * ``` */ export type KeyPath = [SerializableKey, ...(SerializableKey)[]]; /** get the `leaf-key` of a `dot-path` (ie: end point). * * @example * ```ts * let leaf_key: DotPathLeaf<"kill.your.self"> = "self" // correct assignment * // @ts-ignore * let incorrect_leaf_key: DotPathLeaf<"kill.your.self"> = "your" // typescript error * ``` */ export type DotPathLeaf<DP extends DotPath> = DP extends `${string}.${infer K}` ? DotPathLeaf<K> : DP; /** get the `parent-key` `dot-path` of a `dot-path`. * but if there's no parent key, then the key itself is returned. * * @example * ```ts * let dotpath0: DotPathParent<"kill.your.self.once"> = "kill.your.self" * let dotpath1: DotPathParent<typeof dotpath0> = "kill.your" * let dotpath2: DotPathParent<typeof dotpath1> = "kill" * let dotpath3: DotPathParent<typeof dotpath2> = "kill" * ``` */ export type DotPathParent<DP extends DotPath> = DP extends `${infer P}.${DotPathLeaf<DP>}` ? P : DP; /** convert `dot-path` to an array of `key-path` in the format compliant with the type {@link KeyPathsOf}. * * @example * ```ts * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } }, * dotpath_to_noice = "kill.your.self.2.1.noice", * keypath_to_noice: * DotPathToKeyPath<typeof dotpath_to_noice> & KeyPathsOf<typeof data> * = ["kill", "your", "self", "2", "1", "noice"] * ``` */ export type DotPathToKeyPath<DP extends DotPath> = DP extends `${infer P}.${DotPathLeaf<DP>}` ? [...DotPathToKeyPath<P>, DotPathLeaf<DP>] : [DP]; /** get the type of nested data through the use of a dot-path. * * @example * ```ts * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } as const, * dotpath_to_noice_parent = "kill.your.self.2.1", * noice_parent: * DotPathValue<typeof data, typeof dotpath_to_noice_parent> * = { noice: "YAHAHA", 0: "you found me!" } * ``` */ export type DotPathValue<T extends { [key: string]: any; }, DP extends DotPath> = DP extends `${infer P}.${infer C}` ? DotPathValue<T[P], C> : T[DP]; /** get the type of nested data through the use of an array of key-path. * * @example * ```ts * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } as const, * keypath_to_noice_parent: ["kill", "your", "self", 2, 1] = ["kill", "your", "self", 2, 1], * noice_parent: * KeyPathValue<typeof data, typeof keypath_to_noice_parent> * = { noice: "YAHAHA", 0: "you found me!" } * ``` */ export type KeyPathValue<T extends { [key: (SerializableKey)]: any; }, KP extends KeyPath> = (KP extends [KP[0], ...infer R] ? (R extends KeyPath ? KeyPathValue<T[KP[0]], R> : T[KP[0]]) : unknown); /** get value of nested `obj` at a given `key-path`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } as const * * // weakly typed variant * const * keypath_to_noice_parent = ["kill", "your", "self", 2, 1], // note: adding `as const` will not make it strongly typed because of a limitation/bug in `KeyPathsOf` * noice_parent = getKeyPath(data, keypath_to_noice_parent) // type: `unknown` * * assertEquals(noice_parent, { noice: "YAHAHA", 0: "you found me!" }) * * // strongly typed variant * const * strong_keypath_to_noice_parent: ["kill", "your", "self", 2, 1] = ["kill", "your", "self", 2, 1], * strong_noice_parent: { noice: "YAHAHA", 0: "you found me!" } = getKeyPath(data, strong_keypath_to_noice_parent) * * assertEquals(strong_noice_parent, { noice: "YAHAHA", 0: "you found me!" }) * ``` */ export declare const getKeyPath: <T extends object = object, KP = KeyPathsOf<T>>(obj: T, kpath: KP) => KeyPathValue<T, KP>; /** set the value of nested `obj` at a given `key-path`, and then returns the object back. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } } as const * * // weakly typed variant * // note: adding `as const` will not make it strongly typed because of a limitation/bug in `KeyPathsOf` * const keypath_to_yahaha = ["kill", "your", "self", 2, 1, "noice"] * setKeyPath(data, keypath_to_yahaha, "YEEEE") * * assertEquals(getKeyPath(data, keypath_to_yahaha), "YEEEE") * * // strongly typed variant * const strong_keypath_to_yahaha: ["kill", "your", "self", 2, 1, "noice"] = ["kill", "your", "self", 2, 1, "noice"] * // @ts-ignore: since `data` is declared as `const`, we cannot technically assign the value `"YOSHIII"` to the original `"YAHAHA"`. * setKeyPath(data, strong_keypath_to_yahaha, "YOSHIII") * * assertEquals(getKeyPath(data, strong_keypath_to_yahaha), "YOSHIII") * ``` */ export declare const setKeyPath: <T extends object = object, KP = KeyPathsOf<T>>(obj: T, kpath: KP, value: KeyPathValue<T, KP>) => T; /** similar to {@link bindDotPathTo}, but for `key-path`s. */ export declare const bindKeyPathTo: (bind_to: object) => [get: <KP extends KeyPath>(kpath: KP) => KeyPathValue<typeof bind_to, KP>, set: <KP extends KeyPath>(kpath: KP, value: KeyPathValue<typeof bind_to, KP>) => typeof bind_to]; /** convert a `dot-path` to `key-path`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * my_dotpath = "kill.your.self.2abc.1.tasukete", * my_keypath = dotPathToKeyPath(my_dotpath) * * assertEquals(my_keypath, ["kill", "your", "self", "2abc", 1, "tasukete"]) * ``` */ export declare const dotPathToKeyPath: <DP extends DotPath>(dpath: DP) => DotPathToKeyPath<DP>; /** get value of nested `obj` at a given `dot-path`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } }, * dotpath_to_noice_parent = "kill.your.self.2.1" * * assertEquals(getDotPath(data, dotpath_to_noice_parent), { noice: "YAHAHA", 0: "you found me!" }) * ``` */ export declare const getDotPath: <T extends object = object, DP extends DotPath = string>(obj: T, dpath: DP) => DotPathValue<T, DP>; /** set the value of nested `obj` at a given `dot-path`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } }, * dotpath_to_yahaha = "kill.your.self.2.1.noice" * * setDotPath(data, dotpath_to_yahaha, "REEE") * * assertEquals(getDotPath(data, dotpath_to_yahaha), "REEE") * ``` */ export declare const setDotPath: <T extends object = object, DP extends DotPath = string>(obj: T, dpath: DP, value: DotPathValue<T, DP>) => T; /** generate a `dot-path` getter and setter for a specific object given by `bind_to`. * * @example * ```ts * import { assertEquals } from "jsr:@std/assert" * * const * data = { kill: { your: { self: [0, 1, { 0: 0, 1: { noice: "YAHAHA", 0: "you found me!" } }] } } }, * [getData, setData] = bindDotPathTo(data) * * assertEquals(getData("kill.your.self.2.1"), {0: "you found me!", noice: "YAHAHA"}) * * setData("kill.your.self.2.1.noice", ["arr", "ree", "eek"]) * * assertEquals(getData("kill.your.self.2.1"), {0: "you found me!", noice: ["arr", "ree", "eek"]}) * ``` */ export declare const bindDotPathTo: (bind_to: object) => [get: <DP extends DotPath>(dpath: DP) => DotPathValue<typeof bind_to, DP>, set: <DP extends DotPath>(dpath: DP, value: DotPathValue<typeof bind_to, DP>) => typeof bind_to]; export {}; //# sourceMappingURL=dotkeypath.d.ts.map