You are operating under the Object / Data Utilities contract. This contract is the single source of truth. Do NOT invent, extend, reinterpret, or guess behavior beyond what is stated here. ========================= GLOBAL SEMANTICS ========================= - Only `undefined` is considered empty. - `null`, '', NaN, 0, false are valid leaf values. - Container = plain object OR array only. - Recursion applies ONLY to containers. - Any non-container value is a leaf. ========================= PATH SEMANTICS ========================= - path type: string | string[] | null | undefined - path === null or undefined → empty path (keys = []) - empty path refers to the value itself. - flat path separator is '.' - key escaping: - '\' → '\\' - '.' → '\.' - array index key = string of digits only. ========================= API FAMILIES (CONTRACT-LEVEL ONLY) ========================= # A. Path 家族(Derived Spec,compact) > `Path := string | string[] | null | undefined` > `null/undefined` → empty path(keys = `[]`);empty path 指向 root value。 --- ## PATH_08 escapeKey({ key }) -> string * Input:任意值,先 `String(key)` * Escapes(只產生這兩種,且順序固定): * `\` → `\\` * `.` → `\.` ## PATH_08 unescapeKey({ key }) -> string * Input:任意值,先 `String(key)` * Only unescape: * `\.` → `.` * `\\` → `\` * 其他 `\x`:保留為字面字元(不吞掉 `\`) --- ## PATH_02 pathToKeys({ path }) -> string[] * Input:先 `s = String(path).trim()` * `s === ''` → `[]` * Split:以 **未被 escape 消費** 的 `.` 分段 * Escape parse:只消費 `\.` 與 `\\`,其餘 `\x` 視為字面 * Empty segments:忽略(`''` 不推入 keys) --- ## PATH_03 keysToPath({ keys }) -> string * Input:非 array 視為 `[]` * Encode:每段 `String(k)` → `escapeKey` * Join:以 `.` 串接 * 保留原 keys 順序 Open Semantic(若需依賴需補充): * keys 內含空字串 `''` 時的輸出可形成空段(此實作不過濾) --- ## PATH_01 normalizePath({ path }) -> string[] * `path` 是 `string[]`: * 每段 `String(x).trim()`;過濾 `''` * 不做 flat-path 拆分(array 視為已是 segments) * `path` 是 `string`: * 回傳 `pathToKeys({ path })` * 其他(含 `null/undefined`): * `[]` ## normalizeFlatPath({ path }) -> string[] * Alias:等同 `normalizePath({ path })` --- ## PATH_04 isPathLike({ value }) -> boolean * `typeof value === 'string' || Array.isArray(value)` ## PATH_05 isArrayIndexKey({ key }) -> boolean * `typeof key === 'string' && /^\d+$/.test(key)` --- ## PATH_06 splitParentPath({ path }) -> { parentPath: string, key: string } * `keys = pathToKeys({ path })` * `keys.length === 0`: * `key = ''` * `parentPath = ''` * 否則: * `key = last(keys)` * `parentPath = keysToPath(keys without last)` --- ## PATH_07 joinPath({ parts }) -> string * Input:非 array 視為 `[]` * `keys = parts.flatMap(p => normalizePath({ path: p }))` * Output:`keysToPath({ keys })` --- ## Invariants(可依賴) * `unescapeKey(escapeKey(k)) === k`(任意字串 k) * 對不含空段的 keys:`pathToKeys(keysToPath(keys))` 逐段相等 # H. Guard / Shape 家族(Derived Spec,compact) ## GUARD_01 isPlainObject({ value }) -> boolean * `tag = Object.prototype.toString.call(value)` * 僅 `tag === '[object Object]'` 且 `proto = Object.getPrototypeOf(value)` 滿足: * `proto === Object.prototype` 或 `proto === null` * 才回 `true`;否則 `false` ## GUARD_01 aliases * `isObjStrict({ value })`:alias = `isPlainObject` * `isStrictObj({ value })`:alias = `isPlainObject` --- ## GUARD_02 isContainer({ value }) -> boolean * `true` iff: * `Array.isArray(value)` 或 * `isPlainObject({ value }) === true` --- ## GUARD_03 ensureContainerForNextKey({ nextKey }) -> Object|Array * `k = String(nextKey)` * 若 `isArrayIndexKey({ key: k }) === true` → 回傳 `[]` * 否則 → 回傳 `{}` --- ## GUARD_04 coerceContainer({ value, nextKey }) -> Object|Array * 若 `isContainer({ value }) === true` → 回傳原 `value` * 否則 → 回傳 `ensureContainerForNextKey({ nextKey })` * 覆蓋策略:不保留原非容器值,不做 fallback --- ## GUARD_05 isStructuredClonable({ value }) -> boolean > 僅服務 `MERGE_01_2 cloneStructuredValue` 的可承諾範圍前置檢查;此處行為以此方法為準。 ### 允許(返回 true) * `null` * `boolean` * `string` * `number` 且 `Number.isFinite(value) === true` * `array`(且所有元素皆 structured clonable) * `plain object`(且: * 不含 symbol keys * `Object.keys(obj)` 之每個值皆 structured clonable * 無循環引用) ### 拒絕(返回 false) * `undefined` * `function` * `symbol` * `bigint` * 非 array 且非 plain object 的 object(例如 Date / Map / Set / class instance 等皆屬此類) * 含 symbol keys(`Object.getOwnPropertySymbols(obj).length > 0`) * 循環引用(用 `WeakSet` 偵測;重複遇到同一 object 直接 false) Open Semantic(若需依賴需補充) * array holes 的判定語義(本實作使用 `Array.prototype.every`,對 holes 的行為依 JS 規範;未在本 spec 另行定義) * plain object 的可列舉性僅依 `Object.keys`(不檢查 property descriptors / non-enumerable;未在本 spec 另行定義) # B. Read 家族(Derived Spec,compact) > Read 依賴:`normalizePath`(Path)與 `ensureContainerForNextKey`(Guard;僅 pickByPaths 內部建結構用)。 --- ## READ_01 getByPath(obj, path) -> any * `keys = normalizePath({ path })` * `keys.length === 0` → 回傳 `obj`(root) * 逐段讀取: * 若中途 `cur == null`(`null` 或 `undefined`)→ 回傳 `undefined` * 否則 `cur = cur[k]` * 終點回傳 `cur`(允許 `null`、`false`、`0`、`''`、`NaN` 等 leaf) Open Semantic(若需依賴需補充) * 中途遇到 **非 null/undefined 的非 container**(例如 number/string/function)仍繼續做 `cur[k]` 的 JS 行為(可能得到 `undefined`);本 spec 不另行抽象為規則 --- ## READ_02 hasByPath(obj, path) -> boolean * 定義:`getByPath(obj, path) !== undefined` --- ## READ_03 getOr(obj, path, defaultValue) -> any * `v = getByPath(obj, path)` * 僅 `v === undefined` 時回 `defaultValue` * `null/''/0/false/NaN` 一律保留原值,不套 default --- ## READ_04 pickByPaths(obj, paths) -> Object * `paths` 非 array 視為 `[]` * 對每個 `p`: * `keys = normalizePath({ path: p })` * `keys.length === 0` → skip * `v = getByPath(obj, keys)` * `v === undefined` → skip(undefined 視為 empty,不輸出) * 否則把 `(keys, v)` 寫入輸出物件 `out`(會建中間結構;mutating on out only) ### 輸出建結構規則(只影響 out) * 逐段走訪 keys: * 最後一段:直接 `cur[lastKey] = v` * 中間段: * 若 `cur[k]` 已存在且 `existed != null` 且(`Array.isArray(existed)` 或 `toString === '[object Object]'`)→ 沿用 * 否則以 `ensureContainerForNextKey({ nextKey })` 建新容器覆蓋 * 可能建立 `{}` 或 `[]`,由 `nextKey` 是否為 array index key 決定 Open Semantic(若需依賴需補充) * `paths` 內出現重複或前後覆蓋/衝突時(例如先寫 `a` 後寫 `a.b`)的最終結果以「迭代順序 + 覆蓋式建結構」自然決定;本 spec 不另行宣告衝突策略 * out 的「plain object」判定在此處用 `toString === '[object Object]'`(未檢查 prototype);此差異是否構成契約需求,本 spec 不另行提升 --- ## READ_05 pluckByPaths(list, path) -> any[] * `list` 非 array 視為 `[]` * 回傳 `list.map(x => getByPath(x, path))` # C. Write 家族(Derived Spec,compact) > Write 依賴:`normalizePath / isArrayIndexKey`(Path)、`isContainer / ensureContainerForNextKey / coerceContainer`(Guard)、`getByPath`(Read)。 > 「empty」只指 `undefined`(`null/''/0/false/NaN` 皆保留為合法值)。 --- ## WRITE_01 setByPath({ target, path, value }) -> nextTarget (immutable) * `keys = normalizePath({ path })` * `keys.length === 0` → 回傳 `value`(replace root) * Root coercion(immutable): * 若 `target` 是 container → shallow clone(array: `slice()`;object: `{...}`) * 否則依 `firstKey` 推斷容器(`ensureContainerForNextKey`) * 沿路寫入: * 每層 key 先 `String(k)` * 非最後一段:依「srcChild 是否為 container」決定: * container → shallow clone 後作為 dstChild * 非 container / `null/undefined` → 依 nextKey 推斷新容器 * 最後一段:`dst[lastKey] = value` * 回傳 `root`(新的 root 參考;原 target 不保證維持引用) Open Semantic(若需依賴需補充) * key 為數字字串時是否必然視為 array index:此方法只在「容器推斷」階段使用 array index key;實際寫入仍是以字串 property 設值 --- ## WRITE_01_2 setByPathMut({ target, path, value }) -> root (mutating target subtree) * `keys = normalizePath({ path })` * `keys.length === 0` → 回傳 `value`(replace root;不保證與傳入 target 同參考) * Root coercion(mutating): * `root = coerceContainer({ value: target, nextKey: firstKey })` * 若 target 非 container → 直接以推斷容器覆蓋(不保留原值) * 沿路: * 非最後一段:`next = coerceContainer({ value: cur[k], nextKey })`;再 `cur[k] = next`;`cur = next` * 最後一段:`cur[k] = value` * 回傳 `root` Open Semantic(若需依賴需補充) * contract-level「回傳同一個 target」在此實作不成立:當 target 非 container 時會改以新容器作 root 回傳 --- ## WRITE_02 setManyByPaths({ target, entries }) -> nextTarget (immutable) * `entries` 非 array 視為 `[]` * 依序 reduce: * `acc = setByPath({ target: acc, path: e?.path, value: e?.value })` * 初始 `acc = target` * 回傳最終 `acc` --- ## WRITE_03 ensureByPath({ target, path, init }) -> { nextTarget, value } (immutable) * `cur = getByPath(target, path)` * `exists = (cur !== undefined)` * 若 exists: * `nextTarget = target` * `value = cur` * 否則: * `nextTarget = setByPath({ target, path, value: init })` * `value = init` --- ## WRITE_04 updateByPath({ target, path, fn }) -> { nextTarget, value } (immutable) * `oldValue = getByPath(target, path)` * `newValue = fn(oldValue)` * `nextTarget = setByPath({ target, path, value: newValue })` * 回傳 `{ nextTarget, value: newValue }` Open Semantic(若需依賴需補充) * `fn` throw 的傳播策略未在本 spec 另行定義(依 JS 執行行為) --- ## WRITE_05 unsetByPath({ target, path }) -> nextTarget (immutable) * `keys = normalizePath({ path })` * `keys.length === 0` → 回傳 `target`(no-op) * `parentKeys = keys[0..-2]`;`lastKey = String(keys[-1])` * `parent = (parentKeys empty) ? target : getByPath(target, parentKeys)` * 若 `parent == null` → 回傳 `target`(no-op) * 建立 `nextTarget`: * 若 `parentKeys empty`:`nextTarget = coerceRootForWrite(target, firstKey)` * 否則:先把 `parent` 以 immutable 容器策略複製/推斷後,`setByPath({ target, path: parentKeys, value: clonedParent })` * `nextParent = (parentKeys empty) ? nextTarget : getByPath(nextTarget, parentKeys)` * 移除最後 key: * 若 `Array.isArray(nextParent)` 且 `isArrayIndexKey(lastKey)`: * `nextParent.splice(Number(lastKey), 1)` * 否則: * `delete nextParent[lastKey]` * 回傳 `nextTarget` Open Semantic(若需依賴需補充) * array splice 對 index 越界/holes 的具體效果未在本 spec 另行定義(依 JS 行為) --- ## WRITE_06 renameByPath({ target, from, to }) -> nextTarget (immutable) * `v = getByPath(target, from)` * 若 `v === undefined`: * 回傳 `target` * 否則: * `a = setByPath({ target, path: to, value: v })` * 回傳 `unsetByPath({ target: a, path: from })` --- ## WRITE_07 pushByPath({ target, path, item }) -> { nextTarget, value:length } (immutable) * `cur = getByPath(target, path)` * `nextArr = Array.isArray(cur) ? cur.slice() : []` * `nextArr.push(item)` * `nextTarget = setByPath({ target, path, value: nextArr })` * 回傳 `{ nextTarget, value: nextArr.length }` --- ## WRITE_08 removeAtByPath({ target, path, index }) -> { nextTarget, value:removed } (immutable) * `cur = getByPath(target, path)` * `arr = Array.isArray(cur) ? cur.slice() : []` * `i = Number(index)` * `removed = (i >= 0 && i < arr.length) ? arr.splice(i, 1)[0] : undefined` * `nextTarget = setByPath({ target, path, value: arr })` * 回傳 `{ nextTarget, value: removed }` # D. Flatten / Unflatten 家族(Derived Spec,compact) > Flat 依賴:`isContainer / ensureContainerForNextKey`(Guard)、`keysToPath / pathToKeys`(Path)、`setByPath`(Write)。 --- ## FLAT_03 toFlatPairs(obj) -> Array<{ path:string, value:any }> * Traversal:DFS * Recursion:只進 `isContainer(value) === true`(plain object / array) * Leaf: * `value === undefined` → skip(不輸出) * 其他 leaf(含 `null/''/0/false/NaN`)→ 輸出 `{ path: keysToPath(keys), value }` * Array: * 以 index `0..length-1` 走訪 * hole:若 `!(i in arr)` → skip(不遞迴、不輸出) * key segment 使用 `String(i)` * Object: * 以 `Object.keys(obj)` 的順序走訪 * key segment 使用 `String(k)` * Root:初始 keys = `[]`;若 root 本身為 leaf 且非 undefined,path 為 `''`(`keysToPath([]) === ''`) --- ## FLAT_01 fromObjToFlat(obj) -> Object * `pairs = toFlatPairs(obj)` * Reduce 成 plain object:`acc[p.path] = p.value`(後寫覆蓋前寫;一般情況 path 不重複) * Alias:`flatten = fromObjToFlat` Open Semantic(若需依賴需補充) * 若 pairs 出現重複 path(理論上不應由 toFlatPairs 產生),覆蓋策略為「後者覆蓋前者」;未提升為契約承諾 --- ## FLAT_04 fromFlatPairs(pairs) -> Object|Array * `pairs` 非 array 視為 `[]` * `xs.length === 0` → 回 `{}` ### Root 推斷(僅用第一筆) * `firstKeys = pathToKeys(String(xs[0]?.path ?? ''))` * `init = ensureContainerForNextKey({ nextKey: firstSegmentOrEmpty })` * first segment 取 `firstKeys[0]`,若無則 `''` ### 建構 * 依序 reduce(immutable): * `k = String(p?.path ?? '')` * `v = p?.value` * `v === undefined` → skip(與 flatten 對齊) * 否則:`acc = setByPath({ target: acc, path: k, value: v })` * 回傳最終 `acc` Open Semantic(若需依賴需補充) * Root 只看第一筆路徑推斷;若後續 entries 的 root 型態需求不同(例如第一筆推斷 `{}`,後面需要 `[]`),結果以此策略自然決定;未另行宣告衝突處理 * 路徑衝突(例如先設 leaf 再設其子節點,或反之)之最終結果由 `setByPath` 的覆蓋/補齊策略決定;本 spec 不重複定義衝突規則 --- ## FLAT_02 fromFlatToObj(flat) -> Object|Array * `src = (flat && typeof flat === 'object') ? flat : {}` * `entries = Object.keys(src).map(k => ({ path:k, value:src[k] }))` * 回 `fromFlatPairs(entries)` * Alias:`unFlatten = fromFlatToObj` Open Semantic(若需依賴需補充) * `Object.keys(src)` 的列舉順序依 JS 行為;本 spec 不另行提升為承諾(若需 deterministic,需在 spec 中固定) --- ## FLAT_05 flatKeys(obj) -> string[] * `toFlatPairs(obj).map(p => p.path)` ## FLAT_06 flatValues(obj) -> any[] * `toFlatPairs(obj).map(p => p.value)` Ordering(可依賴) * `flatKeys / flatValues / toFlatPairs` 的順序一致,且等同 `toFlatPairs` 的 DFS 走訪順序(array 依 index 升序;object 依 `Object.keys` 順序) Array holes(已固化) * flatten 系列對 array holes:視為 skip(不輸出 pair;等同 undefined leaf 被跳過) # F. Diff / Patch 家族(Derived Spec,compact) > 依你提供的 Diff prod code 固化;供後續不再提供 code 時長期依賴。 > Diff 依賴:`fromObjToFlat`(Flat)、`setByPath / unsetByPath`(Write)。 > Leaf equality:`Object.is`。`undefined` 視為 absence(不產生 set(undefined))。 --- ## internal isSameLeaf(a,b) -> boolean * `Object.is(a, b)` --- ## internal diffFlat(fa, fb) -> { added, modified, removed, unchanged } * `a = (fa && typeof fa === 'object') ? fa : {}` * `b = (fb && typeof fb === 'object') ? fb : {}` * `keys = union(Object.keys(a), Object.keys(b))`(Set) * 對每個 `k`: * `av = a[k]`,`bv = b[k]` * `aHas = (av !== undefined)`;`bHas = (bv !== undefined)` * `!aHas && bHas` → `added[k] = bv` * `aHas && !bHas` → `removed[k] = av`(保存 a 的舊值) * `aHas && bHas`: * `same = Object.is(av, bv)` * `same` → `unchanged[k] = bv` * 否則 → `modified[k] = bv`(保存 b 的新值) * 回傳 `{ added, modified, removed, unchanged }`(皆為 plain object flat map) Open Semantic(若需依賴需補充) * `av !== undefined` 作為存在判定依賴「flat map 不含 undefined leaf」的前提;若使用者手動傳入含 undefined 的 flat map,結果以此存在判定自然決定;本 spec 不另行提升為契約承諾 --- ## DIFF_01 objDiff(a,b) -> { added, modified, removed, unchanged } * `fa = fromObjToFlat(a)`;`fb = fromObjToFlat(b)` * 回 `diffFlat(fa, fb)` --- ## DIFF_04 changedPaths(a,b) -> string[] * 取 `objDiff(a,b)` 的 `{ added, modified, removed }` * 回傳 `Object.keys(added) + Object.keys(modified) + Object.keys(removed)` 合併後: * `.sort()`(字典序升序) --- ## DIFF_05 pickChanged(a,b) -> { added, modified, removed } * 回 `{ added, modified, removed }`(直接來自 `objDiff(a,b)`) --- ## DIFF_02 diffToPatch(a,b) -> Array<{op:'set'|'unset', path:string, value?:any}> * `d = objDiff(a,b)` 取 `{ added, modified, removed }` * 先產生 unset ops: * `unsetPaths = Object.keys(removed)` * sort:深→淺(`p2.length - p1.length`),同長度以字典序升序(`p1 < p2 ? -1 : 1`) * map:`{ op:'unset', path }` * 再產生 set ops: * paths:`Object.keys(added) + Object.keys(modified)` * sort:淺→深(`p1.length - p2.length`),同長度以字典序升序 * map:`{ op:'set', path, value }` * `value = (added[path] !== undefined ? added[path] : modified[path])` * filter:`op.value !== undefined`(禁止 set(undefined)) * 回傳 `unsetPaths.concat(setOps)` Ordering(可依賴) * ops 順序固定:**unset 全部在前**,且 unset 由深到淺;set 全部在後,且 set 由淺到深 Open Semantic(若需依賴需補充) * 深淺比較使用 `path.length`(字串長度)而非 segment depth;例如 `a.b` 與 `aa` 之排序依字串長度決定;本 spec 不另行改寫為「以 segments 計算深度」 --- ## DIFF_03 applyPatch({ target, ops }) -> nextTarget (immutable reduce) * `xs = Array.isArray(ops) ? ops : []` * 依序 reduce: * `type = op?.op`,`path = op?.path` * `type === 'unset'` → `acc = unsetByPath({ target: acc, path })` * `type === 'set'`: * `value = op?.value` * `value === undefined` → no-op(回 acc) * 否則 `acc = setByPath({ target: acc, path, value })` * 其他 `type`(未知 op)→ 忽略(no-op) * 回傳最終 `acc` Unknown ops(可依賴) * 未知 op:忽略,不 throw # G. Query / Traverse 家族(Derived Spec,compact) > 依你提供的 Query prod code 固化;供後續不再提供 code 時長期依賴。 > Query 依賴:`isContainer`(Guard)、`keysToPath`(Path)。 --- ## Traversal 基本規則(全家族共用) * Recursion:**只遞迴 container**(plain object / array) * 非 container 一律視為 **leaf** * Path: * 以 `keysToPath(keys)` 生成(含 escape) * root:`keys=[]` → `path === ''` * Array: * 依 index `0..length-1` * hole(`!(i in arr)`)→ skip * Object: * 依 `Object.keys(obj)` 順序 --- ## QUERY_01 walk(obj, visitor, options?) * Traversal:DFS(預設)或 BFS * `options.mode: 'DFS' | 'BFS'`(default `'DFS'`) * 會對 **每一個 node(container + leaf)** 呼叫一次 `visitor` ### visitor(node) receives ```ts { path: string, keys: string[], value: any, parent: any, key: string|undefined, depth: number, isLeaf: boolean, isContainer: boolean } ``` ### 行為要點 * 先呼叫 visitor,再展開 children * DFS:stack(pop);BFS:queue(shift) --- ## QUERY_01_2 walkSafe(obj, visitor, options?) * 在 `walk` 基礎上加入 **循環引用防護** * 僅對 **container 且為 object** 的節點進行循環追蹤(WeakSet) ### options * `mode`: `'DFS' | 'BFS'`(default `'DFS'`) * `detectCycle`: boolean(default `true`) * `onCycle`: `'skip' | 'throw'`(default `'skip'`) ### visitor(node) extra field ```ts { isCycle: boolean } ``` ### 循環行為 * `isCycle === true`: * `onCycle === 'throw'` → throw Error(立即中止) * `onCycle === 'skip'` → 仍呼叫 visitor,但 **不展開 children** --- ## QUERY_02 mapLeaves(obj, fn) -> any * 只對 **leaf** 呼叫 `fn` * Container 結構保留(immutable 重建) ### fn(value, meta) receives ```ts { path, keys, parent, key, depth } ``` ### 回傳 * 新的 object / array tree * Array holes 不保留(以新 array 重建) --- ## QUERY_03 filterLeaves(obj, fn) -> any * 只對 **leaf** 套用條件 * `fn(value, meta) === false` → 移除該 leaf ### 移除規則 * Object:不寫入該 key(等同 delete) * Array:移除該項並 **壓縮 index** * Container 本身 **不會** 被 `fn` 判斷,僅遞迴其 children ### meta 同 mapLeaves --- ## QUERY_04 findByPredicate(obj, fn, options?) -> {path,value}|undefined * 以 `walk` 實作(DFS/BFS 依 options) * **找到第一個符合即停止** * 回傳: * `{ path, value }` * 若無符合 → `undefined` ### fn(node) receives ```ts { path, keys, value, parent, key, depth, isLeaf, isContainer } ``` --- ## Ordering(可依賴) * walk / walkSafe / findByPredicate 的走訪順序一致 * DFS / BFS 行為由 `options.mode` 決定 * children 順序: * array:index 升序 * object:`Object.keys` 順序 # I. Convenience 家族(Derived Spec,compact) > 原則:只做封裝/組合;底層語義完全由 Read/Write/Flat/Diff 決定。 --- ## CONV_01 readWriteFacade(target) -> { get, set, unset } * 綁定 `target`(閉包捕獲;不更新 target 參考) * `get(path)` → `getByPath(target, path)` * `set({ path, value })` → `setByPath({ target, path, value })` * `unset(path)` → `unsetByPath({ target, path })` Open Semantic(若需依賴需補充) * `set/unset` 回傳的是底層 immutable `nextTarget`,但 facade 不會把它回寫到閉包內 `target`;是否要提供「持續更新 target」語義,本 spec 不另行定義 --- ## CONV_02 flatFacade(object) -> { flatten, unflatten } * 綁定 `object` * `flatten()` → `fromObjToFlat(object)` * `unflatten(flat)` → `fromFlatToObj(flat)` --- ## CONV_03 diffFacade(options?) -> workflow object * `options` 目前不參與行為(僅接收後 `void options`) * 回傳方法對應: * `diff(a,b)` → `objDiff(a,b)` * `toPatch(a,b)` → `diffToPatch(a,b)` * `apply({target, ops})` → `applyPatch({ target, ops })` * `applyDiff({target, a, b})` → `applyPatch({ target, ops: diffToPatch(a,b) })` * `changedPaths(a,b)` → `changedPaths(a,b)` * `pickChanged(a,b)` → `pickChanged(a,b)` Open Semantic(若需依賴需補充) * `options` 的可用欄位與對底層 DIFF 行為的影響未在本 spec 另行定義(目前等價於未使用) --- ## CONV_04 extractByPath(path, obj) -> Object * `src = (obj && typeof obj === 'object') ? obj : {}` * 對 `Object.keys(src)` 每個第一層 key `k`: * `out[k] = getByPath(src[k], path)` * 保留第一層 key,不使用 flatten/unflatten * 回傳 `out` Open Semantic(若需依賴需補充) * `Object.keys` 列舉順序依 JS 行為;本 spec 不另行提升為承諾