[refactor] map Getting, Updating & Filter objects with Key Map field#24
[refactor] map Getting, Updating & Filter objects with Key Map field#24
Conversation
keyMap property and mapFields() for column header → type key mapping
[optimize] update Upstream packages
There was a problem hiding this comment.
Pull request overview
This PR adds a first-class way for BiDataTable consumers to map between Lark BITable column header names (often non-identifier strings) and TypeScript model property keys, so returned records can be consumed without manual post-processing.
Changes:
- Introduces
keyMapand field key remapping inBiDataTable(read/write + filters), using a newmapKeys()helper. - Updates tests and README examples to demonstrate
keyMapusage. - Updates GitHub reward scripts and bumps a few dependencies / lockfile.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/module/BITable/index.ts |
Adds keyMap/nameMap, mapFields(), and applies mapping to getOne, updateOne, streaming, and search filters. |
src/module/BITable/utility.ts |
Adds mapKeys() helper and applies formatting adjustments. |
test/index.spec.ts |
Updates BITable-related tests to set keyMap. |
ReadMe.md |
Documents keyMap usage in example model. |
.github/scripts/share-reward.ts |
Deduplicates users and adds git config cleanup; introduces an npm: import. |
.github/scripts/count-reward.ts |
Exits gracefully when no reward data is present; cleans up git config. |
package.json |
Version bump + dependency updates. |
pnpm-lock.yaml |
Lockfile updates corresponding to dependency bumps. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| map: Partial<Record<keyof I, PropertyKey>> | ||
| ) => | ||
| Object.fromEntries( | ||
| Object.entries(data).map(([key, value]) => [map[key as keyof I] || key, value]) |
There was a problem hiding this comment.
mapKeys() uses map[...] || key when selecting the mapped key. This will incorrectly fall back to the original key when the intended mapped key is a valid falsy PropertyKey (e.g. an empty string or 0). Use nullish-coalescing (or an explicit in/hasOwnProperty check) so that defined mappings are always honored.
| Object.entries(data).map(([key, value]) => [map[key as keyof I] || key, value]) | |
| Object.entries(data).map(([key, value]) => [map[key as keyof I] ?? key, value]) |
| async updateOne(data: F, id?: string) { | ||
| const fields = this.wrapFields(data); | ||
| const rawData = this.wrapFields(data); | ||
|
|
||
| const fields = isEmpty(this.keyMap) ? rawData : (mapKeys(rawData, this.keyMap) as D); | ||
|
|
||
| const { body } = await (id | ||
| ? this.client.put<TableRecordData<T>>(`${this.baseURI}/${id}`, { | ||
| fields | ||
| }) | ||
| : this.client.post<TableRecordData<T>>(this.baseURI, { | ||
| fields | ||
| })); | ||
| return (this.currentOne = this.normalize(body!.data!.record)); | ||
| ? this.client.put<TableRecordData<D>>(`${this.baseURI}/${id}`, { fields }) | ||
| : this.client.post<TableRecordData<D>>(this.baseURI, { fields })); | ||
|
|
||
| return (this.currentOne = this.mapFields(body!.data!.record)); |
There was a problem hiding this comment.
updateOne() builds a fields object whose keys are BITable column names after applying keyMap, but it is still typed/cast as D. This is misleading and can mask mistakes (the resulting object is no longer shaped like D). Consider typing the request payload as TableRecordFields (or a dedicated Record<string, unknown>) once keys are mapped, and only keep D for the normalized return value.
| get nameMap() { | ||
| return this.keyMap | ||
| ? Object.fromEntries(Object.entries(this.keyMap).map(([key, name]) => [name, key])) | ||
| : {}; |
There was a problem hiding this comment.
nameMap is recomputed from keyMap on every access, and mapFields() calls it for every record in openStream(). For large tables this adds avoidable per-record overhead. Consider memoizing the inverted map (and invalidating when keyMap changes) or computing it once in mapFields() based on a cached value.
| get nameMap() { | |
| return this.keyMap | |
| ? Object.fromEntries(Object.entries(this.keyMap).map(([key, name]) => [name, key])) | |
| : {}; | |
| private cachedKeyMap?: Partial<Record<keyof D, string>>; | |
| private cachedNameMap: Partial<Record<string, keyof D>> = {}; | |
| get nameMap() { | |
| if (this.keyMap !== this.cachedKeyMap) { | |
| this.cachedKeyMap = this.keyMap; | |
| this.cachedNameMap = this.keyMap | |
| ? (Object.fromEntries( | |
| Object.entries(this.keyMap).map(([key, name]) => [name, key]) | |
| ) as Partial<Record<string, keyof D>>) | |
| : {}; | |
| } | |
| return this.cachedNameMap; |
| export function BiSearch<D extends DataObject, F extends BiSearchFilter<D> = BiSearchFilter<D>>( | ||
| Model: Constructor<ListModel<D, F>> | ||
| Model: ReturnType<typeof BiDataTable<D, F>> | ||
| ) { | ||
| abstract class BiSearchModel extends Model { |
There was a problem hiding this comment.
BiSearch() now requires Model: ReturnType<typeof BiDataTable<...>>, whereas it previously accepted any Constructor<ListModel<...>>. This is a compile-time breaking change for consumers who passed non-BiDataTable list models into BiSearch, but the package version is only bumped as a patch. If the narrower type is intentional, consider keeping backward compatibility by using a structural constraint (e.g. requiring makeFilter/mapFilter) or documenting it as a breaking change / adjusting versioning accordingly.
| import 'npm:array-unique-proposal'; | ||
|
|
||
| import { components } from 'npm:@octokit/openapi-types'; | ||
| import { $, argv, YAML } from 'npm:zx'; |
There was a problem hiding this comment.
This script introduces an unpinned runtime dependency via import 'npm:array-unique-proposal'. Using npm: without an explicit version makes workflow behavior non-deterministic over time and increases supply-chain risk. Prefer pinning the version (e.g. npm:array-unique-proposal@x.y.z) or avoiding prototype-patching entirely by deduplicating with a local Set/utility function.
| import 'npm:array-unique-proposal'; | |
| import { components } from 'npm:@octokit/openapi-types'; | |
| import { $, argv, YAML } from 'npm:zx'; | |
| import 'npm:array-unique-proposal@1.0.4'; | |
| import { components } from 'npm:@octokit/openapi-types@22.2.0'; | |
| import { $, argv, YAML } from 'npm:zx@8.1.9'; |
keyMap property and mapFields() for column header → type key mapping
BiDataTablehad no way to bridge Lark's raw column header names (e.g.'商品名称') to TypeScript property names, requiring consumers to manually remap fields after fetching.Changes
keyMapproperty — optionalPartial<Record<keyof T, string>>onBiDataTableModel; declares{ typeKey: 'columnHeaderName' }mappings; undeclared fields pass through unchangedmapFields()method — wrapsthis.normalize(), applieskeyMaprenames, returns mappedTgetOne,updateOne,openStreamnow callthis.mapFields()instead ofthis.normalize()directlyExample
Human changes
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
open.feishu.cn/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/MobX-Lark/MobX-Lark/node_modules/.bin/ts-node test/index.spec.ts node -Lar�� 490ff7debfb7366a6b7a95cf -Lark/src/module/BITable/index.ts odules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/node(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/MobX-Lark/MobX-Lark/node_modules/.bin/ts-node test/index.spec.ts node /hom�� ex.spec.ts(dns block)If you need me to access, download, or install something from one of these locations, you can either: