anchor-types
PURPOSE
Type contract and pure CRUD helpers for the named-anchor map used by the VFX workbench. Defines anchor kinds (pivot / edge / radial), an optional direction + radius + local offset, and immutable add / remove / rename / update operations over Record<string, AnchorSpec>. Enforces a name regex and protects the reserved center anchor.
OWNS
AnchorKind— union'pivot' | 'edge' | 'radial'.AnchorDirection— union'forward' | 'backward'.AnchorSpec—{ kind, direction?, radius?, localOffset?: { x, y } }.AnchorMap—Record<string, AnchorSpec>.VALID_NAME— module-private regex/^[a-z][a-z0-9_]{0,31}$/i. Lowercase or uppercase leading letter, then[a-z0-9_], total length 1–32.isValidAnchorName(name)— boolean test againstVALID_NAME.addAnchor(map, name, spec)— returns a new map with the entry; throws on invalid name or existing key.removeAnchor(map, name)— returns a new map without the entry; throws ifname === 'center'; no-op if key absent.renameAnchor(map, oldName, newName)— returns a new map with the key renamed (spec preserved); throws onoldName === 'center', invalidnewName, missingoldName, or collision with an existing differentnewName.updateAnchor(map, name, patch)— returns a new map with{ ...existing, ...patch }merged into the entry; throws if the key is missing.
READS FROM
Nothing. Self-contained module with no imports.
PUSHES TO
Nothing. Pure functions returning new AnchorMap values.
DOES NOT
- Mutate the input
AnchorMap(every helper spreads into a new object). - Persist, serialize, or load anchors.
- Render anchors or resolve anchor positions to world/screen space.
- Validate
radius,localOffset,direction, orkindfield values beyond TypeScript types. - Create the
centeranchor — it only refuses to remove/rename it. - Distinguish casing in name uniqueness (
name in mapis an exact-string check; regex itself is case-insensitive via/i).
Signals
- Throws
Error('invalid anchor name: <name>')fromaddAnchorandrenameAnchorwhen the name failsVALID_NAME. - Throws
Error('anchor already exists: <name>')fromaddAnchorwhen the key is already present. - Throws
Error('cannot remove center anchor')/Error('cannot rename center anchor')fromremoveAnchor/renameAnchor. - Throws
Error('anchor not found: <name>')fromrenameAnchorandupdateAnchorwhen the key is missing. - Throws
Error('target name exists: <name>')fromrenameAnchorwhennewName !== oldNameandnewNameis already in the map. removeAnchorreturns the same map reference when the key is absent (silent no-op).
Entry points
- Importers will call the four CRUD functions to derive a new
AnchorMapfor state updates. isValidAnchorNameis exported for UI prevalidation (e.g., disable a submit button before callingaddAnchor).- Types
AnchorKind,AnchorDirection,AnchorSpec,AnchorMapare the canonical shape consumers should reference.
Pattern notes
- Pure / immutable: every mutator returns a fresh object; safe for Zustand / React state without manual cloning.
- Reserved-name guard is hard-coded to the literal string
'center'— no constant exported. Callers cannot reconfigure the protected name. addAnchorrejects duplicate keys butupdateAnchorrequires existence; together they force callers to pick create-vs-update explicitly.renameAnchorallows a no-op rename to the same name (only collides ifnewName !== oldName && newName in map).- Name regex is case-insensitive at the character class but anchored to a single leading letter; max 32 chars total.
- No
optionalfield defaults — consumers readingdirection,radius,localOffsetmust handleundefined.
EXTRACT-CANDIDATE
- The
VALID_NAMEregex +isValidAnchorNamepair is a generic identifier-validation primitive — if other workbench domains (clip names, layer names, slot keys) reuse the same 32-char[a-z][a-z0-9_]{0,31}rule, hoist to a sharedengine/vfx-workbench/identifier.ts. - The immutable add/remove/rename/update-over-
Record<string, T>pattern is generic. If a second domain in vfx-workbench needs the same CRUD shape, extract akeyedMap<T>()factory that takes a reserved-keys set and a name validator.