feat: Repository typed events, consumer migration + API positioning (PR 2+3 of 3)#777
Merged
Conversation
…events MemoryStore.getRepository() now wraps its MemoryRepository in EventRepository so typed CRUD events (Created/Updated/Patched/Deleted/PartialUpdated) fire on every operation. Consumers reach them via useRepository(model).on(...). - Export EventRepository from @webda/models (was implemented but unexported) - Update CoreTest.registry to unwrap the EventRepository when reaching the backing MemoryRepository storage - New StoreRepositoryEventsTest asserts Created fires on create Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FileStore.getRepository() wraps its MemoryRepository in EventRepository so typed CRUD events fire. find()/simulateFind() only call repository.get(), which EventRepository proxies, so querying is unaffected. - Re-export EventRepository from @webda/core - Unwrap EventRepository in the filestore-unit storage-access tests - New repositoryEmitsTypedCreatedEvent test asserts Created fires Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integration test for the consumer contract Phase 2 establishes: listeners
registered via useRepository(Model).on("Created") receive typed events when
Model.create() runs through the registered EventRepository. This is the
mechanism graphql/elasticsearch migrate onto.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds getMaxListeners/setMaxListeners + removeListener (alias for off) so a repository can be consumed wherever a Node EventEmitter is expected — notably @webda/runtime EventIterator, which graphql uses for subscriptions. The max-listeners value is a hint only; repositories keep an unbounded listener Set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The query, single-instance, and generic event subscriptions now source from useRepository(model) and listen on typed ModelEvents (Created/Updated/Patched/ Deleted/PartialUpdated) — Saved maps to Created. This revives subscriptions that depended on Store.* events nothing emits today. authorizeClientEvent is still consulted on the store. Export useRepository from @webda/core. Build-verified only: graphql's test suite is disabled pending the @testdeck/mocha -> @webda/test migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o repository - PostgresStore.getRepository() wraps PostgresRepository in EventRepository so typed CRUD events fire (consumers use useRepository(model).on(...)) - PostgresRepository owns its DDL via setupTable()/getTable() - PostgresStore.getRepositories() unwraps the EventRepository per managed model - checkTable() delegates to each repository's setupTable(), deduped by table name - Non-DB tests assert the wrapping + unwrap (DB-backed tests remain gated on a live PostgreSQL; the 21 ECONNREFUSED failures are pre-existing/environmental) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #777 +/- ##
==========================================
+ Coverage 74.02% 74.05% +0.02%
==========================================
Files 229 229
Lines 15610 15628 +18
Branches 3790 3792 +2
==========================================
+ Hits 11556 11573 +17
- Misses 2967 2968 +1
Partials 1087 1087 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Store, StoreParameters, StoreInterface, StoreFindResult, StoreEvents, Repositories, registerRepository flagged @internal. App code uses useRepository(Model) or Model.create()/.query()/.ref(); useRepository stays public. No runtime change. (Plan listed useStore() too, but it does not exist in the codebase.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CLAUDE.md: 'Store Pattern' -> 'Repository Pattern' (Model.create/ref/query, useRepository(Model).on for events); DI example no longer @injects a store; store config examples use flat models[] - copilot-instructions: store config uses models[] - AGENTS.md unchanged (no persistence-pattern section) Stores are internal infrastructure; @Inject(storeName) for stores is deprecated for app code.
Repository is the canonical persistence API (Model.create/ref/query, useRepository(Model).on for events). The Stores page gets an @internal banner pointing to Repositories and sorts after it in the sidebar. Markdown-only; the Docusaurus build is pre-existing broken (missing typedoc folder) and unrelated to this change.
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.
Summary
Second of three PRs for the Repository concept refactor (follows #776). This PR makes
Repository<T>the single source of CRUD events: each concrete store wraps its repository inEventRepository, so typedModelEvents(Created/Updated/Patched/Deleted/PartialUpdated) fire on every operation and are reachable viauseRepository(Model).on(...). Per maintainer direction, downstream consumers listen on the repository, not on the legacyStore.*events — so this PR migrates graphql to repository events rather than re-emittingStore.*(a deviation from the original plan's Task 12).Spec/Plan:
docs/superpowers/specs/2026-05-09-repository-design.md(local-only, gitignored).What's in this PR
@webda/models— exportEventRepository(was implemented but unexported); add Node-EventEmitter-compat shims toAbstractRepository(getMaxListeners/setMaxListeners/removeListener) so a repository can be consumed by@webda/runtimeEventIterator.MemoryStore,FileStore,PostgresStore—getRepository()wraps the underlying repository inEventRepository.find()/simulateFind()are unaffected (they only callrepository.get(), whichEventRepositoryproxies).PostgresStore— table DDL pushed down toPostgresRepository.setupTable()/getTable();getRepositories()unwraps theEventRepositoryper managed model;checkTable()delegates per repository (deduped by table).@webda/core— re-exportEventRepositoryanduseRepository.@webda/graphql— query, single-instance, and generic event subscriptions now source fromuseRepository(model)and listen on typed events (Saved→Created);authorizeClientEventis still consulted on the store.Tests
@webda/models— 88 passed (new:AbstractRepositoryEventEmitter-compat shim).@webda/core— 561 passed / 1 todo (new: repository emitsCreated;useRepository(Model).onfires onModel.create()).@webda/fs— 124 passed (new:FileStorerepository emitsCreated; storage-access tests unwrap theEventRepository).@webda/postgres— non-DB tests pass (new:getRepositorywraps inEventRepository;getRepositories()unwraps toPostgresRepository). The 21 DB-backed failures are pre-existingECONNREFUSED(no local PostgreSQL).pnpm run buildgreen (except the pre-existingdocs/Docusaurus failure). Lint clean on all touched packages.Verification caveats (build-only)
"Tests disabled — needs migration from @testdeck/mocha to @webda/test"), so the graphql migration is typecheck/build-verified only. The contract it relies on (useRepository(Model).on("Created")fires onModel.create()) is covered by a new integration test in@webda/core.setupTableDDL) needs a live PostgreSQL; only construction-level tests run here.Out of scope (deferred)
@webda/elasticsearchconsumer migration — the package is excluded from the pnpm workspace (!packages/elasticsearch, "not yet ready"): nonode_modules, stalewebda build. It needs the sameuseRepository(model)migration when revived alongside its test-suite migration.aws/gcp/mongodb— untouched / not in active workspace (as in feat(core): flat models[] config + internal field migration (PR 1 of 3) #776).@internaltagging, sample-app + docs migration → PR 3.🤖 Generated with Claude Code
Update — Phase 3 (API positioning) added to this branch
This PR now also carries Phase 3, so it covers PRs 2 and 3 of the refactor.
@internaltagging —Store,StoreParameters,StoreInterface,StoreFindResult,StoreEvents,Repositories,registerRepositoryflagged@internal;useRepositorystays public. JSDoc-only, build-verified. (The plan also listeduseStore()— it does not exist in the codebase.)CLAUDE.md"Store Pattern" → "Repository Pattern" (Model.create/ref/query,useRepository(Model).onfor events); DI example no longer@Injects a store; store config examples use flatmodels[]..github/copilot-instructions.mdconfig example usesmodels[].AGENTS.mdhad no persistence section (unchanged).Concepts/Stores/Repositories.mdpage (canonical persistence API);Stores.mddemoted to "storage backends — advanced" with an@internalbanner pointing to Repositories. Markdown-only (Docusaurus build is pre-existing broken on a missing typedoc folder).sample-apps/blog-system(and the legacysample-app) are already Repository-first (User.create(),Post.ref().get()), with no@Inject(store)/useStore. Verified blog-system builds clean.Phase 3 verification
pnpm run buildgreen (except pre-existingdocs/Docusaurus).models88,core561+1 todo,fs124 pass;postgresnon-DB pass (21 pre-existingECONNREFUSED). Lint clean on all touched packages.