Implement centralized EntityManager and schema-driven Identity Map#358
Merged
davideast merged 4 commits intoJun 1, 2026
Conversation
### Context & Rationale
Currently, the SDK lacks a formal concept of identity, leading to referential inequality where multiple instances of the same domain object coexist. This creates "stale data" bugs, as updates to one instance aren't reflected in others. Furthermore, the logic for extracting identity from API responses was previously hardcoded in the generator, making it brittle and difficult to maintain.
This PR introduces a centralized `EntityManager` to act as a single source of truth. By moving from direct class instantiation to a managed lifecycle, we ensure that the SDK always returns the same memory reference for a given entity ID ($O(1)$ lookup). This architecture supports complex application state management and provides a professional foundation for developers to sync state globally.
### Key Changes
#### 1. EntityManager & Identity Map
- Implemented `EntityManager` scoped to the `StitchToolClient` to prevent cross-account data leakage.
- Added an **Identity Map** that caches domain entities by their canonical IDs, ensuring 100% referential equality (`sdk.item('1') === sdk.item('1')`).
#### 2. Schema-Driven Entity Resolution
- Introduced `ReferenceSpec` in the IR schema (e.g., `reference: { keys: ["projectId"] }`).
- Replaced fragmented, hardcoded ID extraction logic in the generator with a unified, schema-driven approach.
- Integrated `parseResourceName` to dynamically extract identifiers from structured Google-style API resource strings.
#### 3. Generator Refactoring
- Updated `scripts/generate-sdk.ts` to remove direct `new` keyword usage for domain entities.
- The generator now emits `this.client.entities.resolve(EntityClass, keys, data)`, forcing all entity creation through the lifecycle manager to maintain cache integrity.
#### 4. Lifecycle Management
- Added `onCreate()` and `onDispose()` hooks.
- Provided methods for cache pruning (`manager.dispose(entity)`) and clearing (`manager.clear()`) to manage memory overhead in long-lived sessions.
### Success Metrics & Validation
- **Referential Integrity:** Confirmed via new unit tests that subsequent calls for the same resource return the exact same object reference.
- **Generator Cleanliness:** Removed all `splitOn`, `stripPrefix`, and manual `fieldMapping` logic from the template code.
- **Performance:** Verified $O(1)$ lookup times for cached entities.
### Verification Plan
- [x] Run `npm run generate` to ensure the new `resolve()` pattern is correctly applied to all generated classes.
- [x] Execute `test/unit/entity-manager.test.ts` to validate caching and disposal logic.
- [x] Manual verification of "Global State Synchronization" scenario: updating a property on one reference now updates all observers automatically.
- Add definite assignment assertion (!) to generated domain class properties since EntityManager.resolve() assigns them immediately post-construction - Regenerate SDK with the fix applied - Run prettier --write on all files to fix 110 formatting issues Fixes: typecheck and format:check CI failures on quality-gate
|
|
||
| await fs.writeFile( | ||
| tempScreenshotPath, | ||
| Buffer.from(screenshotBuffer), |
| const tempFullPath = path.join(resolvedTempDir, tempFilename); | ||
|
|
||
| await fs.writeFile(tempFullPath, Buffer.from(buffer), { flag: 'wx', mode: fileMode }); | ||
| await fs.writeFile(tempFullPath, Buffer.from(buffer), { |
- stitch-html.ts: Replace existsSync+readFileSync TOCTOU pattern with try/catch to eliminate file system race condition (CodeQL #30) - download-handler.ts: Add response.ok validation before writing fetched network data to disk (CodeQL #31, #32). The download handler already has strong mitigations (wx flag, random temp names, sanitized filenames, atomic rename) but validating HTTP status makes intent explicit.
Update all mock fetch responses to include ok: true to match the new response.ok validation added to download-handler.ts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context & Rationale
Currently, the SDK lacks a formal concept of identity, leading to referential inequality where multiple instances of the same domain object coexist. This creates "stale data" bugs, as updates to one instance aren't reflected in others. Furthermore, the logic for extracting identity from API responses was previously hardcoded in the generator, making it brittle and difficult to maintain.
This PR introduces a centralized$O(1)$ lookup). This architecture supports complex application state management and provides a professional foundation for developers to sync state globally.
EntityManagerto act as a single source of truth. By moving from direct class instantiation to a managed lifecycle, we ensure that the SDK always returns the same memory reference for a given entity ID (Key Changes
1. EntityManager & Identity Map
EntityManagerscoped to theStitchToolClientto prevent cross-account data leakage.sdk.item('1') === sdk.item('1')).2. Schema-Driven Entity Resolution
ReferenceSpecin the IR schema (e.g.,reference: { keys: ["projectId"] }).parseResourceNameto dynamically extract identifiers from structured Google-style API resource strings.3. Generator Refactoring
scripts/generate-sdk.tsto remove directnewkeyword usage for domain entities.this.client.entities.resolve(EntityClass, keys, data), forcing all entity creation through the lifecycle manager to maintain cache integrity.4. Lifecycle Management
onCreate()andonDispose()hooks.manager.dispose(entity)) and clearing (manager.clear()) to manage memory overhead in long-lived sessions.Success Metrics & Validation
splitOn,stripPrefix, and manualfieldMappinglogic from the template code.Verification Plan
npm run generateto ensure the newresolve()pattern is correctly applied to all generated classes.test/unit/entity-manager.test.tsto validate caching and disposal logic.