mvc-kit 2.12.0 → 2.12.2

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.
Files changed (139) hide show
  1. package/agent-config/bin/postinstall.mjs +5 -3
  2. package/agent-config/bin/setup.mjs +3 -4
  3. package/agent-config/claude-code/agents/mvc-kit-architect.md +14 -0
  4. package/agent-config/claude-code/skills/guide/api-reference.md +24 -2
  5. package/agent-config/lib/install-claude.mjs +19 -33
  6. package/dist/Model.cjs +9 -1
  7. package/dist/Model.cjs.map +1 -1
  8. package/dist/Model.d.ts +1 -1
  9. package/dist/Model.d.ts.map +1 -1
  10. package/dist/Model.js +9 -1
  11. package/dist/Model.js.map +1 -1
  12. package/dist/ViewModel.cjs +9 -1
  13. package/dist/ViewModel.cjs.map +1 -1
  14. package/dist/ViewModel.d.ts +1 -1
  15. package/dist/ViewModel.d.ts.map +1 -1
  16. package/dist/ViewModel.js +9 -1
  17. package/dist/ViewModel.js.map +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/mvc-kit.cjs +3 -0
  21. package/dist/mvc-kit.cjs.map +1 -1
  22. package/dist/mvc-kit.js +3 -0
  23. package/dist/mvc-kit.js.map +1 -1
  24. package/dist/produceDraft.cjs +105 -0
  25. package/dist/produceDraft.cjs.map +1 -0
  26. package/dist/produceDraft.d.ts +19 -0
  27. package/dist/produceDraft.d.ts.map +1 -0
  28. package/dist/produceDraft.js +105 -0
  29. package/dist/produceDraft.js.map +1 -0
  30. package/package.json +4 -2
  31. package/src/Channel.md +408 -0
  32. package/src/Channel.test.ts +957 -0
  33. package/src/Channel.ts +429 -0
  34. package/src/Collection.md +533 -0
  35. package/src/Collection.test.ts +1559 -0
  36. package/src/Collection.ts +653 -0
  37. package/src/Controller.md +306 -0
  38. package/src/Controller.test.ts +380 -0
  39. package/src/Controller.ts +90 -0
  40. package/src/EventBus.md +308 -0
  41. package/src/EventBus.test.ts +295 -0
  42. package/src/EventBus.ts +110 -0
  43. package/src/Feed.md +218 -0
  44. package/src/Feed.test.ts +442 -0
  45. package/src/Feed.ts +101 -0
  46. package/src/Model.md +524 -0
  47. package/src/Model.test.ts +642 -0
  48. package/src/Model.ts +260 -0
  49. package/src/Pagination.md +168 -0
  50. package/src/Pagination.test.ts +244 -0
  51. package/src/Pagination.ts +92 -0
  52. package/src/Pending.md +380 -0
  53. package/src/Pending.test.ts +1719 -0
  54. package/src/Pending.ts +390 -0
  55. package/src/PersistentCollection.md +183 -0
  56. package/src/PersistentCollection.test.ts +649 -0
  57. package/src/PersistentCollection.ts +375 -0
  58. package/src/Resource.ViewModel.test.ts +503 -0
  59. package/src/Resource.md +239 -0
  60. package/src/Resource.test.ts +786 -0
  61. package/src/Resource.ts +231 -0
  62. package/src/Selection.md +155 -0
  63. package/src/Selection.test.ts +326 -0
  64. package/src/Selection.ts +117 -0
  65. package/src/Service.md +440 -0
  66. package/src/Service.test.ts +241 -0
  67. package/src/Service.ts +72 -0
  68. package/src/Sorting.md +170 -0
  69. package/src/Sorting.test.ts +334 -0
  70. package/src/Sorting.ts +135 -0
  71. package/src/Trackable.md +166 -0
  72. package/src/Trackable.test.ts +236 -0
  73. package/src/Trackable.ts +129 -0
  74. package/src/ViewModel.async.test.ts +813 -0
  75. package/src/ViewModel.derived.test.ts +1583 -0
  76. package/src/ViewModel.md +1111 -0
  77. package/src/ViewModel.test.ts +1236 -0
  78. package/src/ViewModel.ts +800 -0
  79. package/src/bindPublicMethods.test.ts +126 -0
  80. package/src/bindPublicMethods.ts +48 -0
  81. package/src/env.d.ts +5 -0
  82. package/src/errors.test.ts +155 -0
  83. package/src/errors.ts +133 -0
  84. package/src/index.ts +49 -0
  85. package/src/produceDraft.md +90 -0
  86. package/src/produceDraft.test.ts +394 -0
  87. package/src/produceDraft.ts +168 -0
  88. package/src/react/components/CardList.md +97 -0
  89. package/src/react/components/CardList.test.tsx +142 -0
  90. package/src/react/components/CardList.tsx +68 -0
  91. package/src/react/components/DataTable.md +179 -0
  92. package/src/react/components/DataTable.test.tsx +599 -0
  93. package/src/react/components/DataTable.tsx +267 -0
  94. package/src/react/components/InfiniteScroll.md +116 -0
  95. package/src/react/components/InfiniteScroll.test.tsx +218 -0
  96. package/src/react/components/InfiniteScroll.tsx +70 -0
  97. package/src/react/components/types.ts +90 -0
  98. package/src/react/derived.test.tsx +261 -0
  99. package/src/react/guards.ts +24 -0
  100. package/src/react/index.ts +40 -0
  101. package/src/react/provider.test.tsx +143 -0
  102. package/src/react/provider.tsx +55 -0
  103. package/src/react/strict-mode.test.tsx +266 -0
  104. package/src/react/types.ts +25 -0
  105. package/src/react/use-event-bus.md +214 -0
  106. package/src/react/use-event-bus.test.tsx +168 -0
  107. package/src/react/use-event-bus.ts +40 -0
  108. package/src/react/use-instance.md +204 -0
  109. package/src/react/use-instance.test.tsx +350 -0
  110. package/src/react/use-instance.ts +60 -0
  111. package/src/react/use-local.md +457 -0
  112. package/src/react/use-local.rapid-remount.test.tsx +503 -0
  113. package/src/react/use-local.test.tsx +692 -0
  114. package/src/react/use-local.ts +165 -0
  115. package/src/react/use-model.md +364 -0
  116. package/src/react/use-model.test.tsx +394 -0
  117. package/src/react/use-model.ts +161 -0
  118. package/src/react/use-singleton.md +415 -0
  119. package/src/react/use-singleton.test.tsx +296 -0
  120. package/src/react/use-singleton.ts +69 -0
  121. package/src/react/use-subscribe-only.ts +39 -0
  122. package/src/react/use-teardown.md +169 -0
  123. package/src/react/use-teardown.test.tsx +86 -0
  124. package/src/react/use-teardown.ts +27 -0
  125. package/src/react-native/NativeCollection.test.ts +250 -0
  126. package/src/react-native/NativeCollection.ts +138 -0
  127. package/src/react-native/index.ts +1 -0
  128. package/src/singleton.md +310 -0
  129. package/src/singleton.test.ts +204 -0
  130. package/src/singleton.ts +70 -0
  131. package/src/types.ts +70 -0
  132. package/src/walkPrototypeChain.ts +22 -0
  133. package/src/web/IndexedDBCollection.test.ts +235 -0
  134. package/src/web/IndexedDBCollection.ts +66 -0
  135. package/src/web/WebStorageCollection.test.ts +214 -0
  136. package/src/web/WebStorageCollection.ts +116 -0
  137. package/src/web/idb.ts +184 -0
  138. package/src/web/index.ts +2 -0
  139. package/src/wrapAsyncMethods.ts +249 -0
@@ -21,14 +21,16 @@ if (resolve(projectRoot) === ownPackageRoot) {
21
21
  }
22
22
 
23
23
  // If the developer previously opted in (files exist), auto-update them
24
- const rulesFile = join(projectRoot, '.claude', 'rules', 'mvc-kit.md');
24
+ const skillFile = join(projectRoot, '.claude', 'commands', 'mvc-kit.md');
25
+ // Also detect legacy rules file from older versions
26
+ const legacyRulesFile = join(projectRoot, '.claude', 'rules', 'mvc-kit.md');
25
27
 
