# `normalizeNodeIds`

> Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated from `configurator-runtime.d.ts` by `services/onsite-scripter/scripts/build-contract-docs.mjs`; edit the source, not this file.

Function · `import { normalizeNodeIds } from '@holostep-configurators/runtime-client';` · stable · since 2.0.0 · section 22. Pure helpers (root of the package; run in Node and the fake)

Repairs old saves: mints missing ids, re-mints duplicates, remaps `defaultNodeId`, preset node
ids, `globalMaterialList[].globalMaterial` and connections, and rewrites legacy
`nodeId:meshName` connector ids, on every node type (preset node ids name GroupNodes). A stored
id stays with its first occurrence in depth-first document order, and each later occurrence
gets a new id from `mint`; every stored id is reserved first, and a `mint` result that is empty
or already used is replaced by crypto.randomUUID(). A parent whose `defaultNodeId` names a
repeated id that none of its direct children kept follows its first re-minted child with that
id; every other reference to a repeated id (preset node ids,
`globalMaterialList`, connections) keeps naming the first occurrence. Idempotent. The input type
is loose on purpose: it is the
document whose ids this function exists to repair, so no cast is needed. Every other field is
copied unchanged. Sync, pure. The returned `doc` is your document from then on: keep it in your
store and save from it, so the minted ids persist. A repair you don't keep is minted anew on the
next load, and placement, connections and presets saved against it go stale (spec section 11.1).

## Declaration

```ts
export declare function normalizeNodeIds<D extends LegacyDocument>(doc: D, options?: {
  mint?: (node: LegacyNode) => string;
}): {
  doc: Omit<D, 'root'> & {
    root: ConfiguratorNode[];
  };
  repairs: ReadonlyArray<{
    from: string | null;
    to: string;
    parent: string | null;
  }>;
};
```

## Example

Imports: values (`has`, `validate`, the `Holo*Error` classes, the pure helpers) from `@holostep-configurators/runtime-client`, types from `@holostep-configurators/runtime-client/contract`, `/math` and `/testing` names from those entries; `boot` from the runtime module the root's `loadRuntimeAtFloor` resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (`examples/declaration/22-normalizeNodeIds.ts`):

```ts
import { normalizeNodeIds } from '@holostep-configurators/runtime-client';
import type { IConfigurator, Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
declare function loadSavedDesign(id: string): Promise<IConfigurator>;
declare function adoptDesign(doc: IConfigurator): void;      // your store's document from now on
// no cast: the input type is loose
const { doc, repairs } = normalizeNodeIds(await loadSavedDesign('d-42'));
if (repairs.length) console.info(`repaired ${repairs.length} node ids`);
adoptDesign(doc);                                           // later saves keep the minted ids
// a load: the floor of history
await session.setSceneModel(doc, { origin: 'load', undo: 'rebase' });
```

## Generic-API test (spec 3.1)

| Rule | Result |
| --- | --- |
| 1. Neutral name | pass: `normalizeNodeIds` names no product |
| 2. A precedent | pass: [DOM Document.getElementById (unique ids)](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) (row `X3`) |
| 3. Two apps, or labs | pass: 4 apps (ledger: app-04c2774145, app-37356bf09f, app-4762caacdc, app-f9a32142f1) |
| 4. The cheaper fix first | decided per need in Appendix B: rows `X3` at tier stable (a member only where no rule, data, asset or app code serves the need) |
