Skip to content

feat(core): flat models[] config + internal field migration (PR 1 of 3)#776

Open
loopingz wants to merge 13 commits into
mainfrom
feat/repository-refactor-pr1
Open

feat(core): flat models[] config + internal field migration (PR 1 of 3)#776
loopingz wants to merge 13 commits into
mainfrom
feat/repository-refactor-pr1

Conversation

@loopingz
Copy link
Copy Markdown
Owner

@loopingz loopingz commented May 9, 2026

Summary

First of three PRs for the Repository concept refactor. This PR introduces the flat models: string[] parameter on StoreParameters, migrates Store internals from _model/_modelMetadata/_modelType to _models[]/_modelMetadatas Map, and updates downstream stores (fs, postgres, runtime) to match.

Spec: docs/superpowers/specs/2026-05-09-repository-design.md (local-only — gitignored).
Plan: docs/superpowers/plans/2026-05-09-repository-design.md (local-only — gitignored).

What's in this PR (Tasks 1–5 of 8)

  • Task 1StoreParameters accepts models: string[] alongside legacy model + additionalModels; deprecation WARN log fires on legacy use; ambiguity guard throws when both shapes are set.
  • Task 2Store class migrated to _models: ModelClass[] and _modelMetadatas: Map<string, ModelMetadata>. _modelType dropped. New getModels() accessor; getModel() deprecated. computeParameters() rewritten to walk parameters.models[]. Latent bug fixed in recursive() subclass walker (the runtime type from Application.setModelMetadata is (string | ModelClass)[], not string[] as the type declared).
  • Task 3FileStore._get() strict-mode __type check rewritten to use _modelsHierarchy (replaces dropped _modelType reference).
  • Task 4PostgresStore.resolveTable() migrated off _modelMetadata; parameters.table now treated as a single-model shortcut, with WARN log when set with multiple models.
  • Task 5 — Final _model/_modelType references in fs/filestore.ts:205, fs/filestore-unit.spec.ts:299, runtime/invitationservice.spec.ts:100 migrated.

What's not in this PR (deferred)

  • Task 6 (migrate spec configs from model: to models:) — cosmetic; deprecation shim keeps existing configs working.
  • Task 7 (regenerate webda.module.json schemas) — surfaced a load-bearing regression where the legacy model: { default: "Webda/CoreModel" } schema default is essential for framework-loaded stores to claim every model. Reverted in commit ce6f4112. Needs a follow-up architectural fix.
  • Task 8 (final verification) — pending Task 7.

PRs 2 (Repository event re-emission) and 3 (API positioning / docs) follow this one.

Backward compatibility

  • All existing { model: "X", additionalModels: [...] } configs keep working via the deprecation shim. A WARN log fires once per service noting the migration path.
  • getModel() still returns the first model for back-compat (marked @deprecated).
  • useStore() is unchanged in this PR.

Test plan

  • pnpm --filter @webda/core test — 554 passed, 1 todo (was 550 before this PR; 4 new tests added).
  • pnpm --filter @webda/fs test — 123 passed (was 122; 1 new strict-mode test).
  • pnpm --filter @webda/postgres test — 3 new resolveTable tests pass; pre-existing PostgresStoreSmokeTest failures unrelated (DB connectivity).
  • CI build green (TBD on push).

How this was built

Each task: TDD (failing test → implementation → passing test → commit). Two-stage subagent review per task (spec compliance, then code quality), with fix loops where reviewers found issues. Two follow-up commits for code-review feedback land on top of the original task commits where applicable.

🤖 Generated with Claude Code

loopingz and others added 13 commits May 9, 2026 07:36
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New `models: string[]` parameter
- `model` and `additionalModels` mapped to `models[]` with WARN log
- Throws if both old and new shapes are set
- Deprecation tests cover the five compat cases
…ll comment

Code review follow-ups for Task 1:
- Conflict between models[] and additionalModels detected even when
  additionalModels is empty (presence check, not length)
