Frontmatter
| id | 9070 |
| title | feat: Implement ''internalId'' via Collection Identity Hook |
| state | Closed |
| labels | enhancementcore |
| assignees | tobiu |
| createdAt | Feb 9, 2026, 11:24 AM |
| updatedAt | Feb 10, 2026, 2:10 AM |
| githubUrl | https://github.com/neomjs/neo/issues/9070 |
| author | tobiu |
| commentsCount | 2 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 10, 2026, 2:10 AM |
feat: Implement 'internalId' via Collection Identity Hook

tobiu
Feb 9, 2026, 11:46 AM
Input from Gemini 3 Pro:
✦ Task Completed: Implement 'internalId' via Collection Identity Hook
Summary: We have successfully implemented a robust, Symbol-based identity system that works seamlessly across "Turbo Mode" (raw objects) and standard Records, without polluting data or coupling Collections to the Store architecture.
Key Implementations:
Symbol-Based Identity:
- Defined
Symbol.for('Neo.internalId')insrc/core/ConfigSymbols.mjs.- This ensures the ID property is non-enumerable (won't be sent to backend) and collision-free.
Identity Provider Hook (Collection):
- Enhanced
src/collection/Base.mjswith anitemFactoryconfig.- The
splicemethod now invokesme.itemFactory?.(item)for every new item.- Architecture: This keeps Collection "dumb" while allowing "smart" owners to inject logic.
Store Integration:
- Updated
src/data/Store.mjsto bindassignInternalIdto theitemFactoryhook.- Implemented lazy ID assignment:
item[internalId] = Neo.getId('record').- Added
getInternalId(item)helper for easy access.Record Factory:
- Updated
src/data/RecordFactory.mjsto automatically assign theinternalIdsymbol upon Record instantiation.Outcome: All data items managed by a Store now possess a stable, globally unique
neo-record-xidentity. This lays the foundation for:
- Stable DOM IDs: Decoupling DOM keys from mutable DB IDs.
- Phantom Record Support: Handling new records (id: null) without collisions.
- Cross-Store D&D: Unique identities across different stores.
Next Steps: Follow-up tickets will be created to leverage this new identity system in DOM rendering (Grid, List, etc.).
tobiu cross-referenced by #9071 on Feb 9, 2026, 11:48 AM
tobiu cross-referenced by #9072 on Feb 9, 2026, 11:50 AM
tobiu cross-referenced by #9073 on Feb 9, 2026, 11:50 AM
tobiu cross-referenced by #9074 on Feb 9, 2026, 11:51 AM
tobiu cross-referenced by #9075 on Feb 9, 2026, 1:18 PM
tobiu cross-referenced by #9076 on Feb 9, 2026, 2:46 PM
tobiu assigned to @tobiu on Feb 10, 2026, 2:10 AM

tobiu
Feb 10, 2026, 2:10 AM
resolved.
tobiu closed this issue on Feb 10, 2026, 2:10 AM
Context: Currently, Neo.mjs relies on the record's primary key (
keyProperty) for DOM element IDs (e.g.,view__1). This presents several challenges:null), causing DOM ID collisions (view__null).-1->100), forcing a destructive DOM update.Objective: Implement a stable, globally unique
internalId(e.g.,neo-record-1) for all data items (Records and Raw Objects), maintained via a lightweight Symbol property.Architecture: The "Identity Provider" Pattern To maintain the separation of concerns between
Neo.collection.Base(dumb container) andNeo.data.Store(smart data manager), we will introduce anitemFactoryhook.Implementation Plan:
Define Symbol:
src/util/Symbol.mjs(or similar) to exportconst internalId = Symbol.for('neoInternalId');.Enhance
Neo.collection.Base:itemFactoryconfig (Function).splicemethod's addition loop, invoke the hook:me.itemFactory?.(item);Update
Neo.data.Store:assignInternalId(item)method.construct, bind this method to theitemFactoryconfig.if (!item[internalId]) { item[internalId] = Neo.getId('record'); }Update
Neo.data.RecordFactory:Expose Accessor:
store.getInternalId(item)helper.Outcome: