primitiveprimer
Version:
Bluntly extend String, Array, Number, Object prototypes with useful methods, or use them as standalone utilities while leaving prototypes untouched
1 lines • 81.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/array.ts","../src/number.ts","../src/object.ts","../src/string.ts","../src/math.ts","../src/primitivetools.ts","../src/pathShim.ts","../src/primitives.ts"],"sourcesContent":["// src/primitives/array.ts\ndeclare type arrayMethod = [string, (this: any[], ...args: any[]) => any];\ndeclare type arrayOfObjectsMethod = arrayMethod & [string, <T extends Record<string, any>>(this: T[], ...args: any[]) => any];\n\nconst assertIsObjectArray: (arr: any) => asserts arr is Record<string, any>[] = (arr: any): asserts arr is Record<string, any>[] => {\n if (Array.isArray(arr) && arr.every((item) => typeof item === \"object\" && item !== null)) {\n return undefined;\n }\n throw new Error(\"not an array of objects\");\n};\nconst assertIsArrayOfStrings: (arr: any) => asserts arr is string[] = (arr: any): asserts arr is string[] => {\n if (arr.every((x: unknown) => typeof x === \"string\")) {\n return undefined;\n }\n throw new Error(\"not an array of strings\");\n};\nconst assertIsArrayOfNumbers: (arr: any) => asserts arr is number[] = (arr: any): asserts arr is number[] => {\n if (arr.every((x: unknown) => typeof x === \"number\")) {\n return undefined;\n }\n throw new Error(\"not an array of numbers\");\n};\n\nexport const arrayMethodsTS: [string, (this: any[], ...args: any[]) => any][] = [\n [\n \"filterGuard\",\n function <T, U extends T>(this: T[], pred: (x: T) => x is U) {\n return this.filter((x): x is U => pred(x)) as U[];\n },\n ],\n [\n \"findGuard\",\n function <T, U extends T>(this: T[], pred: (x: T) => x is U) {\n for (const x of this) if (pred(x)) return x as U;\n return undefined;\n },\n ],\n [\n \"assertIsStringArray\",\n function (this: unknown[]): asserts this is string[] {\n assertIsArrayOfStrings(this);\n },\n ],\n [\n \"assertIsNumberArray\",\n function (this: unknown[]): asserts this is number[] {\n assertIsArrayOfNumbers(this);\n },\n ],\n [\n \"asStrings\",\n function (this: unknown[]): string[] | unknown[] {\n try {\n assertIsArrayOfStrings(this);\n return this as string[];\n } catch {\n return this as unknown[];\n }\n },\n ],\n [\n \"asNumbers\",\n function (this: unknown[]): number[] | unknown[] {\n try {\n assertIsArrayOfNumbers(this);\n return this as number[];\n } catch {\n return this as unknown[];\n }\n },\n ],\n [\n \"tryAsStrings\",\n function (this: unknown[]): string[] | null {\n try {\n assertIsArrayOfStrings(this);\n return this as string[];\n } catch {\n return null;\n }\n },\n ],\n [\n \"tryAsNumbers\",\n function (this: unknown[]): number[] | null {\n try {\n assertIsArrayOfNumbers(this);\n return this as number[];\n } catch {\n return null;\n }\n },\n ],\n];\n\nexport const arrayMethods = [\n [\n \"first\",\n function (this: any[], n = 1): any[] {\n return n === 1 ? this[0] : this.slice(0, n);\n },\n ] as arrayMethod,\n [\n \"last\",\n function (this: any[], n = 1): any[] {\n return n === 1 ? this[this.length - 1] : this.slice(-n);\n },\n ] as arrayMethod,\n [\n \"findByKey\",\n function <T extends Record<string, any>>(this: T[], key: string, value: any): T | null {\n assertIsObjectArray(this);\n for (const item of this) if (item[key] === value) return item;\n return null;\n },\n ] as arrayOfObjectsMethod,\n [\n \"groupBy\",\n function (this: any[], fn: (item: any) => string): Record<string, any[]> {\n return this.reduce((acc: any, item) => {\n const key = typeof fn === \"function\" ? fn(item) : item[fn];\n (acc[key] ||= []).push(item);\n return acc;\n }, {});\n },\n ] as arrayMethod,\n [\n \"sumByKey\",\n function <T extends Record<string, any>>(this: T[], key: string): number {\n assertIsObjectArray(this);\n return this.reduce((acc, item) => acc + (typeof item[key] === \"number\" ? item[key] : 0), 0);\n },\n ] as arrayOfObjectsMethod,\n [\n \"autoParseKeys\",\n function <T extends Record<string, any>>(this: T[]): T[] {\n assertIsObjectArray(this);\n return this.map((obj) => {\n if (obj && typeof obj === \"object\") {\n for (const key in obj) {\n if (typeof obj[key] === \"string\") {\n try {\n obj[key] = JSON.parse(obj[key]);\n } catch {}\n }\n }\n }\n return obj;\n });\n },\n ] as arrayOfObjectsMethod,\n [\n \"unique\",\n function <T>(this: T[]): T[] {\n return [...new Set(this)];\n },\n ] as arrayMethod,\n [\n \"shuffle\",\n function <T>(this: T[]): T[] {\n const arr = [...this];\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n },\n ] as arrayMethod,\n [\n \"highestByKey\",\n function <T extends Record<string, any>>(this: T[], key: string): T | -1 {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n\n this.length;\n return this.reduce((max, item) => (typeof item[key] === \"number\" && item[key] > (max?.[key] ?? -Infinity) ? item : max));\n } catch (e) {\n console.error(\"not an numberarray in object\", e);\n return -1;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"lowestByKey\",\n function <T extends Record<string, any>>(this: T[], key: string): T | -1 {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n return this.reduce((min, item) => (typeof item[key] === \"number\" && item[key] < (min?.[key] ?? Infinity) ? item : min));\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return -1;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"sortByKey\",\n function <T extends Record<string, any>>(this: T[], key: string, ascending = true): T[] {\n try {\n assertIsObjectArray(this);\n return [...this].sort((a, b) => {\n const aVal = a[key] ?? 0;\n const bVal = b[key] ?? 0;\n return ascending ? aVal - bVal : bVal - aVal;\n });\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return [];\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"sortByKeyName\",\n function <T extends Record<string, any>>(this: T[], key: string, ascending = true): T[] {\n try {\n assertIsObjectArray(this);\n return [...this].sort((a, b) => {\n const aVal = String(a[key] ?? \"\");\n const bVal = String(b[key] ?? \"\");\n return ascending ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);\n });\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return [];\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"mapByKey\",\n function <T extends Record<string, any>, K extends keyof T>(this: T[], key: K): Array<T[K]> {\n assertIsObjectArray(this);\n return this.map((item) => (item && typeof item === \"object\" ? item[key] : undefined)) as Array<T[K]>;\n },\n ] as arrayOfObjectsMethod,\n [\n \"sumKey\",\n function <T extends Record<string, any>>(this: T[], key: string) {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n\n return this.reduce((acc, cur) => acc + (parseFloat(String(cur[key])) || 0), 0);\n } catch (e) {\n console.error(\"not an numberarray in object\", e);\n return 0;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"averageKey\",\n function <T extends Record<string, any>>(this: T[], key: string) {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n\n let total = 0,\n count = 0;\n for (const cur of this) {\n const v = parseFloat(String(cur[key]));\n if (!Number.isNaN(v)) {\n total += v;\n count++;\n }\n }\n return count ? total / count : 0;\n } catch (e) {\n console.error(\"not an numberarray in object\", e);\n return 0;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"filterKey\",\n function <T extends Record<string, any>>(this: T[], key: string, pred: (v: any) => boolean) {\n try {\n assertIsObjectArray(this);\n return this.filter((item) => pred(item[key]));\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return [];\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"distinct\",\n function <T extends Record<string, any>>(this: T[], keyOrFn: string | ((x: T) => any)) {\n try {\n assertIsObjectArray(this);\n const seen = new Set<any>();\n const getKey = typeof keyOrFn === \"function\" ? keyOrFn : (x: T) => x[keyOrFn];\n const out: T[] = [];\n for (const item of this) {\n const k = getKey(item);\n if (!seen.has(k)) {\n seen.add(k);\n out.push(item);\n }\n }\n return out;\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return [];\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"aggregate\",\n function <T extends Record<string, any>, R>(this: T[], keyOrFn: string | ((x: T) => any), reducer: (acc: R, cur: T) => R, init: R) {\n try {\n assertIsObjectArray(this);\n const getKey = typeof keyOrFn === \"function\" ? keyOrFn : (x: T) => x[keyOrFn];\n const groups = new Map<any, R>();\n for (const item of this) {\n const k = getKey(item);\n const acc = groups.has(k) ? groups.get(k)! : init;\n groups.set(k, reducer(acc, item));\n }\n const out: Record<string, R> = {} as any;\n for (const [k, v] of groups.entries()) (out as any)[k] = v;\n return out;\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return {};\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"toTable\",\n function <T extends Record<string, any>>(this: T[]) {\n try {\n assertIsObjectArray(this);\n const out: Record<string, any[]> = {};\n for (const item of this) {\n for (const [k, v] of Object.entries(item)) {\n (out[k] ??= []).push(v);\n }\n }\n return out;\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return {};\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"sumBy\",\n function <T extends Record<string, any>>(this: T[], key: string) {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n\n return this.reduce((acc, cur) => {\n const v = parseFloat(String(cur[key]));\n return acc + (Number.isNaN(v) ? 0 : v);\n }, 0);\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return 0;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"averageBy\",\n function <T extends Record<string, any>>(this: T[], key: string) {\n try {\n assertIsObjectArray(this);\n assertIsArrayOfNumbers(this.map((item) => item[key]));\n\n let total = 0,\n count = 0;\n for (const cur of this) {\n const v = parseFloat(String(cur[key]));\n if (!Number.isNaN(v)) {\n total += v;\n count++;\n }\n }\n return count ? total / count : 0;\n } catch (e) {\n console.error(\"not an objectArray\", e);\n return 0;\n }\n },\n ] as arrayOfObjectsMethod,\n [\n \"seededShuffle\",\n function <T>(this: T[], seed: number) {\n // Return a deterministic permutation index based on seed and total\n // Linear Congruential Generator to produce pseudo-random indices\n const thisClone = [...this];\n const total = this.length;\n const res: number[] = Array.from({ length: total }, (_, i) => i);\n const rand = () => (seed = (1103515245 * seed + 12345) >>> 0) / 0xffffffff;\n for (let i = total - 1; i > 0; i--) {\n const j = Math.floor(rand() * (i + 1));\n [res[i], res[j]] = [res[j], res[i]];\n }\n const shuffled = res.map((i) => thisClone[i]);\n return shuffled;\n },\n ] as arrayMethod,\n [\n \"intersect\",\n function <T>(this: T[], other: T[]) {\n const set = new Set(other);\n return this.filter((x) => set.has(x));\n },\n ] as arrayMethod,\n [\n \"difference\",\n function <T>(this: T[], other: T[]) {\n const set = new Set(other);\n return this.filter((x) => !set.has(x));\n },\n ] as arrayMethod,\n [\n \"sum\",\n function (this: any[]) {\n try {\n assertIsArrayOfNumbers(this);\n let total = 0;\n for (const v of this) {\n const n = typeof v === \"number\" ? v : parseFloat(String(v));\n if (!Number.isNaN(n)) total += n;\n else if (v) total += 1; // count truthy when not a number\n }\n return total;\n } catch (e) {\n console.error(\"not an array of numbers\", e);\n return 0;\n }\n },\n ] as arrayMethod,\n [\n \"average\",\n function (this: any[]) {\n try {\n assertIsArrayOfNumbers(this);\n let total = 0;\n let count = 0;\n for (const v of this) {\n const n = typeof v === \"number\" ? v : parseFloat(String(v));\n if (!Number.isNaN(n)) {\n total += n;\n count++;\n } else if (v) {\n total += 1;\n count++;\n }\n }\n return count === 0 ? 0 : total / count;\n } catch (e) {\n console.error(\"not an array of numbers\", e);\n return 0;\n }\n },\n ],\n [\n \"validateEach\",\n function <T>(this: (T | null)[], validatorFn: (item: T) => boolean) {\n return this.map((item) => (item != null && validatorFn(item as T) ? item : null));\n },\n ],\n [\n \"clearNil\",\n function <T>(this: (T | null | undefined)[]) {\n return this.filter((x): x is T => x != null);\n },\n ],\n [\n \"indexOfHighestNumber\",\n function (this: unknown[]) {\n try {\n assertIsArrayOfNumbers(this);\n if (this.length === 0) return -1;\n let highestIndex = 0;\n for (let i = 1; i < this.length; i++) {\n if (this[i] > this[highestIndex]) {\n highestIndex = i;\n }\n }\n return highestIndex;\n } catch (e) {\n console.error(\"not an array of numbers\", e);\n return -1;\n }\n },\n ] as arrayMethod,\n [\n \"indexOfLowestNumber\",\n function (this: unknown[]) {\n try {\n assertIsArrayOfNumbers(this);\n if (this.length === 0) return -1;\n let lowestIndex = 0;\n for (let i = 1; i < this.length; i++) {\n if (this[i] < this[lowestIndex]) {\n lowestIndex = i;\n }\n }\n return lowestIndex;\n } catch (e) {\n console.error(\"not an array of numbers\", e);\n return -1;\n }\n },\n ] as arrayMethod,\n] as arrayMethod[];\n\nexport function extendArray() {\n for (const method of arrayMethods) {\n Object.defineProperty(Array.prototype, method[0], {\n value: method[1],\n writable: true,\n configurable: true,\n enumerable: false,\n });\n }\n for (const method of arrayMethodsTS) {\n Object.defineProperty(Array.prototype, method[0], {\n value: method[1],\n writable: true,\n configurable: true,\n enumerable: false,\n });\n }\n}\n","declare type numberMethod = [string, fn: (this: number, ...args: any[]) => any];\n\nexport const numberMethods: numberMethod[] = [\n [\n \"percentage\",\n function (this: number, percent: number): number {\n return (this * percent) / 100;\n },\n ] as numberMethod,\n [\n \"isEven\",\n function (this: number): boolean {\n return this % 2 === 0;\n },\n ] as numberMethod,\n [\n \"isOdd\",\n function (this: number): boolean {\n return this % 2 !== 0;\n },\n ] as numberMethod,\n [\n \"toFixedNumber\",\n function (this: number, decimals = 2): number {\n return parseFloat(this.toFixed(decimals));\n },\n ] as numberMethod,\n [\n \"between\",\n function (this: number, min: number, max: number): boolean {\n return this >= min && this <= max;\n },\n ] as numberMethod,\n [\n \"assertNrBetween\",\n function (this: number, min: number = 0, max: number = Infinity): this is number {\n return this >= min && this <= max;\n },\n ] as numberMethod,\n\n [\n \"clamp\",\n function (this: number, min: number, max: number): number {\n return Math.min(Math.max(this, min), max);\n },\n ] as numberMethod,\n [\n \"times\",\n function (this: number, fn: (i: number) => void): void {\n for (let i = 0; i < this; i++) fn(i);\n },\n ] as numberMethod,\n [\n \"toStringWithLeadingZeros\",\n function (this: number, length: number): string {\n return String(this).padStart(length, \"0\");\n },\n ] as numberMethod,\n [\n \"toTimeCode\",\n function (this: number): string {\n const totalSeconds = Math.floor(this);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n // gebruik je eigen toStringWithLeadingZeros\n return `${hours}:${(minutes as any).toStringWithLeadingZeros(2)}:${(seconds as any).toStringWithLeadingZeros(2)}`;\n },\n ],\n [\n \"percentOf\",\n function (this: number, total: number) {\n return total === 0 ? 0 : (this / total) * 100;\n },\n ] as numberMethod,\n [\n \"ratioOf\",\n function (this: number, total: number) {\n return total === 0 ? 0 : this / total;\n },\n ] as numberMethod,\n [\n \"isInteger\",\n function (this: number): this is number {\n return Number.isInteger(this);\n },\n ] as numberMethod,\n [\n \"isFinite\",\n function (this: number): this is number {\n return Number.isFinite(this);\n },\n ] as numberMethod,\n [\n \"isSafeInteger\",\n function (this: number): this is number {\n return Number.isSafeInteger(this);\n },\n ] as numberMethod,\n [\n \"isPositive\",\n function (this: number): this is number {\n return this > 0;\n },\n ] as numberMethod,\n [\n \"isNegative\",\n function (this: number): this is number {\n return this < 0;\n },\n ] as numberMethod,\n [\n \"isNonNegative\",\n function (this: number): this is number {\n return this >= 0;\n },\n ] as numberMethod,\n [\n \"assertIsInteger\",\n function (this: number): asserts this is number {\n if (!Number.isInteger(this)) throw new Error(\"Not an integer\");\n },\n ] as numberMethod,\n [\n \"assertIsFinite\",\n function (this: number): asserts this is number {\n if (!Number.isFinite(this)) throw new Error(\"Not finite (NaN or Infinity)\");\n },\n ],\n];\n\nexport function extendNumber() {\n for (const method of numberMethods) {\n Object.defineProperty(Number.prototype, method[0], {\n value: method[1],\n writable: true,\n configurable: true,\n });\n }\n}\n","// src/primitives/object.ts\ndeclare type ObjectMethod = [string, (this: Record<string, any>, ...args: any[]) => any];\ndeclare type ObjectMethodTS = ObjectMethod & [string, <T, U extends T>(this: T) => U | boolean];\nimport { Empty, NonEmpty, isEmpty, AssertError, assert, assertRoute } from \"./polyfills.js\";\n\nexport const objectMethodsTS: ObjectMethodTS[] = [\n [\n \"isObject\",\n function (this: any): this is Record<string, any> | never {\n return assertRoute(this, () => {\n if (this !== null && typeof this === \"object\" && !Array.isArray(this)) {\n return this;\n }\n throw new AssertError(\"Not an object\");\n });\n },\n ],\n [\n \"assertHasKeys\",\n function <K extends string>(this: Record<string, any>, ...keys: K[]): asserts this is Record<string, any> & Record<K, unknown> {\n for (const key of keys) {\n if (!(key in this)) {\n throw new Error(`Missing required key: ${key}`);\n }\n }\n // No return needed with asserts; TS narrows automatically after this call\n },\n ],\n [\n \"asType\", // Generic \"I know what this is\"\n\n function <T>(this: Record<string, any>): T {\n // You've done the validation above; now cast with confidence\n return this as unknown as T;\n },\n ],\n [\n \"isNonEmty\",\n function (this: Record<string, any>): this is Record<string, any> & Record<string, NonEmpty> {\n return Object.values(this).every((value) => typeof value === \"string\" && value.trim().length > 0);\n },\n \"mapEmptyToFalseyKeyObje\",\n function (this: Record<string, any>): Record<string, Boolean> {\n return Object.fromEntries(Object.entries(this).map((a, b) => [a, isEmpty(b)]));\n },\n \"mapEmptyToFalseyValueArray\",\n function (this: Record<string, any>): Record<string, Boolean> {\n return Object.fromEntries(Object.entries(this).map((a, b) => [a, isEmpty(b)]));\n },\n ],\n] as ObjectMethodTS[];\n\nexport const objectMethods = [\n [\n \"sortKeys\",\n function (this: Record<string, any>, sorterFn: ((a: string, b: string) => number) | null = null): Record<string, any> {\n return Object.fromEntries(Object.entries(this).sort(([keyA], [keyB]) => (sorterFn ? sorterFn(keyA, keyB) : keyA.localeCompare(keyB))));\n },\n ] as ObjectMethod,\n [\n \"fill\",\n function <T extends Record<string, any>, U extends Record<string, any>>(this: T, source: U): T & U {\n for (const [key, value] of Object.entries(source)) {\n if (!(key in this)) {\n (this as any)[key] = value;\n }\n }\n return this as T & U;\n },\n ] as ObjectMethod,\n [\n \"parseKeys\",\n function (this: Record<string, any>, ...keys: string[]): Record<string, any> {\n const obj = this.valueOf() as Record<string, any>;\n const result: Record<string, any> = {};\n for (const key of keys) {\n try {\n result[key] = JSON.parse(obj[key]);\n } catch {\n result[key] = obj[key];\n }\n }\n return obj;\n },\n ] as ObjectMethod,\n [\n \"valuesMap\",\n function (this: Record<string, any>, fn: (v: any, k: string) => any): Record<string, any> {\n return Object.fromEntries(Object.entries(this).map(([k, v]) => [k, fn(v, k)]));\n },\n ] as ObjectMethod,\n [\n \"entriesMap\",\n function (this: Record<string, any>, fn: ([key, value]: [string, any]) => [string, any]): Record<string, any> {\n return Object.fromEntries(Object.entries(this).map(fn));\n },\n ] as ObjectMethod,\n [\n \"keysMap\",\n function (this: Record<string, any>, fn: (k: string, v: any) => [string, any]): Record<string, any> {\n return Object.fromEntries(Object.entries(this).map(([k, v]) => fn(k, v)));\n },\n ] as ObjectMethod,\n [\n \"equals\",\n function (this: Record<string, any>, other: Record<string, any>) {\n const keysA = Object.keys(this);\n const keysB = Object.keys(other);\n if (keysA.length !== keysB.length) return false;\n return keysA.every((k) => keysB.includes(k) && typeof this[k] === typeof other[k]);\n },\n ] as ObjectMethod,\n [\n \"omit\",\n function <T extends Record<string, any>>(this: T, ...keys: string[]) {\n const out: Record<string, any> = {};\n for (const k of Object.keys(this)) if (!keys.includes(k)) out[k] = this[k];\n return out as T;\n },\n ] as ObjectMethod,\n [\n \"pick\",\n function <T extends Record<string, any>>(this: T, ...keys: string[]) {\n const out: Record<string, any> = {};\n for (const k of keys) if (k in this) out[k] = this[k];\n return out as T;\n },\n ] as ObjectMethod,\n [\n \"complement\",\n function <T extends Record<string, any>>(this: T, src: Record<string, any>) {\n const out: Record<string, any> = { ...this };\n for (const k of Object.keys(src)) if (!(k in out)) out[k] = src[k];\n return out as T;\n },\n ] as ObjectMethod,\n [\n \"clean\",\n function <T extends Record<string, any>>(this: T) {\n const out: Record<string, any> = {};\n for (const [k, v] of Object.entries(this)) {\n if (v === \"\" || v == null) continue; // remove empty, null, undefined\n out[k] = v;\n }\n return out as T;\n },\n ] as ObjectMethod,\n [\n \"ensureSchema\",\n function (this: Record<string, any>, schema: Record<string, any>, opts: { coerce?: boolean } = {}) {\n const out: Record<string, any> = {};\n for (const [k, def] of Object.entries(schema)) {\n let v = this[k];\n if (v == null) v = def;\n if (opts.coerce) {\n const type = typeof def;\n if (type === \"number\") v = Number(v);\n else if (type === \"boolean\") v = Boolean(v);\n else if (type === \"string\") v = String(v);\n }\n out[k] = v;\n }\n return out;\n },\n ] as ObjectMethod,\n [\n \"filterEntries\",\n function (this: Record<string, any>, predicate: (k: string, v: any) => boolean) {\n const out: Record<string, any> = {};\n for (const [k, v] of Object.entries(this)) if (predicate(k, v)) out[k] = v;\n return out;\n },\n ] as ObjectMethod,\n [\n \"merge\",\n function (this: Record<string, any>, other: Record<string, any>, opts: { arrayStrategy?: \"concat\" | \"replace\" | \"unique\" } = {}) {\n const out: Record<string, any> = { ...this };\n for (const [k, v] of Object.entries(other)) {\n const cur = out[k];\n if (Array.isArray(cur) && Array.isArray(v)) {\n const strat = opts.arrayStrategy ?? \"concat\";\n if (strat === \"replace\") out[k] = v;\n else if (strat === \"unique\") out[k] = Array.from(new Set([...cur, ...v]));\n else out[k] = [...cur, ...v];\n } else if (cur && typeof cur === \"object\" && v && typeof v === \"object\") {\n out[k] = { ...cur, ...v };\n } else {\n out[k] = v;\n }\n }\n return out;\n },\n ],\n [\n \"fromTable\",\n function (this: Record<string, any[]>) {\n const keys = Object.keys(this);\n const len = Math.max(0, ...keys.map((k) => this[k]?.length ?? 0));\n const out: Record<string, any>[] = Array.from({ length: len }, () => ({}));\n for (const k of keys) {\n const col = this[k] || [];\n for (let i = 0; i < len; i++) out[i][k] = col[i];\n }\n return out;\n },\n ] as ObjectMethod,\n] as ObjectMethod[];\n\nexport function extendObject() {\n for (const method of objectMethods) {\n Object.defineProperty(Object.prototype, method[0], {\n value: method[1],\n writable: true,\n configurable: true,\n enumerable: false,\n });\n }\n}\n","import { NonEmpty } from \"./polyfills.js\";\n\nimport { assert, assertRoute } from \"./polyfills.js\";\n\nfunction assertIs<T, U extends T>(value: T, guard: (v: T) => v is U, message: string = \"Type assertion failed\"): asserts value is U {\n if (!guard(value)) {\n throw new Error(message);\n }\n}\n\nexport const stringMethods: [string, (...args: any[]) => any][] = [\n [\n \"assertNonEmptyString\",\n function (this: string) {\n try {\n assertIs(this, (v): v is string => typeof v === \"string\");\n return this as string & NonEmpty;\n } catch {\n return false;\n }\n },\n ],\n\n [\n \"isNonEmty\",\n function (this: string): this is string & NonEmpty {\n return typeof this === \"string\" && this.trim().length > 0;\n },\n ],\n [\n \"changeExtension\",\n function (this: string, ext: string) {\n return this.replace(/\\.[0-9a-z]{1,5}$/i, \".\" + ext.replace(/\\W/, \"\").substring(0, 5));\n },\n ],\n [\n \"reverse\",\n function (this: string) {\n return this.split(\"\").reverse().join(\"\");\n },\n ],\n [\n \"toTitleCase\",\n function (this: string) {\n return this.replace(/\\w\\S*/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());\n },\n ],\n [\n \"words\",\n function (this: string) {\n return this.match(/\\b\\w+\\b/g) || [];\n },\n ],\n [\n \"slashreverse\",\n function (this: string, str: string) {\n return str.replace(/[\\\\/]/g, (ch) => (ch === \"\\\\\" ? \"/\" : \"\\\\\"));\n },\n ],\n [\n \"slashwin\",\n function (this: string) {\n return this.replace(/[\\\\/]/g, \"\\\\\");\n },\n ],\n [\n \"slashlinux\",\n function (this: string) {\n return this.replace(/[\\\\/]/g, \"/\");\n },\n ],\n [\n \"strip\",\n function (this: string) {\n return this.toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/\\s+/g, \"\")\n .trim();\n },\n ],\n [\n \"containsAny\",\n function (this: string, ...arr: string[]) {\n return arr.some((sub) => this.includes(sub));\n },\n ],\n [\n \"toSlug\",\n function (this: string) {\n return this.toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/[^\\w-]/g, \"\")\n .replace(/--+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n },\n ],\n [\n \"stripCompare\",\n function (this: string, other: string) {\n const normalize = (str: string) =>\n str\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/[\\s_]/g, \"\")\n .trim();\n return normalize(this).includes(normalize(other));\n },\n ],\n [\n \"toWordCapitalized\",\n function (this: string) {\n return this ? this.charAt(0).toUpperCase() + this.slice(1).toLowerCase() : \"\";\n },\n ],\n [\n \"truncate\",\n function (this: string, length: number, suffix = \"…\") {\n return this.length > length ? this.slice(0, length) + suffix : this.toString();\n },\n ],\n [\n \"isJson\",\n function (this: string) {\n try {\n JSON.parse(this);\n return true;\n } catch {\n return false;\n }\n },\n ],\n [\n \"toCamelCase\",\n function (this: string) {\n return this.replace(/([-_][a-z])/gi, ($1) => $1.toUpperCase().replace(\"-\", \"\").replace(\"_\", \"\"));\n },\n ],\n [\n \"safeParseJson\",\n function (this: string) {\n try {\n return JSON.parse(this);\n } catch {\n return this.valueOf();\n }\n },\n ],\n [\n \"nullParseJson\",\n function (this: string) {\n if (!this.trim()) return null;\n try {\n return JSON.parse(this);\n } catch {\n return null;\n }\n },\n ],\n [\n \"filenameCompare\",\n function (this: string, otherPath: string) {\n const normalize = (p: string) => p.replace(/\\\\/g, \"/\").split(\"/\").pop()?.toLowerCase() ?? \"\";\n return normalize(this) === normalize(otherPath);\n },\n ],\n [\n \"substringFrom\",\n function (this: string, startStr?: string, stopStr?: string) {\n const s = String(this);\n if (!startStr) return s;\n const i = s.indexOf(startStr);\n if (i === -1) return \"\";\n const from = i + startStr.length;\n if (!stopStr) return s.slice(from);\n const j = s.indexOf(stopStr, from);\n return j === -1 ? s.slice(from) : s.slice(from, j);\n },\n ],\n [\n \"escapeHTML\",\n function (this: string) {\n return this.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\\\"/g, \""\").replace(/'/g, \"'\");\n },\n ],\n [\n \"unescapeHTML\",\n function (this: string) {\n return this.replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\");\n },\n ],\n [\n \"humanize\",\n function (this: string) {\n const s = this.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\") // break camelCase\n .replace(/[\\-_]+/g, \" \") // dash/underscore to space\n .replace(/\\s{2,}/g, \" \") // trim double spaces\n .trim();\n return s.replace(/(^\\w|[.!?]\\s+\\w)/g, (m) => m.toUpperCase()); // capitalize sentences\n },\n ],\n [\n \"underscore\",\n function (this: string) {\n return this.replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .replace(/[\\s\\-]+/g, \"_\")\n .toLowerCase();\n },\n ],\n [\n \"countOccurrence\",\n function (this: string, str2: string, caseSens: boolean = true) {\n const src = caseSens ? this : this.toLowerCase();\n const needle = caseSens ? str2 : str2.toLowerCase();\n if (!needle) return 0;\n let i = 0,\n pos = 0;\n while ((pos = src.indexOf(needle, pos)) !== -1) {\n i++;\n pos += needle.length;\n }\n return i;\n },\n ],\n [\n \"isNumber\",\n function (this: string): this is string {\n return /^\\s*[+-]?(?:\\d+\\.?\\d*|\\.\\d+)\\s*$/.test(this);\n },\n ],\n [\n \"isFloat\",\n function (this: string): this is string {\n return /^\\s*[+-]?\\d*\\.\\d+\\s*$/.test(this) && !Number.isNaN(parseFloat(this));\n },\n ],\n [\n \"isAlphaNumeric\",\n function (this: string): this is string {\n return /^[a-z0-9]+$/i.test(this);\n },\n ],\n [\n \"isLower\",\n function (this: string) {\n return this === this.toLowerCase() && /[a-z]/.test(this);\n },\n ],\n [\n \"isUpper\",\n function (this: string) {\n return this === this.toUpperCase() && /[A-Z]/.test(this);\n },\n ],\n [\n \"hashed\",\n function (this: string, truncate?: number) {\n // Simple non-cryptographic hash (djb2-like)\n let h = 5381;\n for (let i = 0; i < this.length; i++) h = (h << 5) + h + this.charCodeAt(i);\n let out = (h >>> 0).toString(16);\n return typeof truncate === \"number\" ? out.slice(0, Math.max(0, truncate)) : out;\n },\n ],\n [\n \"replaceLast\",\n function (this: string, search: string | RegExp, replacement: string) {\n const s = String(this);\n if (search instanceof RegExp) {\n const m = s.match(search);\n if (!m) return s;\n const last = m[m.length - 1];\n const idx = s.lastIndexOf(last);\n return idx === -1 ? s : s.slice(0, idx) + replacement + s.slice(idx + last.length);\n } else {\n const idx = s.lastIndexOf(search);\n return idx === -1 ? s : s.slice(0, idx) + replacement + s.slice(idx + search.length);\n }\n },\n ],\n [\n \"latinise\",\n function (this: string) {\n return this.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\n },\n ],\n [\n \"ellipsis\",\n function (this: string, total: number) {\n if (total <= 3) return this.slice(0, total);\n return this.length > total ? this.slice(0, total - 3) + \"...\" : this;\n },\n ],\n [\n \"toNumber\",\n function (this: string) {\n const cleaned = this.replace(/[^0-9+\\-\\.eE]/g, \" \");\n const match = cleaned.match(/[+\\-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+\\-]?\\d+)?/);\n return match ? Number(match[0]) : NaN;\n },\n ],\n [\n \"toBoolean\",\n function (this: string) {\n const s = this.trim().toLowerCase();\n if ([\"1\", \"true\", \"yes\", \"on\", \"y\"].includes(s)) return true;\n if ([\"0\", \"false\", \"no\", \"off\", \"n\"].includes(s)) return false;\n return Boolean(s);\n },\n ],\n];\nexport function extendString() {\n for (const method of stringMethods) {\n Object.defineProperty(String.prototype, method[0], {\n value: method[1],\n writable: true,\n configurable: true,\n });\n }\n}\n","// src/primitives/math.ts\nexport const mathUtilsObj: MathUtils = {\n randomRangeFloat(min: number, max: number) {\n return Math.random() * (max - min) + min;\n },\n randomRangeInt(min: number, max: number) {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n },\n lerp(min: number, max: number, t: number) {\n return min + (max - min) * t;\n },\n clamp(value: number, min: number, max: number) {\n return Math.min(Math.max(value, min), max);\n },\n degToRad(deg: number) {\n return deg * (Math.PI / 180);\n },\n radToDeg(rad: number) {\n return rad * (180 / Math.PI);\n },\n distance(x1: number, y1: number, x2: number, y2: number) {\n return Math.hypot(x2 - x1, y2 - y1);\n },\n roundTo(value: number, decimals = 2) {\n return Math.round(value * 10 ** decimals) / 10 ** decimals;\n },\n isPowerOfTwo(value: number) {\n return value > 0 && (value & (value - 1)) === 0;\n },\n nextPowerOfTwo(value: number) {\n return 2 ** Math.ceil(Math.log2(value));\n },\n normalize(value: number, min: number, max: number) {\n return (value - min) / (max - min);\n },\n smoothStep(edge0: number, edge1: number, x: number) {\n const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));\n return t * t * (3 - 2 * t);\n },\n mix(x: number, y: number, a: number) {\n return x * (1 - a) + y * a;\n },\n mixColors(hex1: string, hex2: string, mixPerc: number) {\n const cleanHex = (h: string) => h.replace(\"#\", \"\").padStart(6, \"0\");\n const [h1, h2] = [cleanHex(hex1), cleanHex(hex2)];\n const [r1, g1, b1] = [parseInt(h1.slice(0, 2), 16), parseInt(h1.slice(2, 4), 16), parseInt(h1.slice(4, 6), 16)];\n const [r2, g2, b2] = [parseInt(h2.slice(0, 2), 16), parseInt(h2.slice(2, 4), 16), parseInt(h2.slice(4, 6), 16)];\n const r = Math.round(this.mix(r1, r2, mixPerc));\n const g = Math.round(this.mix(g1, g2, mixPerc));\n const b = Math.round(this.mix(b1, b2, mixPerc));\n const toHex = (n: number) => n.toString(16).padStart(2, \"0\");\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n },\n};\n\nexport function extendMathUtils() {\n if (!(globalThis as any).mathUtils) {\n (globalThis as any).mathUtils = mathUtilsObj;\n }\n if (typeof (globalThis as any).window !== \"undefined\") {\n Object.assign((globalThis as any).window, { mathUtils: mathUtilsObj } as { mathUtils: typeof mathUtilsObj });\n }\n}\n","/* eslint-disable no-redeclare */\n// primitivetools.ts\n\nimport { objectMethods } from \"./object.js\";\nimport { stringMethods } from \"./string.js\";\nimport { numberMethods } from \"./number.js\";\nimport { arrayMethods } from \"./array.js\";\nimport { mathUtilsObj } from \"./math.js\";\nimport type { NonEmpty } from \"./polyfills.js\";\n\n// Add it to pkit\n\n// ---------- TYPES ----------\n/* eslint-disable no-redeclare */\n// primitive-tools.ts\n// ---------- TYPES ----------\n\nexport interface PrimeString {\n /** Replace the file extension with `ext`. */\n changeExtension(ext: string): string;\n /** Return the string reversed. */\n reverse(): string;\n /** Convert the string to Title Case. */\n toTitleCase(): string;\n /** Split string into words. */\n words(): string[];\n /** Reverse slashes relative to `str`. */\n slashreverse(str: string): string;\n /** Convert slashes to Windows style. */\n slashwin(): string;\n /** Convert slashes to POSIX style. */\n slashlinux(): string;\n /** Trim whitespace from both ends. */\n strip(): string;\n /** Return true if contains any of the provided substrings. */\n containsAny(...arr: string[]): boolean;\n /** Create a URL/file-system safe slug. */\n toSlug(): string;\n /** Compare strings after stripping/normalizing. */\n stripCompare(other: string): boolean;\n /** Capitalize each word in the string. */\n toWordCapitalized(): string;\n /** Truncate to `length`, appending optional `suffix`. */\n truncate(length: number, suffix?: string): string;\n /** Return true if string contains valid JSON. */\n isJson(): boolean;\n /** Convert string to camelCase. */\n toCamelCase(): string;\n /** Parse JSON and return value or original on failure. */\n safeParseJson(): any;\n /** Parse JSON and return value or null on failure. */\n nullParseJson(): any | null;\n /** Compare two paths by filename only. */\n filenameCompare(otherPath: string): boolean;\n /** Return substring between optional start and stop markers. */\n substringFrom(startStr?: string, stopStr?: string): string;\n /** Escape HTML special characters. */\n escapeHTML(): string;\n /** Unescape HTML entities to characters. */\n unescapeHTML(): string;\n /** Humanize identifiers: split camelCase, replace dashes/underscores, capitalize sentences. */\n humanize(): string;\n /** Convert to underscore_case. */\n underscore(): string;\n /** True if trimmed string is empty. */\n isEmpty(): boolean;\n /** Count occurrences of substring; case sensitive by default. */\n countOccurrence(str2: string, caseSens?: boolean): number;\n /** True if string represents a number. */\n isNumber(): boolean;\n /** True if string represents a float. */\n isFloat(): boolean;\n /** True if string is alphanumeric. */\n isAlphaNumeric(): boolean;\n /** True if string is all lowercase (with letters present). */\n isLower(): boolean;\n /** True if string is all uppercase (with letters present). */\n isUpper(): boolean;\n /** Simple non-crypto hash (hex); optional truncate. */\n hashed(truncate?: number): string;\n /** Replace the last occurrence of search with replacement. */\n replaceLast(search: string | RegExp, replacement: string): string;\n /** Remove diacritics (latinize). */\n latinise(): string;\n /** Truncate with \"...\" to total width. */\n ellipsis(total: number): string;\n /** Extract a numeric value from the string. */\n toNumber(): number;\n /** Parse boolean from common truthy/falsey words. */\n toBoolean(): boolean;\n /** Assert this is a non-empty string; returns NonEmpty or false. */\n assertNonEmptyString(): (string & NonEmpty) | false;\n /** True if string is non-empty after trimming (type guard). */\n isNonEmty(): this is string & NonEmpty;\n}\nexport interface PrimeNumber {\n /** Assert number is between `min` and `max` (type guard style). */\n assertNrBetween(min?: number, max?: number): this is number;\n /** True if integer. */\n isInteger(): this is number;\n /** True if finite (not NaN/Infinity). */\n isFinite(): this is number;\n /** True if safe integer. */\n isSafeInteger(): this is number;\n /** True if > 0. */\n isPositive(): this is number;\n /** True if < 0. */\n isNegative(): this is number;\n /** True if >= 0. */\n isNonNegative(): this is number;\n /** Assert integer (throws on failure). */\n assertIsInteger(): asserts this is number;\n /** Assert finite (throws on failure). */\n assertIsFinite(): asserts this is number;\n /** Like `toFixed` but returns a number. */\n toFixedNumber(decimals?: number): number;\n /** Check if number is between min and max (inclusive). */\n between(min: number, max: number): boolean;\n /** Clamp the number between min and max. */\n clamp(min: number, max: number): number;\n /** Run `fn` `n` times with index. */\n times(fn: (i: number) => void): void;\n /** Return string with leading zeros to reach `length`. */\n toStringWithLeadingZeros(length: number): string;\n /** Convert seconds/number to a timecode string. */\n toTimeCode(): string;\n /** Calculate what percent this number is of total. */\n percentOf(total: number): number;\n /** Calculate what ratio this number is of total. */\n ratioOf(total: number): number;\n}\n\nexport interface PrimeArray<T> {\n /** Return first element or first n elements. */\n first(n?: number): T | T[];\n /** Return last element or last n elements. */\n last(n?: number): T | T[];\n /** Find an item where `key` equals `value`. */\n findByKey<K extends keyof T & string>(key: K, value: any): T | null;\n /** Group items using a mapping function. */\n groupBy(fn: (item: T) => string): Record<string, T[]>;\n /** Group items by a property key. */\n groupBy<K extends keyof T & string>(key: K): Record<string, T[]>;\n /** Sum numeric values at the given key. */\n sumByKey<K extends keyof T & string>(key: K): number;\n /** Parse item string fields where applicable. */\n autoParseKeys(): T[];\n /** Return array with duplicates removed. */\n unique(): T[];\n /** Return a shuffled copy of the array. */\n shuffle(): T[];\n /** Return item with highest value for key or null. */\n highestByKey<K extends keyof T & string>(key: K): T | null;\n /** Return item with lowest value for key or null. */\n lowestByKey<K extends keyof T & string>(key: K): T | null;\n /** Sort array by key; ascending by default. */\n sortByKey<K extends keyof T & string>(key: K, ascending?: boolean): T[];\n /** Sort array by key name (string) with optional order. */\n sortByKeyName<K extends keyof T & string>(key: K, ascending?: boolean): T[];\n /** Map array to values of the given key. */\n mapByKey<K extends keyof T & string>(key: K): Array<T[K]>;\n /** Sum numeric values at key across objects. */\n sumKey(key: string): number;\n /** Average numeric values at key across objects. */\n averageKey(key: string): number;\n /** Filter by a key using predicate. */\n filterKey(key: string, pred: (v: any) => boolean): T[];\n /** Unique objects by key or projection function. */\n distinct(keyOrFn: string | ((x: T) => any)): T[];\n /** Group-reduce objects by key or projection. */\n aggregate<R>(keyOrFn: string | ((x: T) => any), reducer: (acc: R, cur: T) => R, init: R): Record<string, R>;\n /** Sum numeric values by key (string). */\n sumBy(key: string): number;\n /** Average numeric values by key (string). */\n averageBy(key: string): number;\n /** Sum all numeric values in the array. */\n sum(): number;\n /** Average all numeric values in the array. */\n average(): number;\n /** Return index of the highest number in the array. */\n indexOfHighestNumber(): number;\n /** Return index of the lowest number in the array. */\n indexOfLowestNumber(): number;\n /** Return intersection with another array (items present in both). */\n intersect(other: T[]): T[];\n /** Return difference with another array (items present only in this). */\n difference(other: T[]): T[];\n /** Validate each item; replace invalids with null and keep valid items. */\n validateEach(validatorFn: (item: T) => boolean): (T | null)[];\n /** Remove null and undefined values, returning a narrowed array. */\n clearNil(): T[];\n /** Group-reduce objects by key or projection. */\n toTable(): Record<string, any[]>;\n}\n\n// ---------- OVERLOADS VOOR pkit(...) ----------\n\n// ---------- TYPE VOOR HET CALLABLE PAKKET pkit ----------\n\nexport interface Pkit {\n (value: undefined): null;\n (value: null): boolean;\n (value: string): PrimeString;\n (value: number): PrimeNumber;\n <T>(value: T[]): PrimeArray<T>;\n (value: Record<string, any>): PrimeObject;\n <T>(value: T): { unwrap(): T };\n\n // extra namespaces:\n math: MathUtils;\n path: PathShim;\n}\n\n// losse implementatie-functie\nconst pkitImpl = (value: any): any => {\n if (typeof value === \"string\") return createPrimeString(value);\n if (typeof value === \"number\") return createPrimeNumber(value);\n if (Array.isArray(value)) return createPrimeArray(value);\n if (value && typeof value === \"object\") return createPrimeObject(value);\n if (value === null) return false;\n if (typeof value === \"undefined\") return null;\n if (typeof value === \"function\") return pkitImpl(value());\n};\n// ---------- IMPLEMENTATIES ----------\n\n// STRING IMPLEMENTATIE (op basis van jouw eerdere methods)\n\nfunction createPrimeString(value: string): PrimeString {\n let current = value;\n\n return Object.fromEntries(\n stringMethods.map(([name, fn]) => {\n return [\n name,\n function (...args: any[]) {\n current = fn.apply(current, args);\n return current;\n },\n ];\n })\n ) as unknown as PrimeString;\n}\n\n// NUMBER IMPLEMENTATION\n\nfunction createPrimeNumber(initial: number): PrimeNumber {\n let current = initial;\n let result: number | void | string | boolean;\n return Object.fromEntries(\n numberMethods.map(([name, fn]) => {\n return [\n name,\n function (...args: any[]): number | void | string | boolean {\n result = fn.apply(current, args);\n\n return result;\n },\n ];\n })\n ) as unknown as PrimeNumber;\n}\n\n// ARRAY IMPLEMENTATION\n\nfunction createPrimeArray<T extends any[]>(initial: T): PrimeArray<T> {\n let current = initial;\n let result: any = null;\n return Object.fromEntries(\n arrayMethods.map(([name, fn]) => {\n return [\n name,\n function (...args: T[]) {\n result = fn.apply(current, args);\n return result;\n },\n ];\n })\n ) as unknown as PrimeArray<T>;\n}\n\nexport interface PrimeObject {\n /** Type guard: is a plain object (not array). */\n isObject(): this is Record<string, any>;\n /** Assert object has provided keys. */\n assertHasKeys<K extends string>(...keys: K[]): asserts this is Record<string, any> & Record<K, unknown>;\n /** Cast after validation. */\n asType<T>(): T;\n /** True if all values are non-empty strings. */\n is