- Backfill comment explains why ??= is a no-op in the legacy branch
- Two new tests: empty-additionalModels conflict and additionalModels-only
  fallback to Webda/RegistryEntry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop _model, _modelMetadata, _modelType from Store class
- Add _models: ModelClass[] (index 0 = legacy primary)
- Add _modelMetadatas: Map<string, ModelMetadata>
- computeParameters() walks parameters.models[] (canonical list from Task 1)
- recursive() normalises ModelClass refs to string IDs as _modelsHierarchy keys
- getModel() returns _models[0] (deprecated, kept for back-compat)
- getModels() added as new public method
- setModelDefinitionHelper() sets _models[0] for test-harness compat
- Two new @test methods verify _models/_modelMetadatas and getModel() back-compat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- populatesModelsArrayAndMetadatas now uses models: [...] constructor arg
  and sets getParameters().models directly before resolve() to exercise
  computeParameters() via the canonical models[] path (not the legacy shim)
- Removed dead-code test stubs from memory.spec.ts (file is excluded from vitest)
- Dropped extra useLog TRACE noise from computeParameters()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- walksSubclassHierarchyForNonStrictStore exercises recursive() ModelClass branch
  via Webda/User → Webda/SimpleUser depth-1 traversal
- strictStoreSkipsSubclassWalk verifies strict-mode short-circuit
- populatesModelsArrayAndMetadatas now also asserts getModels().length

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces dropped _modelType field. In strict mode, accept files whose
__type matches a depth-0 model in this store's models[]; subclasses
(depth > 0) remain excluded. Two new tests cover accept and reject paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ters.table for multi-model

- One model in models[]: parameters.table maps to that model (compat shortcut)
- Multiple models: parameters.table is ignored with WARN log at init()
- parameters.tables[id] continues as the canonical per-model override
- Default derivation (id.lower.replace('/', '_')) unchanged
- 3 new tests cover single, multi, and explicit-map paths in a separate
  PostgresStoreResolveTableTest suite (no DB connection required)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fs/filestore.ts:205: this._model → this._models[0]
- fs/filestore-unit.spec.ts: getWithStrictMode now sets _modelsHierarchy
  directly (matching what computeParameters does at runtime)
- runtime/invitationservice.spec.ts: this.store._model = X → setModelDefinitionHelper(X)

Closes the migration started in Task 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After Tasks 1-5, StoreParameters exposes a flat models: string[] alongside
deprecated model + additionalModels. Regenerated schemas accept both shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@loopingz
Copy link
Copy Markdown
Owner Author

loopingz commented May 9, 2026

Task 7 (schema regen) — investigation update

After ~30 min of debugging the regression, found that running pnpm exec webdac build --force in packages/core regenerates webda.module.json such that Store.init() is no longer invoked for the Registry MemoryStore in test mode. As a result Store.computeStores() never runs and no Repository is registered for RegistryEntry, breaking 8 tests (CoreTest > registry, CoreTest > cov, CryptoServiceTest > *).

The schema diff itself looks superficially benign:

  • additionalModels/model lose default: ... (instead get deprecated: true)
  • A new models field is added with default: []

Things ruled out:

  • The models field's default value is not the trigger — restoring Webda/CoreModel as the model default did not fix it.
  • required array is unchanged for MemoryStore ([type] only).
  • MemoryStore's Configuration / Import paths in moddas are correct after regen.
  • Direct pnpm exec vitest run shows the same failure with intact schema (3843 lines), so no test-time corruption is at play.

Best guess: when both legacy model and new models fields are deprecated/added in the schema, something downstream in service registration / Modda lookup silently drops the Registry binding. Needs deeper trace through unpackedapplication.loadWebdaModule and Core.createService to confirm.

For now, Task 7 is reverted via fa27845b and the branch is clean (554/554 core tests pass). Tasks 1–5 are independently shippable thanks to the deprecation shim — schema regen + Tasks 6/8 will follow in a separate PR once the root cause is identified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant