fix(runtime,cloud-connection): multi-tenant seed replay covers every source, not just the first (#3453)#3478
Merged
Merged
Conversation
…source, not just the first (#3453) In multi-tenant mode a new org replays the kernel's `seed-datasets` list on the `sys_organization` insert, so that list must hold the union of every seed source (every config app + every marketplace package). Two framework traps — the same pair #3444 fixed for seed-summary — shrank it to just the first source: - the standard PluginContext has no `.kernel` handle, so `(ctx as any).kernel?.getService('seed-datasets')` always read undefined and each source clobbered the list with only its own datasets; and - `registerService` throws on a duplicate name, so the second source's re-register was swallowed and its datasets (and, for a config app, its replayer) were silently lost. `seed-datasets` is now a single shared array, registered once and mutated in place by every source via a new `mergeSeedDatasets` helper that reads through the context's own resolver first. AppPlugin's per-org replayer reads that live list at invoke time — not a captured snapshot — and is itself registered once and reused by later config apps, so a new org replays the full union. Seam-level unit tests only (accumulation across app + marketplace sources; the replayer reads the live union); true multi-tenant e2e needs the enterprise `@objectstack/organizations` plugin, which lives in the cloud repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
What & why
Fixes #3453. In multi-tenant deployments (enterprise
@objectstack/organizations) a brand-new org gets its own private copy of demo data by replaying the kernel'sseed-datasetslist on thesys_organizationinsert. That list is supposed to hold the union of every seed source — every config-declared app plus every marketplace package — but two framework traps (the same pair #3444 fixed forseed-summary) silently shrank it to just the first source:.kernelhandle. The standardPluginContext(packages/core/src/kernel.ts:76) exposesgetService/registerServicebut has no.kernelproperty, so(ctx as any).kernel?.getService('seed-datasets')always readundefined. Each source then saw "nothing registered" and overwrote the list with only its own datasets.registerService(kernel.ts:195) throws on a duplicate name, so the second source's re-register was swallowed by the surroundingtry/catch— its datasets (and, for a config app, its replayer) silently lost.Net effect: with two config apps, or a config app + marketplace packages, a new org replayed only the first app's seeds.
The fix — register-once-then-mutate (pattern from #3444's
recordSeedOutcome)New
packages/runtime/src/seed-datasets.tshelper:mergeSeedDatasets(ctx, datasets)— registers one shared array the first time and mutates it in place for every later source, reading through the context's own resolver first (ctx.getService), kernel second. Never re-registers → no duplicate-register throw; always accumulates → no clobber.readSeedDatasets(ctx)— reads the live shared array.registerSeedReplayerOnce(ctx, replayer)— registers the per-org replayer once; later config apps reuse it.Wired into both seams the issue names:
packages/runtime/src/app-plugin.ts— the config-app seed path nowmergeSeedDatasets(...)into the shared array, and the per-org replayer readsreadSeedDatasets(ctx)at invoke time instead of the capturedmergedsnapshot, so an org created after a later app/marketplace install still replays their seeds.packages/cloud-connection/src/marketplace-install-local-plugin.ts— the marketplace merge now routes through the runtime helper via a lazyimport('@objectstack/runtime')+typeof-guard (mirroring the existingrecordSeedSummary), with an equivalent inline fallback so the two test files that mock@objectstack/runtimewithout the helper don't crash.Testing
packages/runtime/src/seed-datasets.test.ts(7 tests):getServicethrows on miss,registerServicethrows on dupe, no.kernel— mirrorsseed-summary.test.ts): reads the live service despite no.kernel(defect ①); a second source accumulates with no duplicate-register throw (defect ②); the live array grows for a captured reference;registerSeedReplayerOnceregisters once then reuses.AppPlugintwice + a marketplace-style merge and invokes the registeredseed-replayer, asserting it replays the full union[crm_account, crm_contact, hot_lead]in source order, tenant-scoped.Red-proof: against the pre-fix
app-plugin.tsthe seam test fails exactly as #3453 describes — the replayer dropscrm_contact(the second config app's seeds).Results:
@objectstack/runtime— 44 passed (app-plugin,app-plugin.seed,seed-datasets,seed-summary)@objectstack/cloud-connection— 18 passed (allmarketplace-install-local-*, incl. the two that mock the runtime module and exercise the fallback)build(DTS type-check) green for both packagesScope note
This repo only carries seam-level coverage. True multi-tenant end-to-end coverage requires the enterprise
@objectstack/organizationsplugin (which drives thesys_organization-insert replay), and that lives in the cloud repo — out of tree here.🤖 Generated with Claude Code