@framesquared/app 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Carbone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @framesquared/app – ViewController
3
+ *
4
+ * Tied to a specific view (Component). Provides `control` config
5
+ * for auto-wiring DOM event handlers via CSS selectors, view reference
6
+ * lookup, and lifecycle management.
7
+ */
8
+ interface ViewControllerConfig {
9
+ id?: string;
10
+ control?: Record<string, Record<string, string>>;
11
+ listen?: Record<string, any>;
12
+ routes?: Record<string, string>;
13
+ }
14
+ declare class ViewController {
15
+ private _id;
16
+ private _view;
17
+ private _control;
18
+ private _cleanups;
19
+ constructor(config?: ViewControllerConfig);
20
+ getId(): string;
21
+ init(view: any): void;
22
+ destroy(): void;
23
+ getView(): any;
24
+ lookupReference(ref: string): any;
25
+ fireViewEvent(eventName: string, ...args: unknown[]): void;
26
+ applyControl(): void;
27
+ private toCssSelector;
28
+ redirectTo(hash: string, _force?: boolean): void;
29
+ }
30
+
31
+ /**
32
+ * @framesquared/app – Application
33
+ *
34
+ * The entry point for an framesquared application. Manages lifecycle
35
+ * (init → beforeLaunch → launch → onLaunch), controller and
36
+ * store registries, and provides a singleton accessor.
37
+ */
38
+
39
+ interface ApplicationConfig {
40
+ name: string;
41
+ appFolder?: string;
42
+ controllers?: string[];
43
+ stores?: string[];
44
+ mainView?: string;
45
+ launch?: () => void;
46
+ }
47
+ declare class Application {
48
+ private _name;
49
+ private _launchFn;
50
+ private _controllers;
51
+ private _stores;
52
+ /** Overridable lifecycle hooks. */
53
+ onInit: () => void;
54
+ onBeforeLaunch: () => void;
55
+ onLaunch: () => void;
56
+ constructor(config: ApplicationConfig);
57
+ start(): void;
58
+ getName(): string;
59
+ registerController(name: string, controller: ViewController): void;
60
+ getController(name: string): ViewController | undefined;
61
+ registerStore(name: string, store: any): void;
62
+ getStore(name: string): any;
63
+ static getInstance(): Application | null;
64
+ static clearInstance(): void;
65
+ }
66
+
67
+ /**
68
+ * @framesquared/app – ViewModel
69
+ *
70
+ * Provides data binding for views. Supports nested path access,
71
+ * computed formulas with automatic dependency tracking via get(),
72
+ * named stores, parent→child hierarchy with inheritance, and
73
+ * change notification.
74
+ */
75
+ interface ViewModelConfig {
76
+ data?: Record<string, unknown>;
77
+ formulas?: Record<string, (get: (path: string) => unknown) => unknown>;
78
+ stores?: Record<string, {
79
+ data: unknown[];
80
+ }>;
81
+ parent?: ViewModel;
82
+ }
83
+ declare class ViewModel {
84
+ private _data;
85
+ private _formulas;
86
+ private _stores;
87
+ private _listeners;
88
+ private _parent;
89
+ private _children;
90
+ private _parentHandler;
91
+ constructor(config?: ViewModelConfig);
92
+ get(path: string): unknown;
93
+ set(pathOrObj: string | Record<string, unknown>, value?: unknown): void;
94
+ private computeFormula;
95
+ getStore(name: string): any;
96
+ getParent(): ViewModel | null;
97
+ getRoot(): ViewModel;
98
+ on(event: string, fn: Function): void;
99
+ un(event: string, fn: Function): void;
100
+ private fire;
101
+ private getByPath;
102
+ private setByPath;
103
+ private deepClone;
104
+ }
105
+
106
+ /**
107
+ * @framesquared/app – Binding
108
+ *
109
+ * Parses and evaluates binding expressions that connect ViewModel
110
+ * data to component configs. Supports simple paths, negation,
111
+ * expression templates, and two-way binding.
112
+ */
113
+
114
+ interface ParsedBinding {
115
+ paths: string[];
116
+ negated: boolean;
117
+ expression: boolean;
118
+ template: string;
119
+ }
120
+ declare const Binding: {
121
+ /**
122
+ * Parse a binding expression string.
123
+ *
124
+ * - `'{name}'` → simple path
125
+ * - `'{!isAdmin}'` → negated path
126
+ * - `'{firstName} {last}'` → expression with multiple paths
127
+ * - `'{user.email}'` → nested path
128
+ */
129
+ parse(expr: string): ParsedBinding;
130
+ /**
131
+ * Evaluate a parsed binding against a ViewModel.
132
+ */
133
+ evaluate(binding: ParsedBinding, vm: ViewModel): unknown;
134
+ /**
135
+ * Create a two-way binding. Calls `onChange` whenever the path
136
+ * changes in the ViewModel. Returns a cleanup function.
137
+ */
138
+ twoWay(vm: ViewModel, path: string, onChange: (value: unknown) => void): () => void;
139
+ /**
140
+ * Multi-bind: bind to multiple paths, callback receives all values
141
+ * as an array whenever any path changes.
142
+ */
143
+ multiBind(vm: ViewModel, paths: string[], onChange: (values: unknown[]) => void): () => void;
144
+ /**
145
+ * Deep bind: notifies when any nested property under the given
146
+ * path changes. Uses the datachange event (fires on every set).
147
+ */
148
+ deepBind(vm: ViewModel, path: string, onChange: (value: unknown) => void): () => void;
149
+ };
150
+
151
+ /**
152
+ * @framesquared/app – Router
153
+ *
154
+ * Hash-based routing using the hashchange event. Routes are
155
+ * patterns like 'user/:id' that extract named parameters.
156
+ * Supports wildcard routes, before-route guards, and navigateTo.
157
+ */
158
+ interface RouteEntry {
159
+ pattern: string;
160
+ paramNames: string[];
161
+ regex: RegExp;
162
+ handler: (params: Record<string, string>) => void;
163
+ wildcard: string | null;
164
+ }
165
+ declare const Router: {
166
+ addRoute(pattern: string, handler: (params: Record<string, string>) => void): void;
167
+ getRoutes(): RouteEntry[];
168
+ start(): void;
169
+ stop(): void;
170
+ setBeforeRoute(fn: ((hash: string) => boolean) | null): void;
171
+ navigateTo(hash: string): void;
172
+ getCurrentHash(): string;
173
+ };
174
+
175
+ /**
176
+ * @framesquared/app – Scheduler
177
+ *
178
+ * Batches multiple state changes into a single notification cycle.
179
+ * Uses microtask (queueMicrotask) for async batching. Provides
180
+ * synchronous flush() for immediate processing. Detects circular
181
+ * dependencies during formula evaluation.
182
+ */
183
+ declare const Scheduler: {
184
+ /**
185
+ * Schedule a flush. Multiple calls before the flush coalesce
186
+ * into a single notification cycle.
187
+ */
188
+ schedule(): void;
189
+ /**
190
+ * Synchronous flush — process all pending notifications immediately.
191
+ */
192
+ flush(): void;
193
+ /** Register a flush callback. */
194
+ onFlush(fn: Function): void;
195
+ /** Unregister a flush callback. */
196
+ offFlush(fn: Function): void;
197
+ /** Begin a formula evaluation cycle. */
198
+ beginCycle(): void;
199
+ /** End a formula evaluation cycle. */
200
+ endCycle(): void;
201
+ /**
202
+ * Mark a stub as currently being processed. If it's already
203
+ * being processed, we have a circular dependency.
204
+ */
205
+ markProcessing(name: string): void;
206
+ /** Unmark a stub after processing completes. */
207
+ unmarkProcessing(name: string): void;
208
+ /** Reset all state (for tests). */
209
+ reset(): void;
210
+ };
211
+
212
+ /**
213
+ * @framesquared/app – Stub
214
+ *
215
+ * Internal representation of data nodes in the ViewModel.
216
+ * ValueStub holds plain values with dirty tracking.
217
+ * FormulaStub holds computed functions with dependency management.
218
+ */
219
+ declare abstract class Stub {
220
+ readonly name: string;
221
+ private _dependents;
222
+ constructor(name: string);
223
+ addDependent(stub: Stub): void;
224
+ removeDependent(stub: Stub): void;
225
+ getDependents(): Stub[];
226
+ }
227
+ declare class ValueStub extends Stub {
228
+ private _value;
229
+ private _dirty;
230
+ constructor(name: string, value: unknown);
231
+ getValue(): unknown;
232
+ setValue(value: unknown): void;
233
+ isDirty(): boolean;
234
+ clearDirty(): void;
235
+ }
236
+ declare class FormulaStub extends Stub {
237
+ private _fn;
238
+ private _dependencies;
239
+ private _cachedValue;
240
+ private _dirty;
241
+ constructor(name: string, fn: () => unknown);
242
+ compute(): unknown;
243
+ getCachedValue(): unknown;
244
+ markDirty(): void;
245
+ isDirty(): boolean;
246
+ addDependency(stub: Stub): void;
247
+ getDependencies(): Stub[];
248
+ clearDependencies(): void;
249
+ }
250
+
251
+ export { Application, type ApplicationConfig, Binding, FormulaStub, type ParsedBinding, Router, Scheduler, Stub, ValueStub, ViewController, type ViewControllerConfig, ViewModel, type ViewModelConfig };
package/dist/index.js ADDED
@@ -0,0 +1,565 @@
1
+ // src/Application.ts
2
+ var instance = null;
3
+ var Application = class {
4
+ _name;
5
+ _launchFn;
6
+ _controllers = /* @__PURE__ */ new Map();
7
+ _stores = /* @__PURE__ */ new Map();
8
+ /** Overridable lifecycle hooks. */
9
+ onInit = () => {
10
+ };
11
+ onBeforeLaunch = () => {
12
+ };
13
+ onLaunch = () => {
14
+ };
15
+ constructor(config) {
16
+ this._name = config.name;
17
+ this._launchFn = config.launch ?? null;
18
+ instance = this;
19
+ }
20
+ // -----------------------------------------------------------------------
21
+ // Lifecycle
22
+ // -----------------------------------------------------------------------
23
+ start() {
24
+ this.onInit();
25
+ this.onBeforeLaunch();
26
+ this._launchFn?.();
27
+ this.onLaunch();
28
+ }
29
+ // -----------------------------------------------------------------------
30
+ // Accessors
31
+ // -----------------------------------------------------------------------
32
+ getName() {
33
+ return this._name;
34
+ }
35
+ // -----------------------------------------------------------------------
36
+ // Controllers
37
+ // -----------------------------------------------------------------------
38
+ registerController(name, controller) {
39
+ this._controllers.set(name, controller);
40
+ }
41
+ getController(name) {
42
+ return this._controllers.get(name);
43
+ }
44
+ // -----------------------------------------------------------------------
45
+ // Stores
46
+ // -----------------------------------------------------------------------
47
+ registerStore(name, store) {
48
+ this._stores.set(name, store);
49
+ }
50
+ getStore(name) {
51
+ return this._stores.get(name);
52
+ }
53
+ // -----------------------------------------------------------------------
54
+ // Singleton
55
+ // -----------------------------------------------------------------------
56
+ static getInstance() {
57
+ return instance;
58
+ }
59
+ static clearInstance() {
60
+ instance = null;
61
+ }
62
+ };
63
+
64
+ // src/ViewController.ts
65
+ var ViewController = class {
66
+ _id;
67
+ _view = null;
68
+ _control;
69
+ _cleanups = [];
70
+ constructor(config = {}) {
71
+ this._id = config.id ?? "";
72
+ this._control = config.control ?? {};
73
+ }
74
+ getId() {
75
+ return this._id;
76
+ }
77
+ // -----------------------------------------------------------------------
78
+ // View lifecycle
79
+ // -----------------------------------------------------------------------
80
+ init(view) {
81
+ this._view = view;
82
+ }
83
+ destroy() {
84
+ for (const cleanup of this._cleanups) cleanup();
85
+ this._cleanups = [];
86
+ this._view = null;
87
+ }
88
+ getView() {
89
+ return this._view;
90
+ }
91
+ // -----------------------------------------------------------------------
92
+ // References
93
+ // -----------------------------------------------------------------------
94
+ lookupReference(ref) {
95
+ return this._view?.lookupReference?.(ref) ?? null;
96
+ }
97
+ // -----------------------------------------------------------------------
98
+ // Events
99
+ // -----------------------------------------------------------------------
100
+ fireViewEvent(eventName, ...args) {
101
+ const view = this._view;
102
+ if (!view) return;
103
+ if (typeof view.fireEvent === "function") {
104
+ view.fireEvent(eventName, ...args);
105
+ } else if (typeof view.fire === "function") {
106
+ view.fire(eventName, ...args);
107
+ }
108
+ }
109
+ // -----------------------------------------------------------------------
110
+ // Control — auto-wire DOM events
111
+ // -----------------------------------------------------------------------
112
+ applyControl() {
113
+ if (!this._view?.el) return;
114
+ for (const [selector, events] of Object.entries(this._control)) {
115
+ const cssSelector = this.toCssSelector(selector);
116
+ const elements = this._view.el.querySelectorAll(cssSelector);
117
+ for (const el of elements) {
118
+ for (const [eventName, methodName] of Object.entries(events)) {
119
+ const method = this[methodName];
120
+ if (typeof method !== "function") continue;
121
+ const handler = (...args) => method.call(this, ...args);
122
+ el.addEventListener(eventName, handler);
123
+ this._cleanups.push(() => el.removeEventListener(eventName, handler));
124
+ }
125
+ }
126
+ }
127
+ }
128
+ toCssSelector(sel) {
129
+ return sel.replace(/#(\w+)/g, '[id="$1"]');
130
+ }
131
+ // -----------------------------------------------------------------------
132
+ // Routing
133
+ // -----------------------------------------------------------------------
134
+ redirectTo(hash, _force) {
135
+ window.location.hash = hash;
136
+ }
137
+ };
138
+
139
+ // src/ViewModel.ts
140
+ var ViewModel = class {
141
+ _data;
142
+ _formulas;
143
+ _stores;
144
+ _listeners = {};
145
+ _parent;
146
+ _children = [];
147
+ _parentHandler = null;
148
+ constructor(config = {}) {
149
+ this._data = config.data ? this.deepClone(config.data) : {};
150
+ this._formulas = config.formulas ?? {};
151
+ this._stores = {};
152
+ this._parent = config.parent ?? null;
153
+ if (config.stores) {
154
+ for (const [name, storeCfg] of Object.entries(config.stores)) {
155
+ this._stores[name] = { data: [...storeCfg.data ?? []] };
156
+ }
157
+ }
158
+ if (this._parent) {
159
+ this._parent._children.push(this);
160
+ this._parentHandler = () => this.fire("datachange", this);
161
+ this._parent.on("datachange", this._parentHandler);
162
+ }
163
+ }
164
+ // -----------------------------------------------------------------------
165
+ // Data access
166
+ // -----------------------------------------------------------------------
167
+ get(path) {
168
+ if (path in this._formulas) {
169
+ return this.computeFormula(path);
170
+ }
171
+ const localVal = this.getByPath(this._data, path);
172
+ if (localVal !== void 0) return localVal;
173
+ if (this._parent) return this._parent.get(path);
174
+ return void 0;
175
+ }
176
+ set(pathOrObj, value) {
177
+ if (typeof pathOrObj === "string") {
178
+ this.setByPath(this._data, pathOrObj, value);
179
+ } else {
180
+ for (const [key, val] of Object.entries(pathOrObj)) {
181
+ this.setByPath(this._data, key, val);
182
+ }
183
+ }
184
+ this.fire("datachange", this);
185
+ }
186
+ // -----------------------------------------------------------------------
187
+ // Formulas — get() callback reads both data and other formulas
188
+ // -----------------------------------------------------------------------
189
+ computeFormula(name) {
190
+ const fn = this._formulas[name];
191
+ if (!fn) return void 0;
192
+ const getter = (path) => this.get(path);
193
+ return fn(getter);
194
+ }
195
+ // -----------------------------------------------------------------------
196
+ // Stores
197
+ // -----------------------------------------------------------------------
198
+ getStore(name) {
199
+ return this._stores[name] ?? null;
200
+ }
201
+ // -----------------------------------------------------------------------
202
+ // Hierarchy
203
+ // -----------------------------------------------------------------------
204
+ getParent() {
205
+ return this._parent;
206
+ }
207
+ getRoot() {
208
+ let current = this;
209
+ while (current._parent) current = current._parent;
210
+ return current;
211
+ }
212
+ // -----------------------------------------------------------------------
213
+ // Events
214
+ // -----------------------------------------------------------------------
215
+ on(event, fn) {
216
+ (this._listeners[event] ??= []).push(fn);
217
+ }
218
+ un(event, fn) {
219
+ const list = this._listeners[event];
220
+ if (!list) return;
221
+ const idx = list.indexOf(fn);
222
+ if (idx >= 0) list.splice(idx, 1);
223
+ }
224
+ fire(event, ...args) {
225
+ (this._listeners[event] ?? []).forEach((fn) => fn(...args));
226
+ }
227
+ // -----------------------------------------------------------------------
228
+ // Path helpers
229
+ // -----------------------------------------------------------------------
230
+ getByPath(obj, path) {
231
+ const parts = path.split(".");
232
+ let current = obj;
233
+ for (const part of parts) {
234
+ if (current == null) return void 0;
235
+ current = current[part];
236
+ }
237
+ return current;
238
+ }
239
+ setByPath(obj, path, value) {
240
+ const parts = path.split(".");
241
+ let current = obj;
242
+ for (let i = 0; i < parts.length - 1; i++) {
243
+ if (current[parts[i]] == null) current[parts[i]] = {};
244
+ current = current[parts[i]];
245
+ }
246
+ current[parts[parts.length - 1]] = value;
247
+ }
248
+ deepClone(obj) {
249
+ return JSON.parse(JSON.stringify(obj));
250
+ }
251
+ };
252
+
253
+ // src/Binding.ts
254
+ var PATH_RE = /\{(!?)([^}]+)\}/g;
255
+ var Binding = {
256
+ /**
257
+ * Parse a binding expression string.
258
+ *
259
+ * - `'{name}'` → simple path
260
+ * - `'{!isAdmin}'` → negated path
261
+ * - `'{firstName} {last}'` → expression with multiple paths
262
+ * - `'{user.email}'` → nested path
263
+ */
264
+ parse(expr) {
265
+ const paths = [];
266
+ let negated = false;
267
+ let match;
268
+ const re = new RegExp(PATH_RE.source, PATH_RE.flags);
269
+ while ((match = re.exec(expr)) !== null) {
270
+ if (match[1] === "!") negated = true;
271
+ paths.push(match[2]);
272
+ }
273
+ const expression = paths.length > 1 || expr.replace(PATH_RE, "").trim().length > 0;
274
+ return { paths, negated, expression, template: expr };
275
+ },
276
+ /**
277
+ * Evaluate a parsed binding against a ViewModel.
278
+ */
279
+ evaluate(binding, vm) {
280
+ if (binding.expression) {
281
+ return binding.template.replace(PATH_RE, (_match, _neg, path) => {
282
+ const val = vm.get(path);
283
+ return val === null || val === void 0 ? "" : String(val);
284
+ });
285
+ }
286
+ const value = vm.get(binding.paths[0]);
287
+ if (binding.negated) return !value;
288
+ return value;
289
+ },
290
+ /**
291
+ * Create a two-way binding. Calls `onChange` whenever the path
292
+ * changes in the ViewModel. Returns a cleanup function.
293
+ */
294
+ twoWay(vm, path, onChange) {
295
+ const handler = () => {
296
+ onChange(vm.get(path));
297
+ };
298
+ vm.on("datachange", handler);
299
+ return () => vm.un("datachange", handler);
300
+ },
301
+ /**
302
+ * Multi-bind: bind to multiple paths, callback receives all values
303
+ * as an array whenever any path changes.
304
+ */
305
+ multiBind(vm, paths, onChange) {
306
+ const handler = () => {
307
+ onChange(paths.map((p) => vm.get(p)));
308
+ };
309
+ vm.on("datachange", handler);
310
+ return () => vm.un("datachange", handler);
311
+ },
312
+ /**
313
+ * Deep bind: notifies when any nested property under the given
314
+ * path changes. Uses the datachange event (fires on every set).
315
+ */
316
+ deepBind(vm, path, onChange) {
317
+ const handler = () => {
318
+ onChange(vm.get(path));
319
+ };
320
+ vm.on("datachange", handler);
321
+ return () => vm.un("datachange", handler);
322
+ }
323
+ };
324
+
325
+ // src/Router.ts
326
+ var routes = [];
327
+ var beforeRouteFn = null;
328
+ var hashListener = null;
329
+ var started = false;
330
+ function compileRoute(pattern) {
331
+ const paramNames = [];
332
+ let wildcard = null;
333
+ let regexStr = "^";
334
+ const parts = pattern.split("/");
335
+ for (let i = 0; i < parts.length; i++) {
336
+ const part = parts[i];
337
+ if (i > 0) regexStr += "\\/";
338
+ if (part.startsWith("*")) {
339
+ wildcard = part.slice(1);
340
+ paramNames.push(wildcard);
341
+ regexStr += "(.+)";
342
+ break;
343
+ } else if (part.startsWith(":")) {
344
+ paramNames.push(part.slice(1));
345
+ regexStr += "([^\\/]+)";
346
+ } else {
347
+ regexStr += part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
348
+ }
349
+ }
350
+ regexStr += "$";
351
+ return { paramNames, regex: new RegExp(regexStr), wildcard };
352
+ }
353
+ function onHashChange() {
354
+ const hash = Router.getCurrentHash();
355
+ if (beforeRouteFn && beforeRouteFn(hash) === false) return;
356
+ for (const route of routes) {
357
+ const match = hash.match(route.regex);
358
+ if (match) {
359
+ const params = {};
360
+ for (let i = 0; i < route.paramNames.length; i++) {
361
+ params[route.paramNames[i]] = match[i + 1] ?? "";
362
+ }
363
+ route.handler(params);
364
+ return;
365
+ }
366
+ }
367
+ }
368
+ var Router = {
369
+ addRoute(pattern, handler) {
370
+ const { paramNames, regex, wildcard } = compileRoute(pattern);
371
+ routes.push({ pattern, paramNames, regex, handler, wildcard });
372
+ },
373
+ getRoutes() {
374
+ return [...routes];
375
+ },
376
+ start() {
377
+ if (started) return;
378
+ started = true;
379
+ hashListener = () => onHashChange();
380
+ window.addEventListener("hashchange", hashListener);
381
+ },
382
+ stop() {
383
+ if (hashListener) {
384
+ window.removeEventListener("hashchange", hashListener);
385
+ hashListener = null;
386
+ }
387
+ started = false;
388
+ routes = [];
389
+ beforeRouteFn = null;
390
+ },
391
+ setBeforeRoute(fn) {
392
+ beforeRouteFn = fn;
393
+ },
394
+ navigateTo(hash) {
395
+ window.location.hash = hash;
396
+ },
397
+ getCurrentHash() {
398
+ return window.location.hash.replace(/^#\/?/, "");
399
+ }
400
+ };
401
+
402
+ // src/state/Scheduler.ts
403
+ var pending = false;
404
+ var flushing = false;
405
+ var flushListeners = [];
406
+ var processing = /* @__PURE__ */ new Set();
407
+ var Scheduler = {
408
+ /**
409
+ * Schedule a flush. Multiple calls before the flush coalesce
410
+ * into a single notification cycle.
411
+ */
412
+ schedule() {
413
+ if (pending) return;
414
+ pending = true;
415
+ queueMicrotask(() => {
416
+ if (pending) Scheduler.flush();
417
+ });
418
+ },
419
+ /**
420
+ * Synchronous flush — process all pending notifications immediately.
421
+ */
422
+ flush() {
423
+ if (flushing) return;
424
+ flushing = true;
425
+ pending = false;
426
+ for (const fn of flushListeners) fn();
427
+ flushing = false;
428
+ },
429
+ /** Register a flush callback. */
430
+ onFlush(fn) {
431
+ if (!flushListeners.includes(fn)) flushListeners.push(fn);
432
+ },
433
+ /** Unregister a flush callback. */
434
+ offFlush(fn) {
435
+ const idx = flushListeners.indexOf(fn);
436
+ if (idx >= 0) flushListeners.splice(idx, 1);
437
+ },
438
+ // -----------------------------------------------------------------------
439
+ // Circular dependency detection
440
+ // -----------------------------------------------------------------------
441
+ /** Begin a formula evaluation cycle. */
442
+ beginCycle() {
443
+ processing.clear();
444
+ },
445
+ /** End a formula evaluation cycle. */
446
+ endCycle() {
447
+ processing.clear();
448
+ },
449
+ /**
450
+ * Mark a stub as currently being processed. If it's already
451
+ * being processed, we have a circular dependency.
452
+ */
453
+ markProcessing(name) {
454
+ if (processing.has(name)) {
455
+ throw new Error(`Circular dependency detected: "${name}" is already being evaluated`);
456
+ }
457
+ processing.add(name);
458
+ },
459
+ /** Unmark a stub after processing completes. */
460
+ unmarkProcessing(name) {
461
+ processing.delete(name);
462
+ },
463
+ /** Reset all state (for tests). */
464
+ reset() {
465
+ pending = false;
466
+ flushing = false;
467
+ flushListeners.length = 0;
468
+ processing.clear();
469
+ }
470
+ };
471
+
472
+ // src/state/Stub.ts
473
+ var Stub = class {
474
+ name;
475
+ _dependents = [];
476
+ constructor(name) {
477
+ this.name = name;
478
+ }
479
+ addDependent(stub) {
480
+ if (!this._dependents.includes(stub)) {
481
+ this._dependents.push(stub);
482
+ }
483
+ }
484
+ removeDependent(stub) {
485
+ const idx = this._dependents.indexOf(stub);
486
+ if (idx >= 0) this._dependents.splice(idx, 1);
487
+ }
488
+ getDependents() {
489
+ return [...this._dependents];
490
+ }
491
+ };
492
+ var ValueStub = class extends Stub {
493
+ _value;
494
+ _dirty = false;
495
+ constructor(name, value) {
496
+ super(name);
497
+ this._value = value;
498
+ }
499
+ getValue() {
500
+ return this._value;
501
+ }
502
+ setValue(value) {
503
+ if (this._value !== value) {
504
+ this._value = value;
505
+ this._dirty = true;
506
+ }
507
+ }
508
+ isDirty() {
509
+ return this._dirty;
510
+ }
511
+ clearDirty() {
512
+ this._dirty = false;
513
+ }
514
+ };
515
+ var FormulaStub = class extends Stub {
516
+ _fn;
517
+ _dependencies = [];
518
+ _cachedValue = void 0;
519
+ _dirty = true;
520
+ constructor(name, fn) {
521
+ super(name);
522
+ this._fn = fn;
523
+ }
524
+ compute() {
525
+ this._cachedValue = this._fn();
526
+ this._dirty = false;
527
+ return this._cachedValue;
528
+ }
529
+ getCachedValue() {
530
+ return this._cachedValue;
531
+ }
532
+ markDirty() {
533
+ this._dirty = true;
534
+ }
535
+ isDirty() {
536
+ return this._dirty;
537
+ }
538
+ addDependency(stub) {
539
+ if (!this._dependencies.includes(stub)) {
540
+ this._dependencies.push(stub);
541
+ stub.addDependent(this);
542
+ }
543
+ }
544
+ getDependencies() {
545
+ return [...this._dependencies];
546
+ }
547
+ clearDependencies() {
548
+ for (const dep of this._dependencies) {
549
+ dep.removeDependent(this);
550
+ }
551
+ this._dependencies = [];
552
+ }
553
+ };
554
+ export {
555
+ Application,
556
+ Binding,
557
+ FormulaStub,
558
+ Router,
559
+ Scheduler,
560
+ Stub,
561
+ ValueStub,
562
+ ViewController,
563
+ ViewModel
564
+ };
565
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Application.ts","../src/ViewController.ts","../src/ViewModel.ts","../src/Binding.ts","../src/Router.ts","../src/state/Scheduler.ts","../src/state/Stub.ts"],"sourcesContent":["/**\n * @framesquared/app – Application\n *\n * The entry point for an framesquared application. Manages lifecycle\n * (init → beforeLaunch → launch → onLaunch), controller and\n * store registries, and provides a singleton accessor.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ViewController } from './ViewController.js';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface ApplicationConfig {\n name: string;\n appFolder?: string;\n controllers?: string[];\n stores?: string[];\n mainView?: string;\n launch?: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\nlet instance: Application | null = null;\n\n// ---------------------------------------------------------------------------\n// Application\n// ---------------------------------------------------------------------------\n\nexport class Application {\n private _name: string;\n private _launchFn: (() => void) | null;\n private _controllers = new Map<string, ViewController>();\n private _stores = new Map<string, any>();\n\n /** Overridable lifecycle hooks. */\n onInit: () => void = () => {};\n onBeforeLaunch: () => void = () => {};\n onLaunch: () => void = () => {};\n\n constructor(config: ApplicationConfig) {\n this._name = config.name;\n this._launchFn = config.launch ?? null;\n instance = this;\n }\n\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n\n start(): void {\n this.onInit();\n this.onBeforeLaunch();\n this._launchFn?.();\n this.onLaunch();\n }\n\n // -----------------------------------------------------------------------\n // Accessors\n // -----------------------------------------------------------------------\n\n getName(): string {\n return this._name;\n }\n\n // -----------------------------------------------------------------------\n // Controllers\n // -----------------------------------------------------------------------\n\n registerController(name: string, controller: ViewController): void {\n this._controllers.set(name, controller);\n }\n\n getController(name: string): ViewController | undefined {\n return this._controllers.get(name);\n }\n\n // -----------------------------------------------------------------------\n // Stores\n // -----------------------------------------------------------------------\n\n registerStore(name: string, store: any): void {\n this._stores.set(name, store);\n }\n\n getStore(name: string): any {\n return this._stores.get(name);\n }\n\n // -----------------------------------------------------------------------\n // Singleton\n // -----------------------------------------------------------------------\n\n static getInstance(): Application | null {\n return instance;\n }\n\n static clearInstance(): void {\n instance = null;\n }\n}\n","/**\n * @framesquared/app – ViewController\n *\n * Tied to a specific view (Component). Provides `control` config\n * for auto-wiring DOM event handlers via CSS selectors, view reference\n * lookup, and lifecycle management.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface ViewControllerConfig {\n id?: string;\n control?: Record<string, Record<string, string>>;\n listen?: Record<string, any>;\n routes?: Record<string, string>;\n}\n\nexport class ViewController {\n private _id: string;\n private _view: any = null;\n private _control: Record<string, Record<string, string>>;\n private _cleanups: (() => void)[] = [];\n\n constructor(config: ViewControllerConfig = {}) {\n this._id = config.id ?? '';\n this._control = config.control ?? {};\n }\n\n getId(): string {\n return this._id;\n }\n\n // -----------------------------------------------------------------------\n // View lifecycle\n // -----------------------------------------------------------------------\n\n init(view: any): void {\n this._view = view;\n }\n\n destroy(): void {\n for (const cleanup of this._cleanups) cleanup();\n this._cleanups = [];\n this._view = null;\n }\n\n getView(): any {\n return this._view;\n }\n\n // -----------------------------------------------------------------------\n // References\n // -----------------------------------------------------------------------\n\n lookupReference(ref: string): any {\n return this._view?.lookupReference?.(ref) ?? null;\n }\n\n // -----------------------------------------------------------------------\n // Events\n // -----------------------------------------------------------------------\n\n fireViewEvent(eventName: string, ...args: unknown[]): void {\n const view = this._view;\n if (!view) return;\n if (typeof view.fireEvent === 'function') {\n view.fireEvent(eventName, ...args);\n } else if (typeof view.fire === 'function') {\n view.fire(eventName, ...args);\n }\n }\n\n // -----------------------------------------------------------------------\n // Control — auto-wire DOM events\n // -----------------------------------------------------------------------\n\n applyControl(): void {\n if (!this._view?.el) return;\n\n for (const [selector, events] of Object.entries(this._control)) {\n // Convert ExtJS-style selectors to CSS\n const cssSelector = this.toCssSelector(selector);\n const elements = this._view.el.querySelectorAll(cssSelector);\n\n for (const el of elements) {\n for (const [eventName, methodName] of Object.entries(events)) {\n const method = (this as any)[methodName];\n if (typeof method !== 'function') continue;\n const handler = (...args: unknown[]) => method.call(this, ...args);\n (el as HTMLElement).addEventListener(eventName, handler);\n this._cleanups.push(() => (el as HTMLElement).removeEventListener(eventName, handler));\n }\n }\n }\n }\n\n private toCssSelector(sel: string): string {\n // Convert #id to [id=\"id\"], keep other selectors as-is\n return sel.replace(/#(\\w+)/g, '[id=\"$1\"]');\n }\n\n // -----------------------------------------------------------------------\n // Routing\n // -----------------------------------------------------------------------\n\n redirectTo(hash: string, _force?: boolean): void {\n window.location.hash = hash;\n }\n}\n","/**\n * @framesquared/app – ViewModel\n *\n * Provides data binding for views. Supports nested path access,\n * computed formulas with automatic dependency tracking via get(),\n * named stores, parent→child hierarchy with inheritance, and\n * change notification.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface ViewModelConfig {\n data?: Record<string, unknown>;\n formulas?: Record<string, (get: (path: string) => unknown) => unknown>;\n stores?: Record<string, { data: unknown[] }>;\n parent?: ViewModel;\n}\n\nexport class ViewModel {\n private _data: Record<string, unknown>;\n private _formulas: Record<string, (get: (path: string) => unknown) => unknown>;\n private _stores: Record<string, { data: unknown[] }>;\n private _listeners: Record<string, Function[]> = {};\n private _parent: ViewModel | null;\n private _children: ViewModel[] = [];\n private _parentHandler: Function | null = null;\n\n constructor(config: ViewModelConfig = {}) {\n this._data = config.data ? this.deepClone(config.data) : {};\n this._formulas = config.formulas ?? {};\n this._stores = {};\n this._parent = config.parent ?? null;\n\n // Create stores\n if (config.stores) {\n for (const [name, storeCfg] of Object.entries(config.stores)) {\n this._stores[name] = { data: [...(storeCfg.data ?? [])] };\n }\n }\n\n // Register with parent\n if (this._parent) {\n this._parent._children.push(this);\n this._parentHandler = () => this.fire('datachange', this);\n this._parent.on('datachange', this._parentHandler);\n }\n }\n\n // -----------------------------------------------------------------------\n // Data access\n // -----------------------------------------------------------------------\n\n get(path: string): unknown {\n // Check formulas first (local only)\n if (path in this._formulas) {\n return this.computeFormula(path);\n }\n\n // Check local data\n const localVal = this.getByPath(this._data, path);\n if (localVal !== undefined) return localVal;\n\n // Fall back to parent\n if (this._parent) return this._parent.get(path);\n return undefined;\n }\n\n set(pathOrObj: string | Record<string, unknown>, value?: unknown): void {\n if (typeof pathOrObj === 'string') {\n this.setByPath(this._data, pathOrObj, value);\n } else {\n for (const [key, val] of Object.entries(pathOrObj)) {\n this.setByPath(this._data, key, val);\n }\n }\n this.fire('datachange', this);\n }\n\n // -----------------------------------------------------------------------\n // Formulas — get() callback reads both data and other formulas\n // -----------------------------------------------------------------------\n\n private computeFormula(name: string): unknown {\n const fn = this._formulas[name];\n if (!fn) return undefined;\n // The getter passed to formulas uses this.get() so formulas can\n // read other formulas and parent data transparently.\n const getter = (path: string): unknown => this.get(path);\n return fn(getter);\n }\n\n // -----------------------------------------------------------------------\n // Stores\n // -----------------------------------------------------------------------\n\n getStore(name: string): any {\n return this._stores[name] ?? null;\n }\n\n // -----------------------------------------------------------------------\n // Hierarchy\n // -----------------------------------------------------------------------\n\n getParent(): ViewModel | null {\n return this._parent;\n }\n\n getRoot(): ViewModel {\n let current: ViewModel = this;\n while (current._parent) current = current._parent;\n return current;\n }\n\n // -----------------------------------------------------------------------\n // Events\n // -----------------------------------------------------------------------\n\n on(event: string, fn: Function): void {\n (this._listeners[event] ??= []).push(fn);\n }\n\n un(event: string, fn: Function): void {\n const list = this._listeners[event];\n if (!list) return;\n const idx = list.indexOf(fn);\n if (idx >= 0) list.splice(idx, 1);\n }\n\n private fire(event: string, ...args: unknown[]): void {\n (this._listeners[event] ?? []).forEach(fn => fn(...args));\n }\n\n // -----------------------------------------------------------------------\n // Path helpers\n // -----------------------------------------------------------------------\n\n private getByPath(obj: any, path: string): unknown {\n const parts = path.split('.');\n let current = obj;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n }\n\n private setByPath(obj: any, path: string, value: unknown): void {\n const parts = path.split('.');\n let current = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (current[parts[i]] == null) current[parts[i]] = {};\n current = current[parts[i]];\n }\n current[parts[parts.length - 1]] = value;\n }\n\n private deepClone(obj: Record<string, unknown>): Record<string, unknown> {\n return JSON.parse(JSON.stringify(obj));\n }\n}\n","/**\n * @framesquared/app – Binding\n *\n * Parses and evaluates binding expressions that connect ViewModel\n * data to component configs. Supports simple paths, negation,\n * expression templates, and two-way binding.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ViewModel } from './ViewModel.js';\n\n// ---------------------------------------------------------------------------\n// Parsed binding\n// ---------------------------------------------------------------------------\n\nexport interface ParsedBinding {\n paths: string[];\n negated: boolean;\n expression: boolean;\n template: string;\n}\n\n// ---------------------------------------------------------------------------\n// Binding\n// ---------------------------------------------------------------------------\n\nconst PATH_RE = /\\{(!?)([^}]+)\\}/g;\n\nexport const Binding = {\n /**\n * Parse a binding expression string.\n *\n * - `'{name}'` → simple path\n * - `'{!isAdmin}'` → negated path\n * - `'{firstName} {last}'` → expression with multiple paths\n * - `'{user.email}'` → nested path\n */\n parse(expr: string): ParsedBinding {\n const paths: string[] = [];\n let negated = false;\n let match: RegExpExecArray | null;\n const re = new RegExp(PATH_RE.source, PATH_RE.flags);\n\n while ((match = re.exec(expr)) !== null) {\n if (match[1] === '!') negated = true;\n paths.push(match[2]);\n }\n\n const expression = paths.length > 1 ||\n expr.replace(PATH_RE, '').trim().length > 0;\n\n return { paths, negated, expression, template: expr };\n },\n\n /**\n * Evaluate a parsed binding against a ViewModel.\n */\n evaluate(binding: ParsedBinding, vm: ViewModel): unknown {\n if (binding.expression) {\n // Template expression: replace each {path} with its value\n return binding.template.replace(PATH_RE, (_match, _neg, path) => {\n const val = vm.get(path);\n return val === null || val === undefined ? '' : String(val);\n });\n }\n\n // Single path\n const value = vm.get(binding.paths[0]);\n if (binding.negated) return !value;\n return value;\n },\n\n /**\n * Create a two-way binding. Calls `onChange` whenever the path\n * changes in the ViewModel. Returns a cleanup function.\n */\n twoWay(vm: ViewModel, path: string, onChange: (value: unknown) => void): () => void {\n const handler = () => {\n onChange(vm.get(path));\n };\n vm.on('datachange', handler);\n return () => vm.un('datachange', handler);\n },\n\n /**\n * Multi-bind: bind to multiple paths, callback receives all values\n * as an array whenever any path changes.\n */\n multiBind(vm: ViewModel, paths: string[], onChange: (values: unknown[]) => void): () => void {\n const handler = () => {\n onChange(paths.map(p => vm.get(p)));\n };\n vm.on('datachange', handler);\n return () => vm.un('datachange', handler);\n },\n\n /**\n * Deep bind: notifies when any nested property under the given\n * path changes. Uses the datachange event (fires on every set).\n */\n deepBind(vm: ViewModel, path: string, onChange: (value: unknown) => void): () => void {\n const handler = () => {\n onChange(vm.get(path));\n };\n vm.on('datachange', handler);\n return () => vm.un('datachange', handler);\n },\n};\n","/**\n * @framesquared/app – Router\n *\n * Hash-based routing using the hashchange event. Routes are\n * patterns like 'user/:id' that extract named parameters.\n * Supports wildcard routes, before-route guards, and navigateTo.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface RouteEntry {\n pattern: string;\n paramNames: string[];\n regex: RegExp;\n handler: (params: Record<string, string>) => void;\n wildcard: string | null;\n}\n\nlet routes: RouteEntry[] = [];\nlet beforeRouteFn: ((hash: string) => boolean) | null = null;\nlet hashListener: ((e: Event) => void) | null = null;\nlet started = false;\n\nfunction compileRoute(pattern: string): { paramNames: string[]; regex: RegExp; wildcard: string | null } {\n const paramNames: string[] = [];\n let wildcard: string | null = null;\n\n let regexStr = '^';\n const parts = pattern.split('/');\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (i > 0) regexStr += '\\\\/';\n\n if (part.startsWith('*')) {\n // Wildcard — captures everything after this point\n wildcard = part.slice(1);\n paramNames.push(wildcard);\n regexStr += '(.+)';\n break; // wildcard consumes the rest\n } else if (part.startsWith(':')) {\n paramNames.push(part.slice(1));\n regexStr += '([^\\\\/]+)';\n } else {\n regexStr += part.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n }\n\n regexStr += '$';\n return { paramNames, regex: new RegExp(regexStr), wildcard };\n}\n\nfunction onHashChange(): void {\n const hash = Router.getCurrentHash();\n\n // Before route guard\n if (beforeRouteFn && beforeRouteFn(hash) === false) return;\n\n for (const route of routes) {\n const match = hash.match(route.regex);\n if (match) {\n const params: Record<string, string> = {};\n for (let i = 0; i < route.paramNames.length; i++) {\n params[route.paramNames[i]] = match[i + 1] ?? '';\n }\n route.handler(params);\n return;\n }\n }\n}\n\nexport const Router = {\n addRoute(pattern: string, handler: (params: Record<string, string>) => void): void {\n const { paramNames, regex, wildcard } = compileRoute(pattern);\n routes.push({ pattern, paramNames, regex, handler, wildcard });\n },\n\n getRoutes(): RouteEntry[] {\n return [...routes];\n },\n\n start(): void {\n if (started) return;\n started = true;\n hashListener = () => onHashChange();\n window.addEventListener('hashchange', hashListener);\n },\n\n stop(): void {\n if (hashListener) {\n window.removeEventListener('hashchange', hashListener);\n hashListener = null;\n }\n started = false;\n routes = [];\n beforeRouteFn = null;\n },\n\n setBeforeRoute(fn: ((hash: string) => boolean) | null): void {\n beforeRouteFn = fn;\n },\n\n navigateTo(hash: string): void {\n window.location.hash = hash;\n },\n\n getCurrentHash(): string {\n return window.location.hash.replace(/^#\\/?/, '');\n },\n};\n","/**\n * @framesquared/app – Scheduler\n *\n * Batches multiple state changes into a single notification cycle.\n * Uses microtask (queueMicrotask) for async batching. Provides\n * synchronous flush() for immediate processing. Detects circular\n * dependencies during formula evaluation.\n */\n\nlet pending = false;\nlet flushing = false;\nconst flushListeners: Function[] = [];\nconst processing = new Set<string>();\n\nexport const Scheduler = {\n /**\n * Schedule a flush. Multiple calls before the flush coalesce\n * into a single notification cycle.\n */\n schedule(): void {\n if (pending) return;\n pending = true;\n queueMicrotask(() => {\n if (pending) Scheduler.flush();\n });\n },\n\n /**\n * Synchronous flush — process all pending notifications immediately.\n */\n flush(): void {\n if (flushing) return;\n flushing = true;\n pending = false;\n for (const fn of flushListeners) fn();\n flushing = false;\n },\n\n /** Register a flush callback. */\n onFlush(fn: Function): void {\n if (!flushListeners.includes(fn)) flushListeners.push(fn);\n },\n\n /** Unregister a flush callback. */\n offFlush(fn: Function): void {\n const idx = flushListeners.indexOf(fn);\n if (idx >= 0) flushListeners.splice(idx, 1);\n },\n\n // -----------------------------------------------------------------------\n // Circular dependency detection\n // -----------------------------------------------------------------------\n\n /** Begin a formula evaluation cycle. */\n beginCycle(): void {\n processing.clear();\n },\n\n /** End a formula evaluation cycle. */\n endCycle(): void {\n processing.clear();\n },\n\n /**\n * Mark a stub as currently being processed. If it's already\n * being processed, we have a circular dependency.\n */\n markProcessing(name: string): void {\n if (processing.has(name)) {\n throw new Error(`Circular dependency detected: \"${name}\" is already being evaluated`);\n }\n processing.add(name);\n },\n\n /** Unmark a stub after processing completes. */\n unmarkProcessing(name: string): void {\n processing.delete(name);\n },\n\n /** Reset all state (for tests). */\n reset(): void {\n pending = false;\n flushing = false;\n flushListeners.length = 0;\n processing.clear();\n },\n};\n","/**\n * @framesquared/app – Stub\n *\n * Internal representation of data nodes in the ViewModel.\n * ValueStub holds plain values with dirty tracking.\n * FormulaStub holds computed functions with dependency management.\n */\n\n// ---------------------------------------------------------------------------\n// Base Stub\n// ---------------------------------------------------------------------------\n\nexport abstract class Stub {\n readonly name: string;\n private _dependents: Stub[] = [];\n\n constructor(name: string) {\n this.name = name;\n }\n\n addDependent(stub: Stub): void {\n if (!this._dependents.includes(stub)) {\n this._dependents.push(stub);\n }\n }\n\n removeDependent(stub: Stub): void {\n const idx = this._dependents.indexOf(stub);\n if (idx >= 0) this._dependents.splice(idx, 1);\n }\n\n getDependents(): Stub[] {\n return [...this._dependents];\n }\n}\n\n// ---------------------------------------------------------------------------\n// ValueStub\n// ---------------------------------------------------------------------------\n\nexport class ValueStub extends Stub {\n private _value: unknown;\n private _dirty = false;\n\n constructor(name: string, value: unknown) {\n super(name);\n this._value = value;\n }\n\n getValue(): unknown {\n return this._value;\n }\n\n setValue(value: unknown): void {\n if (this._value !== value) {\n this._value = value;\n this._dirty = true;\n }\n }\n\n isDirty(): boolean {\n return this._dirty;\n }\n\n clearDirty(): void {\n this._dirty = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// FormulaStub\n// ---------------------------------------------------------------------------\n\nexport class FormulaStub extends Stub {\n private _fn: () => unknown;\n private _dependencies: Stub[] = [];\n private _cachedValue: unknown = undefined;\n private _dirty = true;\n\n constructor(name: string, fn: () => unknown) {\n super(name);\n this._fn = fn;\n }\n\n compute(): unknown {\n this._cachedValue = this._fn();\n this._dirty = false;\n return this._cachedValue;\n }\n\n getCachedValue(): unknown {\n return this._cachedValue;\n }\n\n markDirty(): void {\n this._dirty = true;\n }\n\n isDirty(): boolean {\n return this._dirty;\n }\n\n addDependency(stub: Stub): void {\n if (!this._dependencies.includes(stub)) {\n this._dependencies.push(stub);\n stub.addDependent(this);\n }\n }\n\n getDependencies(): Stub[] {\n return [...this._dependencies];\n }\n\n clearDependencies(): void {\n for (const dep of this._dependencies) {\n dep.removeDependent(this);\n }\n this._dependencies = [];\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@framesquared/app",
3
+ "version": "0.1.0",
4
+ "description": "Application architecture (MVC/MVVM)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@framesquared/core": "0.1.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "test": "vitest run",
23
+ "clean": "rm -rf dist",
24
+ "typecheck": "tsc --noEmit --project tsconfig.build.json"
25
+ }
26
+ }