Analyzes when extensions need raw entity IDs and recommends keeping them exposed with clear documentation that they are opaque values. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3.4 KiB
Research: Identity Encapsulation in the Extension API
Date: 2026-05-12
Question
When do extensions need access to raw entity IDs (NodeEntityId, WidgetEntityId, SlotEntityId)? Should these be exposed or hidden?
Current State
The v2 extension API exposes entity IDs as read-only properties:
interface NodeHandle {
readonly entityId: NodeEntityId
// ...
}
interface WidgetHandle {
readonly entityId: WidgetEntityId
// ...
}
interface SlotInfo {
readonly entityId: SlotEntityId
// ...
}
All IDs are branded types to prevent accidental mixing at compile time.
Use Cases for Raw Entity IDs
1. Per-Instance State Mapping
Extensions maintaining external state per node:
const nodeCache = new Map<NodeEntityId, CachedData>()
defineNode({
name: 'my-cache-extension',
nodeCreated(handle) {
nodeCache.set(handle.entityId, computeExpensiveData())
onNodeRemoved(() => nodeCache.delete(handle.entityId))
}
})
2. Logging and Debugging
node.on('executed', (e) => {
console.log(`[${node.entityId}] Output:`, e.output)
})
3. Inter-Extension Communication
Extensions that need to coordinate across multiple nodes:
// Extension A stores data
globalState.set(nodeA.entityId, data)
// Extension B retrieves it
const data = globalState.get(nodeB.entityId)
4. External System Interop
Extensions integrating with analytics, debugging tools, or external services that need stable node identifiers.
Analysis
Arguments FOR Exposing Entity IDs
- Legitimate need exists — The use cases above are real and common.
- Branded types prevent misuse — Can't accidentally use
NodeEntityIdwhereWidgetEntityIdis expected. - Read-only access — Extensions can't mutate the ID or corrupt internal state.
- Opaque value — The format (
node:<graphUuid>:<localId>) is an implementation detail; extensions should treat it as an opaque string.
Arguments AGAINST Exposing Entity IDs
- Format coupling — Extensions might parse the ID string and break if format changes.
- Internal detail leakage — Knowing the ID scheme reveals ECS architecture.
- Future migration friction — Changing ID representation requires careful deprecation.
Mitigations
- Document as opaque: JSDoc clearly states IDs are opaque, not to be parsed.
- Branded types: TypeScript prevents misuse across entity categories.
- Phase A format: Current format includes graph UUID + local ID; this can evolve via semver.
Recommendation
Keep exposing entity IDs. The use cases are legitimate, the branded types provide safety, and the read-only nature limits risk. Document that IDs are opaque strings — extensions should never parse or construct them.
Guidelines for Extension Authors
- Use IDs only for keying — Maps, Sets, logging, external system references.
- Never parse IDs — The format is an implementation detail subject to change.
- Prefer handles over IDs — When passing references between functions, use the handle object, not the raw ID.
- Clean up on removal — Always use
onNodeRemoved()to clean up Maps keyed by entityId.
Future Considerations
If the ID format needs to change significantly, the branded types allow us to:
- Introduce a new branded type (e.g.,
NodeEntityIdV2) - Deprecate the old ID with migration guidance
- Keep both supported during a transition period