# registry — AssetResolver

Single entry point for all asset-side metadata: pump/valve curves, editor menu
trees, monster sample codes, unit families, and anything else we add later.

Replaces (will replace, phase-by-phase):

- `loadCurve(model)` → `assetResolver.resolve('curves', model)`
- `AssetCategoryManager` → `assetResolver.resolve('menu', softwareType)`
- ad-hoc loaders for `monsterSamples.json`, `unitData.json` → `assetResolver.resolve('monsterSamples'|'units', …)`

## Surface

```js
const { assetResolver } = require('generalFunctions');

const curve = assetResolver.resolve('curves', 'hidrostal-H05K-S03R');
const tree  = assetResolver.resolve('menu',   'rotatingmachine');
const meta  = assetResolver.resolveAssetMetadata('rotatingmachine', 'hidrostal-H05K-S03R');
// meta → { supplier, type, units, model, raw }

assetResolver.list('curves');       // ['hidrostal-H05K-S03R', 'ECDV', ...]
assetResolver.namespaces();         // ['curves', 'menu', 'monsterSamples', 'units']
await assetResolver.refresh();      // re-pull everything (FileBackend: re-reads disk; HttpBackend: future)
```

Resolution is synchronous. First call to `resolve(namespace, id)` warms that
namespace's cache; later calls are O(1) map lookups.

## Adding a namespace

Create `src/registry/namespaces/<name>.js`:

```js
const path = require('path');
const FileBackend = require('../backends/FileBackend');

const backend = new FileBackend({
    baseDir: path.resolve(__dirname, '../../../datasets/...'),
    layout: 'per-id',           // or 'single-file'
    caseInsensitive: true,
});

module.exports = {
    name: 'newThing',
    description: 'What this namespace is for',
    loadAll: () => backend.loadAll(),
    refresh: () => backend.refresh(),
};
```

Register it in `namespaces/index.js`. Done.

## Backends

- **FileBackend** — reads JSON from disk. Two layouts: `per-id` (one file per
  id, filename minus `.json` is the id) or `single-file` (one file with an
  array; pick `arrayKey` and `indexField`).
- **HttpBackend** — stub. Disabled unless `EVOLV_ASSET_REMOTE=1`. Will hold
  the future WBD product API client; currently throws if invoked. Exists so
  the resolver contract is backend-agnostic from day one.

Backends are interchangeable per namespace: the namespace file is the
declarative join between "what this metadata is" and "where it comes from".

## Why sync at runtime

Node-RED node constructors aren't async-friendly. Every consumer that used
`loadCurve(model)` expects a synchronous return. The resolver preserves that
contract: cache is warmed lazily (first `resolve()` call pulls everything),
and lookups are O(1) map gets after that. Async `refresh()` exists for future
HttpBackend hydration on a background timer.

## Convention: namespace name is the cache key

`assetResolver.resolve(namespace, id)` lowercases `id` for the lookup. Old
case-mismatched configs (`Hidrostal-H05K-S03R` vs `hidrostal-H05K-S03R`) still
resolve correctly — same as `loadCurve` did historically.
