@gi-tcg/gts-runtime 0.7.4 → 0.7.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/dist/index.d.ts CHANGED
@@ -45,18 +45,32 @@ interface NamedAttributesNode {
45
45
  }
46
46
  declare class View<BlockDef extends AttributeBlockDefinition> {
47
47
  #private;
48
- _node: NamedAttributesNode;
49
- _bindingCtx?: BindingContext;
50
- constructor(_node: NamedAttributesNode, _bindingCtx?: BindingContext | undefined);
51
- }
52
- declare class BindingContext {
53
- #private;
54
- addBinding(value: unknown): void;
55
- getBindings(): unknown[];
48
+ "~node": NamedAttributesNode;
49
+ constructor(node: NamedAttributesNode);
56
50
  }
57
51
  declare function createDefine(rootVM: IViewModel<any, any, any>, node: SingleAttributeNode): void;
58
52
  declare function createBinding(rootVM: IViewModel<any, any, any>, node: SingleAttributeNode): unknown[];
59
53
  //#endregion
54
+ //#region src/execution_context.d.ts
55
+ type RuntimePhase = "action" | "binder";
56
+ /**
57
+ * The ViewModel execution state visible while a Model is being constructed.
58
+ *
59
+ * The context is synchronous and scoped to the current `parse()` call. A View
60
+ * is stable across the binder and action passes for the same definition, while
61
+ * `phase` describes the current pass.
62
+ */
63
+ interface ModelConstructionContext {
64
+ readonly view: View<any>;
65
+ readonly phase: RuntimePhase;
66
+ }
67
+ /** Returns the context for the current synchronous ViewModel parse call. */
68
+ declare function getCurrentModelContext(): ModelConstructionContext | null;
69
+ /** Returns the View currently being parsed, or `null` outside ViewModel parsing. */
70
+ declare function getCurrentView(): View<any> | null;
71
+ /** Returns whether the current ViewModel parse is running actions or binders. */
72
+ declare function getCurrentContext(): RuntimePhase | null;
73
+ //#endregion
60
74
  //#region src/view_model.d.ts
61
75
  interface AttributeBlockDefinition {
62
76
  "~meta": any;
@@ -91,13 +105,12 @@ interface IViewModel<ModelT, BlockDef extends AttributeBlockDefinition, CtorArgs
91
105
  bind<This extends IViewModel<any, any, any>>(this: This, ...args: CtorArgs): IReboundViewModel<This, []>;
92
106
  /**
93
107
  * Rewrite the initial meta.
94
- * @param newMeta
108
+ * @param newMeta
95
109
  */
96
110
  narrow<This extends IViewModel<any, any, any>, const NewMeta extends This["~namedDefinition"]["~meta"]>(this: This, newMeta: NewMeta): INarrowedViewModel<This, NewMeta>;
97
111
  extend<This extends IViewModel<any, any, any>, ChildT extends ModelT, const ChildBlockDef extends PartialAttributeBlockDefinition, ChildCtorArgs extends any[] = []>(this: This, Ctor: new (...args: ChildCtorArgs) => ChildT, modelDefFn: (helper: AttributeDefHelper<ChildT>) => ChildBlockDef): IExtendedViewModel<This, ChildT, ChildBlockDef, ChildCtorArgs>;
98
112
  }
99
113
  type LazyAttributeActionOrBinder<ModelT> = (model: ModelT, positionals: () => unknown[], named: View<any>) => unknown;
100
- declare function getCurrentContext(): "action" | "binder" | null;
101
114
  declare class ViewModelRuntime {
102
115
  #private;
103
116
  constructor(Ctor: new (...args: any[]) => any);
@@ -281,4 +294,19 @@ interface ISimpleViewModel<T, Options extends SimpleViewModelOptions = {}> exten
281
294
  }
282
295
  declare function defineSimpleViewModel<const T extends StandardJSONSchemaV1, const Options extends SimpleViewModelOptions = {}>(schema: T, options?: Options): ISimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options>;
