@cat-factory/app 0.131.0 → 0.132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/modular/registry.spec.ts +31 -0
- package/app/modular/registry.ts +75 -0
- package/app/plugins/modular.client.ts +32 -0
- package/app/utils/modular.ts +11 -0
- package/package.json +7 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineModule } from '@modular-vue/core'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { __resetConsumerModulesForTest, createAppRegistry, registerAppModule } from './registry'
|
|
4
|
+
|
|
5
|
+
describe('app modular registry', () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
__resetConsumerModulesForTest()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('registers the first-party core module', () => {
|
|
11
|
+
const manifest = createAppRegistry().resolveManifest()
|
|
12
|
+
expect(manifest.modules.map((m) => m.id)).toContain('cat-factory:core')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('includes consumer modules contributed via registerAppModule', () => {
|
|
16
|
+
registerAppModule(defineModule({ id: 'consumer:example', version: '1.0.0' }))
|
|
17
|
+
|
|
18
|
+
const ids = createAppRegistry()
|
|
19
|
+
.resolveManifest()
|
|
20
|
+
.modules.map((m) => m.id)
|
|
21
|
+
|
|
22
|
+
expect(ids).toContain('cat-factory:core')
|
|
23
|
+
expect(ids).toContain('consumer:example')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('rejects a module whose id collides with an already-registered one', () => {
|
|
27
|
+
registerAppModule(defineModule({ id: 'cat-factory:core', version: '2.0.0' }))
|
|
28
|
+
|
|
29
|
+
expect(() => createAppRegistry().resolveManifest()).toThrow(/duplicate/i)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineModule } from '@modular-vue/core'
|
|
2
|
+
import type { AnyModuleDescriptor } from '@modular-vue/core'
|
|
3
|
+
import { createRegistry } from '@modular-vue/runtime'
|
|
4
|
+
import type { ModuleRegistry } from '@modular-vue/runtime'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* modular-vue registry for the `@cat-factory/app` layer (slice 0 of the
|
|
8
|
+
* modular-vue adoption — docs/initiatives/modular-vue-adoption.md).
|
|
9
|
+
*
|
|
10
|
+
* This is the frontend analogue of the backend's public registries
|
|
11
|
+
* (`registerAgentKind`, `registerGate`): a single registry into which the layer
|
|
12
|
+
* registers its own first-party feature modules AND a consumer deployment
|
|
13
|
+
* contributes its own, all through the same seam. The registry is resolved and
|
|
14
|
+
* installed by `app/plugins/modular.client.ts`.
|
|
15
|
+
*
|
|
16
|
+
* Slice 0 wires the plumbing behind ZERO behaviour change: the one first-party
|
|
17
|
+
* module carries no navigation / slots / component, so nothing new renders.
|
|
18
|
+
* Later slices convert real areas (navigation, result views, wizards, inspector
|
|
19
|
+
* panels) into modules registered here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* First-party modules the layer always registers. Kept tiny on purpose for
|
|
24
|
+
* slice 0 — a single descriptor with no contributions, present only to prove the
|
|
25
|
+
* registration pipeline end to end and give the seam a stable anchor. Real
|
|
26
|
+
* feature modules land here as later slices convert each area.
|
|
27
|
+
*/
|
|
28
|
+
const FIRST_PARTY_MODULES: readonly AnyModuleDescriptor[] = [
|
|
29
|
+
defineModule({ id: 'cat-factory:core', version: '1.0.0' }),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Consumer-contributed modules, collected before the layer resolves its
|
|
34
|
+
* registry. A deployment extending `@cat-factory/app` calls
|
|
35
|
+
* {@link registerAppModule} from its own Nuxt plugin (which runs before the
|
|
36
|
+
* layer's install plugin — see the ordering note in
|
|
37
|
+
* `app/plugins/modular.client.ts`). Module descriptors carry Vue components, so
|
|
38
|
+
* they can't travel through serializable Nuxt config; this in-process seam is
|
|
39
|
+
* how the layer stays unforked while a consumer contributes real components.
|
|
40
|
+
*/
|
|
41
|
+
const consumerModules: AnyModuleDescriptor[] = []
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Contribute a module to the app registry from a consumer deployment (or a
|
|
45
|
+
* first-party plugin). Call this at plugin-setup time, before the layer's
|
|
46
|
+
* install plugin resolves the registry. Registering the same id twice makes
|
|
47
|
+
* `resolve()` throw at build time (duplicate-id validation), which is the
|
|
48
|
+
* intended guard.
|
|
49
|
+
*/
|
|
50
|
+
export function registerAppModule(module: AnyModuleDescriptor): void {
|
|
51
|
+
consumerModules.push(module)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Test-only: drop every consumer-registered module so a spec can exercise
|
|
56
|
+
* {@link registerAppModule} without leaking state into the next test. Not part
|
|
57
|
+
* of the runtime seam.
|
|
58
|
+
*/
|
|
59
|
+
export function __resetConsumerModulesForTest(): void {
|
|
60
|
+
consumerModules.length = 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build a fresh registry with the first-party modules plus everything a consumer
|
|
65
|
+
* contributed via {@link registerAppModule}. A new registry per call because
|
|
66
|
+
* `resolve()` / `resolveManifest()` are single-commit; the install plugin calls
|
|
67
|
+
* this exactly once at client startup (`ssr: false`, so a singleton app).
|
|
68
|
+
*/
|
|
69
|
+
export function createAppRegistry(): ModuleRegistry<Record<string, unknown>> {
|
|
70
|
+
const registry = createRegistry<Record<string, unknown>>({})
|
|
71
|
+
for (const mod of [...FIRST_PARTY_MODULES, ...consumerModules]) {
|
|
72
|
+
registry.register(mod)
|
|
73
|
+
}
|
|
74
|
+
return registry
|
|
75
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { installModularApp } from '@modular-vue/nuxt/runtime'
|
|
2
|
+
import { createAppRegistry } from '~/modular/registry'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wire the modular-vue registry into the Nuxt app (slice 0 of the modular-vue
|
|
6
|
+
* adoption — docs/initiatives/modular-vue-adoption.md).
|
|
7
|
+
*
|
|
8
|
+
* `enforce: 'post'` is load-bearing for the consumer-contribution seam. Nuxt
|
|
9
|
+
* loads layer plugins before the consuming app's plugins within the same enforce
|
|
10
|
+
* bucket, so a consumer that calls `registerAppModule(...)` from a normal plugin
|
|
11
|
+
* would otherwise run AFTER this one and miss the resolve. Putting this plugin in
|
|
12
|
+
* the `post` bucket flips the order: the consumer's default-bucket registration
|
|
13
|
+
* runs first, then this resolves the registry with everything registered. A
|
|
14
|
+
* consumer therefore contributes from a default (or `pre`) plugin — documented
|
|
15
|
+
* in the framework-mode-nuxt guide upstream.
|
|
16
|
+
*
|
|
17
|
+
* `ssr: false`, so this runs once on the client and a singleton registry is
|
|
18
|
+
* fine. `installModularApp` is the router-owning path: it grafts each module's
|
|
19
|
+
* routes onto Nuxt's router (none yet — slice 0 modules are non-routed) and
|
|
20
|
+
* installs the modular contexts (shared deps, navigation, slots) app-wide. The
|
|
21
|
+
* resolved manifest is exposed as `$modular` for later slices; nothing reads it
|
|
22
|
+
* yet, which is what keeps this behaviour-neutral.
|
|
23
|
+
*/
|
|
24
|
+
export default defineNuxtPlugin({
|
|
25
|
+
name: 'cat-factory:modular',
|
|
26
|
+
enforce: 'post',
|
|
27
|
+
setup(nuxtApp) {
|
|
28
|
+
const registry = createAppRegistry()
|
|
29
|
+
const manifest = installModularApp({ vueApp: nuxtApp.vueApp, $router: useRouter() }, registry)
|
|
30
|
+
return { provide: { modular: manifest } }
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-imported seam for the modular-vue registry. Re-exported from `app/utils/`
|
|
3
|
+
* (which Nuxt auto-imports across the whole `extends` chain) so a consumer
|
|
4
|
+
* deployment can contribute modules with a bare `registerAppModule(...)` call
|
|
5
|
+
* from its own plugin — no deep import into the layer's internals, the same
|
|
6
|
+
* ergonomics as the layer's auto-imported stores and composables.
|
|
7
|
+
*
|
|
8
|
+
* See `app/modular/registry.ts` for the seam itself and
|
|
9
|
+
* docs/initiatives/modular-vue-adoption.md for the adoption plan.
|
|
10
|
+
*/
|
|
11
|
+
export { registerAppModule } from '~/modular/registry'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.132.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@modular-frontend/core": "^0.1.0",
|
|
22
|
+
"@modular-vue/core": "^1.0.1",
|
|
23
|
+
"@modular-vue/nuxt": "^0.1.1",
|
|
24
|
+
"@modular-vue/runtime": "^1.0.1",
|
|
25
|
+
"@modular-vue/vue": "^1.0.0",
|
|
21
26
|
"@nuxt/ui": "^4.9.0",
|
|
22
27
|
"@nuxtjs/i18n": "^10.4.1",
|
|
23
28
|
"@pinia/nuxt": "^0.11.3",
|
|
@@ -32,7 +37,7 @@
|
|
|
32
37
|
"pinia": "^3.0.4",
|
|
33
38
|
"pinia-plugin-persistedstate": "^4.7.1",
|
|
34
39
|
"valibot": "^1.4.2",
|
|
35
|
-
"vue": "3.5.
|
|
40
|
+
"vue": "3.5.40",
|
|
36
41
|
"wretch": "^3.0.9",
|
|
37
42
|
"@cat-factory/contracts": "0.147.1"
|
|
38
43
|
},
|