Skip to content

Commit 4242e49

Browse files
committed
Added DEV_GUIDE.md and updated README.md
1 parent 8f2590b commit 4242e49

File tree

2 files changed

+378
-0
lines changed

2 files changed

+378
-0
lines changed

DEV_GUIDE.md

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
# PureMVC TypeScript MultiCore — Developer Guide
2+
3+
This guide explains how the PureMVC MultiCore framework works in TypeScript and shows how to build an app using Facades, Proxies, Mediators, Commands, and Notifications. All examples are TypeScript and use the published NPM package.
4+
5+
- Package: `@puremvc/puremvc-typescript-multicore-framework`
6+
- Docs: https://puremvc.org/pages/docs/TypeScript/multicore/
7+
8+
## Installation
9+
10+
```sh
11+
npm install @puremvc/puremvc-typescript-multicore-framework
12+
```
13+
14+
## Core Concepts
15+
16+
PureMVC implements the classic Model–View–Controller meta‑pattern. In MultiCore, each application module (a “Core”) has its own set of MVC actors, identified by a unique Multiton key.
17+
18+
- Model: manages application data via `Proxy` instances.
19+
- View: manages presentation and event routing via `Mediator` instances.
20+
- Controller: maps `Notification` names to `Command` classes.
21+
- Facade: the single API surface that exposes Model, View, Controller for a Core. In MultiCore, Facade is a Multiton; one instance per Core key.
22+
- Notification: the event/message traveling through the system.
23+
24+
Key properties of MultiCore:
25+
- Every Core is referenced by a unique string key.
26+
- Each `Notifier` (Proxy, Mediator, Command) is automatically associated with the correct Core when it is executed/registered; it can then call `sendNotification` to communicate within that Core.
27+
28+
## Public API (what you usually import)
29+
30+
```ts
31+
import {
32+
Facade,
33+
Proxy,
34+
Mediator,
35+
SimpleCommand,
36+
MacroCommand,
37+
Notification,
38+
// Types (optional)
39+
type IFacade,
40+
type IProxy,
41+
type IMediator,
42+
type ICommand,
43+
type INotification,
44+
} from '@puremvc/puremvc-typescript-multicore-framework';
45+
```
46+
47+
## Multiton: Creating and Accessing a Core
48+
49+
Each Core is identified by a string key. You never `new Facade()` directly. Instead call `Facade.getInstance(key, factory)` and supply a factory that constructs your Facade subclass.
50+
51+
```ts
52+
const CORE_KEY = 'com.example.myapp';
53+
54+
// Your concrete Facade (defined below)
55+
const facade = Facade.getInstance(CORE_KEY, (key) => new AppFacade(key));
56+
57+
// Later, retrieve the same core anywhere:
58+
const same = Facade.getInstance(CORE_KEY, (key) => new AppFacade(key));
59+
```
60+
61+
Notes:
62+
- The factory is only used the first time for a given key; subsequent calls return the existing instance.
63+
- Use different keys to run multiple, isolated cores simultaneously.
64+
65+
## Subclassing Facade
66+
67+
You typically subclass `Facade` to register Proxies, Mediators, and Commands at startup.
68+
69+
```ts
70+
// AppFacade.ts
71+
import { Facade } from '@puremvc/puremvc-typescript-multicore-framework';
72+
73+
export class AppFacade extends Facade {
74+
// app-specific notification names
75+
public static readonly STARTUP = 'AppFacade/STARTUP';
76+
77+
protected initializeModel(): void {
78+
super.initializeModel();
79+
// Register your Proxies here
80+
this.registerProxy(new UserProxy());
81+
}
82+
83+
protected initializeController(): void {
84+
super.initializeController();
85+
// Map notifications to commands
86+
this.registerCommand(AppFacade.STARTUP, () => new StartupCommand());
87+
}
88+
89+
protected initializeView(): void {
90+
super.initializeView();
91+
// Register Mediators here (optionally in StartupCommand)
92+
}
93+
}
94+
```
95+
96+
## Proxies (Model)
97+
98+
Extend `Proxy` to manage data. Proxies are given the Core key when they are registered with the Model, after which they can send notifications.
99+
100+
```ts
101+
// UserProxy.ts
102+
import { Proxy } from '@puremvc/puremvc-typescript-multicore-framework';
103+
104+
export class UserProxy extends Proxy {
105+
public static readonly NAME = 'UserProxy';
106+
public static readonly USERS_LOADED = 'UserProxy/USERS_LOADED';
107+
108+
constructor() {
109+
super(UserProxy.NAME);
110+
}
111+
112+
async loadUsers(): Promise<void> {
113+
// Simulate remote call
114+
const users = await Promise.resolve([
115+
{ id: 1, name: 'Ada' },
116+
{ id: 2, name: 'Grace' },
117+
]);
118+
this.data = users;
119+
this.sendNotification(UserProxy.USERS_LOADED, users);
120+
}
121+
}
122+
```
123+
124+
## Mediators (View)
125+
126+
Extend `Mediator` to coordinate a view component and react to notifications. Mediators are given the Core key when registered with the View.
127+
128+
```ts
129+
// UserListMediator.ts
130+
import { Mediator, type INotification } from '@puremvc/puremvc-typescript-multicore-framework';
131+
import { UserProxy } from './UserProxy';
132+
133+
export class UserListMediator extends Mediator {
134+
public static readonly NAME = 'UserListMediator';
135+
136+
constructor(viewComponent: HTMLUListElement) {
137+
super(UserListMediator.NAME, viewComponent);
138+
}
139+
140+
public override listNotificationInterests(): string[] {
141+
return [UserProxy.USERS_LOADED];
142+
}
143+
144+
public override handleNotification(note: INotification): void {
145+
switch (note.name) {
146+
case UserProxy.USERS_LOADED:
147+
this.render(note.body as Array<{ id: number; name: string }>);
148+
break;
149+
}
150+
}
151+
152+
private render(users: Array<{ id: number; name: string }>): void {
153+
const ul = this.viewComponent as HTMLUListElement;
154+
ul.innerHTML = users.map((u) => `<li>${u.name}</li>`).join('');
155+
}
156+
}
157+
```
158+
159+
Registering Mediators typically happens during startup (see `StartupCommand` below) or in `AppFacade.initializeView`.
160+
161+
## Commands (Controller)
162+
163+
Commands encapsulate application logic executed in response to notifications. They are given the Core key when executed by the Controller, after which they can access the Facade and send notifications.
164+
165+
```ts
166+
// StartupCommand.ts
167+
import { SimpleCommand, type INotification } from '@puremvc/puremvc-typescript-multicore-framework';
168+
import { UserProxy } from './UserProxy';
169+
import { UserListMediator } from './UserListMediator';
170+
171+
export class StartupCommand extends SimpleCommand {
172+
public override execute(note: INotification): void {
173+
// Optionally, get something from the startup body
174+
const root = note.body as { userListEl: HTMLUListElement } | undefined;
175+
176+
// Ensure Proxy exists and kick off initial load
177+
const userProxy = this.facade.retrieveProxy(UserProxy.NAME) as UserProxy | null;
178+
if (userProxy) userProxy.loadUsers();
179+
180+
// Register Mediator now that we have the view element
181+
if (root?.userListEl) {
182+
this.facade.registerMediator(new UserListMediator(root.userListEl));
183+
}
184+
}
185+
}
186+
```
187+
188+
`MacroCommand` allows sequencing multiple `SimpleCommand`s:
189+
190+
```ts
191+
import { MacroCommand } from '@puremvc/puremvc-typescript-multicore-framework';
192+
193+
export class AppStartupMacro extends MacroCommand {
194+
protected override initializeMacroCommand(): void {
195+
this.addSubCommand(() => new PrepModelCommand());
196+
this.addSubCommand(() => new PrepViewCommand());
197+
this.addSubCommand(() => new KickoffCommand());
198+
}
199+
}
200+
```
201+
202+
## Wiring It Up (Startup)
203+
204+
Create the Facade for your Core, register your startup mapping, and send a `STARTUP` notification.
205+
206+
```ts
207+
// main.ts
208+
import { Facade } from '@puremvc/puremvc-typescript-multicore-framework';
209+
import { AppFacade } from './AppFacade';
210+
211+
const CORE_KEY = 'com.example.myapp';
212+
const facade = Facade.getInstance(CORE_KEY, (key) => new AppFacade(key));
213+
214+
// Somewhere you create or obtain your view root(s)
215+
const userListEl = document.getElementById('users') as HTMLUListElement;
216+
217+
// Kick off the app
218+
facade.sendNotification(AppFacade.STARTUP, { userListEl });
219+
```
220+
221+
Notes:
222+
- Don’t call `new AppFacade()` directly. Always use `Facade.getInstance(key, factory)`.
223+
- The Notifier caveat in MultiCore: a `Notifier` (Proxy, Mediator, Command) cannot use `sendNotification` until it has been given a multitonKey. This happens automatically when:
224+
- a Proxy is registered with the Model,
225+
- a Mediator is registered with the View,
226+
- a Command is executed by the Controller.
227+
228+
## Communicating with Notifications
229+
230+
Notifications are identified by string names and may carry an optional `body` and `type`.
231+
232+
```ts
233+
// From inside any Notifier (Proxy, Mediator, Command):
234+
this.sendNotification('User/CREATE', { id: 3, name: 'Margaret' }, 'immediate');
235+
236+
// Handling in a Mediator subclass
237+
import { Mediator, type INotification } from '@puremvc/puremvc-typescript-multicore-framework';
238+
239+
class ExampleMediator extends Mediator {
240+
listNotificationInterests(): string[] {
241+
return ['User/CREATE'];
242+
}
243+
244+
handleNotification(note: INotification): void {
245+
if (note.name === 'User/CREATE') {
246+
// note.body and note.type as needed
247+
}
248+
}
249+
}
250+
```
251+
252+
Guidelines:
253+
- Keep notification names unique and scoped (e.g., `'UserProxy/USERS_LOADED'`).
254+
- Co-locate names with the class that owns them (e.g., `UserProxy.USERS_LOADED`).
255+
256+
## Multiple Cores (Modules)
257+
258+
Run multiple isolated Cores by using different Multiton keys. Each Core has its own Facade, Model, View, Controller, and its own set of Notifiers.
259+
260+
```ts
261+
const AdminKey = 'com.example.app/admin';
262+
const ShopKey = 'com.example.app/shop';
263+
264+
const admin = Facade.getInstance(AdminKey, (k) => new AdminFacade(k));
265+
const shop = Facade.getInstance(ShopKey, (k) => new ShopFacade(k));
266+
267+
admin.sendNotification(AdminFacade.STARTUP);
268+
shop.sendNotification(ShopFacade.STARTUP);
269+
```
270+
271+
Cross-core communication is usually achieved at a higher level (e.g., a shell module) by listening to one core and translating into another core’s notifications. For inter-module message bus patterns, see the PureMVC Pipes utility.
272+
273+
## Shutting Down a Core
274+
275+
Remove a Core and its MVC actors when you’re done with it:
276+
277+
```ts
278+
import { Facade } from '@puremvc/puremvc-typescript-multicore-framework';
279+
280+
Facade.removeCore('com.example.myapp');
281+
```
282+
283+
This removes the `Model`, `View`, `Controller`, and `Facade` instances for that key.
284+
285+
## Testing Tips
286+
287+
- Prefer deterministic `Notification` names; expose them as `public static readonly` on the owning class.
288+
- Query Proxies via `facade.retrieveProxy(ProxyClass.NAME)`; check `proxy.data`.
289+
- Mediators are unit-testable by directly calling `handleNotification` with a constructed `Notification`.
290+
291+
## Reference: Common Facade Methods
292+
293+
- `registerProxy(proxy)` / `retrieveProxy(name)` / `removeProxy(name)` / `hasProxy(name)`
294+
- `registerMediator(mediator)` / `retrieveMediator(name)` / `removeMediator(name)` / `hasMediator(name)`
295+
- `registerCommand(notificationName, factory)` / `removeCommand(notificationName)` / `hasCommand(notificationName)`
296+
- `sendNotification(name, body?, type?)`
297+
- `notifyObservers(notification)` (rarely used directly; prefer `sendNotification`)
298+
299+
## Minimal End-to-End Example
300+
301+
```ts
302+
import {
303+
Facade,
304+
Proxy,
305+
Mediator,
306+
SimpleCommand,
307+
type INotification,
308+
} from '@puremvc/puremvc-typescript-multicore-framework';
309+
310+
// 1) Proxy
311+
class CounterProxy extends Proxy {
312+
static readonly NAME = 'CounterProxy';
313+
static readonly UPDATED = 'CounterProxy/UPDATED';
314+
315+
constructor() {
316+
super(CounterProxy.NAME, { value: 0 });
317+
}
318+
319+
increment() {
320+
this.data = { value: (this.data?.value ?? 0) + 1 };
321+
this.sendNotification(CounterProxy.UPDATED, this.data);
322+
}
323+
}
324+
325+
// 2) Mediator
326+
class CounterMediator extends Mediator {
327+
static readonly NAME = 'CounterMediator';
328+
constructor(span: HTMLSpanElement) {
329+
super(CounterMediator.NAME, span);
330+
}
331+
listNotificationInterests(): string[] {
332+
return [CounterProxy.UPDATED];
333+
}
334+
handleNotification(note: INotification): void {
335+
if (note.name === CounterProxy.UPDATED) {
336+
(this.viewComponent as HTMLSpanElement).textContent = String(note.body.value);
337+
}
338+
}
339+
}
340+
341+
// 3) Command
342+
class StartupCommand extends SimpleCommand {
343+
execute(note: INotification): void {
344+
const { span } = note.body as { span: HTMLSpanElement };
345+
346+
// Register Mediator
347+
this.facade.registerMediator(new CounterMediator(span));
348+
349+
// Use the Proxy
350+
const proxy = this.facade.retrieveProxy(CounterProxy.NAME) as CounterProxy | null;
351+
proxy?.increment();
352+
}
353+
}
354+
355+
// 4) Facade
356+
class AppFacade extends Facade {
357+
static readonly STARTUP = 'App/STARTUP';
358+
protected initializeModel(): void {
359+
super.initializeModel();
360+
this.registerProxy(new CounterProxy());
361+
}
362+
protected initializeController(): void {
363+
super.initializeController();
364+
this.registerCommand(AppFacade.STARTUP, () => new StartupCommand());
365+
}
366+
}
367+
368+
// 5) Bootstrap
369+
const KEY = 'example.counter';
370+
const facade = Facade.getInstance(KEY, (k) => new AppFacade(k));
371+
const span = document.getElementById('count') as HTMLSpanElement;
372+
facade.sendNotification(AppFacade.STARTUP, { span });
373+
```
374+
375+
That’s it! You now have the basic shape to build robust, modular, testable TypeScript applications with PureMVC MultiCore.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
PureMVC is a lightweight framework for creating applications based upon the classic [Model-View-Controller](http://en.wikipedia.org/wiki/Model-view-controller) design meta-pattern. It supports [modular programming](http://en.wikipedia.org/wiki/Modular_programming) through the use of [Multiton](http://en.wikipedia.org/wiki/Multiton) Core actors instead of the [Singletons](http://en.wikipedia.org/wiki/Singleton_pattern).
44

5+
## Dev Guide
6+
* [PureMVC TypeScript MultiCore Framework — Developer Guide](DEV_GUIDE.md)
7+
58
## Installation
69
```shell
710
npm install @puremvc/puremvc-typescript-multicore-framework

0 commit comments

Comments
 (0)