26
- if (existsSync(rulesFile)) {
28
+ if (existsSync(skillFile) || existsSync(legacyRulesFile)) {
27
29
  try {
28
30
  const { installClaude } = await import('../lib/install-claude.mjs');
29
31
  installClaude(projectRoot);
30
32
  console.log('');
31
- console.log(' mvc-kit — Claude Code rules updated');
33
+ console.log(' mvc-kit — Claude Code files updated');
32
34
  console.log('');
33
35
  } catch {
34
36
  // Never break npm install
@@ -23,7 +23,7 @@ function printUsage() {
23
23
  Usage: npx mvc-kit-setup [targets...] [options]
24
24
 
25
25
  Targets:
26
- claude Install Claude Code rules and commands into .claude/
26
+ claude Install Claude Code skill and agent into .claude/
27
27
  cursor Copy mvc-kit rules to .cursorrules
28
28
  copilot Copy mvc-kit instructions to .github/copilot-instructions.md
29
29
  all Set up all targets (default)
@@ -87,9 +87,8 @@ function setupClaude() {
87
87
  console.log(` ${f}`);
88
88
  }
89
89
  console.log('');
90
- console.log(' Commands: /project:mvc-kit-scaffold, /project:mvc-kit-review');
91
- console.log(' Agent: mvc-kit-architect (architecture planning)');
92
- console.log(' Reference: Auto-loaded via .claude/rules/mvc-kit.md\n');
90
+ console.log(' Skill: /project:mvc-kit (framework reference, on-demand)');
91
+ console.log(' Agent: mvc-kit-architect (architecture planning)\n');
93
92
  }
94
93
 
95
94
  async function setupCursor(force) {
@@ -6,6 +6,20 @@ model: sonnet
6
6
 
7
7
  You are an architecture planning agent for applications built with **mvc-kit**, a TypeScript-first reactive state management library for React. You help developers plan features by deciding which classes to create, designing state shapes, and selecting sharing patterns.
8
8
 
9
+ ## Documentation
10
+
11
+ For detailed, up-to-date documentation on any class or hook, search the `.md` files colocated with source in `node_modules/mvc-kit/src/`. Read these when you need specifics beyond the summary tables below.
12
+
13
+ **Core classes:** `ViewModel.md`, `Model.md`, `Collection.md`, `PersistentCollection.md`, `Resource.md`, `Service.md`, `EventBus.md`, `Channel.md`, `Controller.md`, `Trackable.md`, `singleton.md`
14
+ **Composable helpers:** `Sorting.md`, `Pagination.md`, `Selection.md`, `Feed.md`, `Pending.md`, `produceDraft.md`
15
+ **React hooks:** `react/use-local.md`, `react/use-instance.md`, `react/use-singleton.md`, `react/use-model.md`, `react/use-event-bus.md`, `react/use-teardown.md`
16
+ **Headless components:** `react/components/DataTable.md`, `react/components/CardList.md`, `react/components/InfiniteScroll.md`
17
+
18
+ Additional reference files in `node_modules/mvc-kit/agent-config/claude-code/skills/guide/`:
19
+ - `api-reference.md` — Full API reference for all classes and hooks
20
+ - `patterns.md` — Prescribed patterns with code examples
21
+ - `anti-patterns.md` — Anti-patterns to reject with fixes
22
+
9
23
  ## Core Classes
10
24
 
11
25
  | Class | Role | Scope |
@@ -6,7 +6,7 @@
6
6
  // Core classes and utilities
7
7
  import { ViewModel, Model, Collection, PersistentCollection, Resource, Controller, Service, EventBus, Channel } from 'mvc-kit';
8
8
  import { Sorting, Pagination, Selection, Feed, Pending } from 'mvc-kit';
9
- import { Trackable, bindPublicMethods } from 'mvc-kit';
9
+ import { Trackable, bindPublicMethods, produceDraft } from 'mvc-kit';
10
10
  import { singleton, hasSingleton, teardown, teardownAll } from 'mvc-kit';
11
11
  import { HttpError, isAbortError, classifyError } from 'mvc-kit';
12
12
  import type { Subscribable, Disposable, Initializable, Listener, Updater, ValidationErrors, TaskState, EventSource, EventPayload, AppError, AsyncMethodKeys, ResourceAsyncMethodKeys, ChannelStatus, SortDescriptor, FeedPage, PendingOperation, PendingEntry } from 'mvc-kit';
@@ -37,7 +37,8 @@ new MyViewModel(initialState: S)
37
37
  ### State
38
38
  - `state: S` — Current frozen state. Read `state.x` for raw values.
39
39
  - `set(partial: Partial<S>)` — Merge partial state. Skips if no values change.
40
- - `set(updater: (prev: S) => Partial<S>)` — Functional update.
40
+ - `set(updater: (prev: S) => Partial<S>)` — Functional update (return a partial).
41
+ - `set(drafter: (draft: S) => void)` — Draft mode: mutate the draft proxy, return nothing. Uses `produceDraft` internally.
41
42
  - `reset(newState?: S)` — Tear down lifecycle, re-initialize. Clears async tracking, re-runs `onInit()`.
42
43
 
43
44
  ### Computed Getters
@@ -94,6 +95,7 @@ new MyModel(initialState: S)
94
95
  ### State & Validation
95
96
  - `state: S` — Current state.
96
97
  - `set(partial: Partial<S>)` — Update state, re-validates.
98
+ - `set(drafter: (draft: S) => void)` — Draft mode: mutate the draft, return nothing.
97
99
  - `errors: ValidationErrors<S>` — `Partial<Record<keyof S, string>>`.
98
100
  - `valid: boolean` — True when `errors` is empty.
99
101
  - `dirty: boolean` — True when state differs from committed state.
@@ -327,6 +329,26 @@ greet(); // 'hello' — no lost `this`
327
329
 
328
330
  ---
329
331
 
332
+ ## produceDraft(state, mutator)
333
+
334
+ Copy-on-write draft utility. Creates a proxy of a frozen state object, runs a mutator, and returns only changed top-level keys as a `Partial<S>` (or `null` if nothing changed). Used internally by `ViewModel.set()` and `Model.set()` for draft mode.
335
+
336
+ ```typescript
337
+ import { produceDraft } from 'mvc-kit';
338
+
339
+ const changes = produceDraft(state, draft => {
340
+ draft.user.name = 'Bob';
341
+ });
342
+ // { user: { name: 'Bob', ...unchanged fields } } or null
343
+ ```
344
+
345
+ - Nested plain objects use structural sharing (unchanged subtrees keep original references)
346
+ - Same-value assignment is a no-op (returns `null`)
347
+ - Arrays must be replaced via assignment (`draft.items = [...]`), not mutated in place
348
+ - Only POJOs are proxied; class instances, Dates, Maps pass through as-is
349
+
350
+ ---
351
+
330
352
  ## Composable Helpers
331
353
 
332
354
  Plain classes that extend `Trackable` — auto-tracked when declared as ViewModel instance properties. Each has an `apply()` method that transforms arrays. Helpers manage state; ViewModels compose them in getters.
@@ -24,33 +24,38 @@ function ensureDir(dir) {
24
24
  * Install Claude Code integration files into a project's .claude/ directory.
25
25
  *
26
26
  * Creates:
27
- * .claude/rules/mvc-kit.md — Framework reference (always loaded in context)
28
- * .claude/commands/mvc-kit-scaffold.md — Scaffold command (/project:mvc-kit-scaffold)
29
- * .claude/commands/mvc-kit-review.md — Review command (/project:mvc-kit-review)
30
- * .claude/agents/mvc-kit-architect.md — Architecture planning agent
27
+ * .claude/commands/mvc-kit.md — Framework reference skill (on-demand via /project:mvc-kit)
28
+ * .claude/agents/mvc-kit-architect.md — Architecture planning agent
31
29
  *
32
30
  * @param {string} projectRoot — Absolute path to the consuming project's root
33
31
  * @returns {{ files: string[] }} — List of created/updated file paths (relative to projectRoot)
34
32
  */
35
33
  export function installClaude(projectRoot) {
36
34
  const claudeDir = join(projectRoot, '.claude');
37
- const rulesDir = join(claudeDir, 'rules');
38
35
  const commandsDir = join(claudeDir, 'commands');
39
36
  const agentsDir = join(claudeDir, 'agents');
40
37
 
41
- ensureDir(rulesDir);
42
38
  ensureDir(commandsDir);
43
39
  ensureDir(agentsDir);
44
40
 
45
41
  const files = [];
46
42
 
47
- // 1. Rules file — framework reference (always loaded in context)
43
+ // 1. Guide skill — framework reference (on-demand, model-invocable)
48
44
  const guideSkill = readFileSync(join(SKILLS_DIR, 'guide', 'SKILL.md'), 'utf-8');
49
45
  const guideBody = stripFrontmatter(guideSkill)
50
46
  // Remove the "Supporting Files" section — replaced by "Detailed Reference" below
51
47
  .replace(/\n## Supporting Files[\s\S]*$/, '');
52
48
 
53
- const rulesContent = AUTO_HEADER + guideBody + `
49
+ const SKILL_FRONTMATTER = `---
50
+ name: mvc-kit
51
+ description: "mvc-kit framework reference — class roles, architecture rules, React hooks, and decision framework. Loaded on-demand when working with mvc-kit code."
52
+ invocable_by:
53
+ - model
54
+ ---
55
+
56
+ `;
57
+
58
+ const skillContent = AUTO_HEADER + SKILL_FRONTMATTER + guideBody + `
54
59
 
55
60
  ## Detailed Reference
56
61
 
@@ -60,34 +65,15 @@ For complete API details, patterns, and anti-patterns, read the files in:
60
65
  - \`api-reference.md\` — Full API reference for all classes and hooks
61
66
  - \`patterns.md\` — Prescribed patterns with code examples
62
67
  - \`anti-patterns.md\` — Anti-patterns to reject with fixes
63
- `;
64
68
 
65
- writeFileSync(join(rulesDir, 'mvc-kit.md'), rulesContent, 'utf-8');
66
- files.push('.claude/rules/mvc-kit.md');
67
-
68
- // 2. Scaffold command
69
- const scaffoldSkill = readFileSync(join(SKILLS_DIR, 'scaffold', 'SKILL.md'), 'utf-8');
70
- const scaffoldBody = stripFrontmatter(scaffoldSkill)
71
- .replace(
72
- 'Read the template file from `templates/<type>.md` in this skill directory.',
73
- 'Read the template file from `node_modules/mvc-kit/agent-config/claude-code/skills/scaffold/templates/<type>.md`.'
74
- );
75
-
76
- writeFileSync(join(commandsDir, 'mvc-kit-scaffold.md'), AUTO_HEADER + scaffoldBody, 'utf-8');
77
- files.push('.claude/commands/mvc-kit-scaffold.md');
78
-
79
- // 3. Review command
80
- const reviewSkill = readFileSync(join(SKILLS_DIR, 'review', 'SKILL.md'), 'utf-8');
81
- const reviewBody = stripFrontmatter(reviewSkill)
82
- .replaceAll(
83
- '`checklist.md`',
84
- '`node_modules/mvc-kit/agent-config/claude-code/skills/review/checklist.md`'
85
- );
69
+ For detailed per-class documentation, read the \`.md\` files colocated with source in:
70
+ \`node_modules/mvc-kit/src/\`
71
+ `;
86
72
 
87
- writeFileSync(join(commandsDir, 'mvc-kit-review.md'), AUTO_HEADER + reviewBody, 'utf-8');
88
- files.push('.claude/commands/mvc-kit-review.md');
73
+ writeFileSync(join(commandsDir, 'mvc-kit.md'), skillContent, 'utf-8');
74
+ files.push('.claude/commands/mvc-kit.md');
89
75
 
90
- // 4. Architect agent
76
+ // 2. Architect agent
91
77
  const architectAgent = readFileSync(join(AGENTS_DIR, 'mvc-kit-architect.md'), 'utf-8');
92
78
  writeFileSync(join(agentsDir, 'mvc-kit-architect.md'), AUTO_HEADER + architectAgent, 'utf-8');
93
79
  files.push('.claude/agents/mvc-kit-architect.md');
package/dist/Model.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const bindPublicMethods = require("./bindPublicMethods.cjs");
4
+ const produceDraft = require("./produceDraft.cjs");
4
5
  const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
5
6
  const PROTECTED_KEYS = /* @__PURE__ */ new Set(["set", "validate", "addCleanup", "subscribeTo", "listenTo"]);
6
7
  function freeze(obj) {
@@ -85,7 +86,14 @@ class Model {
85
86
  if (this._disposed) {
86
87
  throw new Error("Cannot set state on disposed Model");
87
88
  }
88
- const partial = typeof partialOrUpdater === "function" ? partialOrUpdater(this._state) : partialOrUpdater;
89
+ let partial;
90
+ if (typeof partialOrUpdater === "function") {
91
+ const result = produceDraft.resolveDraftUpdater(this._state, partialOrUpdater);
92
+ if (!result) return;
93
+ partial = result;
94
+ } else {
95
+ partial = partialOrUpdater;
96
+ }
89
97
  const keys = Object.keys(partial);
90
98
  const hasChanges = keys.some(
91
99
  (key) => partial[key] !== this._state[key]
@@ -1 +1 @@
1
- {"version":3,"file":"Model.cjs","sources":["../src/Model.ts"],"sourcesContent":["import type { Listener, Updater, Subscribable, ValidationErrors, EventPayload } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\nconst PROTECTED_KEYS = new Set(['set', 'validate', 'addCleanup', 'subscribeTo', 'listenTo']);\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n/**\n * Reactive entity with validation and dirty tracking.\n */\nexport abstract class Model<S extends object> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _committed: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _cachedDirty: boolean | null = null;\n private _cachedErrors: ValidationErrors<S> | null = null;\n\n constructor(initialState: S) {\n const frozen = freeze({ ...initialState });\n this._state = frozen;\n this._committed = frozen;\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /**\n * The baseline state for dirty tracking.\n */\n get committed(): S {\n return this._committed;\n }\n\n /**\n * True if current state differs from committed state.\n */\n get dirty(): boolean {\n if (this._cachedDirty === null) {\n this._cachedDirty = !this._shallowEqual(this._state, this._committed);\n }\n return this._cachedDirty;\n }\n\n /**\n * Validation errors for the current state.\n */\n get errors(): ValidationErrors<S> {\n if (this._cachedErrors === null) {\n this._cachedErrors = this.validate(this._state);\n }\n return this._cachedErrors;\n }\n\n /**\n * True if there are no validation errors.\n */\n get valid(): boolean {\n return Object.keys(this.errors).length === 0;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /**\n * Merges partial state with validation. No-op if no values changed by reference.\n * @protected\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S>): void {\n if (this._disposed) {\n throw new Error('Cannot set state on disposed Model');\n }\n\n const partial =\n typeof partialOrUpdater === 'function'\n ? partialOrUpdater(this._state)\n : partialOrUpdater;\n\n // Check if any values actually changed (shallow equality)\n const keys = Object.keys(partial) as (keyof S)[];\n const hasChanges = keys.some(\n (key) => partial[key] !== this._state[key]\n );\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Mark current state as the new baseline (not dirty).\n */\n commit(): void {\n if (this._disposed) {\n throw new Error('Cannot commit on disposed Model');\n }\n this._committed = this._state;\n this._cachedDirty = null;\n this._cachedErrors = null;\n }\n\n /**\n * Revert state to committed baseline.\n */\n rollback(): void {\n if (this._disposed) {\n throw new Error('Cannot rollback on disposed Model');\n }\n\n if (this._shallowEqual(this._state, this._committed)) {\n return;\n }\n\n const prev = this._state;\n this._state = this._committed;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, this._state);\n\n for (const listener of this._listeners) {\n listener(this._state, prev);\n }\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Override to provide validation logic.\n * Return an object mapping field keys to error messages.\n */\n protected validate(_state: S): ValidationErrors<S> {\n return {};\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n private _shallowEqual(a: S, b: S): boolean {\n const keysA = Object.keys(a) as (keyof S)[];\n const keysB = Object.keys(b) as (keyof S)[];\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n }\n}\n"],"names":["bindPublicMethods"],"mappings":"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"}
1
+ {"version":3,"file":"Model.cjs","sources":["../src/Model.ts"],"sourcesContent":["import type { Listener, Updater, Subscribable, ValidationErrors, EventPayload } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\nimport { resolveDraftUpdater } from './produceDraft';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\nconst PROTECTED_KEYS = new Set(['set', 'validate', 'addCleanup', 'subscribeTo', 'listenTo']);\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n/**\n * Reactive entity with validation and dirty tracking.\n */\nexport abstract class Model<S extends object> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _committed: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _cachedDirty: boolean | null = null;\n private _cachedErrors: ValidationErrors<S> | null = null;\n\n constructor(initialState: S) {\n const frozen = freeze({ ...initialState });\n this._state = frozen;\n this._committed = frozen;\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /**\n * The baseline state for dirty tracking.\n */\n get committed(): S {\n return this._committed;\n }\n\n /**\n * True if current state differs from committed state.\n */\n get dirty(): boolean {\n if (this._cachedDirty === null) {\n this._cachedDirty = !this._shallowEqual(this._state, this._committed);\n }\n return this._cachedDirty;\n }\n\n /**\n * Validation errors for the current state.\n */\n get errors(): ValidationErrors<S> {\n if (this._cachedErrors === null) {\n this._cachedErrors = this.validate(this._state);\n }\n return this._cachedErrors;\n }\n\n /**\n * True if there are no validation errors.\n */\n get valid(): boolean {\n return Object.keys(this.errors).length === 0;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /**\n * Merges partial state with validation. No-op if no values changed by reference.\n * @protected\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S> | ((draft: S) => void)): void {\n if (this._disposed) {\n throw new Error('Cannot set state on disposed Model');\n }\n\n let partial: Partial<S>;\n if (typeof partialOrUpdater === 'function') {\n const result = resolveDraftUpdater<S>(this._state, partialOrUpdater as (s: S) => Partial<S> | void);\n if (!result) return;\n partial = result;\n } else {\n partial = partialOrUpdater;\n }\n\n // Check if any values actually changed (shallow equality)\n const keys = Object.keys(partial) as (keyof S)[];\n const hasChanges = keys.some(\n (key) => partial[key] !== this._state[key]\n );\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Mark current state as the new baseline (not dirty).\n */\n commit(): void {\n if (this._disposed) {\n throw new Error('Cannot commit on disposed Model');\n }\n this._committed = this._state;\n this._cachedDirty = null;\n this._cachedErrors = null;\n }\n\n /**\n * Revert state to committed baseline.\n */\n rollback(): void {\n if (this._disposed) {\n throw new Error('Cannot rollback on disposed Model');\n }\n\n if (this._shallowEqual(this._state, this._committed)) {\n return;\n }\n\n const prev = this._state;\n this._state = this._committed;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, this._state);\n\n for (const listener of this._listeners) {\n listener(this._state, prev);\n }\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Override to provide validation logic.\n * Return an object mapping field keys to error messages.\n */\n protected validate(_state: S): ValidationErrors<S> {\n return {};\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n private _shallowEqual(a: S, b: S): boolean {\n const keysA = Object.keys(a) as (keyof S)[];\n const keysB = Object.keys(b) as (keyof S)[];\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n }\n}\n"],"names":["bindPublicMethods","resolveDraftUpdater"],"mappings":"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"}
package/dist/Model.d.ts CHANGED
@@ -43,7 +43,7 @@ export declare abstract class Model<S extends object> implements Subscribable<S>
43
43
  * Merges partial state with validation. No-op if no values changed by reference.
44
44
  * @protected
45
45
  */
46
- protected set(partialOrUpdater: Partial<S> | Updater<S>): void;
46
+ protected set(partialOrUpdater: Partial<S> | Updater<S> | ((draft: S) => void)): void;
47
47
  /**
48
48
  * Mark current state as the new baseline (not dirty).
49
49
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAU/F;;GAEG;AACH,8BAAsB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,aAAa,CAAoC;gBAE7C,YAAY,EAAE,CAAC;IAO3B,mCAAmC;IACnC,IAAI,KAAK,IAAI,CAAC,CAEb;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,CAAC,CAEjB;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAKnB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAKhC;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B;;;OAGG;IACH,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAiC9D;;OAEG;IACH,MAAM,IAAI,IAAI;IASd;;OAEG;IACH,QAAQ,IAAI,IAAI;IAqBhB,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAY5C,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAef;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAIlD,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,2FAA2F;IAC3F,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,yGAAyG;IACzG,SAAS,CAAC,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS;QAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;KAAE,EAC1G,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,GAC7C,MAAM,IAAI;IAMb,kFAAkF;IAClF,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI;IACxC,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B,OAAO,CAAC,aAAa;CAgBtB"}
1
+ {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAW/F;;GAEG;AACH,8BAAsB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,aAAa,CAAoC;gBAE7C,YAAY,EAAE,CAAC;IAO3B,mCAAmC;IACnC,IAAI,KAAK,IAAI,CAAC,CAEb;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,CAAC,CAEjB;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAKnB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAKhC;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B;;;OAGG;IACH,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI;IAqCrF;;OAEG;IACH,MAAM,IAAI,IAAI;IASd;;OAEG;IACH,QAAQ,IAAI,IAAI;IAqBhB,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAY5C,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAef;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAIlD,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,2FAA2F;IAC3F,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,yGAAyG;IACzG,SAAS,CAAC,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS;QAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;KAAE,EAC1G,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,GAC7C,MAAM,IAAI;IAMb,kFAAkF;IAClF,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI;IACxC,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B,OAAO,CAAC,aAAa;CAgBtB"}
package/dist/Model.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { bindPublicMethods } from "./bindPublicMethods.js";
2
+ import { resolveDraftUpdater } from "./produceDraft.js";
2
3
  const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
3
4
  const PROTECTED_KEYS = /* @__PURE__ */ new Set(["set", "validate", "addCleanup", "subscribeTo", "listenTo"]);
4
5
  function freeze(obj) {
@@ -83,7 +84,14 @@ class Model {
83
84
  if (this._disposed) {
84
85
  throw new Error("Cannot set state on disposed Model");
85
86
  }
86
- const partial = typeof partialOrUpdater === "function" ? partialOrUpdater(this._state) : partialOrUpdater;
87
+ let partial;
88
+ if (typeof partialOrUpdater === "function") {
89
+ const result = resolveDraftUpdater(this._state, partialOrUpdater);
90
+ if (!result) return;
91
+ partial = result;
92
+ } else {
93
+ partial = partialOrUpdater;
94
+ }
87
95
  const keys = Object.keys(partial);
88
96
  const hasChanges = keys.some(
89
97
  (key) => partial[key] !== this._state[key]
package/dist/Model.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Model.js","sources":["../src/Model.ts"],"sourcesContent":["import type { Listener, Updater, Subscribable, ValidationErrors, EventPayload } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\nconst PROTECTED_KEYS = new Set(['set', 'validate', 'addCleanup', 'subscribeTo', 'listenTo']);\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n/**\n * Reactive entity with validation and dirty tracking.\n */\nexport abstract class Model<S extends object> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _committed: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _cachedDirty: boolean | null = null;\n private _cachedErrors: ValidationErrors<S> | null = null;\n\n constructor(initialState: S) {\n const frozen = freeze({ ...initialState });\n this._state = frozen;\n this._committed = frozen;\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /**\n * The baseline state for dirty tracking.\n */\n get committed(): S {\n return this._committed;\n }\n\n /**\n * True if current state differs from committed state.\n */\n get dirty(): boolean {\n if (this._cachedDirty === null) {\n this._cachedDirty = !this._shallowEqual(this._state, this._committed);\n }\n return this._cachedDirty;\n }\n\n /**\n * Validation errors for the current state.\n */\n get errors(): ValidationErrors<S> {\n if (this._cachedErrors === null) {\n this._cachedErrors = this.validate(this._state);\n }\n return this._cachedErrors;\n }\n\n /**\n * True if there are no validation errors.\n */\n get valid(): boolean {\n return Object.keys(this.errors).length === 0;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /**\n * Merges partial state with validation. No-op if no values changed by reference.\n * @protected\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S>): void {\n if (this._disposed) {\n throw new Error('Cannot set state on disposed Model');\n }\n\n const partial =\n typeof partialOrUpdater === 'function'\n ? partialOrUpdater(this._state)\n : partialOrUpdater;\n\n // Check if any values actually changed (shallow equality)\n const keys = Object.keys(partial) as (keyof S)[];\n const hasChanges = keys.some(\n (key) => partial[key] !== this._state[key]\n );\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Mark current state as the new baseline (not dirty).\n */\n commit(): void {\n if (this._disposed) {\n throw new Error('Cannot commit on disposed Model');\n }\n this._committed = this._state;\n this._cachedDirty = null;\n this._cachedErrors = null;\n }\n\n /**\n * Revert state to committed baseline.\n */\n rollback(): void {\n if (this._disposed) {\n throw new Error('Cannot rollback on disposed Model');\n }\n\n if (this._shallowEqual(this._state, this._committed)) {\n return;\n }\n\n const prev = this._state;\n this._state = this._committed;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, this._state);\n\n for (const listener of this._listeners) {\n listener(this._state, prev);\n }\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Override to provide validation logic.\n * Return an object mapping field keys to error messages.\n */\n protected validate(_state: S): ValidationErrors<S> {\n return {};\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n private _shallowEqual(a: S, b: S): boolean {\n const keysA = Object.keys(a) as (keyof S)[];\n const keysB = Object.keys(b) as (keyof S)[];\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Model.js","sources":["../src/Model.ts"],"sourcesContent":["import type { Listener, Updater, Subscribable, ValidationErrors, EventPayload } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\nimport { resolveDraftUpdater } from './produceDraft';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\nconst PROTECTED_KEYS = new Set(['set', 'validate', 'addCleanup', 'subscribeTo', 'listenTo']);\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n/**\n * Reactive entity with validation and dirty tracking.\n */\nexport abstract class Model<S extends object> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _committed: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _cachedDirty: boolean | null = null;\n private _cachedErrors: ValidationErrors<S> | null = null;\n\n constructor(initialState: S) {\n const frozen = freeze({ ...initialState });\n this._state = frozen;\n this._committed = frozen;\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /**\n * The baseline state for dirty tracking.\n */\n get committed(): S {\n return this._committed;\n }\n\n /**\n * True if current state differs from committed state.\n */\n get dirty(): boolean {\n if (this._cachedDirty === null) {\n this._cachedDirty = !this._shallowEqual(this._state, this._committed);\n }\n return this._cachedDirty;\n }\n\n /**\n * Validation errors for the current state.\n */\n get errors(): ValidationErrors<S> {\n if (this._cachedErrors === null) {\n this._cachedErrors = this.validate(this._state);\n }\n return this._cachedErrors;\n }\n\n /**\n * True if there are no validation errors.\n */\n get valid(): boolean {\n return Object.keys(this.errors).length === 0;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /**\n * Merges partial state with validation. No-op if no values changed by reference.\n * @protected\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S> | ((draft: S) => void)): void {\n if (this._disposed) {\n throw new Error('Cannot set state on disposed Model');\n }\n\n let partial: Partial<S>;\n if (typeof partialOrUpdater === 'function') {\n const result = resolveDraftUpdater<S>(this._state, partialOrUpdater as (s: S) => Partial<S> | void);\n if (!result) return;\n partial = result;\n } else {\n partial = partialOrUpdater;\n }\n\n // Check if any values actually changed (shallow equality)\n const keys = Object.keys(partial) as (keyof S)[];\n const hasChanges = keys.some(\n (key) => partial[key] !== this._state[key]\n );\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Mark current state as the new baseline (not dirty).\n */\n commit(): void {\n if (this._disposed) {\n throw new Error('Cannot commit on disposed Model');\n }\n this._committed = this._state;\n this._cachedDirty = null;\n this._cachedErrors = null;\n }\n\n /**\n * Revert state to committed baseline.\n */\n rollback(): void {\n if (this._disposed) {\n throw new Error('Cannot rollback on disposed Model');\n }\n\n if (this._shallowEqual(this._state, this._committed)) {\n return;\n }\n\n const prev = this._state;\n this._state = this._committed;\n this._cachedDirty = null;\n this._cachedErrors = null;\n\n this.onSet?.(prev, this._state);\n\n for (const listener of this._listeners) {\n listener(this._state, prev);\n }\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Override to provide validation logic.\n * Return an object mapping field keys to error messages.\n */\n protected validate(_state: S): ValidationErrors<S> {\n return {};\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n this.addCleanup(unsubscribe);\n return unsubscribe;\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n private _shallowEqual(a: S, b: S): boolean {\n const keysA = Object.keys(a) as (keyof S)[];\n const keysB = Object.keys(b) as (keyof S)[];\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const EventBus = require("./EventBus.cjs");
4
4
  const walkPrototypeChain = require("./walkPrototypeChain.cjs");
5
5
  const wrapAsyncMethods = require("./wrapAsyncMethods.cjs");
6
+ const produceDraft = require("./produceDraft.cjs");
6
7
  const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
7
8
  function freeze(obj) {
8
9
  return __DEV__ ? Object.freeze(obj) : obj;
@@ -123,7 +124,14 @@ class ViewModel {
123
124
  );
124
125
  return;
125
126
  }
126
- const partial = typeof partialOrUpdater === "function" ? partialOrUpdater(this._state) : partialOrUpdater;
127
+ let partial;
128
+ if (typeof partialOrUpdater === "function") {
129
+ const result = produceDraft.resolveDraftUpdater(this._state, partialOrUpdater);
130
+ if (!result) return;
131
+ partial = result;
132
+ } else {
133
+ partial = partialOrUpdater;
134
+ }
127
135
  let hasChanges = false;
128
136
  const current = this._state;
129
137
  for (const key in partial) {
@@ -1 +1 @@
1
- {"version":3,"file":"ViewModel.cjs","sources":["../src/ViewModel.ts"],"sourcesContent":["import { EventBus } from './EventBus';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { wrapAsyncMethods } from './wrapAsyncMethods';\nimport type { InternalTaskState } from './wrapAsyncMethods';\nimport type { Listener, Updater, Subscribable, TaskState, EventPayload } from './types';\n\n// Re-export for backwards compatibility\nexport type { Listener, Updater } from './types';\nexport { walkPrototypeChain } from './walkPrototypeChain';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n// ── Class metadata cache ─────────────────────────────────────────\n// Caches prototype walk results per class to avoid repeated Object.getOwnPropertyDescriptors\n// on every init(). Single walk extracts getters, methods, and reserved key violations.\n\ninterface ClassMemberInfo {\n getters: Array<{ key: string; get: () => unknown }>;\n methods: Array<{ key: string; fn: Function }>;\n reservedKeys: string[];\n}\n\nconst classMembers = new WeakMap<Function, ClassMemberInfo>();\n\nfunction getClassMemberInfo(\n instance: object,\n stopPrototype: object,\n reservedKeys: readonly string[],\n lifecycleHooks: Set<string>,\n): ClassMemberInfo {\n const ctor = instance.constructor;\n let info = classMembers.get(ctor);\n if (info) return info;\n\n const getters: ClassMemberInfo['getters'] = [];\n const methods: ClassMemberInfo['methods'] = [];\n const found: string[] = [];\n const processedGetters = new Set<string>();\n const processedMethods = new Set<string>();\n\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n // Check reserved keys\n if (reservedKeys.includes(key as any)) {\n found.push(key);\n }\n\n // Collect getters (most-derived wins)\n if (desc.get && !processedGetters.has(key)) {\n processedGetters.add(key);\n getters.push({ key, get: desc.get });\n }\n\n // Collect wrappable methods (most-derived wins)\n if (!desc.get && !desc.set && typeof desc.value === 'function' &&\n !key.startsWith('_') && !lifecycleHooks.has(key) && !processedMethods.has(key)) {\n processedMethods.add(key);\n methods.push({ key, fn: desc.value });\n }\n });\n\n info = { getters, methods, reservedKeys: found };\n classMembers.set(ctor, info);\n return info;\n}\n\n// ── Auto-tracking types ──────────────────────────────────────────\n\ninterface TrackedSource {\n source: { subscribe(cb: () => void): () => void };\n revision: number;\n unsubscribe: () => void;\n}\n\n// ── Module-scoped tracking context ──────────────────────────────\n// Active during a memoized getter's Tier 3 recompute. Child getters\n// check these to bubble their cached deps to the parent. Module-scoped\n// (like Vue's activeEffect / Solid's Listener) for zero-cost Tier 1 checks.\n\nlet _activeStateTracking: Set<string> | null = null;\nlet _activeSourceTracking: Map<string, TrackedSource> | null = null;\n\n// ── Auto-tracking utilities ──────────────────────────────────────\n\nfunction isAutoTrackable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as any).subscribe === 'function'\n );\n}\n\n// ── Async tracking types ─────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type AsyncMethodKeys<T, Base = ViewModel<any, any>> = {\n [K in Exclude<keyof T, keyof Base>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Base>];\n\ntype AsyncMap<T> = {\n readonly [K in AsyncMethodKeys<T>]: TaskState;\n};\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onSet', 'onDispose']);\n\n// ── ViewModel ────────────────────────────────────────────────────\n\ntype EmptyViewModelState = { __brand: 'EmptyViewModelState'}\n\n/**\n * Reactive state container with computed getters, automatic async tracking, and typed events.\n * Subclass and define state shape, getters, and action methods. Use with `useLocal` in React.\n */\nexport abstract class ViewModel<S extends object = EmptyViewModelState, E extends Record<string, any> = {}> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _initialState: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _subscriptionCleanups: (() => void)[] | null = null;\n private _eventBus: EventBus<E> | null = null;\n\n // ── Reactive derived state (RFC 1) ─────────────────────────────\n private _revision = 0;\n private _trackedSources = new Map<string, TrackedSource>();\n\n // ── Async tracking (RFC 2) ──────────────────────────────────────\n // Lazily allocated on first async method wrap to keep construction fast.\n private _asyncStates: Map<string, InternalTaskState> | null = null;\n private _asyncSnapshots: Map<string, TaskState> | null = null;\n private _asyncListeners: Set<() => void> | null = null;\n private _asyncProxy: AsyncMap<this> | null = null;\n private _activeOps: Map<string, number> | null = null;\n\n /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */\n static GHOST_TIMEOUT = 3000;\n\n constructor(...args: EmptyViewModelState extends S ? [] | [initialState: S] : [initialState: S]) {\n const initialState = (args[0] ?? {} as S);\n this._state = freeze({ ...initialState });\n this._initialState = this._state;\n this._guardAndBind();\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Lazily-created typed EventBus for emitting and subscribing to events. */\n get events(): EventBus<E> {\n if (!this._eventBus) {\n this._eventBus = new EventBus<E>();\n }\n return this._eventBus;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n this._trackSubscribables();\n this._installStateProxy();\n this._processMembers();\n return this.onInit?.();\n }\n\n /**\n * Merges partial state into current state. If no values actually\n * changed by reference, this is a no-op.\n *\n * Triggers React re-render via listener notification. Called when:\n * - User code calls set() to update source state\n *\n * NOT called for subscribable member notifications — those use\n * a separate notification path (see _trackSubscribables).\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S>): void {\n if (this._disposed) return;\n\n // __DEV__ guard: set() inside a getter would cause infinite loops.\n // After init(), getters are auto-memoized; set() → notify → recompute → set() → ∞\n if (__DEV__ && _activeStateTracking) {\n console.error(\n '[mvc-kit] set() called inside a getter. ' +\n 'Getters must be pure — they read state and return a value. ' +\n 'They must never call set(), which would cause an infinite ' +\n 'render loop. Move this logic to an action method.'\n );\n return;\n }\n\n const partial =\n typeof partialOrUpdater === 'function'\n ? partialOrUpdater(this._state)\n : partialOrUpdater;\n\n // Check if any values actually changed (shallow equality).\n // Uses for..in to avoid Object.keys() array allocation.\n let hasChanges = false;\n const current = this._state;\n for (const key in partial) {\n if ((partial as any)[key] !== (current as any)[key]) {\n hasChanges = true;\n break;\n }\n }\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._revision++;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Emits a typed event via the internal EventBus.\n * Safe to call during dispose cleanup callbacks.\n * @protected\n */\n protected emit<K extends keyof E>(event: K, payload: E[K]): void {\n // During dispose sequence: _disposed is true but eventBus not yet disposed.\n // Cleanup callbacks can still emit. After eventBus.dispose(), this is a no-op.\n // If eventBus was never created, fall back to _disposed check.\n if (this._eventBus?.disposed ?? this._disposed) return;\n this.events.emit(event, payload);\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n\n this._teardownSubscriptions();\n\n // Async tracking cleanup — handled by addCleanup registered in _processMembers()\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this._eventBus?.dispose();\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Resets state to initial values (or provided state), aborts in-flight work,\n * clears async tracking, and re-runs onInit().\n */\n reset(newState?: S): void | Promise<void> {\n if (this._disposed) return;\n\n // 1. Abort in-flight, lazy-recreate on next disposeSignal access\n this._abortController?.abort();\n this._abortController = null;\n\n this._teardownSubscriptions();\n\n // 2. Reset state\n this._state = newState ? freeze({ ...newState }) : this._initialState;\n this._revision++;\n\n // 3. Clear async tracking (preserve listeners — React still subscribed)\n this._asyncStates?.clear();\n this._asyncSnapshots?.clear();\n this._notifyAsync();\n\n // 4. Re-track subscribable members (fresh subscriptions)\n this._trackSubscribables();\n\n // 5. Notify state listeners (React re-renders with new state)\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n\n // 6. Re-run onInit\n return this.onInit?.();\n }\n\n /**\n * Registers a cleanup function to be called on dispose. Used internally for things like method wrapper\n * cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.\n * @param fn\n * @protected\n */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose and reset. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Pipes a Channel event into a Collection via upsert. Calls channel.init() and registers auto-cleanup on dispose and reset. @protected */\n protected pipeChannel<\n K extends string,\n C extends { init(): void | Promise<void>; on(event: K, handler: (payload: any) => void): () => void },\n >(\n channel: C,\n type: K,\n target: { upsert(item: EventPayload<C, K>): void },\n ): () => void {\n channel.init();\n return this.listenTo(channel, type, (payload) => {\n target.upsert(payload);\n });\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n // ── Async tracking API ──────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): AsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as AsyncMap<this>, {\n get(_, prop: string) {\n return self._asyncSnapshots?.get(prop) ?? DEFAULT_TASK_STATE;\n },\n has(_, prop: string) {\n return self._asyncSnapshots?.has(prop) ?? false;\n },\n ownKeys() {\n return self._asyncSnapshots ? Array.from(self._asyncSnapshots.keys()) : [];\n },\n getOwnPropertyDescriptor(_, prop: string) {\n if (self._asyncSnapshots?.has(prop)) {\n return { configurable: true, enumerable: true, value: self._asyncSnapshots.get(prop) };\n }\n return undefined;\n },\n });\n }\n return this._asyncProxy;\n }\n\n /** Subscribes to async state changes. Used for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this._disposed) return () => {};\n if (!this._asyncListeners) this._asyncListeners = new Set();\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners!.delete(listener); };\n }\n\n private _notifyAsync(): void {\n if (!this._asyncListeners) return;\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n // ── Async tracking internals ────────────────────────────────────\n\n private _teardownSubscriptions(): void {\n for (const tracked of this._trackedSources.values()) tracked.unsubscribe();\n this._trackedSources.clear();\n\n if (this._subscriptionCleanups) {\n for (const fn of this._subscriptionCleanups) fn();\n this._subscriptionCleanups = null;\n }\n }\n\n /**\n * Guards reserved keys and auto-binds subclass methods in a single pass\n * using the cached class metadata from getClassMemberInfo.\n */\n private _guardAndBind(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n if (info.reservedKeys.length > 0) {\n throw new Error(\n `[mvc-kit] \"${info.reservedKeys[0]}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n for (let i = 0; i < info.methods.length; i++) {\n (this as any)[info.methods[i].key] = info.methods[i].fn.bind(this);\n }\n }\n\n // ── Member processing (merged getter memoization + async method wrapping) ──\n\n /**\n * Uses cached class metadata to memoize getters (RFC 1) and delegates\n * async method wrapping to the shared wrapAsyncMethods helper (RFC 2).\n * Class metadata is computed once per class via getClassMemberInfo() and reused\n * across all instances — avoids repeated prototype walks.\n */\n private _processMembers(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n\n // Memoize getters\n for (let i = 0; i < info.getters.length; i++) {\n this._wrapGetter(info.getters[i].key, info.getters[i].get);\n }\n\n // DEV: Instance property reserved key check (must run even if no methods to wrap)\n if (__DEV__) {\n for (const key of RESERVED_ASYNC_KEYS) {\n if (Object.getOwnPropertyDescriptor(this, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n }\n }\n\n // Skip async wrapping if no methods to wrap\n if (info.methods.length === 0) return;\n\n // Lazily allocate async tracking collections\n if (!this._asyncStates) this._asyncStates = new Map();\n if (!this._asyncSnapshots) this._asyncSnapshots = new Map();\n if (!this._asyncListeners) this._asyncListeners = new Set();\n\n // Initialize DEV-only active ops tracking\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n // Wrap async methods (shared with Resource)\n wrapAsyncMethods({\n instance: this,\n stopPrototype: ViewModel.prototype,\n reservedKeys: RESERVED_ASYNC_KEYS,\n lifecycleHooks: LIFECYCLE_HOOKS,\n isDisposed: () => this._disposed,\n isInitialized: () => this._initialized,\n asyncStates: this._asyncStates,\n asyncSnapshots: this._asyncSnapshots,\n asyncListeners: this._asyncListeners,\n notifyAsync: () => this._notifyAsync(),\n addCleanup: (fn) => this.addCleanup(fn),\n ghostTimeout: (this.constructor as typeof ViewModel).GHOST_TIMEOUT,\n className: 'ViewModel',\n activeOps: this._activeOps,\n methods: info.methods,\n });\n }\n\n // ── Auto-tracking internals ────────────────────────────────────\n\n /**\n * Installs a context-sensitive state getter on the instance.\n *\n * During getter tracking (_activeStateTracking is active): returns a Proxy\n * that records which state properties are accessed. The Proxy is created\n * lazily on first tracking access to keep init() fast.\n *\n * Otherwise: returns the frozen state object directly. This is critical\n * for React's useSyncExternalStore — it needs a changing reference to\n * detect state updates and trigger re-renders.\n */\n private _installStateProxy(): void {\n let stateProxy: S | null = null;\n\n Object.defineProperty(this, 'state', {\n get: () => {\n if (_activeStateTracking) {\n if (!stateProxy) {\n stateProxy = new Proxy({} as S, {\n get: (_, prop: string) => {\n _activeStateTracking?.add(prop);\n return (this._state as any)[prop];\n },\n ownKeys: () => Reflect.ownKeys(this._state as object),\n getOwnPropertyDescriptor: (_, prop) =>\n Reflect.getOwnPropertyDescriptor(this._state as object, prop),\n set: () => {\n throw new Error('Cannot mutate state directly. Use set() instead.');\n },\n has: (_, prop) => prop in (this._state as object),\n });\n }\n return stateProxy;\n }\n return this._state;\n },\n configurable: true,\n enumerable: true,\n });\n }\n\n /**\n * Scans own instance properties for Subscribable objects and sets up\n * automatic dependency tracking for each one found.\n *\n * For each subscribable member:\n * 1. Subscribe to it. On notification: bump its tracked revision\n * AND the VM's global revision, then force a new state reference\n * and notify listeners so React re-renders.\n * 2. Replace the instance property with a getter that participates\n * in dependency tracking.\n * 3. Register unsubscribe in the dispose chain.\n *\n * Called during init(), AFTER all subclass property initializers\n * have run (they execute during the constructor, before init()).\n */\n private _trackSubscribables(): void {\n for (const key of Object.getOwnPropertyNames(this)) {\n const value = (this as any)[key];\n if (!isAutoTrackable(value)) continue;\n\n let tracked: TrackedSource;\n\n const onSourceNotify = () => {\n if (this._disposed) return;\n\n // Source notified — bump revisions for getter invalidation\n tracked.revision++;\n this._revision++;\n\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n };\n\n const unsubState = value.subscribe(onSourceNotify);\n const unsubAsync =\n typeof value.subscribeAsync === 'function'\n ? value.subscribeAsync(onSourceNotify)\n : undefined;\n\n tracked = {\n source: value,\n revision: 0,\n unsubscribe: unsubAsync\n ? () => { unsubState(); unsubAsync(); }\n : unsubState,\n };\n\n this._trackedSources.set(key, tracked);\n\n // Replace the instance property with a tracking getter.\n // The original value is captured in the closure.\n Object.defineProperty(this, key, {\n get: () => {\n _activeSourceTracking?.set(key, tracked);\n return value;\n },\n configurable: true,\n enumerable: false,\n });\n }\n }\n\n /**\n * Bubbles cached dependency records to the active parent tracking context.\n * Called from Tier 1/Tier 2 cache hits during nested getter composition\n * so the parent getter records the full transitive dependency set.\n * Extracted to keep the getter closure small for V8 inlining.\n */\n private _bubbleDeps(stateDepKeys: string[] | undefined, sourceDepKeys: string[] | undefined): void {\n const st = _activeStateTracking!;\n if (stateDepKeys) {\n for (let i = 0; i < stateDepKeys.length; i++) st.add(stateDepKeys[i]);\n }\n if (sourceDepKeys) {\n const srt = _activeSourceTracking!;\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts) srt.set(sourceDepKeys[i], ts);\n }\n }\n }\n\n /**\n * Replaces a single prototype getter with a memoized version on this\n * instance. The memoized getter tracks both state dependencies and\n * subscribable member dependencies, caching its result and\n * revalidating through a three-tier strategy:\n *\n * Tier 1 (fast): revision unchanged → return cached (1 int compare)\n * Tier 2 (medium): revision changed but this getter's deps didn't → return cached\n * Tier 3 (slow): at least one dep changed → full recompute with tracking\n */\n private _wrapGetter(key: string, original: () => unknown): void {\n // Per-getter cache state, private to this getter on this instance.\n let cached: unknown;\n let validatedAtRevision = -1;\n\n // Array-based dep tracking — avoids Map iterator allocation in Tier 2\n let stateDepKeys: string[] | undefined;\n let stateDepValues: unknown[] | undefined;\n let sourceDepKeys: string[] | undefined;\n let sourceDepRevisions: number[] | undefined;\n\n // Reusable tracking containers — allocated on first Tier 3, reused via clear()\n let trackingSet: Set<string> | undefined;\n let trackingMap: Map<string, TrackedSource> | undefined;\n\n Object.defineProperty(this, key, {\n get: () => {\n // ── Tier 1: Fast path (1 integer compare) ───────────────\n if (validatedAtRevision === this._revision) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n return cached;\n }\n\n // After dispose, revision never changes so Tier 1 hits if\n // getter was ever called. Guard the uncalled-before-dispose edge case.\n if (this._disposed) return cached;\n\n // ── Tier 2: Medium path — array-based dep check ─────────\n if (stateDepKeys !== undefined) {\n let fresh = true;\n\n // Check state deps by reference (array iteration, no iterator alloc)\n const state = this._state as any;\n for (let i = 0; i < stateDepKeys.length; i++) {\n if (state[stateDepKeys[i]] !== stateDepValues![i]) {\n fresh = false;\n break;\n }\n }\n\n // Check subscribable deps by revision\n if (fresh && sourceDepKeys !== undefined && sourceDepKeys.length > 0) {\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts && ts.revision !== sourceDepRevisions![i]) {\n fresh = false;\n break;\n }\n }\n }\n\n if (fresh) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n validatedAtRevision = this._revision;\n return cached;\n }\n }\n\n // ── Tier 3: Slow path — full recompute ─────────────────\n // Save parent tracking context for nested getter composition\n const parentStateTracking = _activeStateTracking;\n const parentSourceTracking = _activeSourceTracking;\n\n // Reuse tracking containers (clear instead of allocate)\n if (trackingSet) {\n trackingSet.clear();\n } else {\n trackingSet = new Set();\n }\n if (trackingMap) {\n trackingMap.clear();\n } else {\n trackingMap = new Map();\n }\n\n _activeStateTracking = trackingSet;\n _activeSourceTracking = trackingMap;\n\n try {\n cached = original.call(this);\n } catch (e) {\n // Don't cache failed computations\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n throw e;\n }\n\n // Restore parent tracking context\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n\n // Bubble deps up to parent getter if nested\n if (parentStateTracking) {\n for (const d of trackingSet) parentStateTracking.add(d);\n }\n if (parentSourceTracking) {\n for (const [k, v] of trackingMap) {\n parentSourceTracking.set(k, v);\n }\n }\n\n // Snapshot state dep values into arrays for Tier 2\n const depCount = trackingSet.size;\n if (!stateDepKeys || stateDepKeys.length !== depCount) {\n stateDepKeys = new Array(depCount);\n stateDepValues = new Array(depCount);\n }\n {\n let i = 0;\n const state = this._state as any;\n for (const d of trackingSet) {\n stateDepKeys[i] = d;\n stateDepValues![i] = state[d];\n i++;\n }\n }\n\n // Snapshot subscribable revisions into arrays\n const sourceCount = trackingMap.size;\n if (sourceCount > 0) {\n if (!sourceDepKeys || sourceDepKeys.length !== sourceCount) {\n sourceDepKeys = new Array(sourceCount);\n sourceDepRevisions = new Array(sourceCount);\n }\n let i = 0;\n for (const [memberKey, tracked] of trackingMap) {\n sourceDepKeys[i] = memberKey;\n sourceDepRevisions![i] = tracked.revision;\n i++;\n }\n } else {\n sourceDepKeys = undefined;\n sourceDepRevisions = undefined;\n }\n\n validatedAtRevision = this._revision;\n\n return cached;\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n"],"names":["walkPrototypeChain","EventBus","wrapAsyncMethods"],"mappings":"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"}
1
+ {"version":3,"file":"ViewModel.cjs","sources":["../src/ViewModel.ts"],"sourcesContent":["import { EventBus } from './EventBus';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { wrapAsyncMethods } from './wrapAsyncMethods';\nimport { resolveDraftUpdater } from './produceDraft';\nimport type { InternalTaskState } from './wrapAsyncMethods';\nimport type { Listener, Updater, Subscribable, TaskState, EventPayload } from './types';\n\n// Re-export for backwards compatibility\nexport type { Listener, Updater } from './types';\nexport { walkPrototypeChain } from './walkPrototypeChain';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n// ── Class metadata cache ─────────────────────────────────────────\n// Caches prototype walk results per class to avoid repeated Object.getOwnPropertyDescriptors\n// on every init(). Single walk extracts getters, methods, and reserved key violations.\n\ninterface ClassMemberInfo {\n getters: Array<{ key: string; get: () => unknown }>;\n methods: Array<{ key: string; fn: Function }>;\n reservedKeys: string[];\n}\n\nconst classMembers = new WeakMap<Function, ClassMemberInfo>();\n\nfunction getClassMemberInfo(\n instance: object,\n stopPrototype: object,\n reservedKeys: readonly string[],\n lifecycleHooks: Set<string>,\n): ClassMemberInfo {\n const ctor = instance.constructor;\n let info = classMembers.get(ctor);\n if (info) return info;\n\n const getters: ClassMemberInfo['getters'] = [];\n const methods: ClassMemberInfo['methods'] = [];\n const found: string[] = [];\n const processedGetters = new Set<string>();\n const processedMethods = new Set<string>();\n\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n // Check reserved keys\n if (reservedKeys.includes(key as any)) {\n found.push(key);\n }\n\n // Collect getters (most-derived wins)\n if (desc.get && !processedGetters.has(key)) {\n processedGetters.add(key);\n getters.push({ key, get: desc.get });\n }\n\n // Collect wrappable methods (most-derived wins)\n if (!desc.get && !desc.set && typeof desc.value === 'function' &&\n !key.startsWith('_') && !lifecycleHooks.has(key) && !processedMethods.has(key)) {\n processedMethods.add(key);\n methods.push({ key, fn: desc.value });\n }\n });\n\n info = { getters, methods, reservedKeys: found };\n classMembers.set(ctor, info);\n return info;\n}\n\n// ── Auto-tracking types ──────────────────────────────────────────\n\ninterface TrackedSource {\n source: { subscribe(cb: () => void): () => void };\n revision: number;\n unsubscribe: () => void;\n}\n\n// ── Module-scoped tracking context ──────────────────────────────\n// Active during a memoized getter's Tier 3 recompute. Child getters\n// check these to bubble their cached deps to the parent. Module-scoped\n// (like Vue's activeEffect / Solid's Listener) for zero-cost Tier 1 checks.\n\nlet _activeStateTracking: Set<string> | null = null;\nlet _activeSourceTracking: Map<string, TrackedSource> | null = null;\n\n// ── Auto-tracking utilities ──────────────────────────────────────\n\nfunction isAutoTrackable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as any).subscribe === 'function'\n );\n}\n\n// ── Async tracking types ─────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type AsyncMethodKeys<T, Base = ViewModel<any, any>> = {\n [K in Exclude<keyof T, keyof Base>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Base>];\n\ntype AsyncMap<T> = {\n readonly [K in AsyncMethodKeys<T>]: TaskState;\n};\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onSet', 'onDispose']);\n\n// ── ViewModel ────────────────────────────────────────────────────\n\ntype EmptyViewModelState = { __brand: 'EmptyViewModelState'}\n\n/**\n * Reactive state container with computed getters, automatic async tracking, and typed events.\n * Subclass and define state shape, getters, and action methods. Use with `useLocal` in React.\n */\nexport abstract class ViewModel<S extends object = EmptyViewModelState, E extends Record<string, any> = {}> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _initialState: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _subscriptionCleanups: (() => void)[] | null = null;\n private _eventBus: EventBus<E> | null = null;\n\n // ── Reactive derived state (RFC 1) ─────────────────────────────\n private _revision = 0;\n private _trackedSources = new Map<string, TrackedSource>();\n\n // ── Async tracking (RFC 2) ──────────────────────────────────────\n // Lazily allocated on first async method wrap to keep construction fast.\n private _asyncStates: Map<string, InternalTaskState> | null = null;\n private _asyncSnapshots: Map<string, TaskState> | null = null;\n private _asyncListeners: Set<() => void> | null = null;\n private _asyncProxy: AsyncMap<this> | null = null;\n private _activeOps: Map<string, number> | null = null;\n\n /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */\n static GHOST_TIMEOUT = 3000;\n\n constructor(...args: EmptyViewModelState extends S ? [] | [initialState: S] : [initialState: S]) {\n const initialState = (args[0] ?? {} as S);\n this._state = freeze({ ...initialState });\n this._initialState = this._state;\n this._guardAndBind();\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Lazily-created typed EventBus for emitting and subscribing to events. */\n get events(): EventBus<E> {\n if (!this._eventBus) {\n this._eventBus = new EventBus<E>();\n }\n return this._eventBus;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n this._trackSubscribables();\n this._installStateProxy();\n this._processMembers();\n return this.onInit?.();\n }\n\n /**\n * Merges partial state into current state. If no values actually\n * changed by reference, this is a no-op.\n *\n * Triggers React re-render via listener notification. Called when:\n * - User code calls set() to update source state\n *\n * NOT called for subscribable member notifications — those use\n * a separate notification path (see _trackSubscribables).\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S> | ((draft: S) => void)): void {\n if (this._disposed) return;\n\n // __DEV__ guard: set() inside a getter would cause infinite loops.\n // After init(), getters are auto-memoized; set() → notify → recompute → set() → ∞\n if (__DEV__ && _activeStateTracking) {\n console.error(\n '[mvc-kit] set() called inside a getter. ' +\n 'Getters must be pure — they read state and return a value. ' +\n 'They must never call set(), which would cause an infinite ' +\n 'render loop. Move this logic to an action method.'\n );\n return;\n }\n\n let partial: Partial<S>;\n if (typeof partialOrUpdater === 'function') {\n const result = resolveDraftUpdater<S>(this._state, partialOrUpdater as (s: S) => Partial<S> | void);\n if (!result) return;\n partial = result;\n } else {\n partial = partialOrUpdater;\n }\n\n // Check if any values actually changed (shallow equality).\n // Uses for..in to avoid Object.keys() array allocation.\n let hasChanges = false;\n const current = this._state;\n for (const key in partial) {\n if ((partial as any)[key] !== (current as any)[key]) {\n hasChanges = true;\n break;\n }\n }\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._revision++;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Emits a typed event via the internal EventBus.\n * Safe to call during dispose cleanup callbacks.\n * @protected\n */\n protected emit<K extends keyof E>(event: K, payload: E[K]): void {\n // During dispose sequence: _disposed is true but eventBus not yet disposed.\n // Cleanup callbacks can still emit. After eventBus.dispose(), this is a no-op.\n // If eventBus was never created, fall back to _disposed check.\n if (this._eventBus?.disposed ?? this._disposed) return;\n this.events.emit(event, payload);\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n\n this._teardownSubscriptions();\n\n // Async tracking cleanup — handled by addCleanup registered in _processMembers()\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this._eventBus?.dispose();\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Resets state to initial values (or provided state), aborts in-flight work,\n * clears async tracking, and re-runs onInit().\n */\n reset(newState?: S): void | Promise<void> {\n if (this._disposed) return;\n\n // 1. Abort in-flight, lazy-recreate on next disposeSignal access\n this._abortController?.abort();\n this._abortController = null;\n\n this._teardownSubscriptions();\n\n // 2. Reset state\n this._state = newState ? freeze({ ...newState }) : this._initialState;\n this._revision++;\n\n // 3. Clear async tracking (preserve listeners — React still subscribed)\n this._asyncStates?.clear();\n this._asyncSnapshots?.clear();\n this._notifyAsync();\n\n // 4. Re-track subscribable members (fresh subscriptions)\n this._trackSubscribables();\n\n // 5. Notify state listeners (React re-renders with new state)\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n\n // 6. Re-run onInit\n return this.onInit?.();\n }\n\n /**\n * Registers a cleanup function to be called on dispose. Used internally for things like method wrapper\n * cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.\n * @param fn\n * @protected\n */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose and reset. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Pipes a Channel event into a Collection via upsert. Calls channel.init() and registers auto-cleanup on dispose and reset. @protected */\n protected pipeChannel<\n K extends string,\n C extends { init(): void | Promise<void>; on(event: K, handler: (payload: any) => void): () => void },\n >(\n channel: C,\n type: K,\n target: { upsert(item: EventPayload<C, K>): void },\n ): () => void {\n channel.init();\n return this.listenTo(channel, type, (payload) => {\n target.upsert(payload);\n });\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n // ── Async tracking API ──────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): AsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as AsyncMap<this>, {\n get(_, prop: string) {\n return self._asyncSnapshots?.get(prop) ?? DEFAULT_TASK_STATE;\n },\n has(_, prop: string) {\n return self._asyncSnapshots?.has(prop) ?? false;\n },\n ownKeys() {\n return self._asyncSnapshots ? Array.from(self._asyncSnapshots.keys()) : [];\n },\n getOwnPropertyDescriptor(_, prop: string) {\n if (self._asyncSnapshots?.has(prop)) {\n return { configurable: true, enumerable: true, value: self._asyncSnapshots.get(prop) };\n }\n return undefined;\n },\n });\n }\n return this._asyncProxy;\n }\n\n /** Subscribes to async state changes. Used for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this._disposed) return () => {};\n if (!this._asyncListeners) this._asyncListeners = new Set();\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners!.delete(listener); };\n }\n\n private _notifyAsync(): void {\n if (!this._asyncListeners) return;\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n // ── Async tracking internals ────────────────────────────────────\n\n private _teardownSubscriptions(): void {\n for (const tracked of this._trackedSources.values()) tracked.unsubscribe();\n this._trackedSources.clear();\n\n if (this._subscriptionCleanups) {\n for (const fn of this._subscriptionCleanups) fn();\n this._subscriptionCleanups = null;\n }\n }\n\n /**\n * Guards reserved keys and auto-binds subclass methods in a single pass\n * using the cached class metadata from getClassMemberInfo.\n */\n private _guardAndBind(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n if (info.reservedKeys.length > 0) {\n throw new Error(\n `[mvc-kit] \"${info.reservedKeys[0]}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n for (let i = 0; i < info.methods.length; i++) {\n (this as any)[info.methods[i].key] = info.methods[i].fn.bind(this);\n }\n }\n\n // ── Member processing (merged getter memoization + async method wrapping) ──\n\n /**\n * Uses cached class metadata to memoize getters (RFC 1) and delegates\n * async method wrapping to the shared wrapAsyncMethods helper (RFC 2).\n * Class metadata is computed once per class via getClassMemberInfo() and reused\n * across all instances — avoids repeated prototype walks.\n */\n private _processMembers(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n\n // Memoize getters\n for (let i = 0; i < info.getters.length; i++) {\n this._wrapGetter(info.getters[i].key, info.getters[i].get);\n }\n\n // DEV: Instance property reserved key check (must run even if no methods to wrap)\n if (__DEV__) {\n for (const key of RESERVED_ASYNC_KEYS) {\n if (Object.getOwnPropertyDescriptor(this, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n }\n }\n\n // Skip async wrapping if no methods to wrap\n if (info.methods.length === 0) return;\n\n // Lazily allocate async tracking collections\n if (!this._asyncStates) this._asyncStates = new Map();\n if (!this._asyncSnapshots) this._asyncSnapshots = new Map();\n if (!this._asyncListeners) this._asyncListeners = new Set();\n\n // Initialize DEV-only active ops tracking\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n // Wrap async methods (shared with Resource)\n wrapAsyncMethods({\n instance: this,\n stopPrototype: ViewModel.prototype,\n reservedKeys: RESERVED_ASYNC_KEYS,\n lifecycleHooks: LIFECYCLE_HOOKS,\n isDisposed: () => this._disposed,\n isInitialized: () => this._initialized,\n asyncStates: this._asyncStates,\n asyncSnapshots: this._asyncSnapshots,\n asyncListeners: this._asyncListeners,\n notifyAsync: () => this._notifyAsync(),\n addCleanup: (fn) => this.addCleanup(fn),\n ghostTimeout: (this.constructor as typeof ViewModel).GHOST_TIMEOUT,\n className: 'ViewModel',\n activeOps: this._activeOps,\n methods: info.methods,\n });\n }\n\n // ── Auto-tracking internals ────────────────────────────────────\n\n /**\n * Installs a context-sensitive state getter on the instance.\n *\n * During getter tracking (_activeStateTracking is active): returns a Proxy\n * that records which state properties are accessed. The Proxy is created\n * lazily on first tracking access to keep init() fast.\n *\n * Otherwise: returns the frozen state object directly. This is critical\n * for React's useSyncExternalStore — it needs a changing reference to\n * detect state updates and trigger re-renders.\n */\n private _installStateProxy(): void {\n let stateProxy: S | null = null;\n\n Object.defineProperty(this, 'state', {\n get: () => {\n if (_activeStateTracking) {\n if (!stateProxy) {\n stateProxy = new Proxy({} as S, {\n get: (_, prop: string) => {\n _activeStateTracking?.add(prop);\n return (this._state as any)[prop];\n },\n ownKeys: () => Reflect.ownKeys(this._state as object),\n getOwnPropertyDescriptor: (_, prop) =>\n Reflect.getOwnPropertyDescriptor(this._state as object, prop),\n set: () => {\n throw new Error('Cannot mutate state directly. Use set() instead.');\n },\n has: (_, prop) => prop in (this._state as object),\n });\n }\n return stateProxy;\n }\n return this._state;\n },\n configurable: true,\n enumerable: true,\n });\n }\n\n /**\n * Scans own instance properties for Subscribable objects and sets up\n * automatic dependency tracking for each one found.\n *\n * For each subscribable member:\n * 1. Subscribe to it. On notification: bump its tracked revision\n * AND the VM's global revision, then force a new state reference\n * and notify listeners so React re-renders.\n * 2. Replace the instance property with a getter that participates\n * in dependency tracking.\n * 3. Register unsubscribe in the dispose chain.\n *\n * Called during init(), AFTER all subclass property initializers\n * have run (they execute during the constructor, before init()).\n */\n private _trackSubscribables(): void {\n for (const key of Object.getOwnPropertyNames(this)) {\n const value = (this as any)[key];\n if (!isAutoTrackable(value)) continue;\n\n let tracked: TrackedSource;\n\n const onSourceNotify = () => {\n if (this._disposed) return;\n\n // Source notified — bump revisions for getter invalidation\n tracked.revision++;\n this._revision++;\n\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n };\n\n const unsubState = value.subscribe(onSourceNotify);\n const unsubAsync =\n typeof value.subscribeAsync === 'function'\n ? value.subscribeAsync(onSourceNotify)\n : undefined;\n\n tracked = {\n source: value,\n revision: 0,\n unsubscribe: unsubAsync\n ? () => { unsubState(); unsubAsync(); }\n : unsubState,\n };\n\n this._trackedSources.set(key, tracked);\n\n // Replace the instance property with a tracking getter.\n // The original value is captured in the closure.\n Object.defineProperty(this, key, {\n get: () => {\n _activeSourceTracking?.set(key, tracked);\n return value;\n },\n configurable: true,\n enumerable: false,\n });\n }\n }\n\n /**\n * Bubbles cached dependency records to the active parent tracking context.\n * Called from Tier 1/Tier 2 cache hits during nested getter composition\n * so the parent getter records the full transitive dependency set.\n * Extracted to keep the getter closure small for V8 inlining.\n */\n private _bubbleDeps(stateDepKeys: string[] | undefined, sourceDepKeys: string[] | undefined): void {\n const st = _activeStateTracking!;\n if (stateDepKeys) {\n for (let i = 0; i < stateDepKeys.length; i++) st.add(stateDepKeys[i]);\n }\n if (sourceDepKeys) {\n const srt = _activeSourceTracking!;\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts) srt.set(sourceDepKeys[i], ts);\n }\n }\n }\n\n /**\n * Replaces a single prototype getter with a memoized version on this\n * instance. The memoized getter tracks both state dependencies and\n * subscribable member dependencies, caching its result and\n * revalidating through a three-tier strategy:\n *\n * Tier 1 (fast): revision unchanged → return cached (1 int compare)\n * Tier 2 (medium): revision changed but this getter's deps didn't → return cached\n * Tier 3 (slow): at least one dep changed → full recompute with tracking\n */\n private _wrapGetter(key: string, original: () => unknown): void {\n // Per-getter cache state, private to this getter on this instance.\n let cached: unknown;\n let validatedAtRevision = -1;\n\n // Array-based dep tracking — avoids Map iterator allocation in Tier 2\n let stateDepKeys: string[] | undefined;\n let stateDepValues: unknown[] | undefined;\n let sourceDepKeys: string[] | undefined;\n let sourceDepRevisions: number[] | undefined;\n\n // Reusable tracking containers — allocated on first Tier 3, reused via clear()\n let trackingSet: Set<string> | undefined;\n let trackingMap: Map<string, TrackedSource> | undefined;\n\n Object.defineProperty(this, key, {\n get: () => {\n // ── Tier 1: Fast path (1 integer compare) ───────────────\n if (validatedAtRevision === this._revision) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n return cached;\n }\n\n // After dispose, revision never changes so Tier 1 hits if\n // getter was ever called. Guard the uncalled-before-dispose edge case.\n if (this._disposed) return cached;\n\n // ── Tier 2: Medium path — array-based dep check ─────────\n if (stateDepKeys !== undefined) {\n let fresh = true;\n\n // Check state deps by reference (array iteration, no iterator alloc)\n const state = this._state as any;\n for (let i = 0; i < stateDepKeys.length; i++) {\n if (state[stateDepKeys[i]] !== stateDepValues![i]) {\n fresh = false;\n break;\n }\n }\n\n // Check subscribable deps by revision\n if (fresh && sourceDepKeys !== undefined && sourceDepKeys.length > 0) {\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts && ts.revision !== sourceDepRevisions![i]) {\n fresh = false;\n break;\n }\n }\n }\n\n if (fresh) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n validatedAtRevision = this._revision;\n return cached;\n }\n }\n\n // ── Tier 3: Slow path — full recompute ─────────────────\n // Save parent tracking context for nested getter composition\n const parentStateTracking = _activeStateTracking;\n const parentSourceTracking = _activeSourceTracking;\n\n // Reuse tracking containers (clear instead of allocate)\n if (trackingSet) {\n trackingSet.clear();\n } else {\n trackingSet = new Set();\n }\n if (trackingMap) {\n trackingMap.clear();\n } else {\n trackingMap = new Map();\n }\n\n _activeStateTracking = trackingSet;\n _activeSourceTracking = trackingMap;\n\n try {\n cached = original.call(this);\n } catch (e) {\n // Don't cache failed computations\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n throw e;\n }\n\n // Restore parent tracking context\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n\n // Bubble deps up to parent getter if nested\n if (parentStateTracking) {\n for (const d of trackingSet) parentStateTracking.add(d);\n }\n if (parentSourceTracking) {\n for (const [k, v] of trackingMap) {\n parentSourceTracking.set(k, v);\n }\n }\n\n // Snapshot state dep values into arrays for Tier 2\n const depCount = trackingSet.size;\n if (!stateDepKeys || stateDepKeys.length !== depCount) {\n stateDepKeys = new Array(depCount);\n stateDepValues = new Array(depCount);\n }\n {\n let i = 0;\n const state = this._state as any;\n for (const d of trackingSet) {\n stateDepKeys[i] = d;\n stateDepValues![i] = state[d];\n i++;\n }\n }\n\n // Snapshot subscribable revisions into arrays\n const sourceCount = trackingMap.size;\n if (sourceCount > 0) {\n if (!sourceDepKeys || sourceDepKeys.length !== sourceCount) {\n sourceDepKeys = new Array(sourceCount);\n sourceDepRevisions = new Array(sourceCount);\n }\n let i = 0;\n for (const [memberKey, tracked] of trackingMap) {\n sourceDepKeys[i] = memberKey;\n sourceDepRevisions![i] = tracked.revision;\n i++;\n }\n } else {\n sourceDepKeys = undefined;\n sourceDepRevisions = undefined;\n }\n\n validatedAtRevision = this._revision;\n\n return cached;\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n"],"names":["walkPrototypeChain","EventBus","resolveDraftUpdater","wrapAsyncMethods"],"mappings":"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"}