@pyreon/state-tree 0.24.5 → 0.24.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/state-tree",
3
- "version": "0.24.5",
3
+ "version": "0.24.6",
4
4
  "description": "Structured reactive state tree — composable models with snapshots, patches, and middleware",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/state-tree#readme",
6
6
  "bugs": {
@@ -15,7 +15,6 @@
15
15
  "files": [
16
16
  "lib",
17
17
  "!lib/**/*.map",
18
- "src",
19
18
  "README.md",
20
19
  "LICENSE"
21
20
  ],
@@ -26,12 +25,10 @@
26
25
  "types": "./lib/types/index.d.ts",
27
26
  "exports": {
28
27
  ".": {
29
- "bun": "./src/index.ts",
30
28
  "import": "./lib/index.js",
31
29
  "types": "./lib/types/index.d.ts"
32
30
  },
33
31
  "./devtools": {
34
- "bun": "./src/devtools.ts",
35
32
  "import": "./lib/devtools.js",
36
33
  "types": "./lib/types/devtools.d.ts"
37
34
  }
@@ -49,10 +46,10 @@
49
46
  "devDependencies": {
50
47
  "@happy-dom/global-registrator": "^20.8.9",
51
48
  "@pyreon/manifest": "0.13.1",
52
- "@pyreon/reactivity": "^0.24.5",
49
+ "@pyreon/reactivity": "^0.24.6",
53
50
  "bun-types": "^1.3.12"
54
51
  },
55
52
  "dependencies": {
56
- "@pyreon/reactivity": "^0.24.5"
53
+ "@pyreon/reactivity": "^0.24.6"
57
54
  }
58
55
  }
package/src/devtools.ts DELETED
@@ -1,85 +0,0 @@
1
- /**
2
- * @pyreon/state-tree devtools introspection API.
3
- * Import: `import { ... } from "@pyreon/state-tree/devtools"`
4
- */
5
-
6
- import { getSnapshot } from './snapshot'
7
-
8
- // Track active model instances (devtools-only, opt-in)
9
- const _activeModels = new Map<string, WeakRef<object>>()
10
- const _listeners = new Set<() => void>()
11
-
12
- function _notify(): void {
13
- for (const listener of _listeners) listener()
14
- }
15
-
16
- /**
17
- * Register a model instance for devtools inspection.
18
- * Call this when creating instances you want visible in devtools.
19
- *
20
- * @example
21
- * const counter = Counter.create()
22
- * registerInstance("app-counter", counter)
23
- */
24
- export function registerInstance(name: string, instance: object): void {
25
- _activeModels.set(name, new WeakRef(instance))
26
- _notify()
27
- }
28
-
29
- /**
30
- * Unregister a model instance.
31
- */
32
- export function unregisterInstance(name: string): void {
33
- _activeModels.delete(name)
34
- _notify()
35
- }
36
-
37
- /**
38
- * Get all registered model instance names.
39
- * Automatically cleans up garbage-collected instances.
40
- */
41
- export function getActiveModels(): string[] {
42
- for (const [name, ref] of _activeModels) {
43
- if (ref.deref() === undefined) _activeModels.delete(name)
44
- }
45
- return [..._activeModels.keys()]
46
- }
47
-
48
- /**
49
- * Get a model instance by name (or undefined if GC'd or not registered).
50
- */
51
- export function getModelInstance(name: string): object | undefined {
52
- const ref = _activeModels.get(name)
53
- if (!ref) return undefined
54
- const instance = ref.deref()
55
- if (!instance) {
56
- _activeModels.delete(name)
57
- return undefined
58
- }
59
- return instance
60
- }
61
-
62
- /**
63
- * Get a snapshot of a registered model instance.
64
- */
65
- export function getModelSnapshot(name: string): Record<string, unknown> | undefined {
66
- const instance = getModelInstance(name)
67
- if (!instance) return undefined
68
- return getSnapshot(instance)
69
- }
70
-
71
- /**
72
- * Subscribe to model registry changes. Returns unsubscribe function.
73
- */
74
- export function onModelChange(listener: () => void): () => void {
75
- _listeners.add(listener)
76
- return () => {
77
- _listeners.delete(listener)
78
- }
79
- }
80
-
81
- /** @internal — reset devtools registry (for tests). */
82
- export function _resetDevtools(): void {
83
- _activeModels.clear()
84
- _listeners.clear()
85
- }
package/src/index.ts DELETED
@@ -1,29 +0,0 @@
1
- // ─── Core ─────────────────────────────────────────────────────────────────────
2
-
3
- export type { ModelDefinition } from './model'
4
- export { model, resetAllHooks, resetHook } from './model'
5
-
6
- // ─── Snapshot ─────────────────────────────────────────────────────────────────
7
-
8
- export { applySnapshot, getSnapshot } from './snapshot'
9
-
10
- // ─── Patches ─────────────────────────────────────────────────────────────────
11
-
12
- export { applyPatch, onPatch } from './patch'
13
-
14
- // ─── Middleware ───────────────────────────────────────────────────────────────
15
-
16
- export { addMiddleware } from './middleware'
17
-
18
- // ─── Types ────────────────────────────────────────────────────────────────────
19
-
20
- export type {
21
- ActionCall,
22
- MiddlewareFn,
23
- ModelInstance,
24
- ModelSelf,
25
- Patch,
26
- PatchListener,
27
- Snapshot,
28
- StateShape,
29
- } from './types'
package/src/instance.ts DELETED
@@ -1,128 +0,0 @@
1
- import type { Computed, Signal } from '@pyreon/reactivity'
2
- import { signal } from '@pyreon/reactivity'
3
- import { runAction } from './middleware'
4
- import { onPatch, trackedSignal } from './patch'
5
- import { instanceMeta } from './registry'
6
- import type { InstanceMeta, ModelInstance, Snapshot, StateShape } from './types'
7
- import { MODEL_BRAND } from './types'
8
-
9
- // ─── Model definition detection ───────────────────────────────────────────────
10
-
11
- interface AnyModelDef {
12
- readonly [MODEL_BRAND]: true
13
- readonly _config: ModelConfig<
14
- StateShape,
15
- Record<string, (...args: unknown[]) => unknown>,
16
- Record<string, Signal<unknown>>
17
- >
18
- }
19
-
20
- function isModelDef(v: unknown): v is AnyModelDef {
21
- if (v == null || typeof v !== 'object') return false
22
- return (v as Record<string, unknown>)[MODEL_BRAND] === true
23
- }
24
-
25
- // ─── Config shape ─────────────────────────────────────────────────────────────
26
-
27
- export interface ModelConfig<TState extends StateShape, TActions, TViews> {
28
- state: TState
29
- views?: (self: any) => TViews
30
- actions?: (self: any) => TActions
31
- }
32
-
33
- // ─── createInstance ───────────────────────────────────────────────────────────
34
-
35
- /**
36
- * Create a live model instance from a config + optional initial snapshot.
37
- * Called by `ModelDefinition.create()`.
38
- */
39
- export function createInstance<
40
- TState extends StateShape,
41
- TActions extends Record<string, (...args: any[]) => any>,
42
- TViews extends Record<string, Signal<any> | Computed<any>>,
43
- >(
44
- config: ModelConfig<TState, TActions, TViews>,
45
- initial: Partial<Snapshot<TState>>,
46
- ): ModelInstance<TState, TActions, TViews> {
47
- // Raw object that will become the instance.
48
- const instance: Record<string, unknown> = {}
49
-
50
- // Metadata for this instance.
51
- const meta: InstanceMeta = {
52
- stateKeys: [],
53
- patchListeners: new Set(),
54
- middlewares: [],
55
- emitPatch(patch) {
56
- // Guard avoids iterating an empty Set on the hot signal-write path.
57
- if (this.patchListeners.size === 0) return
58
- for (const listener of this.patchListeners) listener(patch)
59
- },
60
- }
61
- instanceMeta.set(instance, meta)
62
-
63
- // `self` is a live proxy so that actions/views always see the final
64
- // (fully-populated) instance — including wrapped actions added later.
65
- const self = new Proxy(instance, {
66
- get(_, k) {
67
- return instance[k as string]
68
- },
69
- })
70
-
71
- // ── 1. State signals ──────────────────────────────────────────────────────
72
- // Per-state-key signal allocation at instance CREATION (createInstance
73
- // runs once per model instance), not per-render — this is the model's
74
- // fine-grained reactive architecture, not the signal-in-render-loop
75
- // anti-pattern the rule targets. Disabled per-site below with rationale.
76
- for (const [key, defaultValue] of Object.entries(config.state)) {
77
- meta.stateKeys.push(key)
78
- const path = `/${key}`
79
- const initValue: unknown =
80
- key in initial ? (initial as Record<string, unknown>)[key] : undefined
81
-
82
- let rawSig: Signal<unknown>
83
-
84
- if (isModelDef(defaultValue)) {
85
- // Nested model — create its instance from the supplied snapshot (or defaults).
86
- const nestedInstance = createInstance(
87
- defaultValue._config,
88
- (initValue as Record<string, unknown>) ?? {},
89
- )
90
- // pyreon-lint-disable-next-line pyreon/no-signal-in-loop
91
- rawSig = signal(nestedInstance)
92
-
93
- // Propagate nested patches upward with the key as path prefix.
94
- onPatch(nestedInstance, (patch) => {
95
- meta.emitPatch({ ...patch, path: path + patch.path })
96
- })
97
- } else {
98
- // pyreon-lint-disable-next-line pyreon/no-signal-in-loop
99
- rawSig = signal(initValue !== undefined ? initValue : defaultValue)
100
- }
101
-
102
- const tracked = trackedSignal(
103
- rawSig,
104
- path,
105
- (p) => meta.emitPatch(p),
106
- () => meta.patchListeners.size > 0,
107
- )
108
- instance[key] = tracked
109
- }
110
-
111
- // ── 2. Views ──────────────────────────────────────────────────────────────
112
- if (config.views) {
113
- const views = config.views(self)
114
- for (const [key, view] of Object.entries(views as Record<string, unknown>)) {
115
- instance[key] = view
116
- }
117
- }
118
-
119
- // ── 3. Actions (wrapped with middleware runner) ───────────────────────────
120
- if (config.actions) {
121
- const rawActions = config.actions(self) as Record<string, (...args: unknown[]) => unknown>
122
- for (const [key, actionFn] of Object.entries(rawActions)) {
123
- instance[key] = (...args: unknown[]) => runAction(meta, key, actionFn, args)
124
- }
125
- }
126
-
127
- return instance as ModelInstance<TState, TActions, TViews>
128
- }
package/src/manifest.ts DELETED
@@ -1,161 +0,0 @@
1
- import { defineManifest } from '@pyreon/manifest'
2
-
3
- export default defineManifest({
4
- name: '@pyreon/state-tree',
5
- title: 'State Tree',
6
- tagline:
7
- 'Structured reactive state tree — composable models with snapshots, patches, and middleware',
8
- description:
9
- 'MobX-State-Tree-inspired structured state management built on Pyreon signals. Models compose state (signals), views (computeds), and actions into self-contained units that support typed snapshots, JSON-patch record/replay, and action interception middleware. Models can nest other models for tree-shaped state, and `.asHook(id)` provides singleton instances scoped to a store-like registry.',
10
- category: 'universal',
11
- longExample: `import { model, getSnapshot, applySnapshot, onPatch, applyPatch, addMiddleware } from '@pyreon/state-tree'
12
-
13
- // Define a model — state (signals), views (derived), actions (mutations):
14
- const Todo = model({
15
- state: { title: '', done: false },
16
- views: (self) => ({
17
- summary: () => \`\${self.title()} [\${self.done() ? 'x' : ' '}]\`,
18
- }),
19
- actions: (self) => ({
20
- toggle: () => self.done.set(!self.done()),
21
- rename: (title: string) => self.title.set(title),
22
- }),
23
- })
24
-
25
- const TodoList = model({
26
- state: { todos: [] as ReturnType<typeof Todo.create>[] },
27
- actions: (self) => ({
28
- add: (title: string) => {
29
- const todo = Todo.create({ title, done: false })
30
- self.todos.update(list => [...list, todo])
31
- },
32
- }),
33
- })
34
-
35
- // Create instances:
36
- const list = TodoList.create({ todos: [] })
37
- list.add('Write tests')
38
- list.todos()[0].toggle()
39
-
40
- // Snapshots — typed recursive serialization:
41
- const snap = getSnapshot(list)
42
- applySnapshot(list, { todos: [{ title: 'Restored', done: true }] })
43
-
44
- // JSON patches — record/replay for undo, sync, debugging:
45
- const patches: Patch[] = []
46
- const dispose = onPatch(list, (patch) => patches.push(patch))
47
- list.add('New item')
48
- // Later: applyPatch(list, patches[0]) to replay
49
-
50
- // Middleware — intercept any action in the tree:
51
- addMiddleware(list, (call, next) => {
52
- console.log(\`Action: \${call.name}\`, call.args)
53
- return next(call)
54
- })
55
-
56
- // Singleton hook for app-wide state:
57
- const useTodoList = TodoList.asHook('todo-list')
58
- const { store } = useTodoList() // same instance on every call`,
59
- features: [
60
- 'model({ state, views, actions }) — structured reactive models',
61
- 'Nested model composition for tree-shaped state',
62
- 'getSnapshot / applySnapshot — typed recursive serialization',
63
- 'onPatch / applyPatch — JSON patch record and replay',
64
- 'addMiddleware — action interception chain',
65
- '.create(initial) for instances, .asHook(id) for singleton hooks',
66
- 'Devtools subpath export with WeakRef-based registry',
67
- ],
68
- api: [
69
- {
70
- name: 'model',
71
- kind: 'function',
72
- signature: '(definition: { state: StateShape, views?: (self: ModelSelf) => Record<string, () => any>, actions?: (self: ModelSelf) => Record<string, (...args: any[]) => any> }) => ModelDefinition',
73
- summary:
74
- 'Define a structured reactive model. `state` declares signal-backed fields with their initial values. `views` are computed derivations. `actions` are the only way to mutate state — enabling middleware interception and patch recording. Returns a `ModelDefinition` with `.create(initial?)` for instances and `.asHook(id)` for singleton access.',
75
- example: `const Counter = model({
76
- state: { count: 0 },
77
- views: (self) => ({
78
- doubled: () => self.count() * 2,
79
- }),
80
- actions: (self) => ({
81
- increment: () => self.count.update(n => n + 1),
82
- }),
83
- })
84
-
85
- const counter = Counter.create({ count: 10 })
86
- counter.count() // 10
87
- counter.increment()
88
- counter.doubled() // 22`,
89
- mistakes: [
90
- 'Mutating state outside of actions — bypasses middleware and patch recording, breaks the structured contract',
91
- 'Forgetting that `self.count` is a signal — read with `self.count()`, write with `self.count.set(v)` or `.update(fn)` inside actions',
92
- 'Nesting plain objects in state instead of child models — plain objects are not signal-backed, changes to their properties are not reactive',
93
- ],
94
- seeAlso: ['getSnapshot', 'applySnapshot', 'onPatch', 'addMiddleware'],
95
- },
96
- {
97
- name: 'getSnapshot',
98
- kind: 'function',
99
- signature: '(instance: ModelInstance) => Snapshot',
100
- summary:
101
- 'Recursively serialize a model instance into a plain JSON-safe snapshot. Reads all signal values via `.peek()` to avoid tracking subscriptions. Nested models are recursively serialized.',
102
- example: `const snap = getSnapshot(counter) // { count: 10 }`,
103
- seeAlso: ['applySnapshot', 'model'],
104
- },
105
- {
106
- name: 'applySnapshot',
107
- kind: 'function',
108
- signature: '(instance: ModelInstance, snapshot: Snapshot) => void',
109
- summary:
110
- 'Replace a model instance\'s state wholesale from a snapshot. Recursively applies to nested models. Triggers patch listeners with replace operations.',
111
- example: `applySnapshot(counter, { count: 0 }) // reset to zero`,
112
- seeAlso: ['getSnapshot', 'model'],
113
- },
114
- {
115
- name: 'onPatch',
116
- kind: 'function',
117
- signature: '(instance: ModelInstance, listener: PatchListener) => () => void',
118
- summary:
119
- 'Subscribe to JSON patches emitted by actions on a model instance. Each patch records the path, operation (add/replace/remove), and value. Returns an unsubscribe function. Pairs with `applyPatch` for undo/redo and state synchronization.',
120
- example: `const dispose = onPatch(counter, (patch) => {
121
- console.log(patch) // { op: 'replace', path: '/count', value: 11 }
122
- })`,
123
- seeAlso: ['applyPatch', 'model'],
124
- },
125
- {
126
- name: 'applyPatch',
127
- kind: 'function',
128
- signature: '(instance: ModelInstance, patch: Patch | Patch[]) => void',
129
- summary:
130
- 'Apply one or more JSON patches to a model instance. Accepts a single patch or an array for batch replay. Used with `onPatch` for undo/redo and state synchronization.',
131
- example: `applyPatch(counter, { op: 'replace', path: '/count', value: 0 })`,
132
- seeAlso: ['onPatch', 'model'],
133
- },
134
- {
135
- name: 'addMiddleware',
136
- kind: 'function',
137
- signature: '(instance: ModelInstance, middleware: MiddlewareFn) => () => void',
138
- summary:
139
- 'Add an action interception middleware to a model instance. The middleware receives the action call context and a `next` function — call `next(call)` to proceed or return early to block the action. Returns an unsubscribe function.',
140
- example: `addMiddleware(counter, (call, next) => {
141
- console.log(\`\${call.name}(\${call.args.join(', ')})\`)
142
- return next(call)
143
- })`,
144
- seeAlso: ['model'],
145
- },
146
- ],
147
- gotchas: [
148
- {
149
- label: 'Actions only',
150
- note: 'State mutations must go through actions — direct `.set()` calls on state signals bypass middleware and patch recording. The model enforces this in dev mode.',
151
- },
152
- {
153
- label: 'Snapshot serialization',
154
- note: '`getSnapshot` reads via `.peek()` so it does not subscribe to signals. The snapshot is a one-time read, not a reactive computed.',
155
- },
156
- {
157
- label: 'Devtools',
158
- note: 'Import `@pyreon/state-tree/devtools` for a WeakRef-based registry of live model instances. Tree-shakeable — zero cost unless imported.',
159
- },
160
- ],
161
- })
package/src/middleware.ts DELETED
@@ -1,53 +0,0 @@
1
- import { instanceMeta } from './registry'
2
- import type { ActionCall, InstanceMeta, MiddlewareFn } from './types'
3
-
4
- // ─── Action runner ────────────────────────────────────────────────────────────
5
-
6
- /**
7
- * Run an action through the middleware chain registered on `meta`.
8
- * Each middleware receives the call descriptor and a `next` function.
9
- * If no middlewares, the action runs directly.
10
- */
11
- export function runAction(
12
- meta: InstanceMeta,
13
- name: string,
14
- fn: (...fnArgs: unknown[]) => unknown,
15
- args: unknown[],
16
- ): unknown {
17
- const call: ActionCall = { name, args, path: `/${name}` }
18
-
19
- const dispatch = (idx: number, c: ActionCall): unknown => {
20
- if (idx >= meta.middlewares.length) return fn(...c.args)
21
- const mw = meta.middlewares[idx]
22
- if (!mw) return fn(...c.args)
23
- return mw(c, (nextCall) => dispatch(idx + 1, nextCall))
24
- }
25
-
26
- return dispatch(0, call)
27
- }
28
-
29
- // ─── addMiddleware ────────────────────────────────────────────────────────────
30
-
31
- /**
32
- * Intercept every action call on `instance`.
33
- * Middlewares run in registration order — call `next(call)` to continue.
34
- *
35
- * Returns an unsubscribe function.
36
- *
37
- * @example
38
- * const unsub = addMiddleware(counter, (call, next) => {
39
- * console.log(`> ${call.name}(${call.args})`)
40
- * const result = next(call)
41
- * console.log(`< ${call.name}`)
42
- * return result
43
- * })
44
- */
45
- export function addMiddleware(instance: object, middleware: MiddlewareFn): () => void {
46
- const meta = instanceMeta.get(instance)
47
- if (!meta) throw new Error('[@pyreon/state-tree] addMiddleware: not a model instance')
48
- meta.middlewares.push(middleware)
49
- return () => {
50
- const idx = meta.middlewares.indexOf(middleware)
51
- if (idx !== -1) meta.middlewares.splice(idx, 1)
52
- }
53
- }
package/src/model.ts DELETED
@@ -1,107 +0,0 @@
1
- import type { Computed, Signal } from '@pyreon/reactivity'
2
- import { createInstance, type ModelConfig } from './instance'
3
- import type { ModelInstance, Snapshot, StateShape } from './types'
4
- import { MODEL_BRAND } from './types'
5
-
6
- // ─── Hook registry ────────────────────────────────────────────────────────────
7
-
8
- // Module-level singleton registry for `asHook()` — isolated per package import.
9
- // Use `resetHook(id)` or `resetAllHooks()` to clear entries (useful for tests / HMR).
10
- const _hookRegistry = new Map<string, unknown>()
11
-
12
- /** Destroy a hook singleton by id so next call re-creates the instance. */
13
- export function resetHook(id: string): void {
14
- _hookRegistry.delete(id)
15
- }
16
-
17
- /** Destroy all hook singletons. */
18
- export function resetAllHooks(): void {
19
- _hookRegistry.clear()
20
- }
21
-
22
- // ─── ModelDefinition ──────────────────────────────────────────────────────────
23
-
24
- /**
25
- * Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
26
- * a Zustand-style singleton hook.
27
- */
28
- export class ModelDefinition<
29
- TState extends StateShape,
30
- TActions extends Record<string, (...args: any[]) => any>,
31
- TViews extends Record<string, Signal<any> | Computed<any>>,
32
- > {
33
- /** Brand used to identify ModelDefinition objects at runtime (without instanceof). */
34
- readonly [MODEL_BRAND] = true as const
35
-
36
- /** @internal — exposed so nested instance creation can read it. */
37
- readonly _config: ModelConfig<TState, TActions, TViews>
38
-
39
- constructor(config: ModelConfig<TState, TActions, TViews>) {
40
- this._config = config
41
- }
42
-
43
- /**
44
- * Create a new independent model instance.
45
- * Pass a partial snapshot to override defaults.
46
- *
47
- * @example
48
- * const counter = Counter.create({ count: 5 })
49
- */
50
- create(initial?: Partial<Snapshot<TState>>): ModelInstance<TState, TActions, TViews> {
51
- return createInstance(this._config, initial ?? {})
52
- }
53
-
54
- /**
55
- * Returns a hook function that always returns the same singleton instance
56
- * for the given `id` — Zustand / Pinia style.
57
- *
58
- * @example
59
- * const useCounter = Counter.asHook("app-counter")
60
- * // Any call to useCounter() returns the same instance.
61
- * const store = useCounter()
62
- */
63
- asHook(id: string): () => ModelInstance<TState, TActions, TViews> {
64
- return () => {
65
- if (!_hookRegistry.has(id)) {
66
- _hookRegistry.set(id, this.create())
67
- }
68
- return _hookRegistry.get(id) as ModelInstance<TState, TActions, TViews>
69
- }
70
- }
71
- }
72
-
73
- // ─── model() factory ──────────────────────────────────────────────────────────
74
-
75
- /**
76
- * Define a reactive model with state, views, and actions.
77
- *
78
- * - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
79
- * - **views** — factory receiving `self`; return computed signals for derived state.
80
- * - **actions** — factory receiving `self`; return functions that mutate state.
81
- *
82
- * Use nested `ModelDefinition` values in `state` to compose models.
83
- *
84
- * @example
85
- * const Counter = model({
86
- * state: { count: 0 },
87
- * views: (self) => ({
88
- * doubled: computed(() => self.count() * 2),
89
- * }),
90
- * actions: (self) => ({
91
- * inc: () => self.count.update(c => c + 1),
92
- * reset: () => self.count.set(0),
93
- * }),
94
- * })
95
- *
96
- * const c = Counter.create({ count: 5 })
97
- * c.count() // 5
98
- * c.inc()
99
- * c.doubled() // 12
100
- */
101
- export function model<
102
- TState extends StateShape,
103
- TActions extends Record<string, (...args: any[]) => any> = Record<never, never>,
104
- TViews extends Record<string, Signal<any> | Computed<any>> = Record<never, never>,
105
- >(config: ModelConfig<TState, TActions, TViews>): ModelDefinition<TState, TActions, TViews> {
106
- return new ModelDefinition(config)
107
- }