data_tables.ts (4873B)
1 import { ResolveType, ZipKeysWithValues } from './types.js'; 2 3 export type valueof<K> = K[keyof K]; 4 5 export function keysOf<T extends string>(obj: { [k in T]?: unknown }): readonly T[] { 6 return Object.keys(obj) as unknown[] as T[]; 7 } 8 9 export function numericKeysOf<T extends number>(obj: { [k in T]?: unknown }): readonly T[] { 10 return Object.keys(obj).map(n => Number(n) as T); 11 } 12 13 /** 14 * @returns a new Record from `objects`, using the string returned by Object.toString() as the keys 15 * and the objects as the values. 16 */ 17 export function objectsToRecord<T extends Object>(objects: readonly T[]): Record<string, T> { 18 const record = {}; 19 return objects.reduce((obj, type) => { 20 return { 21 ...obj, 22 [type.toString()]: type, 23 }; 24 }, record); 25 } 26 27 /** 28 * Creates an info lookup object from a more nicely-formatted table. See below for examples. 29 * 30 * Note: Using `as const` on the arguments to this function is necessary to infer the correct type. 31 */ 32 export function makeTable< 33 Members extends readonly string[], 34 Defaults extends readonly unknown[], 35 Table extends { readonly [k: string]: readonly unknown[] }, 36 >( 37 members: Members, 38 defaults: Defaults, 39 table: Table 40 ): { 41 readonly [k in keyof Table]: ResolveType<ZipKeysWithValues<Members, Table[k], Defaults>>; 42 } { 43 const result: { [k: string]: { [m: string]: unknown } } = {}; 44 for (const [k, v] of Object.entries<readonly unknown[]>(table)) { 45 const item: { [m: string]: unknown } = {}; 46 for (let i = 0; i < members.length; ++i) { 47 item[members[i]] = v[i] ?? defaults[i]; 48 } 49 result[k] = item; 50 } 51 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 52 return result as any; 53 } 54 55 /** 56 * Creates an info lookup object from a more nicely-formatted table. 57 * 58 * Note: Using `as const` on the arguments to this function is necessary to infer the correct type. 59 * 60 * Example: 61 * 62 * ``` 63 * const t = makeTableWithDefaults( 64 * { c: 'default' }, // columnRenames 65 * ['a', 'default', 'd'], // columnsKept 66 * ['a', 'b', 'c', 'd'], // columns 67 * [123, 456, 789, 1011], // defaults 68 * { // table 69 * foo: [1, 2, 3, 4], 70 * bar: [5, , , 8], 71 * moo: [ , 9,10, ], 72 * } 73 * ); 74 * 75 * // t = { 76 * // foo: { a: 1, default: 3, d: 4 }, 77 * // bar: { a: 5, default: 789, d: 8 }, 78 * // moo: { a: 123, default: 10, d: 1011 }, 79 * // }; 80 * ``` 81 * 82 * MAINTENANCE_TODO: `ZipKeysWithValues<Members, Table[k], Defaults>` is incorrect 83 * because Members no longer maps to Table[k]. It's not clear if this is even possible to fix 84 * because it requires mapping, not zipping. Maybe passing in a index mapping 85 * would fix it (which is gross) but if you have columnsKept as [0, 2, 3] then maybe it would 86 * be possible to generate the correct type? I don't think we can generate the map at compile time 87 * so we'd have to hand code it. Other ideas, don't generate kLimitsInfoCore and kLimitsInfoCompat 88 * where they are keys of infos. Instead, generate kLimitsInfoCoreDefaults, kLimitsInfoCoreMaximums, 89 * kLimitsInfoCoreClasses where each is just a `{[k: string]: type}`. Could zip those after or, 90 * maybe that suggests passing in the hard coded indices would work. 91 * 92 * @param columnRenames the name of the column in the table that will be assigned to the 'default' property of each entry. 93 * @param columnsKept the names of properties you want in the generated lookup table. This must be a subset of the columns of the tables except for the name 'default' which is looked from the previous argument. 94 * @param columns the names of the columns of the name 95 * @param defaults the default value by column for any element in a row of the table that is undefined 96 * @param table named table rows. 97 */ 98 export function makeTableRenameAndFilter< 99 Members extends readonly string[], 100 DataMembers extends readonly string[], 101 Defaults extends readonly unknown[], 102 Table extends { readonly [k: string]: readonly unknown[] }, 103 >( 104 columnRenames: { [key: string]: string }, 105 columnsKept: Members, 106 columns: DataMembers, 107 defaults: Defaults, 108 table: Table 109 ): { 110 readonly [k in keyof Table]: ResolveType<ZipKeysWithValues<Members, Table[k], Defaults>>; 111 } { 112 const result: { [k: string]: { [m: string]: unknown } } = {}; 113 const keyToIndex = new Map<string, number>( 114 columnsKept.map(name => { 115 const remappedName = columnRenames[name] === undefined ? name : columnRenames[name]; 116 return [name, columns.indexOf(remappedName)]; 117 }) 118 ); 119 for (const [k, v] of Object.entries<readonly unknown[]>(table)) { 120 const item: { [m: string]: unknown } = {}; 121 for (const member of columnsKept) { 122 const ndx = keyToIndex.get(member)!; 123 item[member] = v[ndx] ?? defaults[ndx]; 124 } 125 result[k] = item; 126 } 127 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 128 return result as any; 129 }