@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 +39 -11
- package/dist/index.js +97 -30
- package/package.json +1 -1
- package/src/action_view_model.ts +51 -0
- package/src/execution_context.ts +68 -0
- package/src/index.ts +5 -0
- package/src/view.ts +42 -13
- package/src/view_model.ts +64 -48
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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> =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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,
|