(
value: unknown,
prevData: ChildrenData | undefined,
forceMaterialize: boolean,
)
| 299 | } |
| 300 | |
| 301 | private computeChildrenMap( |
| 302 | value: unknown, |
| 303 | prevData: ChildrenData | undefined, |
| 304 | forceMaterialize: boolean, |
| 305 | ): ChildrenData | undefined { |
| 306 | if (!isObject(value)) { |
| 307 | // Non-object values have no children. This short-circuit path makes `childrenMap` fast |
| 308 | // for primitive-valued fields. |
| 309 | return undefined; |
| 310 | } |
| 311 | |
| 312 | // Determine if we actually need to materialize children right now. |
| 313 | // If not forced, and NO child has any logic rules, we can safely return `undefined` |
| 314 | // to keep instantiation lazy. However, if `prevData` is already defined, we MUST |
| 315 | // NOT return `undefined` or we will orphan the already instantiated children. |
| 316 | if (!forceMaterialize && prevData === undefined) { |
| 317 | // Check if any child of this field has logic rules. This check only needs to run once per |
| 318 | // structure since the presence of schema logic rules is static across value changes. |
| 319 | if (!(this._anyChildHasLogic ??= this.logic.anyChildHasLogic())) { |
| 320 | return undefined; |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | // Previous `ChildrenData` (immutable). This is also where we first initialize our map if |
| 325 | // needed. |
| 326 | prevData ??= { |
| 327 | byPropertyKey: new Map(), |
| 328 | }; |
| 329 | |
| 330 | // The next `ChildrenData` object to be returned. Initialized lazily when we know there's |
| 331 | // been a structural change to the model. |
| 332 | let materializedChildren: MutableChildrenData | undefined; |
| 333 | |
| 334 | const parentIsArray = isArray(value); |
| 335 | |
| 336 | // Remove fields that have disappeared since the last time this map was computed. |
| 337 | if (prevData !== undefined) { |
| 338 | if (parentIsArray) { |
| 339 | materializedChildren = maybeRemoveStaleArrayFields(prevData, value, this.identitySymbol); |
| 340 | } else { |
| 341 | materializedChildren = maybeRemoveStaleObjectFields(prevData, value); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | // Now, go through the values and add any new ones. |
| 346 | for (const key of Object.keys(value)) { |
| 347 | let trackingKey: TrackingKey | undefined = undefined; |
| 348 | const childValue = value[key] as unknown; |
| 349 | |
| 350 | // Fields explicitly set to `undefined` are treated as if they don't exist. |
| 351 | // This ensures that `{value: undefined}` and `{}` have the same behavior for their `value` |
| 352 | // field. |
| 353 | if (childValue === undefined) { |
| 354 | // The value might have _become_ `undefined`, so we need to delete it here. |
| 355 | if (prevData.byPropertyKey.has(key)) { |
| 356 | materializedChildren ??= {...(prevData as MutableChildrenData)}; |
| 357 | materializedChildren.byPropertyKey.delete(key); |
| 358 | } |
nothing calls this directly
no test coverage detected
searching dependent graphs…