283
296
  //#endregion
284
- export { type AttributeReturn as AR, type AttributeReturn, type AttributeBlockDefinition, type AttributeDefinition, type IExtendedViewModel, type INarrowedViewModel, type IReboundViewModel, type ISimpleViewModel, type IViewModel, type IViewModelInstance, type NamedAttributesNode, type SimpleAttributeOptions, type SimpleViewModelOptions, type SingleAttributeNode, type View, createBinding, createDefine, defineSimpleViewModel, defineViewModel, extendViewModel, getCurrentContext };
297
+ //#region src/action_view_model.d.ts
298
+ type AnyAction = (fnArg: any) => void;
299
+ declare class ActionModel<Fn extends AnyAction> {
300
+ action!: Fn;
301
+ }
302
+ /**
303
+ * Defines a ViewModel whose only attribute is a direct `~action` that assigned to `ActionModel`'s `action`.
304
+ */
305
+ declare function defineActionViewModel<Signature extends (this: any, actionArg: AnyAction) => AttributeReturn.Done, InitMeta = unknown, ModelActionSig extends AnyAction = OverloadedParameters<Signature>[0]>(): IViewModel<ActionModel<ModelActionSig>, {
306
+ "~action": Signature & {
307
+ required(): true;
308
+ };
309
+ "~meta": InitMeta;
310
+ }, []>;
311
+ //#endregion
312
+ export { type AttributeReturn as AR, type AttributeReturn, ActionModel, type AttributeBlockDefinition, type AttributeDefinition, type IExtendedViewModel, type INarrowedViewModel, type IReboundViewModel, type ISimpleViewModel, type IViewModel, type IViewModelInstance, type ModelConstructionContext, type NamedAttributesNode, type RuntimePhase, type SimpleAttributeOptions, type SimpleViewModelOptions, type SingleAttributeNode, type View, createBinding, createDefine, defineActionViewModel, defineSimpleViewModel, defineViewModel, extendViewModel, getCurrentContext, getCurrentModelContext, getCurrentView };
package/dist/index.js CHANGED
@@ -1,12 +1,50 @@
1
1
  import { Ajv2020 } from "ajv/dist/2020.js";
2
+ //#region src/execution_context.ts
3
+ let currentExecution = null;
4
+ let currentModelContext = null;
5
+ /** Returns the context for the current synchronous ViewModel parse call. */
6
+ function getCurrentModelContext() {
7
+ return currentModelContext;
8
+ }
9
+ /** Returns the View currently being parsed, or `null` outside ViewModel parsing. */
10
+ function getCurrentView() {
11
+ return currentModelContext?.view ?? null;
12
+ }
13
+ /** Returns whether the current ViewModel parse is running actions or binders. */
14
+ function getCurrentContext() {
15
+ return currentModelContext?.phase ?? null;
16
+ }
17
+ function getCurrentViewModelExecution() {
18
+ return currentExecution;
19
+ }
20
+ function runInViewModelExecution(execution, callback) {
21
+ const previousExecution = currentExecution;
22
+ currentExecution = execution;
23
+ try {
24
+ return callback();
25
+ } finally {
26
+ currentExecution = previousExecution;
27
+ }
28
+ }
29
+ function runWithCurrentView(view, callback) {
30
+ const previousModelContext = currentModelContext;
31
+ currentModelContext = {
32
+ view,
33
+ phase: currentExecution?.phase ?? "action"
34
+ };
35
+ try {
36
+ return callback();
37
+ } finally {
38
+ currentModelContext = previousModelContext;
39
+ }
40
+ }
41
+ //#endregion
2
42
  //#region src/view.ts
3
43
  var View = class {
4
44
  #phantom;
5
- _node;
6
- _bindingCtx;
7
- constructor(_node, _bindingCtx) {
8
- this._node = _node;
9
- this._bindingCtx = _bindingCtx;
45
+ "~node";
46
+ constructor(node) {
47
+ this["~node"] = node;
10
48
  }
11
49
  };
12
50
  var BindingContext = class {
@@ -18,22 +56,37 @@ var BindingContext = class {
18
56
  return this.#bindings;
19
57
  }
20
58
  };
59
+ const viewRegistry = /* @__PURE__ */ new WeakMap();
60
+ function getViewForNode(node, kind) {
61
+ let registered = viewRegistry.get(node);
62
+ if (!registered) {
63
+ registered = {};
64
+ viewRegistry.set(node, registered);
65
+ }
66
+ let view = registered[kind];
67
+ if (!view) {
68
+ view = new View(kind === "root" ? { attributes: [node] } : node.named ?? { attributes: [] });
69
+ registered[kind] = view;
70
+ }
71
+ return view;
72
+ }
21
73
  function createDefine(rootVM, node) {
22
- const view = new View({ attributes: [node] });
23
- rootVM.parse(view);
74
+ runInViewModelExecution({ phase: "action" }, () => {
75
+ rootVM.parse(getViewForNode(node, "root"));
76
+ });
24
77
  }
25
78
  function createBinding(rootVM, node) {
26
79
  const bindingCtx = new BindingContext();
27
- const view = new View({ attributes: [node] }, bindingCtx);
28
- rootVM.parse(view);
80
+ runInViewModelExecution({
81
+ phase: "binder",
82
+ bindingContext: bindingCtx
83
+ }, () => {
84
+ rootVM.parse(getViewForNode(node, "root"));
85
+ });
29
86
  return bindingCtx.getBindings();
30
87
  }
31
88
  //#endregion
32
89
  //#region src/view_model.ts
33
- let currentContext = null;
34
- function getCurrentContext() {
35
- return currentContext;
36
- }
37
90
  var ViewModelRuntime = class ViewModelRuntime {
38
91
  #registeredActions = /* @__PURE__ */ new Map();
39
92
  #registeredBinders = /* @__PURE__ */ new Map();
@@ -46,23 +99,23 @@ var ViewModelRuntime = class ViewModelRuntime {
46
99
  else this.#registeredBinders.set(name, action);
47
100
  }
48
101
  parse(view, ...args) {
49
- currentContext = view._bindingCtx ? "binder" : "action";
50
- const model = new this.#Ctor(...args);
51
- for (const attrNode of view._node.attributes) {
52
- let { name, positionals, named, binding } = attrNode;
53
- const insideBindingCtx = !!view._bindingCtx;
54
- let fn = (insideBindingCtx ? this.#registeredBinders : this.#registeredActions).get(name);
55
- if (!insideBindingCtx && !fn) {
56
- const modelName = this.#Ctor.name;
57
- throw new Error(`No action registered for attribute "${String(name)}" on model "${modelName}"`);
102
+ return runWithCurrentView(view, () => {
103
+ const execution = getCurrentViewModelExecution();
104
+ const phase = execution?.phase ?? "action";
105
+ const model = new this.#Ctor(...args);
106
+ for (const attrNode of view["~node"].attributes) {
107
+ const { name, positionals, binding } = attrNode;
108
+ let fn = (phase === "binder" ? this.#registeredBinders : this.#registeredActions).get(name);
109
+ if (phase === "action" && !fn) {
110
+ const modelName = this.#Ctor.name;
111
+ throw new Error(`No action registered for attribute "${String(name)}" on model "${modelName}"`);
112
+ }
113
+ fn ??= () => {};
114
+ const value = fn(model, positionals, getViewForNode(attrNode, "named"));
115
+ if (binding && execution?.bindingContext) execution.bindingContext.addBinding(value);
58
116
  }
59
- fn ??= () => {};
60
- named ??= { attributes: [] };
61
- const value = fn(model, positionals, new View(named, view._bindingCtx));
62
- if (binding && view._bindingCtx) view._bindingCtx.addBinding(value);
63
- }
64
- currentContext = null;
65
- return model;
117
+ return model;
118
+ });
66
119
  }
67
120
  #clone(Ctor = this.#Ctor) {
68
121
  const newVMR = new ViewModelRuntime(Ctor);
@@ -236,4 +289,18 @@ function defineSimpleViewModel(schema, options) {
236
289
  return createSimpleViewModelFromJsonSchema(schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }), resolvedOptions);
237
290
  }
238
291
  //#endregion
239
- export { createBinding, createDefine, defineSimpleViewModel, defineViewModel, extendViewModel, getCurrentContext };
292
+ //#region src/action_view_model.ts
293
+ var ActionModel = class {
294
+ action;
295
+ };
296
+ /**
297
+ * Defines a ViewModel whose only attribute is a direct `~action` that assigned to `ActionModel`'s `action`.
298
+ */
299
+ function defineActionViewModel() {
300
+ return defineViewModel(ActionModel, (helper) => ({ "~action": helper.attribute((model, [actionArg]) => {
301
+ model.action = actionArg;
302
+ }) }), null);
303
+ }
304
+ defineActionViewModel();
305
+ //#endregion
306
+ export { ActionModel, createBinding, createDefine, defineActionViewModel, defineSimpleViewModel, defineViewModel, extendViewModel, getCurrentContext, getCurrentModelContext, getCurrentView };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-runtime",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/piovium/gts.git"
@@ -0,0 +1,51 @@
1
+ import {
2
+ type AttributeDefinition,
3
+ type AttributeDefHelper,
4
+ defineViewModel,
5
+ type IViewModel,
6
+ type OverloadedParameters,
7
+ } from "./view_model.ts";
8
+ import type { AR } from "./attribute_return.ts";
9
+
10
+ type AnyAction = (fnArg: any) => void;
11
+
12
+ export class ActionModel<Fn extends AnyAction> {
13
+ action!: Fn;
14
+ }
15
+
16
+ /**
17
+ * Defines a ViewModel whose only attribute is a direct `~action` that assigned to `ActionModel`'s `action`.
18
+ */
19
+ export function defineActionViewModel<
20
+ Signature extends (this: any, actionArg: AnyAction) => AR.Done,
21
+ InitMeta = unknown,
22
+ ModelActionSig extends AnyAction = OverloadedParameters<Signature>[0],
23
+ >(): IViewModel<
24
+ ActionModel<ModelActionSig>,
25
+ {
26
+ "~action": Signature & {
27
+ required(): true;
28
+ };
29
+ "~meta": InitMeta;
30
+ },
31
+ []
32
+ > {
33
+ return defineViewModel(
34
+ ActionModel<ModelActionSig>,
35
+ (helper) => ({
36
+ "~action": helper.attribute<Signature & { required(): true }>(
37
+ (model, [actionArg]) => {
38
+ model.action = actionArg as ModelActionSig;
39
+ },
40
+ ),
41
+ }),
42
+ null as InitMeta,
43
+ );
44
+ }
45
+
46
+ type Operation<Meta> = (arg: { meta: Meta }) => void;
47
+
48
+ const VM =
49
+ defineActionViewModel<
50
+ <Meta>(this: AR.This<Meta>, op: Operation<Meta>) => AR.Done
51
+ >();
@@ -0,0 +1,68 @@
1
+ import type { BindingContext, View } from "./view.ts";
2
+
3
+ export type RuntimePhase = "action" | "binder";
4
+
5
+ /**
6
+ * The ViewModel execution state visible while a Model is being constructed.
7
+ *
8
+ * The context is synchronous and scoped to the current `parse()` call. A View
9
+ * is stable across the binder and action passes for the same definition, while
10
+ * `phase` describes the current pass.
11
+ */
12
+ export interface ModelConstructionContext {
13
+ readonly view: View<any>;
14
+ readonly phase: RuntimePhase;
15
+ }
16
+
17
+ interface ViewModelExecution {
18
+ readonly phase: RuntimePhase;
19
+ readonly bindingContext?: BindingContext;
20
+ }
21
+
22
+ let currentExecution: ViewModelExecution | null = null;
23
+ let currentModelContext: ModelConstructionContext | null = null;
24
+
25
+ /** Returns the context for the current synchronous ViewModel parse call. */
26
+ export function getCurrentModelContext(): ModelConstructionContext | null {
27
+ return currentModelContext;
28
+ }
29
+
30
+ /** Returns the View currently being parsed, or `null` outside ViewModel parsing. */
31
+ export function getCurrentView(): View<any> | null {
32
+ return currentModelContext?.view ?? null;
33
+ }
34
+
35
+ /** Returns whether the current ViewModel parse is running actions or binders. */
36
+ export function getCurrentContext(): RuntimePhase | null {
37
+ return currentModelContext?.phase ?? null;
38
+ }
39
+
40
+ export function getCurrentViewModelExecution(): ViewModelExecution | null {
41
+ return currentExecution;
42
+ }
43
+
44
+ export function runInViewModelExecution<T>(
45
+ execution: ViewModelExecution,
46
+ callback: () => T,
47
+ ): T {
48
+ const previousExecution = currentExecution;
49
+ currentExecution = execution;
50
+ try {
51
+ return callback();
52
+ } finally {
53
+ currentExecution = previousExecution;
54
+ }
55
+ }
56
+
57
+ export function runWithCurrentView<T>(view: View<any>, callback: () => T): T {
58
+ const previousModelContext = currentModelContext;
59
+ currentModelContext = {
60
+ view,
61
+ phase: currentExecution?.phase ?? "action",
62
+ };
63
+ try {
64
+ return callback();
65
+ } finally {
66
+ currentModelContext = previousModelContext;
67
+ }
68
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@ export {
2
2
  defineViewModel,
3
3
  extendViewModel,
4
4
  getCurrentContext,
5
+ getCurrentModelContext,
6
+ getCurrentView,
5
7
  type AttributeDefinition,
6
8
  type IViewModel,
7
9
  type IViewModelInstance,
@@ -10,12 +12,15 @@ export {
10
12
  type IExtendedViewModel,
11
13
  type SimpleAttributeOptions,
12
14
  type AttributeBlockDefinition,
15
+ type ModelConstructionContext,
16
+ type RuntimePhase,
13
17
  } from "./view_model.ts";
14
18
  export {
15
19
  defineSimpleViewModel,
16
20
  type ISimpleViewModel,
17
21
  type SimpleViewModelOptions,
18
22
  } from "./simple_view_model.ts";
23
+ export { defineActionViewModel, ActionModel } from "./action_view_model.ts";
19
24
  export type { AttributeReturn, AR } from "./attribute_return.ts";
20
25
 
21
26
  export {
package/src/view.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AttributeBlockDefinition, IViewModel } from "./view_model.ts";
2
+ import { runInViewModelExecution } from "./execution_context.ts";
2
3
 
3
4
  export type AttributeName = string | symbol;
4
5
 
@@ -17,15 +18,10 @@ export interface NamedAttributesNode {
17
18
 
18
19
  export class View<BlockDef extends AttributeBlockDefinition> {
19
20
  #phantom!: BlockDef;
20
- _node: NamedAttributesNode;
21
- _bindingCtx?: BindingContext;
22
-
23
- constructor(
24
- _node: NamedAttributesNode,
25
- _bindingCtx?: BindingContext | undefined,
26
- ) {
27
- this._node = _node;
28
- this._bindingCtx = _bindingCtx;
21
+ "~node": NamedAttributesNode;
22
+
23
+ constructor(node: NamedAttributesNode) {
24
+ this["~node"] = node;
29
25
  }
30
26
  }
31
27
 
@@ -39,12 +35,41 @@ export class BindingContext {
39
35
  }
40
36
  }
41
37
 
38
+ interface RegisteredViews {
39
+ root?: View<any>;
40
+ named?: View<any>;
41
+ }
42
+
43
+ const viewRegistry = new WeakMap<SingleAttributeNode, RegisteredViews>();
44
+
45
+ export function getViewForNode(
46
+ node: SingleAttributeNode,
47
+ kind: "root" | "named",
48
+ ): View<any> {
49
+ let registered = viewRegistry.get(node);
50
+ if (!registered) {
51
+ registered = {};
52
+ viewRegistry.set(node, registered);
53
+ }
54
+ let view = registered[kind];
55
+ if (!view) {
56
+ view = new View<any>(
57
+ kind === "root"
58
+ ? { attributes: [node] }
59
+ : (node.named ?? { attributes: [] }),
60
+ );
61
+ registered[kind] = view;
62
+ }
63
+ return view;
64
+ }
65
+
42
66
  export function createDefine(
43
67
  rootVM: IViewModel<any, any, any>,
44
68
  node: SingleAttributeNode,
45
69
  ): void {
46
- const view = new View<any>({ attributes: [node] });
47
- rootVM.parse(view);
70
+ runInViewModelExecution({ phase: "action" }, () => {
71
+ rootVM.parse(getViewForNode(node, "root"));
72
+ });
48
73
  }
49
74
 
50
75
  export function createBinding(
@@ -52,7 +77,11 @@ export function createBinding(
52
77
  node: SingleAttributeNode,
53
78
  ): unknown[] {
54
79
  const bindingCtx = new BindingContext();
55
- const view = new View<any>({ attributes: [node] }, bindingCtx);
56
- rootVM.parse(view);
80
+ runInViewModelExecution(
81
+ { phase: "binder", bindingContext: bindingCtx },
82
+ () => {
83
+ rootVM.parse(getViewForNode(node, "root"));
84
+ },
85
+ );
57
86
  return bindingCtx.getBindings();
58
87
  }
package/src/view_model.ts CHANGED
@@ -3,7 +3,20 @@ import type {
3
3
  AttributeReturn,
4
4
  Computed,
5
5
  } from "./attribute_return.ts";
6
- import { View } from "./view.ts";
6
+ import {
7
+ getCurrentViewModelExecution,
8
+ runInViewModelExecution,
9
+ runWithCurrentView,
10
+ } from "./execution_context.ts";
11
+ import { getViewForNode, View } from "./view.ts";
12
+
13
+ export {
14
+ getCurrentContext,
15
+ getCurrentModelContext,
16
+ getCurrentView,
17
+ type ModelConstructionContext,
18
+ type RuntimePhase,
19
+ } from "./execution_context.ts";
7
20
 
8
21
  export interface AttributeBlockDefinition {
9
22
  "~meta": any;
@@ -95,13 +108,16 @@ export interface IViewModel<
95
108
 
96
109
  /**
97
110
  * Rewrite the initial meta.
98
- * @param newMeta
111
+ * @param newMeta
99
112
  */
100
- narrow<This extends IViewModel<any, any, any>, const NewMeta extends This["~namedDefinition"]["~meta"]>(
113
+ narrow<
114
+ This extends IViewModel<any, any, any>,
115
+ const NewMeta extends This["~namedDefinition"]["~meta"],
116
+ >(
101
117
  this: This,
102
118
  newMeta: NewMeta,
103
119
  ): INarrowedViewModel<This, NewMeta>;
104
-
120
+
105
121
  extend<
106
122
  This extends IViewModel<any, any, any>,
107
123
  ChildT extends ModelT,
@@ -120,11 +136,6 @@ type LazyAttributeActionOrBinder<ModelT> = (
120
136
  named: View<any>,
121
137
  ) => unknown;
122
138
 
123
- let currentContext: "action" | "binder" | null = null;
124
- export function getCurrentContext(): "action" | "binder" | null {
125
- return currentContext;
126
- }
127
-
128
139
  export class ViewModelRuntime {
129
140
  #registeredActions = new Map<PropertyKey, LazyAttributeActionOrBinder<any>>();
130
141
  #registeredBinders = new Map<PropertyKey, LazyAttributeActionOrBinder<any>>();
@@ -147,29 +158,33 @@ export class ViewModelRuntime {
147
158
  }
148
159
 
149
160
  parse(view: View<any>, ...args: any[]): any {
150
- currentContext = view._bindingCtx ? "binder" : "action";
151
- const model = new this.#Ctor(...args);
152
- for (const attrNode of view._node.attributes) {
153
- let { name, positionals, named, binding } = attrNode;
154
- const insideBindingCtx = !!view._bindingCtx;
155
- let fn = (
156
- insideBindingCtx ? this.#registeredBinders : this.#registeredActions
157
- ).get(name);
158
- if (!insideBindingCtx && !fn) {
159
- const modelName = this.#Ctor.name;
160
- throw new Error(
161
- `No action registered for attribute "${String(name)}" on model "${modelName}"`,
161
+ return runWithCurrentView(view, () => {
162
+ const execution = getCurrentViewModelExecution();
163
+ const phase = execution?.phase ?? "action";
164
+ const model = new this.#Ctor(...args);
165
+ for (const attrNode of view["~node"].attributes) {
166
+ const { name, positionals, binding } = attrNode;
167
+ let fn = (
168
+ phase === "binder" ? this.#registeredBinders : this.#registeredActions
169
+ ).get(name);
170
+ if (phase === "action" && !fn) {
171
+ const modelName = this.#Ctor.name;
172
+ throw new Error(
173
+ `No action registered for attribute "${String(name)}" on model "${modelName}"`,
174
+ );
175
+ }
176
+ fn ??= () => {};
177
+ const value = fn(
178
+ model,
179
+ positionals,
180
+ getViewForNode(attrNode, "named"),
162
181
  );
182
+ if (binding && execution?.bindingContext) {
183
+ execution.bindingContext.addBinding(value);
184
+ }
163
185
  }
164
- fn ??= () => {};
165
- named ??= { attributes: [] };
166
- const value = fn(model, positionals, new View(named, view._bindingCtx));
167
- if (binding && view._bindingCtx) {
168
- view._bindingCtx.addBinding(value);
169
- }
170
- }
171
- currentContext = null;
172
- return model;
186
+ return model;
187
+ });
173
188
  }
174
189
 
175
190
  #clone(Ctor = this.#Ctor): ViewModelRuntime {
@@ -426,24 +441,25 @@ export interface AttributeDefinition {
426
441
  mergeMeta?(meta: any, subMeta: any): any;
427
442
  }
428
443
 
429
- type OverloadedParameters<T extends (...args: any[]) => any> = T extends {
430
- (...args: infer A1): any;
431
- (...args: infer A2): any;
432
- (...args: infer A3): any;
433
- (...args: infer A4): any;
434
- }
435
- ? A1 | A2 | A3 | A4
436
- : T extends {
437
- (...args: infer A1): any;
438
- (...args: infer A2): any;
439
- (...args: infer A3): any;
440
- }
441
- ? A1 | A2 | A3
442
- : T extends { (...args: infer A1): any; (...args: infer A2): any }
443
- ? A1 | A2
444
- : T extends (...args: infer A) => any
445
- ? A
446
- : never;
444
+ export type OverloadedParameters<T extends (...args: any[]) => any> =
445
+ T extends {
446
+ (...args: infer A1): any;
447
+ (...args: infer A2): any;
448
+ (...args: infer A3): any;
449
+ (...args: infer A4): any;
450
+ }
451
+ ? A1 | A2 | A3 | A4
452
+ : T extends {
453
+ (...args: infer A1): any;
454
+ (...args: infer A2): any;
455
+ (...args: infer A3): any;
456
+ }
457
+ ? A1 | A2 | A3
458
+ : T extends { (...args: infer A1): any; (...args: infer A2): any }
459
+ ? A1 | A2
460
+ : T extends (...args: infer A) => any
461
+ ? A
462
+ : never;
447
463
 
448
464
  export type AttributeAction<Model, T extends AttributeDefinition> = (
449
465
  model: Model,